Пришло время поиграться на плате TZT RK-ZYNQ7020-F v1.1 (Zynq-7000, XC7Z020-CLG484-2) с имеющейся у меня камерой на базе сенсора OV5647 (CSI-2, RAW10) и вывести одновременную картинку по HDMI и по Ethernet с использованием стриминга сжатого потока по UDP. Данная статья представляет собой практический мануал: если выполнять шаги по порядку и не переходить дальше, пока предыдущий не дал ожидаемый результат, вы получите живую картинку на мониторе и поток, который открывается в браузере, VLC или ffplay.
Собрать «камеру на ПЛИС» — это не набрать IP-блоки с именами MIPI и HDMI в Vivado. Настоящая работа — в цепочке невидимых договорённостей: кто поднимает тактовые и память, у какого сброса должен быть фронт, в каком порядке включаются источник и сток, какое соглашение о цвете живёт в кадровом буфере, и почему молчаливый отказ почти всегда выглядит как поломка совсем другого блока. Картинка на экране и поток в браузере появляются только тогда, когда все эти договорённости одновременно верны.
Все это мы разберем в этом материале. Всем интересующимся – добро пожаловать под кат!

Дисклеймер. Перед началом повествования, хотелось бы заранее оговориться, что основная цель, которую я преследую при написании этой статьи — рассказать о своем опыте. Я не являюсь профессиональным разработчиком под ПЛИС на языке Verilog и могу допускать какие-либо ошибки в использовании терминологии, использовать не самые оптимальные пути решения задач, etc. Но отмечу, что любая конструктивная и аргументированная критика только приветствуется. Что ж, поехали…
Репозиторий с артефактами
Репозиторий со всеми артефактами: https://github.com/megalloid/zynq_mipi_csi_to_hdmi_ethernet_udp
Содержание
Часть 0. Введение (главы 1–4)
Что именно получается в конце и что из эт
ого студент делает своими руками; в чём ценность проекта — шесть дисциплин, которые здесь проверяются одним индикатором, и методика измерений с заранее известным правильным ответом. Затем главный вопрос выбора: чем эта плата пригодна для такой задачи — семь проверяемых требований с измеренными числами и честный список того, чего плата не умеет. Здесь же система на кристалле, три тактовых мира, инструменты и договорённость о том, как читать разборы ошибок.
-
Что мы строим и в чём ценность этого проекта.
-
Почему именно эта плата пригодна: семь требований и список ограничений.
-
Что такое SoC, три тактовых мира и инструменты.
-
Как читать: доказательства, ошибки и что нужно для повтора.
Часть I. Задумка и архитектура (главы 5–8)
Главное решение проекта: какую часть пути пикселя делает логика, а какую процессор — три варианта границы, посчитанные в тактах и мегабайтах, и следствия выбранного. Затем то, что навязывает сенсор: мозаика фильтров, драйвер mainline как источник таблиц режима, выбор режима, устраняющий масштабатор. Затем развилка ядра. И в финале — шесть контрактов, по которым блоки конвейера договариваются между собой, от направления готовности до владения памятью.
-
Где провести границу между логикой и процессором.
-
Что нам навязывает сенсор.
-
Развилка ядра: mainline против вендорского дерева.
-
Контракты внутри конвейера.
Часть II. Физический уровень (главы 9–12)
Уровень ниже архитектуры: два режима работы пары и что делает пассивная сеть, почему напряжение банка защёлкнуто выбором стандартов, что такое тап задержки в долях бита. Затем протокол: выход в высокоскоростной режим, расчёт допуска на окно установления для своей скорости, структура пакета, арифметика занятости линии — и таблица «какой характер отказа о какой причине говорит». Затем разъём: правило зеркала, механизм гибели модуля, прозвонка до питания и проверка состояния линий одним чтением регистра. В финале — требования, которые физика выставляет тактированию.
-
Электрический уровень: что приходит по шлейфу.
-
Протокол CSI-2: от битов к строкам.
-
Разъём J2 и шлейф.
-
Что физический уровень требует от тактирования.
Часть III. Инструмент, который молчит (главы 13–16)
Продолжение правила «убедись, что значение имеет эффект», применённое к самому Vivado: неверное имя параметра он игнорирует, ограничение для несуществующего вывода применяет в пустоту, устаревший результат синтеза переиспользует без предупреждения. Ни одно из этих событий не печатает ошибку — поэтому сборка устроена как набор заслонов, и глава 14 разбирает каждый. Плюс чужой IP, два проекта в репозитории, и чтение четырёх отчётов: три числа таймингов вместо одного, занятость как портрет интерфейса, таблица частот как независимое подтверждение скорости линии.
-
Окружение и чужой IP: что спросить до первой сборки.
-
Скрипт как спецификация: два проекта, кэш синтеза, заслоны.
-
Ограничения и связывание имён.
-
Первый битстрим и что читать в отчётах.
Часть IV. Расследование на живой плате (главы 17–21)
Первая встреча с железом, где выясняется, что теория описывает происходящее верно, а симптомы не указывают на свои причины. Захват платы у самой себя: почему процессор надо остановить за десятки миллисекунд и что делать с заклинившим отладочным портом. Чужой битстрим как способ, сокращающий число неизвестных. Затем два расследования — молчащий приёмник и поток без кадров — каждое в формате «симптом, ложная гипотеза, улика, причина, правило», включая ловушку в самом методе измерения. В финале процедура подъёма как результат, и главный вывод: узкое временное окно надо убирать при сборке, а не попадать в него софтом.
-
Как забрать плату у самой себя.
-
Угадайка: чужой битстрим и чужая программа.
-
Расследование первое: приёмник, который молчит.
-
Расследование второе: поток есть, кадров нет.
-
Порядок подъёма как результат расследования.
Часть V. Четыре болезни почти правильной картинки (главы 22–27)
Свой битстрим даёт изображение сразу — и оно почти правильное, четыре раза подряд. Каждый раз выглядит как «камера шумит», и каждый раз причина в другом месте: нерасставленные входные задержки, не та микросхема памяти в конфигурации, две накладывающиеся перестановки байт, оба канала в одном буфере. Часть про то, как «почти правильно» разбирается на отдельные вопросы, и почему эти четыре дефекта нельзя было отлаживать в произвольном порядке — они маскировали друг друга.
-
Свой битстрим: что значит пересобрать эталон.
-
Болезнь первая: чистый приём и рваная картинка.
-
Болезнь вторая: шум, который приходит не из камеры.
-
Болезнь третья: две перестановки цвета.
-
Болезнь четвёртая: разрывы кадра.
-
Что общего у четырёх болезней.
Часть VI. Чужое ядро в своём дизайне (главы 28–32)
Первое ядро, написанное не для нашей платы, и почти вся часть — про то, что происходит на его границе. Шесть несоответствий решены снаружи, ни одного изменения внутри, и это стратегия, а не аккуратность: локальная правка в чужом клоне невидима и недолговечна, а попытка починить внутреннее замыкание сдвинула тупик на шаг и изменила размер кадра необъяснимо. Здесь же три правила, которые переносятся дальше: цифры производительности относятся к конфигурации, в которой их измеряли; размер сжатого кадра работает как контрольная сумма арифметики; флаг состояния должен относиться к тому объекту, о котором вы спрашиваете.
-
Что вообще можно сжать на этом кристалле.
-
Граница между ядром и дизайном.
-
Арифметика, которую проверяет размер файла.
-
Ядро, которое не переваривает непрерывный поток.
-
Модель дешевле платы.
Часть VII. Передача состояния (главы 33–38)
В части IV платой владели целиком; после кнопки питания появляются четыре независимых участника, и каждый наследует состояние от предыдущего. Ни один дефект этой части не находится в камере — все они дефекты передачи: кто-то предположил, что другой уже что-то сделал. Три вида передаваемого состояния (что настроено, что не тронуто, что произошло однажды) ломались по-разному, причём третий самый коварный: состояние после него выглядит правильным. Здесь же три неправильных файла инициализации против одного правильного и главная мина серии — снять сброс и сбросить оказались разными операциями.
-
Кто теперь распоряжается платой.
-
Buildroot: почему снаружи и почему именно эта версия.
-
Один файл инициализации: три роли в одном.
-
Описание железа для ядра.
-
Загрузочный скрипт: две строки, которые всё решают.
-
Сборка образа и первая загрузка.
Часть VIII. Поток: программа, транспорт, частоты (главы 39–43)
Всё доказанное в частях IV–VII превращается в одну программу, которая не изобретает ничего, а переводит последовательность подъёма из скриптов отладчика в код. Её три проверки при старте — компактная сводка самых дорогих ошибок серии. Дисциплина двух слотов оказывается способом обойтись без синхронизации вообще. Транспорт выбран по тестируемости, а не по красоте. И главный количественный результат: цепочка частот 30,6 → 24 → 23, где каждая потеря имеет свою причину и своё лекарство, а предел лежит там, где его не ждали — в некэшированном чтении, которым куплена корректность.
-
Программа как исполняемая форма доказанного.
-
Дисциплина двух слотов.
-
Транспорт первого захода: MJPEG поверх HTTP.
-
UDP: перепаковка сегодня, нативный путь завтра.
-
Цепочка частот: где что теряется.
Часть IX. Маршрут, занятия и правила (главы 44–46)
Восемь предыдущих частей шли вперёд; эта идёт назад — она для того, кто уже внутри процесса. Маршрут из девяти доказательств, обещанный во введении, где у каждого шага сказано не только что запустить, но и что именно этим доказано и что после него можно перестать подозревать. Затем программа занятий с артефактами, критериями приёма и разбором четырёх способов «сдать, не сделав». И в финале двадцать пять правил, сгруппированных по типу, каждое со ссылкой на место, где оплачено, — и одно, из которого следуют остальные.
-
Сквозной маршрут: девять доказательств.
-
Программа занятий.
-
Итоги и правила.
Приложения A–E
A. Глоссарий.
B. Карта регистров и памяти.
C. Шпаргалка команд.
D. Каталог дефектов.
E. Что смотреть, когда картинки нет.
Часть 0. Введение
Глава 1. Что мы строим и в чём ценность этого проекта
1.1. Результат, который должен получиться
В конце серии на столе стоит плата Zynq. В разъём камеры вставлен шлейф модуля Raspberry Pi Camera v1 (сенсор OV5647), к HDMI подключён монитор, витая пара идёт к ноутбуку. Плата включается кнопкой питания, без единой команды руками: через несколько секунд на мониторе появляется живое видео 1920×1080, а в браузере ноутбука по адресу http://<адрес платы>:8080/ открывается тот же поток в формате Motion JPEG.
Цифры, которые при этом измеряются и печатаются самой платой:
clocks iopll=1000.0 fclk0=100.000 fclk1=142.857 fclk2=200.000
sensor OV5647
csi lines_per_second=33062 dphy_bursts_per_second=33712
rate about 30.6 frames per second at the receiver
С хоста за восемь секунд curl забирает 182 кадра — 22,8 кадра в секунду, средний кадр 275 КиБ при качестве 75, поток около 50 Мбит/с, и все 182 кадра различны. Это и есть «готово»: не «на экране что-то мелькает», а числа, каждое из которых можно перепроверить.
1.2. Что именно вы делаете своими руками
Ключевое отличие от лабораторной «запустите демо производителя»: почти всё в этой цепочке собирается вами.
|
Слой |
Что делаете вы |
Что берётся готовым |
|---|---|---|
|
Плата |
прозвонка разъёма, ориентация шлейфа |
сама плата, согласующая сеть |
|
Ограничения |
свой XDC, напряжения, |
номера выводов из штатного проекта |
|
Блок-дизайн |
скрипт на TCL целиком, все параметры IP |
сами IP-блоки: CSI-2 RX, демозаик, гамма, VDMA |
|
Видеовыход |
тактирование, VTC, порядок подъёма |
|
|
Сжатие |
обвязка |
ядро |
|
Bare-metal |
скрипты захвата платы, подъём сенсора по JTAG |
XSCT-скрипты |
|
Загрузка |
|
U-Boot, ядро Linux |
|
Система |
конфигурация Buildroot, пакеты |
Buildroot, mainline Linux |
|
Софт |
|
libc |
Готовыми берутся ровно те вещи, которые не учат ничему новому и стоили бы месяцев: демозаик, JPEG-энкодер, сериализатор HDMI, ядро Linux. Всё, что касается стыков между ними, делается руками — потому что именно на стыках и живут все интересные ошибки.
1.3. Ценность первая: один проект вместо шести курсов
Обычно эти темы преподают отдельными дисциплинами, и студент не видит, как они связаны. Здесь они связаны физически: ошибка в одной ломает наблюдаемый результат в другой.
-
Физический уровень. Дифференциальные пары, согласование, LP/HS состояния, окно установления приёмника, программируемые задержки входа. Всё это обычно остаётся абстракцией из учебника — здесь неверное окно установления даёт ноль пакетов и ноль ошибок, и это приходится продиагностировать.
-
Проектирование под ограничения. Файл XDC, банки, напряжения, стандарты ввода-вывода, отчёт таймингов, который надо уметь читать не по заголовку «constraints are not met», а по конкретной проверке.
-
Системная интеграция. Карта адресов, шины AXI, кто мастер, кто ведомый, какие сбросы и в каком порядке, где домены тактирования пересекаются.
-
Bare-metal отладка. Захват процессора по JTAG, плоское адресное пространство, чтение регистров живого железа — навык, который спасает, когда операционная система ещё не грузится.
-
Встраиваемый Linux. Цепочка загрузки от BootROM до
init, Device Tree, резервирование памяти, сборка образа, устройство/dev/mem. -
Сеть и видео. Формат JFIF, сжатие и битрейт, HTTP как транспорт, RTP-нагрузка для JPEG, где именно упирается частота кадров.
Ценность не в том, что тем много. Ценность в том, что они проверяются одним и тем же индикатором. Когда на мониторе шум, виноватым может оказаться любой из шести слоёв, и научиться сужать поиск — важнее, чем знать каждый слой по отдельности.
1.4. Ценность вторая: методика, которая переносится на любую плату
Битстрим устареет вместе с платой. Останется другое.
Главный навык, который тренирует этот проект, — строить измерение с заранее известным правильным ответом. Не «посмотрим на картинку и подумаем», а:
-
заменить таблицу гаммы константой — из конвейера обязан выйти ровно серый кадр, любая неоднородность приходит после гаммы;
-
залить одну цветовую компоненту в максимум — экран обязан стать ровно одного цвета, и какого именно, отвечает на вопрос о порядке байт;
-
положить в кадровый буфер метку
0xDEADBEEF— работающий DMA обязан её затереть; -
посчитать, сколько строк в секунду сенсор должен отдавать, и сравнить с тем, сколько приёмник принял.
Каждая такая проверка занимает минуту и отвечает «да» или «нет». Без них отладка превращается в перебор гипотез на глаз: в этом проекте так были потеряны дни на неверную фазу цветового фильтра, на подозрение к шлейфу и на входные задержки, тогда как настоящей причиной один раз оказалась неправильно сконфигурированная микросхема памяти, а другой — сброс, у которого не было фронта.
Второй переносимый навык — дисциплина порядка: следующий блок включают, только когда предыдущий доказан. Она звучит скучно до первого раза, когда «всё собрал и ничего не работает», и после него уже не звучит скучно никогда.
1.5. Ценность третья: видно цену каждого решения
Учебные проекты обычно скрывают компромиссы: там всё «правильно». Здесь каждое решение имеет цену, и она измерена.
|
Решение |
Что выигрываем |
Чем платим |
|---|---|---|
|
MJPEG, а не H.264/H.265 |
помещается в кристалл |
битрейт впятеро выше |
|
Сжатие в логике, а не на процессоре |
процессор почти свободен |
своя обвязка и своя отладка |
|
|
подъём в одном читаемом файле |
нет |
|
HTTP на плате, RTP на хосте |
проверяется браузером |
нативный UDP — отдельная работа |
|
Гигабитная сеть в процессорной системе |
поток уходит без участия логики |
видеотракт получил 142,857 МГц вместо 150 |
|
Развёртка 1080p60 |
обычный монитор без настроек |
сериализатор работает за пределом характеристик |
|
Ворота кадров перед энкодером |
ядро не заклинивает |
часть кадров отбрасывается |
Умение назвать цену своего решения — то, что отличает инженера от человека, собравшего схему по инструкции. Поэтому в статье эти таблицы повторяются: в конце каждой части написано, что именно было отдано.
1.6. Ценность четвёртая: результат видно и вне лаборатории
Отдельно стоит сказать про мотивацию, которую легко обесценить как «несерьёзную». Этот проект видно. Не осциллограмма на экране прибора и не строка в логе, а лицо на мониторе и видео в браузере телефона. Для учебной работы это важно: длинная цепочка из шести слоёв доводится до конца гораздо чаще, когда результат можно показать.
И он честно масштабируется дальше: тот же тракт с заменой энкодера — основа для системы видеонаблюдения, для машинного зрения на границе сети, для измерительной камеры. Ни один шаг серии не является «учебным упрощением, которое в реальном проекте делают иначе», кроме одного явно названного: базовое описание платы для Linux взято от отладочной платы ZC702 и снабжено списком правок, а «правильный» путь — описание, выведенное из штатного проекта производителя.
1.7. Чем эта серия не является
Это не курс Verilog с нуля: предполагается, что вы знаете, что такое такт, регистр и комбинационная логика. Это не курс Linux с нуля: командную строку вы знаете. Это не пересказ документации PG232 на приёмник CSI-2 и не замена даташита OV5647 — читать их придётся, статья говорит, какие именно места и зачем.
И это сознательно не путь через V4L2 с /dev/video0. Почему — глава 7 в части I.
Глава 2. Почему именно эта плата пригодна для проекта
2.1. Как вообще выбирают плату под такую задачу
Вопрос «подойдёт ли плата» для камерного проекта распадается на семь проверяемых требований. Их полезно выписать до покупки, а не после:
-
Есть ли физический интерфейс камеры и кто построил согласующую сеть.
-
Попадает ли скорость линии нужного сенсора в диапазон, который эта плата уже вытягивала.
-
Хватает ли кристалла на весь тракт вместе с обработкой.
-
Хватает ли памяти и полосы пропускания на все одновременные потоки.
-
Есть ли выход изображения и чего он стоит.
-
Сколько независимых каналов наблюдения даёт плата, когда всё сломано.
-
Есть ли эталон, с которым можно сравниться.
Дальше — как на каждое требование отвечает TZT RK-ZYNQ7020-F v1.1 с кристаллом XC7Z020-CLG484-2. В конце главы — честный список того, чего эта плата не умеет.
2.2. Требование 1: самую трудную часть MIPI уже сделал производитель
Это главная причина, по которой проект выполним в рамках короткого цикла статей.
У Zynq-7000 нет выделенного приёмника D-PHY в кремнии. В отличие от Zynq UltraScale+, где есть аппаратный блок, здесь приём MIPI собирают из обычных ISERDES и внешней пассивной сети, которая делит уровни D-PHY до того, что может пережить банк ввода-вывода общего назначения. Документ AMD, описывающий такую сеть, — XAPP894: делители 100 Ω / 150 Ω на каждую пару.
Обычно это означает: разработать сеть, развести её на плате, изготовить плату, проверить целостность сигнала. Недели и деньги, причём с риском, что не получится.
На этой плате сеть уже распаяна: резисторы R137…R147 рядом с разъёмом J2. Паять нечего, считать делители не нужно, разводить плату не нужно.
Второй подарок того же класса — размещение выводов. Тактовый лейн заведён на вывод Y9, а это IO_L12_T1_MRCC_13, то есть clock-capable пара банка 13. Приёмник обязан тактироваться с такой пары; посади производитель клок на обычный вывод, приёмник либо не собрался бы, либо собрался бы через обходные маршруты с непредсказуемым дрожанием. Проверить это можно механически, а не на веру: scripts/query_pins.tcl печатает банк и функцию каждого вывода из базы устройства Vivado.
Разложение выводов интерфейса (полностью — в constraints/camera_pins.xdc):
|
Сигнал |
Выводы |
Стандарт |
|---|---|---|
|
тактовый лейн HS |
|
|
|
лейн данных 0 HS |
|
|
|
лейн данных 1 HS |
|
|
|
LP тактового лейна |
|
|
|
LP лейнов данных |
|
|
|
I²C сенсора |
|
|
|
сброс/включение модуля |
|
|
Цена этого подарка — два ограничения, которые нельзя нарушить. Высокоскоростные входы описаны как LVDS_25, но физически сидят в банке на 3,3 В; это законно ровно до тех пор, пока выключена дифференциальная терминация (AMD AR 69322). А низкоскоростным входам HSUL_12 нужно опорное напряжение 0,6 В, которого на плате нет, поэтому в ограничениях обязателен INTERNAL_VREF. Обе строки живут в XDC с комментарием; электрическая причина каждой разобрана в части II, глава 9.3, а сами строки — в части III.
2.3. Требование 2: скорость линии сенсора попадает в доказанный диапазон
Пассивная сеть XAPP894 не является compliance-решением MIPI. Её предел определяется конкретной разводкой платы, а не документом. Значит, единственный надёжный аргумент — «эта плата уже работала на такой скорости».
Якорь есть: штатное демо производителя гоняет сенсор OV5640 в режиме 1080p30 формата RAW10 по двум лейнам, то есть около 400 Мбит/с на лейн.
Теперь считаем, что требует наш сенсор. Драйвер ov5647 из mainline объявляет четыре режима, все в формате RAW10 с порядком фильтра BGGR. Скорость на лейн считается как pixel_rate × 10 бит / 2 лейна:
|
Режим |
pixel_rate |
Кадров/с |
Мбит/с на лейн |
|---|---|---|---|
|
2592×1944, полный кадр |
87,5 МП/с |
15,6 |
437 |
|
1920×1080, кроп |
81,7 МП/с |
30,6 |
408 |
|
1296×972, биннинг |
81,7 МП/с |
30,0 |
408 |
|
640×480, биннинг и кроп |
55,0 МП/с |
58,9 |
275 |
Весь диапазон OV5647 — 275…437 Мбит/с — лежит вокруг доказанных 400. Риск, который в таких проектах обычно главный, снимается пересчётом ещё до заказа камеры.
Для контраста: IMX219 из Raspberry Pi Camera v2 в mainline работает на частоте линии 456 МГц, то есть 912 Мбит/с на лейн — вдвое выше всего, что на этой плате проверено. Тот же проект на камере v2 начинался бы с исследования «а вытянет ли плата», и это совсем другая работа.
Что приятно, требование проверяется потом инструментом: в таблице тактовых сигналов отчёта Vivado появляется строка
mipi_phy_if_clk_hs_p {0.000 2.451} 4.901 204.040
204,04 МГц при передаче по обоим фронтам — это ровно 408 Мбит/с на лейн, то самое число из таблицы выше.
2.4. Требование 3: кристалла хватает на тракт вместе с обработкой
Кристалл XC7Z020 — средний Zynq: 85 тысяч логических ячеек, 53 200 LUT, 106 400 регистров, 140 блоков памяти по 36 Кбит, 220 блоков DSP.
Оценка «до»: вендорский конвейер занимает примерно 25 % LUT, 20 % регистров, 27 % блочной памяти и 4 % DSP. То есть три четверти кристалла свободны, и в них должно уместиться сжатие.
Измерение «после» — из отчёта reports/camera_utilization.rpt на разведённом дизайне со всем, включая JPEG-энкодер:
|
Ресурс |
Использовано |
Доступно |
% |
|---|---|---|---|
|
LUT |
21 663 |
53 200 |
40,7 |
|
— как логика |
19 753 |
53 200 |
37,1 |
|
— как память |
1 910 |
17 400 |
11,0 |
|
Регистры |
30 850 |
106 400 |
29,0 |
|
Блочная память |
59,5 |
140 |
42,5 |
|
DSP |
38 |
220 |
17,3 |
|
Выводы |
27 |
200 |
13,5 |
|
MMCM |
2 |
4 |
50,0 |
Сравнение двух состояний полезно само по себе. Видеотракт без энкодера — это 26,6 % LUT, 20,8 % регистров, 25,4 % памяти, 3,6 % DSP. Значит, сжатие стоит примерно четырнадцать процентных пунктов LUT, семнадцать пунктов блочной памяти (буферы восьми строк и таблицы) и почти всё, что видно по DSP (умножения цветового преобразования). И даже после этого свободно около 60 % логики — там поместится ещё детектор движения, наложение текста или второй выход.
Обратите внимание на предпоследнюю строку: занято 27 выводов из 200. Выводы программируемой логики — не дефицит в этом проекте, потому что консоль, сеть, карта памяти и USB висят на выводах процессорной системы и логике не достаются вовсе. Плата, где камеру пришлось бы вешать на гребёнку расширения рядом с остальной периферией, была бы гораздо теснее.
2.5. Требование 4: памяти и полосы хватает на три одновременных потока
Кадр 1920×1080 по три байта на пиксель — 6 220 800 байт, чуть меньше 6 МиБ. Именно поэтому кадровые буферы разнесены на 6 МиБ, а не «на всякий случай на 16».
Одновременных потоков через память три с половиной, и их несложно посчитать:
|
Поток |
Кадров/с |
Полоса |
|---|---|---|
|
Захват пишет кадры |
30,6 |
≈ 190 МБ/с |
|
Дисплей читает кадры |
60 |
≈ 373 МБ/с |
|
Энкодер читает кадры |
≈ 24 |
≈ 150 МБ/с |
|
Готовый JPEG пишется |
≈ 23 × 275 КиБ |
≈ 6 МБ/с |
|
Итого |
|
≈ 720 МБ/с |
Дисплей читает больше всех, и это не ошибка: монитор просит шестьдесят кадров в секунду независимо от того, что камера отдаёт тридцать, поэтому половина кадров читается повторно.
Против этих 720 МБ/с у платы стоит микросхема MT41J256M16 — 4 Гбит, шина 16 бит, 1 ГБ в адресном пространстве (проверяется в скрипте сборки: верхний адрес обязан быть 0x3FFFFFFF). Интерфейс шириной 16 бит на типичной для этого контроллера скорости DDR3 даёт порядка двух гигабайт в секунду теоретического максимума; с учётом накладных расходов на переключение банков и обновление реальная доступная полоса меньше, но запас относительно 720 МБ/с остаётся кратным. Со стороны логики потоки разведены по трём отдельным портам процессорной системы шириной 64 бита, каждый из которых на частоте 142,857 МГц способен унести около 1,1 ГБ/с.
Практическое доказательство сильнее расчёта: тракт работает с тремя каналами одновременно, монитор не мерцает, кадры не рвутся, а частота на приёмнике держится на 30,6 без провалов.
Объём памяти тоже не является ограничением: три кадровых буфера и область под сжатые кадры — это 18 МиБ плюс 4 МиБ, около двух процентов гигабайта. Ядру Linux остаётся всё остальное. Отдельная история — что эти области нужно у ядра забрать, иначе видео и распределитель страниц пишут в одну память; это часть VII.
2.6. Требование 5: изображение выходит с платы без внешней микросхемы
На плате нет отдельного передатчика HDMI, который надо было бы настраивать по I²C. Линии HDMI идут прямо на выводы кристалла, а кодирование и сериализация TMDS делаются внутри логики примитивами OSERDES. Из отчёта видно ровно восемь таких примитивов — четыре пары дифференциальных линий.
Что это даёт: одним внешним устройством меньше в цепочке подъёма. Нет ситуации «логика всё делает правильно, а микросхема-передатчик не сконфигурирована», которая на многих платах съедает первый день.
Что это стоит: развёртка 1080p60 требует пиксельной частоты 148,5 МГц и, значит, последовательной частоты 742,5 МГц. Ровно эти числа стоят в таблице тактовых сигналов:
clk_out1_system_pixclk_0 148.500 MHz
PixelClkInX5 742.500 MHz
clk_fpga_1 142.857 MHz
clk_fpga_2 200.000 MHz
Примитивы OSERDESE2 этого кристалла аттестованы примерно до 680 МГц, поэтому в итоговом отчёте есть нарушение минимальной ширины импульса −0,124 нс на десяти точках — и только оно:
WNS +0.338 ns WHS +0.015 ns WPWS -0.124 ns (10 endpoints)
Timing constraints are not met.
Запас по установлению и по удержанию положительный, нарушена одна проверка в сериализаторе HDMI. Штатный дизайн производителя гоняет ту же плату ровно с такими же таймингами и даёт стабильную картинку, поэтому нарушение оставлено сознательно; честная альтернатива — 1080p30 с пиксельной частотой 74,25 МГц. Это хороший учебный пример: «отчёт красный» и «устройство не работает» — разные утверждения, и различать их надо по конкретной проверке, а не по заголовку.
2.7. Требование 6: три независимых канала наблюдения
Когда сломано всё, ценность платы определяется тем, сколько у вас способов посмотреть, что происходит. Здесь их три, и они не зависят друг от друга.
-
Светодиоды на выводах логики (
V15,V13, активный уровень — единица). Работают до консоли, до сети и до операционной системы. Запись в регистр, которая их зажигает, доказывает одновременно битстрим, тактовую, преобразователи уровней, карту адресов и шину. -
Консоль UART через USB-мост, 115200 8N1. Показывает загрузчик и ядро.
-
JTAG, тоже через USB. Позволяет захватить процессор, исполнить инициализацию платы и читать любой физический адрес — в том числе тогда, когда операционная система не грузится вообще.
Плюс кнопка PL_KEY1 на W18 как сброс и разъём расширения на 40 контактов, если понадобится логический анализатор.
Именно наличие JTAG-канала делает возможной часть IV, где камера поднимается и отлаживается до Linux. На плате, где есть только консоль, ту же работу пришлось бы делать сквозь загрузчик и драйверы, то есть с тремя дополнительными подозреваемыми в каждом измерении.
2.8. Требование 7: есть эталон, с которым можно сравниться
В комплекте платы поставляется factory image, и это не только прошивка. Там лежит исходный проект Vivado, собранный битстрим, готовый исполняемый файл для запуска без операционной системы и файл инициализации процессорной системы. Кроме того, есть проект an5641_mipi_hdmi с тем самым конвейером, который нам нужен.
Как это используется в серии:
-
Номера выводов берутся из вендорского XDC и сверяются со схемой.
-
Параметры блоков сверяются с вендорским блок-дизайном, копия которого лежит в
reference/vendor_mipi/design_1.bd. -
Живое железо проверяется чужим битстримом до того, как появился свой: можно поднять плату, прочитать регистры приёмника и убедиться, что HDMI и тактовая часть на вашем экземпляре целы.
-
Диагностика по сравнению. Самая дорогая находка проекта — грязный линк при формально работающем приёме — была вскрыта прямым сравнением двух битстримов на одном кабеле: у вендора входные задержки расставлены, у нас не были.
Ценность эталона трудно переоценить именно в учебном проекте. Он превращает вопрос «работает ли моя конфигурация» в вопрос «чем моя конфигурация отличается от заведомо работающей», а на второй вопрос ответ можно найти механически.
Здесь же и предупреждение, которое стоило в этом проекте одной пересборки и одной неудачной загрузки: вендорский референс — не описание платы, а один из способов её сконфигурировать. Поле, которое референс не использовал, вполне может содержать умолчание инструмента. Так случилось с определением присутствия карты памяти: в вендорском блок-дизайне стоит один вывод, а работающая заводская инициализация использует другой. Правы оказались железо и заводской файл.
2.9. Требование 8, неформальное: плата уже «оплачена» предыдущим проектом
Формально это не свойство платы, а свойство репозитория, но на трудозатраты оно влияет сильнее всего перечисленного.
На этой же плате раньше выполнялся другой проект — перенос SPI-контроллера с Altera Cyclone IV на Zynq. Там были найдены и оплачены: молчащая консоль, чужая инициализация процессорной системы в загрузчике, сеть с горящим Link и нулевым трафиком, зависание ядра на первом обращении к логике, паника из-за таблицы частот процессора, «не найден корневой раздел» при исправной карте. Всё это собрано в отдельном месте, где каждая правка снабжена симптомом, который она предотвращает.
Практический смысл: в этом проекте вы не выясняете заново, на каких выводах консоль и почему ядро виснет после pinctrl. Вы занимаетесь камерой. Если вы повторяете проект на другой плате, будьте готовы к тому, что этот список придётся проверять самостоятельно, и заложите на него время.
2.10. Чего эта плата не умеет: честный список
Пригодность — не универсальность. Ограничения, которые сформировали архитектуру проекта:
-
Нет аппаратного видеокодека. VCU есть только в Zynq UltraScale+ линейки EV. Отсюда MJPEG вместо H.264/H.265.
-
Нет выделенного приёмника D-PHY. Отсюда пассивная сеть, окно установления, входные задержки и весь сюжет части IV. Практический потолок линии — около 700 Мбит/с, причём выше 700 замеры лучше не становились.
-
Нет входа HDMI. Плата умеет отдавать изображение, а не принимать.
-
Сеть висит на выводах процессорной системы. Отдать поток напрямую из логики, минуя процессор – значит расходовать дополнительные ресурсы ПЛИС. Отсюда транспорт через сокет в Linux, а не RTP-передатчик в логике.
-
Сериализатор HDMI работает за пределами характеристик на 1080p60, см. 2.6.
-
Гигабитная сеть отнимает удобную частоту. Она требует ровно 125 МГц, из чего следует конфигурация PLL, в которой круглые 150 МГц для видеотракта недостижимы, и остаётся 142,857.
Ни одно из этих ограничений не мешает поставленной цели. Все они мешали бы другой цели: «поток H.265 по RTSP с приёмом второго видеовхода» на этой плате не собрать, и полезно понимать это до, а не после.
2.11. Короткий ответ на вопрос главы
Плата пригодна потому, что она снимает единственный по-настоящему трудный и дорогой вопрос — физику MIPI на 7-й серии, — оставляя все остальные вопросы решаемыми и, главное, проверяемыми: скорость линии сенсора попадает в доказанный диапазон, кристалла хватает вдвое, памяти и полосы хватает кратно, изображение выходит без внешних микросхем, каналов наблюдения три, а рядом лежит заведомо работающий эталон для сравнения.
Глава 3. Что такое SoC, три тактовых мира и инструменты
Коротко, для самых новичков о заезженном из статьи в статью. Не могу не упомянуть об этом.
3.1. Две половины одного кристалла
Zynq-7000 — это не «ПЛИС с процессорным ядром внутри» и не «ARM с куском FPGA сбоку». Это система на кристалле: на одном куске кремния две равноправные части.
Процессорная система (PS). Два ядра Cortex-A9, контроллер DDR, UART, Ethernet, SD, USB, I²C, GPIO. Она исполняет программы ещё до того, как в логику залита конфигурация. Именно она поднимает память и выбирает функции выводов MIO — выводов, назначение которых определяется регистрами, а не битстримом.
Программируемая логика (PL). Та самая FPGA. Сюда мы кладём приёмник CSI-2, демозаик, гамму, три канала VDMA, генератор видеотаймингов, сериализатор HDMI и JPEG-энкодер. Конфигурация логики — файл .bit.
Между ними мосты AXI. Процессор ходит в регистры логики как в память: записал число по адресу 0x43C00000 — попал в приёмник CSI-2. Логика ходит в DDR процессора через порты высокой производительности: записала кадр по адресу 0x10000000 — пиксели легли в ту же микросхему, из которой ядро Linux берёт страницы. Это удобно и опасно одновременно.
3.2. Вы больше не «тот, кто дёргает провода»
На учебной плате с одной FPGA вы были всем: тактовой, сбросом и тем, кто решает судьбу пикселя. На Zynq ваш модуль — периферийное устройство. Он получает адрес в карте памяти, тактовую от PLL процессора и сброс от блока proc_sys_reset.
Если процессор не запрограммировал PLL, не снял сброс и не включил преобразователи уровней между половинами кристалла, ваша логика существует в битстриме и никуда не подключена. Симптом: «залил .bit, светодиоды не моргают, шина не отвечает». Причина почти никогда не в Verilog.
Отсюда правило, которое повторяется всю серию: сначала доказать, что процессор дотягивается до логики, и только потом включать камеру.
3.3. Карта памяти как контракт между железом и софтом
Адрес блока задаётся в блок-дизайне и повторяется в программе. Это два места для одного числа, и они обязаны совпадать.
Расхождение не ломает сборку и не печатает ошибку во время работы: обращение уходит по адресу, на котором никто не отвечает, а неотвеченное чтение на Zynq вешает процессор без сообщения. Не bus error, не запись в лог — тишина с замершей консолью.
Поэтому скрипт сборки печатает карту адресов между метками CAM_ADDRESS_MAP_BEGIN и CAM_ADDRESS_MAP_END, а адреса в проекте намеренно совпадают с вендорскими: так все скрипты, написанные во время отладки на чужом битстриме, продолжают работать со своим. Полная карта — в приложении B.
3.4. Три тактовых мира, которые нельзя путать
На плате два кварцевых генератора, и из них получается несколько несовместимых доменов.
-
Генератор 50 МГц на выводе
W17тактирует дизайны без процессорной системы. В камерном проекте почти не нужен: видеотракт берёт частоты у PS. -
Генератор 33,333 МГц питает процессорную систему. Её PLL дают частоты ядер, памяти, периферии и три сигнала
FCLK_CLK0/1/2, которые PS отдаёт в логику. -
Пиксельная частота HDMI синтезируется уже в логике из
FCLK0блокомclk_wiz, а сериализатор делает из неё частоту в пять раз выше.
Договор этого дизайна:
|
Сигнал |
Частота |
Роль |
|---|---|---|
|
FCLK0 |
100 МГц |
регистры AXI4-Lite и опора пиксельного MMCM |
|
FCLK1 |
142,857 МГц |
видеотракт: CSI, демозаик, гамма, три VDMA |
|
FCLK2 |
200 МГц |
опора калибровки входных задержек приёмника |
|
pixel |
148,5 МГц |
видеотайминги, видеовыход, сериализатор |
Каждая из трёх ролей нужна отдельно. Регистры не требуют скорости — но из FCLK0 делается пиксельная частота, поэтому его ошибка масштабирует видеорежим целиком. Видеотракт обрабатывает пиксель за такт против 81,7 миллиона пикселей в секунду от сенсора — запас двукратный. А FCLK2 — опора блока, который калибрует входные задержки один раз при выходе из сброса; без годной опоры калибровка не завершается, и приём разваливается неотличимо от плохого шлейфа.
Запомнить сейчас: эти три числа должны совпасть в блок-дизайне, в инициализации процессорной системы и в проверке при старте программы. Расхождение не печатает ошибку синтеза — оно печатает шум на экране.
Почему FCLK1 не круглое 150, хотя в скрипте запрошено именно 150, — история про гигабитную сеть в части VII. Инструмент честно сообщает результат в таблице тактовых сигналов: clk_fpga_1 142.857.
3.5. Инструменты на хосте
-
Vivado с поддержкой части
xc7z020clg484-2; проект собирался в 2025.2, вендорский эталон — в 2023.1. Приёмник CSI-2 должен быть доступен в лицензии. -
XSCT для работы по JTAG (входит в поставку инструментов).
-
Buildroot 2026.02.3 — корневая система, ядро, загрузчик. Версия важна: конфигурация опирается на символы, которых в старых сериях нет, и один из них при отсутствии молча меняет libc.
-
Обычный набор хоста:
make,git, компилятор,iverilogдля тестбенчей. -
На стороне наблюдателя — браузер, либо
ffplay,vlc,curl,gst-launch-1.0.
Первая полная сборка битстрима — порядка сорока минут, первая сборка Buildroot — порядка трёх часов, потому что тулчейн собирается с нуля. Повторные сборки существенно быстрее.
3.6. Как устроен репозиторий
constraints/ выводы платы и камеры
rtl/ свой Verilog: rgb_to_yuyv, mjpeg_wrap, тестбенчи
scripts/ сборка, JTAG, библиотеки подъёма конвейера
linux/ Device Tree, ps7_init для загрузчика, программа mjpeg_pipe
buildroot-external/ конфигурация образа
ip_repo/ rgb2dvi и mjpegZero, скачиваются отдельно
docs/ board_notes, camera_plan, pinout, эта статья
reference/ копии вендорских файлов, в сборке не участвуют
Целями Makefile в корне управляется всё: каждая цель — одна строка вызова скрипта, и каждый скрипт работает сам. Makefile существует, чтобы правильный вызов не восстанавливался по истории командной оболочки.
В репозитории намеренно нет самого Buildroot и нет чужих IP: это сторонние деревья со своей историей версий, и подключаются они по ссылке, а не копией.
Глава 4. Как читать: доказательства, ошибки и что нужно для повтора
4.1. Три слоя текста
В каждой части есть три слоя, и их полезно различать.
Мотивация. Зачем шаг существует. Если шаг называется «настроить окно установления», мотивация не «так в даташите», а «приёмник, собранный под чужую скорость линии, молча пропускает синхробайт, и при этом не сообщает ни об одной ошибке».
Действие. Команда, которую можно скопировать, и то, что должно появиться на экране, в логе или на светодиодах.
Разбор ошибки. Если не получилось. Сводка всех таких историй — в приложении D.
Не читайте разборы ошибок заранее «на всякий случай». Они осмысленны, когда вы уже видели симптом или понимаете, какую проверку шаг закрывает.
4.2. Правило «один шаг — одно доказательство»
Соблазн собрать всё сразу и посмотреть, что получится, обходится днями: когда не работает всё, непонятно, что именно. Порядок доказательств такой:
-
Инструменты видят кристалл, проект собирается.
-
Процессор по JTAG виден, память отвечает.
-
Запись в регистр зажигает светодиоды — мост между половинами кристалла жив.
-
На шине I²C отвечает устройство
0x36, идентификатор0x5647. -
Приёмник считает высокоскоростные посылки и видит пакеты RAW10.
-
В кадровом буфере появляются живые пиксели, монитор показывает картинку.
-
Энкодер отдаёт файл, который открывается на хосте.
-
Система грузится с карты, сеть поднимается, программа стартует сама.
-
Браузер на хосте показывает поток.
Не переходите с пятого шага на шестой, пока пятый не зелёный. Картинка на мониторе — плохой первый индикатор: она зависит одновременно от приёмника, демозаика, памяти, каналов DMA, таймингов видеовыхода и кабеля.
Каждый из девяти шагов разобран в части IX, глава 44 — с командой, ожидаемым результатом и указанием, что именно этим доказано. Пока вы читаете серию впервые, туда заглядывать не нужно; она понадобится, когда вы сядете это повторять.
4.3. Как устроен разбор ошибки
Симптом. Что видно: чёрный экран, ноль пакетов, сиреневый уклон, «карта не вставлена» про вставленную карту.
Ложная гипотеза. Что казалось разумным: плохой шлейф, неверная фаза цветового фильтра, «надо подобрать задержку».
Улика. Какой регистр, счётчик или опыт перевернул картину.
Причина. Что было на самом деле.
Правило. Как не повторить и как проверить за минуту.
Ложная гипотеза записывается не для самоиронии. В следующий раз она снова покажется разумной, и полезно помнить, чем она была соблазнительна.
4.4. Где правда, если текст и плата разошлись
-
Поведение живой платы и числа, которые печатают скрипты.
-
Код:
scripts/build_camera.tcl,linux/app/mjpeg_pipe.c, Device Tree. -
Инженерный дневник
../camera_plan.md. -
Эта статья.
Статья объясняет, дневник фиксирует измерения, код — то, что в кристалле. Пример: круглые 150 МГц были правдой несколько дней, пока не включили сеть. Если статья говорит одно, а программа при старте печатает 142.857 и отказывается работать при другом значении — верьте программе.
4.5. Что нужно, чтобы повторять, а не только читать
-
плата TZT RK-ZYNQ7020-F v1.1;
-
камера с сенсором OV5647 на шлейфе 15 контактов (вставлять контактами вверх, см. часть II);
-
монитор HDMI с поддержкой 1080p60;
-
Ethernet до хоста и USB-кабель для консоли;
-
карта microSD на 8 ГБ и больше;
-
хост с Vivado и десятками гигабайт под Buildroot.
Если камеры нет, части 0–III и большую часть VII всё равно можно пройти: скелет, светодиоды, образ системы. Без монитора поток проверяется файлом JPEG и по HTTP.
Дальше — часть I: почему выбран именно этот сенсор, почему отложены V4L2 и H.265, и как выглядит конвейер, который предстоит собрать.
Часть I. Задумка и архитектура
Глава 5. Где провести границу между логикой и процессором
5.1. Единственное решение, от которого зависит всё остальное
У проекта есть один настоящий развилочный вопрос, и он не про выбор IP-блоков. Он звучит так: какую часть пути пикселя делает программируемая логика, а какую — процессор.
Всё остальное — следствия. Нужен ли драйвер ядра, нужен ли V4L2, сколько кадровых буферов, какой транспорт в сеть, можно ли обойтись одной программой в пользовательском пространстве, — определяется тем, где проведена эта граница. Поэтому её стоит выбрать осознанно и с числами, а не «как принято».
Границу можно провести в трёх местах. Назовём варианты A, B и C и посчитаем каждый.
5.2. Вариант A: процессор обрабатывает пиксели
Классическая, «учебниковая» архитектура для Linux. Логика принимает пакеты CSI-2 и складывает сырой Байер в память. Дальше процессор делает демозаик, тональную кривую, при необходимости масштабирование, и выкладывает результат в буфер дисплея.
Считаем бюджет. Кадр 1920×1080 — это 2 073 600 пикселей, при 30,6 кадра в секунду выходит 63,4 миллиона пикселей в секунду. Процессор — два ядра Cortex-A9 на 667 МГц, то есть суммарно 1,33 миллиарда тактов в секунду.
1,33 · 10⁹ тактов/с ÷ 63,4 · 10⁶ пикселей/с ≈ 21 такт на пиксель
Двадцать один такт на пиксель — это весь бюджет на оба ядра, включая чтение из памяти, запись обратно, промахи кэша и работу операционной системы. Демозаик по своей природе смотрит на окрестность 3×3 и выдаёт три компоненты: это несколько загрузок, несколько умножений и запись на каждый выходной пиксель. Двадцати одного такта не хватит даже на аккуратно написанный цикл копирования, не говоря об интерполяции.
Вариант A умирает на арифметике, до написания первой строки кода. Причём заметьте, чем он умирает: не «медленно и надо оптимизировать», а на порядок мимо. Оптимизации порядок не отыгрывают.
5.3. Вариант B: логика доводит до RGB, процессор перекладывает кадры
Более скромный вариант, и именно он был заложен в исходный план проекта. Логика принимает CSI-2, делает демозаик и гамму, кладёт готовый RGB в память. Процессор забирает кадры и копирует их в буфер дисплея (/dev/fb0 через simple-framebuffer), а при необходимости ещё и сжимает.
Здесь арифметика уже не смертельная, но неприятная. Кадр RGB — 6 220 800 байт. Копирование кадра означает прочитать столько и столько же записать:
6,22 МБ × 30,6 кадра/с × 2 (чтение + запись) ≈ 380 МБ/с
Это трафик, который добавляется к тем примерно 720 МБ/с, которые видеотракт уже гоняет через ту же микросхему памяти (посчитано в части 0, глава 2.5). Полторы тысячи против двух с небольшим гигабайт теоретического максимума — уже не «есть запас», а «живём на грани», и всё это ради операции, которая ничего не добавляет к изображению. Плюс каждый байт проходит через кэш процессора, вытесняя оттуда то, чем процессор действительно занят.
И главный вопрос к варианту B: зачем? Логика уже держит готовый RGB-кадр в памяти. Прочитать его оттуда и выдать на HDMI — работа для канала DMA и генератора видеотаймингов, которые уже есть в каталоге IP.
5.4. Вариант C: логика доводит до конца, процессор только распоряжается
Выбранная архитектура. Логика делает весь путь пикселя: приём, демозаик, гамма, кадровый буфер, чтение обратно и выдача на HDMI. Вторая ветка логики читает тот же буфер и оставляет в памяти готовый файл JPEG. Процессор не касается ни одного пикселя.
Что при этом делает процессор:
6,22 МБ на кадр → логика (процессор не участвует)
275 КиБ на кадр → процессор копирует в сокет ≈ 6 МБ/с
Шесть мегабайт в секунду вместо трёхсот восьмидесяти. Разница в шестьдесят раз — и она получена не оптимизацией, а тем, что процессор поставлен в цепочку после сжатия, а не до него.
Именно этот множитель делает возможным всё остальное в проекте: одна однопоточная программа в пользовательском пространстве успевает и поднять конвейер, и отдавать поток в сеть. Не нужен ни драйвер с прерываниями, ни dma-buf, ни нулевое копирование, ни второй поток — потому что задача, которая осталась процессору, слишком мала, чтобы за неё бороться.
5.5. Что следует из выбора C
Стоит выписать следствия явно, потому что дальше по тексту они будут появляться как «само собой»:
|
Следствие |
Почему так |
|---|---|
|
Кадры не проходят через процессор |
по определению варианта C |
|
Медиа-граф не нужен |
нечего описывать: путь целиком внутри логики |
|
Очередь буферов не нужна |
буферами распоряжается genlock в железе |
|
Согласование форматов не нужно |
форматы зафиксированы при сборке битстрима |
|
Драйвер ядра не нужен |
остались запись регистров и копирование файла |
|
Транспорт может быть простым |
6 МБ/с уносит обычный сокет |
|
Изображение живёт без операционной системы |
логика не спрашивает разрешения |
Последняя строка — неочевидная и самая приятная. Поскольку процессор в тракте не участвует, картинка на мониторе живёт независимо от того, что происходит в Linux. Программа, отдающая поток, может завершиться — в её выходном пути нет ни одной операции, останавливающей каналы, — и видео на мониторе останется живым, а не замрёт на последнем кадре: сенсор продолжает передавать, каналы продолжают крутиться по кругу. Это же свойство позволило отладить всю камеру до операционной системы, в части IV.
У этой автономности есть обратная сторона, о которой честнее сказать сразу: повторный запуск программы не является безобидной операцией. Приёмник CSI-2 не умеет заново захватить уже работающую линию, поэтому программа первым делом импульсом сбрасывает логику — и вся последовательность подъёма проходит с нуля. Почему без этого импульса второй запуск читает нули при живом сенсоре — часть VII, глава 37.
5.6. Чем за вариант C приходится платить
Честно: тем, что вся конфигурация живёт в регистрах логики и после выключения питания её нет. Нет драйвера — значит, нет и того, кто восстановит состояние при загрузке. Ответ на это не «не забыть запустить скрипт», а более надёжный: настройка встроена в ту же программу, которая отдаёт поток. Забыть её нельзя, потому что без неё нет и потока.
Вторая плата — диагностическая. У V4L2 есть готовая инфраструктура наблюдения: v4l2-ctl, счётчики кадров, понятные коды ошибок. Здесь всего этого нет, и поэтому программа сама печатает то, что печатали JTAG-скрипты: частоты, состояние приёмника, число строк в секунду, состояние каналов DMA. Наблюдаемость пришлось спроектировать вручную — см. 8.7.
5.7. Где граница всё-таки сдвинется в будущем
Вариант C не догма, и полезно знать, что именно вернёт процессор в тракт:
-
прерывание вместо опроса. Сейчас программа опрашивает состояние DMA. Если понадобится точная метка времени кадра или снижение задержки — нужен обработчик прерывания, то есть драйвер;
-
dma-buf. Если кадры надо отдать другому устройству без копии (например, аппаратному ускорителю или GPU) — нужен драйвер, экспортирующий буфер; -
чужой софт. Если поверх должно работать что-то, ожидающее
/dev/video0— придётся отдать сырые кадры в пользовательское пространство, и тогда возвращается вариант B со всеми его копированиями.
Ни одно из трёх не нужно для «картинка на мониторе и поток в сеть». Поэтому V4L2 в конфигурации ядра оставлен закомментированным с пояснением, при каком условии его включать, а не удалён.
Глава 6. Что нам навязывает сенсор
6.1. Сенсор — не камера
Модуль Raspberry Pi Camera v1 удобно считать «камерой», но для проекта важно видеть в нём то, чем он является: матрица с аналоговым трактом, набором регистров и передатчиком CSI-2. Он не отдаёт изображение. Он отдаёт поток чисел, каждое из которых — яркость одного цветового фильтра над одним светочувствительным элементом.
Матрица накрыта мозаикой цветных фильтров: в квадрате 2×2 два зелёных, один синий, один красный. Драйвер mainline объявляет для этого сенсора формат MEDIA_BUS_FMT_SBGGR10_1X10 — десять бит на элемент и порядок фильтров BGGR, то есть в левом верхнем углу квадрата синий.
Отсюда два блока в конвейере, которых иначе бы не было:
-
демозаик восстанавливает три компоненты в каждой точке, интерполируя недостающие по соседям. Без него изображение выглядит как сетка;
-
гамма переводит линейные показания датчика в нелинейную шкалу, в которой работает дисплей и человеческий глаз. Без неё картинка тёмная и «плоская».
Эти два блока — весь наш процессор изображения. Ни баланса белого, ни компенсации падения яркости к краям кадра, ни подавления шума в проекте нет, и это стоит проговорить, чтобы не ждать от результата телефонного качества. Экспозицией и усилением распоряжается сам сенсор своими внутренними регистрами, которые таблица режима оставляет в состоянии по умолчанию; собственного контура автоэкспозиции мы не пишем. Единственная тональная настройка, которая у нас есть, — показатель гамма-таблицы, и программа загружает её со значением 0,7.
6.2. Драйвер mainline как источник документации
Здесь стоит остановиться на приёме, который сильно экономит время и не очевиден новичку.
Даташит сенсора описывает регистры, но не даёт готовой последовательности «как включить режим 1080p30». Такая последовательность есть в драйвере ядра: drivers/media/i2c/ov5647.c содержит таблицы регистров для каждого режима, выверенные сообществом на живых модулях. Мы не пишем инициализацию сенсора с нуля — мы извлекаем таблицу из драйвера в файл scripts/ov5647_1080p30.tcl и подаём её по I²C.
У этого приёма есть ловушка, и она архитектурная, а не техническая. Драйвер — это не только таблица. Драйвер задаёт часть параметров отдельно от таблицы, в других своих функциях: когда пользователь применяет кадровый интервал, драйвер программирует вертикальный размер кадра; когда меняет экспозицию — соответствующие регистры. Взяв только таблицу, вы получаете режим без этих параметров.
Именно это и произошло. Таблица задаёт горизонтальный размер и не задаёт вертикальный полный размер кадра, а он остаётся равным значению по умолчанию — 1968 строк при 1080 активных. Кадровая частота падает до примерно 17 в секунду, и приёмник принимает около 18 600 строк в секунду вместо ожидаемых 33 000. Выглядит это ровно как «линия теряет сорок процентов строк» — то есть как проблема физики. Разбор — часть IV, глава 19.
Правило, которое из этого выросло: прежде чем чинить физику, посчитайте, сколько строк вы вообще должны получать. Тактирование сенсора выводится из его же регистров, и скрипт jtag_ov_timing.tcl печатает и скорость линии, и кадровую частоту, и ожидаемое число строк в секунду. Сравнение расчёта с измерением занимает минуту и снимает целый класс ложных диагнозов.
6.3. Два вида регистров: про изображение и про транспорт
Полезное различение, которое пригодится в части IV. Регистры сенсора делятся на две группы, и они ломаются по-разному.
Регистры изображения — размеры кадра, кроп, биннинг, полные размеры с гашением, экспозиция, усиление. Их неверная настройка даёт неправильное изображение или неправильную частоту, но поток идёт.
Регистры транспорта — режим передатчика CSI-2: непрерывный или гашеный тактовый лейн, разрешение строчной синхронизации, момент выхода из состояния покоя. Их неверная настройка даёт отсутствие изображения при полностью исправной физике, и это гораздо более неприятный класс отказа.
В этом проекте один бит из второй группы стоил отдельного расследования: регистр 0x4800 со значением 0x04 вместо 0x34 давал поток без разметки кадров, при котором приёмник считает пакеты, а флага «кадр принят» не появляется ни разу. Подробности — часть IV; здесь важно запомнить само различение, потому что оно подсказывает, где искать.
6.4. Почему рабочий режим — 1080p, и почему это про монитор, а не про сенсор
Скорости линии всех четырёх режимов и вывод о том, что они попадают в доказанный платой диапазон, — в части 0, глава 2.3. Здесь — другой аргумент, архитектурный, и он оказался решающим.
Полный кадр сенсора — 2592×1944. Режим 1920×1080 получается кропом, то есть вырезкой центральной части: поле зрения сужается, но каждый пиксель остаётся пикселем матрицы. Режим 1296×972 — биннинг: полное поле зрения, но вдвое меньше размер.
Теперь посмотрим со стороны монитора. Он ждёт 1920×1080. Значит:
|
Режим сенсора |
Что нужно в логике |
Поле зрения |
|---|---|---|
|
1920×1080 (кроп) |
ничего |
сужено |
|
1296×972 (биннинг) |
масштабатор с увеличением |
полное |
|
2592×1944 (полный) |
масштабатор с уменьшением |
полное |
|
640×480 |
масштабатор с увеличением |
сужено |
Единственный режим, который совпадает с монитором один в один, — 1080p. Любой другой требует масштабатора: отдельный IP-блок, отдельные тактовые ограничения, отдельный источник артефактов и ещё один блок, который надо настроить и в котором можно ошибиться. Пока в проекте нет живой картинки, масштабатор не помогает, а мешает — он добавляет подозреваемого в каждое измерение.
Поэтому 1080p выбран не потому, что он «лучший режим сенсора» (он даже не даёт полного поля зрения), а потому, что он устраняет целый блок из конвейера. Полное поле зрения обозначено как развитие проекта, и там честно написано, что дешевле децимировать в логике, чем тащить пять мегапикселей через память.
6.5. Один регистр, который надо прочитать раньше всех
Модули с виду одинаковы, а сенсоры внутри бывают разные. В комплекте платы часто идёт модуль на OV5640 — это другой сенсор с другой таблицей регистров, и вендорский проект написан именно под него.
Идентификатор читается из регистров 0x300A/0x300B:
0x56 0x47 → OV5647, наша таблица подходит
0x56 0x40 → OV5640, таблица не подходит
Читать его надо до всякой отладки потока, и относиться к нему как к контрольной сумме: если идентификатор не тот, дальше идти бессмысленно, а «подкрутить таблицу на глаз» — худшее из возможных решений. Нужную таблицу берут из драйвера соответствующего сенсора тем же приёмом, что описан в 6.2.
Глава 7. Развилка ядра: mainline против вендорского дерева
7.1. Что понадобилось бы, если идти по учебнику
Допустим, мы выбрали бы вариант B из главы 5 — процессор участвует в тракте. В Linux это описывается медиа-графом: каждый блок обработки становится узлом, связи между узлами описаны в Device Tree, драйвер сверяет форматы на связях, а пользовательское пространство получает /dev/video0.
Инвентаризация драйверов для такого пути (проверено по mainline 6.6 и по вендорскому дереву linux-xlnx 6.1) даёт неполный комплект.
В mainline есть: драйвер сенсора ov5647, приёмник xilinx-csi2rxss, сборщик графа xilinx-vipp, драйвер канала DMA xilinx-dma, генератор таймингов xilinx-vtc, а также simplefb и simpledrm, которые умеют показывать готовый буфер.
В mainline нет трёх вещей:
-
драйвера демозаика;
-
драйвера гамма-таблицы;
-
дисплея в программируемой логике вообще — каталог
drivers/gpu/drm/xlnx/содержит DisplayPort от ZynqMP, а не HDMI на 7-й серии.
В вендорском дереве всё три есть. Отсюда и развилка.
7.2. Путь A: перейти на вендорское дерево
Что получаем даром: HDMI поднимается как полноценное устройство DRM, ровно так, как это делает готовый образ PetaLinux; демозаик и гамма становятся узлами медиа-графа; появляется /dev/video0.
Чем платим — и это дороже, чем кажется на первый взгляд. Все правки, которые делают эту плату загружаемой, выверены на mainline: консоль на нужном выводе, описание сетевого физического уровня, обход определения карты памяти, снятая таблица частот процессора. Список и симптом каждой правки — в ../board_notes.md, а обзор того, почему он вообще существует, — в части 0, глава 2.9. Переезд на другое дерево означает перенести этот список заново и заново убедиться, что каждая правка ещё нужна и ещё работает. Вторая цена — постоянная: вендорское дерево живёт своим циклом, и обновление ядра перестаёт быть рутиной.
7.3. Путь B: остаться на mainline и обойти пробелы
Выбранный путь. Он опирается на наблюдение, что оба пробела дешевле обойти, чем закрыть.
Демозаик и гамма не обязаны быть узлами медиа-графа. Это блоки, полученные из высокоуровневого синтеза, и снаружи они выглядят как несколько регистров: размеры кадра, фаза цветового фильтра, управляющее слово с автоперезапуском и область таблиц. Их достаточно настроить один раз при старте. Драйвер здесь не добавил бы ничего, кроме места, где эти же записи будут выполнены.
Дисплею драйвер не нужен вовсе. Канал чтения непрерывно вычитывает кадровый буфер по фиксированному адресу и отдаёт пиксели в тракт HDMI. Linux об этом не обязан знать ничего: он не участник, а сосед по памяти. Даже simple-framebuffer не потребовался — он был бы нужен, если бы кадр в этот буфер рисовал процессор.
Заметьте, что путь B стал возможен именно из-за решения главы 5. Если бы процессор участвовал в тракте, обойти отсутствующий дисплейный драйвер было бы нечем: пришлось бы либо переносить его из вендорского дерева, либо переходить на путь A. Архитектурная граница и выбор ядра — не два независимых решения, а одно и его следствие.
7.4. Почему V4L2 отложен, а не «не осилен»
Стоит развернуть аргумент до конца, потому что «мы не стали использовать стандартную подсистему» — заявление, требующее обоснования.
V4L2 решает три задачи. Описать граф обработки. Управлять очередью буферов между железом и пользовательским пространством. Согласовать форматы между узлами.
Проверим каждую применительно к варианту C:
|
Задача V4L2 |
Есть ли она у нас |
|---|---|
|
Описать граф обработки |
графа нет: путь целиком внутри логики, снаружи не видно ни одного промежуточного формата |
|
Очередь буферов |
буферов три, и распоряжается ими genlock в железе; процессор не выделяет и не возвращает буферы |
|
Согласование форматов |
форматы зашиты в битстрим при сборке; согласовывать в рантайме нечего и не с кем |
Ни одна из трёх. Остаются запись регистров при старте и копирование готового файла — то, что пользовательское пространство делает через /dev/mem без строки кода в ядре.
Побочная выгода, которая на этапе отладки оказалась важнее правильности архитектуры: последовательность подъёма живёт в одном читаемом файле, и её можно построчно сверить с TCL-скриптом, которым она была доказана на голом железе. Когда вы ещё не уверены, что окно установления приёмника выбрано верно, возможность сравнить две реализации строка к строке стоит дороже, чем абстракция /dev/video0.
Была и историческая сторона: исходный план проекта описывал именно путь через V4L2 — медиа-граф, захват в память, цикл копирования кадров в буфер дисплея. Этот план не был ошибочным, он был написан под предположение, что часть работы достанется процессору. Предположение умерло, когда выяснилось, что логика довозит кадр до монитора сама. Три этапа плана после этого оказались не «невыполненными», а ненужными — и это, пожалуй, самый полезный вид правки плана.
Глава 8. Контракты внутри конвейера
8.1. Конвейер и его тактовые области
Вот тракт целиком. Пунктиром отмечены границы тактовых областей — они важнее стрелок данных, потому что именно на них ломаются наивные предположения.

Частоты здесь не выдуманы: 51,01 МГц на выходе приёмника и 148,5 МГц пиксельной — из таблицы тактовых сигналов отчёта Vivado, роль каждой из трёх FCLK разобрана в части 0, глава 3.4.
Переходы между областями делают готовые блоки с буферами внутри — своих синхронизаторов писать не нужно. А вот что переходит границы вместе с данными и о чём легко забыть — это обратное давление: сток, который не готов принимать, останавливает цепочку назад, через все границы, до самого приёмника. Отсюда первый контракт.
8.2. Архитектурный шов: кадровый буфер
Прежде чем перечислять контракты, стоит назвать главное свойство этой схемы.
Кадровый буфер — не «место, где полежат пиксели». Это шов, который разделяет проект на две независимые половины. До буфера всё синхронно сенсору: темп задаёт он, и никто другой на него не влияет. После буфера каждый потребитель живёт своим темпом: монитор просит 60 кадров в секунду, энкодер выдаёт около 24, а сеть отдаёт около 23.
Три разных темпа за одним швом — это и есть причина, по которой три последующих главы существуют. И это же объясняет, почему ветку сжатия удалось добавить к готовому проекту, не тронув камерную сторону: новый потребитель просто подключился к шву третьим.
Правило на будущее: если в системе есть место, где темп меняется, сделайте его явным и наблюдаемым. Наш шов и явный (три буфера по известным адресам), и наблюдаемый (содержимое можно прочитать и проверить).
8.3. Контракт 1: готовность идёт против потока данных
Данные идут вперёд, готовность — назад. Блок, который не выставил признак готовности, останавливает предыдущий, тот — предыдущего, и так до приёмника, у которого внутри всего лишь буфер на несколько строк. Когда он переполняется, приём прекращается.
Практическое следствие: блок, полученный из высокоуровневого синтеза, пока не запущен, готовность не выставляет. Значит, если запустить сенсор раньше, чем демозаик, приём остановится через доли секунды — и выглядеть это будет как отказ физики, потому что смотреть вы будете на счётчики приёмника.
Отсюда правило порядка: стоки поднимаются раньше источников. Демозаик и гамма запускаются до сенсора. Ветка показа поднимается раньше камеры ещё и по другой причине: она от камеры не зависит вовсе, поэтому тёмный монитор после этого шага однозначно указывает на HDMI, а не на сенсор. Это разделение подозреваемых, купленное порядком операций, — и оно бесплатно.
Ровно одно исключение из правила: канал захвата. Взводить его до потока нельзя — границы кадра ещё нет, и он начнёт посреди первого, обрезанного; под идущим потоком тоже нельзя — начнёт посреди строки. На чужом битстриме из этого следовало единственное узкое окно, кадровый гасящий интервал, и подъём сенсора приходилось встраивать в середину настройки конвейера. В своём дизайне окно убрано при сборке: канал собран так, что частичный первый кадр он отбрасывает вместо остановки с ошибкой. Порядок «сток раньше источника» при этом остался — канал по-прежнему взводят прямо перед тем, как разрешить сенсору передавать. Разбор обоих вариантов — в части IV, глава 21.
8.4. Контракт 2: кто владеет кадровым буфером
Захват пишет со скоростью сенсора, показ читает со скоростью монитора. 30 против 60. Совпасть они не могут в принципе: это два независимых генератора.
Если оба ходят через одну область памяти, показ неизбежно читает кадр, который захват дописывает в этот же момент. На экране это горизонтальный разрыв: верхняя часть от нового кадра, нижняя от старого. Соблазн «подогнать частоты» здесь бесполезен, и это важно понять до попытки: подгонять нечего, один темп задан сенсором, другой монитором.
Решается не темпом, а владением. Буферов три, и в каждый момент известно, какой из них чей. Захват, закончив кадр, публикует индекс буфера, в который только что писал, и обязуется не занимать тот, который сейчас читает показ. Показ следит за публикуемым индексом и, если нового кадра ещё нет, повторяет предыдущий — при 60 против 30 это каждый второй раз.
Ветка сжатия подключается к тому же набору буферов третьим потребителем и читает кадр, который уже считается готовым.
Тонкости настройки этого механизма — три бита, каждый из которых умеет тихо всё испортить, — в части V, глава 26. Здесь важен принцип: разрыв кадра — это дефект владения, а не дефект синхронизации.
8.5. Контракт 3: единица данных определяет тип канала DMA
Видеокадр и файл JPEG — данные разной природы, и это диктует разные каналы.
Кадр имеет постоянный размер. 1920 пикселей по три байта, 1080 строк, известный шаг между строками. Канал для видео такими и настраивается: геометрия задаётся заранее, и он циклически перекладывает кадры между памятью и потоком.
Файл JPEG имеет переменный размер. При качестве 75 это порядка 230 КБ, при 85 — порядка 350, и точное число зависит от содержимого кадра. Канал с фиксированной геометрией здесь не годится ни в какую сторону: либо не допишет файл, либо допишет мусором. Нужен канал, который останавливается по признаку «конец данных» от источника.
Отсюда правило, полезное далеко за пределами этого проекта: тип канала DMA выбирается по тому, кто знает длину. Если длину знает конфигурация — годится канал с геометрией. Если длину знает только источник, и знает её в момент окончания, — нужен канал, завершающийся по признаку конца.
Из этого же контракта вырастает и требование к обвязке энкодера: она обязана выставлять признак конца файла ровно там, где кончился файл, и обязана сообщать, не потерялся ли по дороге байт. Как это сделано и какие две ловушки там встретились — часть VI.
8.6. Контракт 4: соглашение о порядке байт нигде не записано
Самый недооценённый контракт проекта, и стоил он двух заходов отладки.
Кадровый буфер — это интерфейс между блоками, которые друг о друге не знают. Упаковщик кладёт три байта в слово. Тракт HDMI читает слово обратно. Ветка сжатия читает то же слово. Каждый из трёх имеет собственное представление о том, какой байт какая компонента, и наблюдаема только композиция всех трёх.
Практический вывод: вычитать правильный порядок из документации на каждый блок по отдельности практически невозможно, зато измерить композицию можно за минуту — залить одну компоненту в максимум и посмотреть, каким цветом стал экран и в каком байте памяти она оказалась.
И правило, которое из этого следует: соглашение, известное одному потребителю памяти, не известно второму. Каждая новая ветка, читающая кадровый буфер, обязана свериться тем же измерением, а не тем, что кажется естественным порядком RGB. Ветка сжатия была добавлена позже и написана «по учебнику» — и получила ту же перестановку во второй раз, уже как отдельный дефект. История обоих случаев — часть V, глава 25 и часть VI, глава 30.
8.7. Контракт 5: наблюдаемость как часть проекта
Из главы 5 следовало, что готовой диагностики у нас нет. Значит, её надо спроектировать. В конвейере намеренно оставлены точки, в которых он разрывается для наблюдения, и каждая отвечает на вопрос с заранее известным правильным ответом.
|
Точка наблюдения |
Что даёт |
|---|---|
|
Счётчики приёмника: посылки на линии и принятые пакеты |
различают «физика молчит» и «декодер не понимает» |
|
Расчёт строк в секунду по регистрам сенсора |
отделяет потерю данных от неверной кадровой частоты |
|
Метка в кадровом буфере |
работающий канал записи обязан её затереть |
|
Таблица гаммы, заполненная константой |
из конвейера обязан выйти ровно серый кадр |
|
Заливка одной компоненты |
отвечает на вопрос о порядке байт |
|
Признак конца передачи у канала DMA |
файл кончился там, где кончил энкодер, а не где кончился буфер |
|
Признак потери байта, защёлкнутый на конце кадра |
относится к кадру, который лежит в памяти |
Обратите внимание на общее свойство: ни одна из точек не требует смотреть на картинку. Это принципиально, потому что статичная сцена перед камерой выглядит одинаково при десятке разных неисправностей, а два одинаковых снимка буфера ничего не доказывают. Методика, стоящая за этой таблицей, разобрана в части 0, глава 1.4; здесь она превращается в требование к архитектуре: точки наблюдения закладываются заранее, а не дорисовываются, когда всё сломалось.
8.8. Контракт 6: границы владения памятью относительно ядра
Последний контракт — не внутри логики, а между логикой и операционной системой, и он отложен до части VII, где обретает конкретность. Сформулировать его стоит здесь, потому что он тоже следствие варианта C.
Логика пишет в физическую память по адресам, которые зашиты в битстрим и в скрипты. Ядро Linux распоряжается той же микросхемой. Никакого механизма, который бы автоматически их развёл, нет: логика не спрашивает разрешения у распределителя страниц. Значит, области под кадровые буферы и под сжатые кадры надо у ядра забрать — явно, при загрузке, вместе с указанием не отображать их в кэшируемую память.
Что бывает без этого, полезно знать заранее: сначала портится картинка, потом ядро. То есть симптом появляется на той стороне, которая ни при чём, и некоторое время выглядит как дефект видео.
8.9. Что из этой части следует для порядка работ
Шесть контрактов дают порядок сборки, и он не совпадает с порядком, в котором данные текут.
-
Сначала доказать, что процессор дотягивается до логики. Без этого любое измерение — гадание.
-
Затем поднять ветку показа: она не зависит от камеры и делит подозреваемых пополам.
-
Затем запустить блоки обработки — стоки раньше источника.
-
Затем канал захвата — и сразу за ним разрешение сенсору передавать.
-
Затем ветку сжатия, подключив её к уже работающему шву.
-
И только в конце — сеть, где к цепочке добавляются драйверы и сокеты.
Ровно в этом порядке идут части IV–VIII. А перед ними — то, без чего ни один шаг невозможен: физика интерфейса камеры и файл ограничений, в котором нельзя ошибиться.
Дальше — часть II: уровень ниже всех контрактов — что физически происходит на парах шлейфа, как из напряжений получаются строки изображения, почему разъём пронумерован в обратную сторону и какие проверки делаются до подачи питания.
Часть II. Физический уровень: шлейф, пары, протокол
Глава 9. Электрический уровень: что приходит по шлейфу
9.1. Одна линия, два совершенно разных режима
Первое, что удивляет в D-PHY: каждая пара работает в двух режимах с разной электрикой, разной логикой и разным назначением. Не «быстро и медленно», а именно два разных способа передавать.
Низкоскоростной режим (LP, low power). Обе линии пары работают как независимые однопроводные сигналы с логическими уровнями около 1,2 В. Скорость низкая, потребление маленькое. В этом режиме передаются не данные, а состояния: линия свободна, сейчас начнётся передача, передача кончилась.
Высокоскоростной режим (HS, high speed). Пара работает как дифференциальная, с очень маленьким размахом — порядка 200 мВ — и низким общим уровнем, тоже около 200 мВ. Маленький размах — это и есть причина низкого энергопотребления на высокой скорости, и одновременно причина всех сложностей приёма.
Названия состояний LP строятся из уровней двух линий. Состояние LP-11 — обе линии высокие — означает «стоп», линия простаивает. Это то состояние, в котором находится исправный модуль, которому ещё не сказали передавать, и именно поэтому оно окажется полезным для проверки шлейфа (11.5).
Зачем такая двойственность: перед каждой высокоскоростной посылкой передатчик должен сообщить приёмнику «сейчас начнётся», и сделать это надо в режиме, который приёмник слышит всегда. Отсюда и последовательность выхода в HS, разобранная в 10.1, и необходимость двух наборов входов в кристалле — по два на каждую пару.
Пар всего три: одна тактовая и две информационные. Каждая заведена в кристалл дважды — как дифференциальный вход для HS и как два однопроводных для LP. Шесть дифференциальных линий и шесть однопроводных, двенадцать выводов; конкретные шары перечислены в части 0, глава 2.2.
9.2. Что делает пассивная сеть между разъёмом и кристаллом
Теперь понятно, зачем нужна сеть согласования, о существовании которой уже сказано в части 0. Проблема арифметически проста: сенсор в высокоскоростном режиме выдаёт дифференциальный сигнал размахом около 200 мВ с общим уровнем около 200 мВ, а вход банка общего назначения 7-й серии рассчитан на сигналы с существенно более высоким общим уровнем. Подать одно на другое напрямую — не «будет хуже качество», а «входной буфер не в своём режиме».
Сеть по XAPP894 делает две вещи:
-
приводит уровни высокоскоростной пары в окно, в котором входной буфер банка работает как задумано;
-
терминирует пару, чтобы линия не звенела отражениями.
Номиналы на этой плате — 100 и 150 Ω (резисторы R137…R147 рядом с разъёмом); точная схема — в самом XAPP894 и на схеме платы версии 1.1. Пересчитывать её не нужно: она распаяна.
С низкоскоростными линиями проще, и это приятная симметрия. Их логические уровни — около 1,2 В, а стандарт ввода-вывода HSUL_12, которым они описаны, как раз рассчитан на 1,2 В и требует опорного напряжения в половину размаха, то есть 0,6 В. Внешнего источника такого опорного напряжения на плате нет, поэтому в файле ограничений стоит INTERNAL_VREF — кристалл делает опору сам. Строка не косметическая: без неё дизайн не имплементируется, потому что стандарт без опоры не определён.
9.3. Банк 13: три стандарта, одно напряжение, защёлкнутый выбор
Здесь важная тонкость, которая объясняет, почему кажущиеся независимые решения в этом проекте связаны.
Банк ввода-вывода имеет одно напряжение питания на все свои выводы. В банке 13 у нас одновременно живут три разных стандарта:
|
Стандарт |
Кто |
Природа сигнала |
|---|---|---|
|
|
три высокоскоростные пары |
дифференциальный вход |
|
|
шесть низкоскоростных линий |
однопроводный вход с опорой 0,6 В |
|
|
I²C сенсора, линия сброса модуля, гребёнка расширения |
обычная логика 3,3 В |
Напряжение банка — 3,3 В, и это диктуется третьей строкой: шина I²C и гребёнка работают на 3,3 В, менять им уровень нельзя. Первые две строки при этом законны, но с оговорками, и оговорки не одинаковы.
HSUL_12 в банке 3,3 В — это просто входы с опорой, всё честно.
А вот LVDS_25 в банке 3,3 В законен ровно до тех пор, пока не включена встроенная дифференциальная терминация. Терминация на кристалле специфицирована для напряжения банка 2,5 В; если её включить, инструмент потребует именно 2,5 В, и имплементация остановится. Отсюда правило «держать калибровку приёмника в None или Fixed», о котором предупреждает часть 0: режим Auto включает терминацию неявно, и симптомом будет не деградация сигнала, а упавшая сборка.
Практическое следствие, которое стоит понять до того, как захочется что-нибудь улучшить: напряжение банка 13 — не свободный параметр. На плате оно в принципе переставляется резисторами (1,8 / 2,5 / 3,3 В по руководству), и у кого-нибудь обязательно возникнет мысль поставить 2,5 В, чтобы включить «правильную» терминацию. Тогда сломается I²C сенсора и всё, что висит на гребёнке. Пассивная сеть на плате рассчитана именно на эту конфигурацию, и трогать резисторы стоит только в одном случае — если вы вешаете камеру не на штатный разъём, а на гребёнку, и строите свою сеть.
Заодно в ограничениях объявлены свойства конфигурационного напряжения (CFGBVS VCCO, CONFIG_VOLTAGE 3.3). Они относятся ко всему кристаллу, а не к камере, и держатся в камерном файле для того, чтобы сборка без файла платы всё равно их получила.
9.4. Точка выборки и что такое тап задержки
Последнее электрическое понятие, без которого не читается часть V.
Приёмник должен решить, в какой момент считать значение бита. Идеально — в середине бита. Но сигнал приходит по шлейфу и по дорожкам платы, и момент прихода сдвинут на величину, которую никто не знает заранее: она зависит от длины дорожек, от температуры, от разброса производства.
Поэтому на входе каждой информационной линии стоит программируемая линия задержки. Её шаг называется тапом, и его величина не произвольна: она получается делением опорной частоты, и при опоре 200 МГц один тап — около 78 пикосекунд.
Посчитаем, что это значит для нашей скорости:
скорость линии 408 Мбит/с
длительность бита 1 / 408·10⁶ ≈ 2,45 нс
один тап задержки ≈ 78 пс
пять тапов ≈ 390 пс ≈ 1/6 длительности бита
Шестая часть бита кажется мелочью. В части V, глава 23 выяснится, что именно эта мелочь отделяет «каждая строка верна» от «одна из нескольких сотен строк испорчена», причём при внешне полностью рабочем приёме. Здесь достаточно запомнить механизм: тап сдвигает точку выборки, значение задаётся при сборке битстрима, и без калибровки по опорной частоте вся эта механика не имеет смысла — почему именно, в 12.1.
Глава 10. Протокол CSI-2: от битов к строкам
10.1. Как линия выходит в высокоскоростной режим
Последовательность, которую проходит информационная пара перед каждой посылкой:
LP-11 стоп: обе линии высокие, ничего не передаётся
↓
LP-01 передатчик просит слово
↓
LP-00 мостик: обе линии низкие
↓
HS-0 пара перешла в дифференциальный режим,
приёмник ждёт окно установления
↓
0xB8 синхробайт: по нему приёмник понимает, где границы байтов
↓
данные байты пакета
↓
LP-11 стоп
Ключевой момент — между переходом в HS и синхробайтом. Приёмник не может начать искать байты сразу: линия ещё «устраивается» после смены режима, и попытка декодировать даст мусор. Поэтому он выжидает интервал, который называется временем установления, и только потом начинает искать 0xB8.
Отсюда очень характерный вид отказа. Если интервал выбран слишком коротким, приёмник ловит мусор и жалуется на ошибку начала передачи. Если слишком длинным — синхробайт уже прошёл, искать нечего, и приёмник не жалуется вообще: он не начал декодировать, значит, ему не на что ругаться.
10.2. Окно установления: считаем допуск для своей скорости
Спецификация D-PHY задаёт допустимое время установления не одним числом, а интервалом, причём границы зависят от скорости линии:
минимум 85 нс + 6 · UI
максимум 145 нс + 10 · UI
UI (unit interval) — длительность одного бита. Для нашей скорости 408 Мбит/с она равна 2,45 нс, значит:
минимум 85 + 6 · 2,45 ≈ 100 нс
максимум 145 + 10 · 2,45 ≈ 170 нс
Семьдесят наносекунд допуска — это на самом деле широко. Свип по значениям подтвердил ровно расчёт: всё от 80 до 180 нс работает одинаково, ниже и выше приём разваливается. Значение в нашей сборке — 130 нс, середина.
Теперь самое поучительное. Штатная подсистема производителя собрана под скорость 1000 Мбит/с, потому что вендорскому модулю нужно столько. Внутренний счётчик, отмеряющий это окно, отмасштабирован под ту скорость. Если такой приёмник поставить на нашу линию 408 Мбит/с, заданные в нём 145 нс превращаются в
145 нс × 1000 / 408 ≈ 355 нс
то есть вдвое больше максимума допуска. Приёмник начинает слушать спустя столетия после синхробайта — и молчит без единой ошибки. Обратный пересчёт даёт и рецепт для чужого битстрима: чтобы получить фактические 145 нс, в такой приёмник надо записать 145 × 408 / 1000 ≈ 59. На живой плате заработало 60, что подтвердило и расчёт, и диагноз.
Правильное решение, разумеется, не подбирать число, а собрать приёмник под свою реальную скорость — тогда все внутренние константы согласованы. Именно это и сделано в части V. История поиска — в части IV, глава 19.
Полезная деталь напоследок: параметр этого окна в подсистеме задаётся в наносекундах, а не в тактах или тапах. Из этого сразу следует, что он привязан к скорости сборки, и следует то, почему пересчёт по отношению скоростей вообще работает.
10.3. Что лежит в пакете
Приёмник состоит из двух этажей, и это разделение стоит держать в голове, потому что диагностика у них разная.
Нижний этаж (D-PHY) превращает напряжения в байты: следит за состояниями линии, выжидает окно установления, находит синхробайт, десериализует поток. Его собственные счётчики считают высокоскоростные посылки — не пакеты, не строки, а факты «линия выходила в HS».
Верхний этаж (контроллер CSI-2) превращает байты в строки: разбирает пакеты, проверяет их целостность, размечает кадры.
Пакеты бывают двух видов. Длинный пакет несёт данные:
┌──────────────────────────────────────────────┐
│ заголовок 4 байта: │
│ идентификатор (виртуальный канал + тип) │
│ счётчик слов, 2 байта │
│ контрольный код заголовка (ECC) │
├──────────────────────────────────────────────┤
│ полезная нагрузка: счётчик слов байт │
├──────────────────────────────────────────────┤
│ контрольная сумма нагрузки (CRC), 2 байта │
└──────────────────────────────────────────────┘
Короткие пакеты несут события — начало кадра, конец кадра. Именно из них контроллер узнаёт, где границы изображения, и именно они не появятся, если сенсору не разрешить строчную и кадровую разметку (это те самые «регистры транспорта» из части I, глава 6.3).
Что мы должны увидеть в регистрах исправно работающего приёмника:
data_type = 0x2B RAW10
bytes_per_line = 2400
Число 2400 не магическое, оно проверяемое: 1920 пикселей по 10 бит — это 19 200 бит, то есть ровно 2400 байт. Если приёмник показывает другое число, значит, сенсор настроен не на тот размер строки или не на тот формат, и искать надо в его регистрах, а не в физике.
Про два вида контроля целостности полезно знать, что они разные по последствиям. Ошибка в заголовке (ECC) означает, что пакет неправильно опознан — потерян целый фрагмент строки. Ошибка в нагрузке (CRC) означает, что байты доехали, но часть искажена — строка на месте, но с шумом. Именно вторая картина будет в части V, когда линк формально работает, а картинка рваная.
10.4. Байтовый клок и занятость линии
Приятная особенность CSI-2: почти всё в нём можно проверить арифметикой, и несколько минут счёта экономят часы подозрений.
Приёмник отдаёт данные наверх на байтовой частоте, которая получается из скорости линии делением на восемь:
408 Мбит/с ÷ 8 = 51 МГц
Ровно это и стоит в таблице тактовых сигналов отчёта Vivado: rxbyteclkhs 51,01 МГц (а сама тактовая пара — 204,04 МГц, потому что данные передаются по обоим фронтам). То есть инструмент подтверждает скорость линии независимо от любых наших измерений на плате.
Теперь посчитаем, насколько плотно занята линия. На строку приходится 2400 байт на две линии, то есть 1200 байт на каждую:
время передачи строки 1200 / 51,01·10⁶ ≈ 23,5 мкс
период строки 1 / 33 062 ≈ 30,25 мкс
занятость 23,5 / 30,25 ≈ 78 %
Перепроверим с другой стороны, через байты в секунду:
полезный поток 2400 × 33 062 ≈ 79,4 МБ/с
ёмкость линии 2 × 408 / 8 = 102 МБ/с
занятость 79,4 / 102 ≈ 78 %
Два независимых способа дают одно число — значит, картина мира согласована. И заодно видно, что запас по времени есть: 22 % периода строки линия простаивает, это межстрочное гашение.
Этот приём стоит запомнить как рабочий инструмент: посчитайте, сколько строк в секунду вы должны получать, и сравните с тем, что показывает приёмник. Именно так был разоблачён ложный диагноз «линия теряет сорок процентов строк», за которым стоял всего лишь незаданный вертикальный размер кадра (часть I, глава 6.2).
10.5. Что характер отказа говорит о его причине
Из устройства двух этажей приёмника выводится диагностическая таблица, которая дальше в серии будет использоваться постоянно. Её стоит выписать здесь, пока речь идёт о протоколе, а не о конкретных дефектах.
|
Что видно |
Что это означает физически |
|---|---|
|
Счётчики D-PHY не растут |
линия не выходит в HS: нет питания модуля, нет ориентации, сенсору не сказали передавать |
|
Счётчики D-PHY растут, пакетов ноль, ошибок ноль |
байты не доходят до разбора: десериализатор не нашёл синхробайт — окно установления, калибровка задержек |
|
Есть ошибки начала передачи |
байты приходят, но границы найдены неверно: окно слишком короткое, точка выборки не там |
|
Ошибки заголовка |
пакеты бьются целиком, теряются фрагменты строк |
|
Ошибки контрольной суммы нагрузки |
строки доезжают с искажениями: точка выборки на краю |
|
Всё чисто, а кадров нет |
транспорт исправен, разметка кадров не разрешена в сенсоре |
Обратите внимание на вторую строку: отсутствие ошибок — это не отсутствие улик, это сама улика. Приёмник, который получает байты и не может их разобрать, обязан жаловаться. Приёмник, который молчит совсем, байтов не получает. Это соображение сэкономит целый день в части IV и ещё один в части VII.
Глава 11. Разъём J2 и шлейф
11.1. Правило зеркала и механизм гибели модуля
Разъём J2 — тот же типоразмер, что у Raspberry Pi: плоский шлейф на 15 контактов. Механически совместим. Электрически — пронумерован в обратную сторону.
Документированные точки:
|
Контакт |
Сигнал |
Тот же сигнал на разъёме Pi |
|---|---|---|
|
1 |
+3,3 В |
15 |
|
2 |
SDA |
14 |
|
3 |
SCL |
13 |
|
4 |
|
12 |
|
5 |
|
11 |
Правило простое: контакт N на плате соответствует контакту 16 − N на стороне модуля. Пары данных и земли подчиняются тому же зеркалу.
Именно поэтому руководство производителя требует вставлять шлейф контактами вверх: переворот шлейфа компенсирует переворот нумерации.
Теперь механизм отказа, который стоит проговорить пин к пину, потому что он необратим. Если вставить шлейф «как на Pi», то контакт 1 платы, на котором +3,3 В, окажется напротив контакта 1 модуля, а на нём — земля. То есть вы подаёте питание прямо на землю модуля. Камера умирает мгновенно, и никакой диагностики после этого не будет: сенсор просто не отвечает по I²C, и отличить его от неправильно собранного проекта нечем.
Это единственный шаг всей серии, который нельзя откатить. Поэтому дальше — прозвонка.
11.2. Контакты 4 и 5: ни данные, ни питание
Две линии разъёма не относятся ни к передаче, ни к питанию, и обе легко понять неправильно.
CAM_GPIO (контакт 5, шар AB15) на модуле Raspberry Pi — вход включения и сброса. Вендорское приложение дёргает его низким уровнем примерно на секунду при старте. Важная деталь: в файле ограничений на этой линии объявлена подтяжка, поэтому линия высокая ещё до того, как какая-либо программа выполнится, и питание модуля не пропадает само по себе.
Вторая важная деталь — эта линия не является выводом какого-либо блока в программируемой логике. Она приходит из процессорной системы через механизм EMIO и оказывается для программы линией GPIO номер 54. Это объясняет, почему управление ею в скриптах и в программе выглядит как обращение к регистрам процессорной системы, а не к блоку в логике.
CAM_CLK (контакт 4, шар U14) — линия внешней тактовой частоты для сенсора, и стандартному модулю Pi версии 1 она не нужна: у него собственный генератор 24 МГц. На самом Raspberry Pi эту линию давно приспособили под светодиод активности. Вывод существует, но в нашем дизайне не задействован. Тактировать его «на всякий случай» не надо — понадобится только на клоне, которому нужен внешний XCLK.
11.3. Шлейф как участок тракта
Шлейф — не провод, а часть линии передачи, и относиться к нему стоит соответственно.
Он плоский, без экрана, с фиксированным волновым сопротивлением, на которое и рассчитана сеть согласования на плате. Из этого три практических правила.
Не удлинять. Переходники и удлинители меняют характеристики линии, а запаса у нас нет: часть V покажет, что разница в шестую часть длительности бита уже видна на картинке.
Не изгибать резко и не защемлять. Механическое повреждение одной линии пары даст не «чуть хуже», а стойкие ошибки контрольной суммы, которые вы будете искать в конфигурации.
Не переносить камеру на гребёнку расширения. Соблазн понятен: гребёнка рядом, выводов свободных много. Но там нет ни сети согласования, ни гарантии, что тактовая линия попадёт на вывод, способный тактировать десериализатор (об этом 12.2), и придётся ещё переставлять напряжение банка со всеми последствиями из 9.3. Это не правка файла ограничений на пять минут, а отдельный проект.
11.4. Лабораторная 0: прозвонка до подачи питания
Цель: получить на бумаге карту «контакт разъёма → сигнал» и убедиться, что она совпадает с файлом ограничений constraints/camera_pins.xdc.
Инструмент: обычный тестер в режиме прозвонки. Плата без питания, шлейф вставлен так, как вы собираетесь его оставить.
Порядок:
-
Найдите на плате контакт, который считаете первым, и убедитесь, что он идёт к цепи +3,3 В, а не к земле.
-
Убедитесь, что этот контакт не соединён с землёй модуля на другом конце шлейфа. Это главная проверка: она защищает от необратимой ошибки из 11.1.
-
Прозвоните SDA и SCL до шаров
V4иV5. -
Прозвоните тактовую пару до
Y9и информационные пары доW6/W5иT4/U4. Полный список — в части 0, глава 2.2, и в файле ограничений.
Результат: карта соответствия на бумаге и уверенность, что подача питания не убьёт модуль. Если хотя бы одна точка не сошлась — не включайте питание, разберитесь с ориентацией.
Отдельно стоит сказать, почему в этой серии вообще есть шаг с тестером в руках, хотя вся остальная работа — на экране. Все дальнейшие проверки предполагают, что модуль жив. Если он умер на первом включении, то каждая следующая проверка будет давать правдоподобный отрицательный результат, и вы будете последовательно чинить исправные вещи. Единственная защита от этого сценария — пятиминутная прозвонка.
11.5. Первая проверка под питанием: состояние LP-11
Как только плата ожила и битстрим залит, есть проверка, которая стоит одного чтения регистра и отвечает сразу на несколько вопросов. Её можно сделать до всякого I²C, до таблиц режима и до отладки потока.
Прочитайте состояние линий у приёмника. Исправная картина в простое:
тактовый лейн stop = 1
лейн 0 stop = 1
лейн 1 stop = 1
То есть все три пары стоят в состоянии LP-11. И вот почему это ценно: пассивно такое состояние не возникает. Обе линии каждой пары высокие потому, что их активно держит высокими передатчик модуля. Значит, одновременно доказано:
-
на модуль пришло питание;
-
его собственный D-PHY запустился;
-
шлейф вставлен правильной стороной и контакт есть;
-
наш приёмник видит эти линии и правильно их интерпретирует.
Одно чтение регистра закрывает весь физический уровень. Если картина другая — дальше идти нет смысла: возвращайтесь к 11.4.
Именно так и выглядела первая удачная проверка на живой плате: приёмник показал стоп-состояние на всех трёх парах до первой транзакции по I²C. После этого можно было спокойно заниматься сенсором, зная, что провода в порядке.
Глава 12. Что физический уровень требует от тактирования
Роли трёх частот процессорной системы уже перечислены в части 0, глава 3.4. Здесь — обратная сторона: какие требования к тактированию выставляет физика приёма, и почему их нельзя обойти настройкой.
12.1. Опора 200 МГц: откуда берётся разрешение задержки
Вернёмся к тапу задержки из 9.4. Величина тапа — около 78 пс — не заложена в кремний константой. Она получается из опорной частоты, и блок калибровки непрерывно подстраивает элементы задержки так, чтобы шаг оставался верным при изменении температуры и напряжения питания.
Из этого три следствия, каждое из которых в проекте уже проявилось.
Опора обязательна. Блок калибровки выставляет признак готовности только после успешной калибровки. Без годной опоры признак не появляется, и все задержки на входах остаются такими, какими проснулись при включении. Приём при этом не отказывает честно: он выглядит как проблема целостности сигнала, то есть отправляет вас к шлейфу и разъёму.
Опора должна быть именно 200 МГц. Не «примерно», а в пределах допуска блока: от этой частоты напрямую считается шаг задержки. Скрипт подъёма и программа читают частоту обратно и отказываются работать, если она не та.
Калибровка происходит один раз, при выходе из сброса. Это самое неприятное свойство: если сброс не имел фронта, калибровки не было, и никакого сообщения об этом не будет. Целая глава части VII посвящена одной строке загрузочного скрипта, которая снимала сброс, никогда его не выставив, — и приёмник от этого молчал при идеально исправной физике.
Отсюда вывод, который стоит унести из этой главы: опорная частота и фронт сброса — часть физического уровня, а не «настройки системы». Их отсутствие проявляется как электрический дефект.
12.2. Почему тактовая пара обязана прийти на особый вывод
Десериализатор — это блок внутри ячейки ввода-вывода, который принимает биты на высокой частоте и отдаёт байты на низкой. Ему нужна тактовая частота, и получить её он может только по специальным путям тактирования внутри области кристалла. Эти пути доступны не с любого вывода, а только с тех, которые объявлены способными принимать тактовый сигнал.
На плате тактовая пара HS заведена на вывод Y9, и он именно такой. Проверить это можно механически, не доверяя ни статье, ни схеме: скрипт scripts/query_pins.tcl печатает банк и функцию каждого вывода из базы устройства.
А подтверждение того, что вся конструкция действительно собралась именно так, видно в отчёте о занятости ресурсов. Вот выдержка, и это фактически портрет нашего физического интерфейса:
|
Примитив |
Сколько |
Что это |
|---|---|---|
|
Дифференциальные входные буферы |
3 |
три высокоскоростные пары |
|
Элементы задержки на входе |
2 |
две информационные линии |
|
Блок калибровки задержек |
1 |
тот, которому нужна опора 200 МГц |
|
Десериализаторы на входе |
2 |
две информационные линии |
|
Буферы тактирования области |
2 + 3 |
пути от тактовой пары внутрь |
|
Сериализаторы на выходе |
8 |
четыре пары HDMI |
|
Задействованные выводы |
27 из 200 |
камера, HDMI, светодиоды |
Три дифференциальных входа, два десериализатора, две задержки, один блок калибровки — ровно то, что и должно быть у двухлейнового приёмника с одной тактовой парой. Если бы в вашей сборке эти числа оказались другими, это был бы повод остановиться и разобраться до всякой отладки на плате.
Последняя строка попутно объясняет, почему в этом проекте выводы логики не являются дефицитом: занято 27 из 200, потому что консоль, сеть, карта памяти и USB висят на выводах процессорной системы и до логики не доходят вовсе.
12.3. Камера — источник частоты, которым мы не управляем
Есть ещё одно следствие физического уровня, которое проще всего упустить, а оно определяет архитектуру.
Тактовую частоту высокоскоростной передачи задаёт сенсор. Она получается из его собственного генератора 24 МГц, умноженного его внутренним умножителем по его же регистрам. Мы её не задаём и не подстраиваем: мы её принимаем и из неё получаем байтовую частоту 51 МГц, на которой работает выход приёмника.
Значит, в системе есть тактовая область, которая приезжает по кабелю и не синхронна ничему внутри платы. Дальше по конвейеру обработка идёт уже на частоте процессорной системы, а показ — на пиксельной частоте, привязанной к требованиям монитора. Три независимых области, и ни одна не может «догнать» другую.
Это ровно то, из чего в части I, глава 8.2 вырос кадровый буфер как архитектурный шов. Теперь видно, что шов не был проектным предпочтением: он следствие физики. Сенсор диктует один темп, монитор другой, и единственное место, где два темпа могут встретиться, не мешая друг другу, — память с несколькими буферами и правилом владения.
12.4. Что нельзя починить настройкой
Полезный список на будущее: вещи, которые определяются при сборке или платой и не подбираются в работающей системе.
|
Параметр |
Почему не подбирается |
|---|---|
|
Напряжение банка 13 |
одно на банк, и на нём висит логика 3,3 В |
|
Дифференциальная терминация |
включение делает дизайн неимплементируемым |
|
Значение тапа задержки |
регистр при некоторых режимах сборки только для чтения; свип даст шестнадцать одинаковых результатов |
|
Скорость, под которую собран приёмник |
внутренние константы масштабированы при сборке |
|
Вывод тактовой пары |
нужен вывод, способный тактировать десериализатор |
|
Потолок скорости линии |
определяется разводкой платы, а не документацией; практически около 700 Мбит/с |
Первая строка и третья — самые обидные, потому что попытка подобрать их в рантайме выглядит успешной: регистр читается, значение пишется, ничего не падает, и только результат не меняется. Отсюда общее правило: прежде чем перебирать значение, убедитесь, что оно вообще имеет эффект — например, что записанное читается обратно изменившимся.
Дальше — часть III: то же правило, применённое к инструменту. Vivado молча игнорирует неверное имя параметра и молча не применяет ограничение к выводу, которого нет, — поэтому сборка устроена как набор заслоновв. Плюс файл ограничений, в котором запреты этой части превращаются в строки, и чтение отчётов, подтверждающих скорость линии независимо от платы.
Часть III. Инструмент, который молчит: сборка и ограничения
Глава 13. Окружение и чужой IP: что спросить до первой сборки
13.1. Почему установка проверяется отдельной командой
Vivado — большой комбайн, и «установлен» не означает «пригоден». Он может открываться, собирать примеры и при этом не иметь поддержки нужной части или лицензии на нужный блок. Симптом в худшем варианте — сборка, которая умирает через сорок минут, на этапе генерации битстрима.
Поэтому первый шаг — задать вопрос дёшево:
make env
# vivado -mode batch -source scripts/check_env.tcl
Что печатается и что в этом смотреть:
CHECK_VERSION: 2025.2 версия
CHECK_PART_COUNT: <n> сколько частей семейства видно
CHECK_PART: xc7z020clg484-2 нужная должна быть в списке
CHECK_BOARD_COUNT: 0 файлов платы нет, и это нормально
CHECK_DONE
Отсутствие файлов платы (CHECK_BOARD_COUNT: 0) — ожидаемое: для этого модуля их и не существует, вся распиновка приходит из наших файлов ограничений. А вот если xc7z020clg484-2 не появился в списке частей, чинить надо установку, а не скрипты.
13.2. Три вопроса про IP, которые дешевле задать заранее
Второй скрипт задаёт вопросы уже про конкретные блоки, из которых собран конвейер. Он создаёт проект в памяти, ничего не пишет на диск и работает секунды:
vivado -mode batch -source scripts/check_ip.tcl
Вопросов три, и каждый закрывает свой сценарий провала.
Существует ли блок для этой части. Каталог IP фильтруется текущей частью, поэтому пустой ответ означает не «нет такого IP», а «на этом кристалле не поддерживается». Вендорский проект собран в Vivado 2023.1 с фиксированным набором версий; новая версия инструмента переименовывает, меняет версии и иногда снимает поддержку устройств.
Лицензирован ли он. Здесь главный подозреваемый — подсистема приёмника MIPI CSI-2: она была платной, и отсутствие лицензии позволяет спокойно настроить блок, а отказывает только на генерации битстрима.
Примет ли он наши параметры. Это и есть самое интересное: скрипт создаёт приёмник и пытается задать ему две линии, формат RAW10 и скорость линии 408 Мбит/с вместо вендорских 1000 — ту самую скорость, ради которой мы вообще собираем свой дизайн (обоснование — в части 0, глава 2.3, расчёт допуска — в части II, глава 10.2).
Две строки в выводе, которые выглядят как проблема и ею не являются:
IPCHK_MISSING xilinx.com:ip:axi_interconnect
IPCHK_MISSING digilentinc.com:ip:axi_dynclk
Первого блока в Vivado 2025.2 действительно больше нет, и наш дизайн его не использует: вместо него стоит smartconnect. Второй — программируемый генератор пиксельной частоты из вендорского проекта, от которого мы отказались сознательно. Обе строки — наследство списка проверок, а не дефект сборки.
13.3. Правило, которое здесь становится главным: читать обратно, а не верить записи
Теперь то, из-за чего эта глава называется так, как называется.
Команда, которой в Vivado задают параметры блока, не сообщает об ошибке в имени параметра. Опечатались, взяли имя из документации другой версии, придумали по аналогии — команда выполнится успешно и не сделает ничего. Блок останется с настройками по умолчанию.
Последствия у этого класса ошибок особенно неприятные, потому что дизайн собирается. Приёмник, которому «задали» две линии, а он остался с четырьмя. Тайминг-контроллер, которому «задали» 1080p, а он остался с чем-то своим. Никакой ошибки, обычный битстрим, и молчаливо неправильный размер кадра где-то в середине конвейера.
Отсюда два инструмента и одна хорошая привычка.
Инструмент первый — узнать настоящие имена параметров у установленного IP:
vivado -mode batch -source scripts/dump_ip_params.tcl -tclargs v_tc clk_wiz
# без аргументов печатает набор блоков конвейера
Скрипт печатает все параметры блока с текущими значениями и, где они есть, допустимыми диапазонами. Пять минут чтения этого вывода дешевле одной сборки, собранной с угаданным именем.
Инструмент второй — сам check_ip.tcl, который после записи параметров читает их обратно и печатает:
IPCHK_CSI CMN_NUM_LANES = 2
IPCHK_CSI CMN_PXL_FORMAT = RAW10
IPCHK_CSI C_HS_LINE_RATE = 408
IPCHK_CSI C_IDLY_TAP = ...
IPCHK_CSI C_HS_SETTLE_NS = ...
Привычка же формулируется одной строкой и относится ко всему проекту: записал — прочитай обратно. Дальше в серии это правило встретится ещё трижды: в самой сборке (14.4), при проверке частот процессорной системы и при попытке подобрать задержку в регистре, который только для чтения.
13.4. Первый чужой блок: сериализатор HDMI
У 7-й серии нет бесплатного сериализатора TMDS в каталоге AMD, а без него изображение из кристалла не выйдет — микросхемы-передатчика на плате нет (часть 0, глава 2.6). Digilent отдаёт такой блок под свободной лицензией, и им же пользуется вендорский проект.
./scripts/get_ip.sh
Скрипт делает разряжённое клонирование библиотеки Digilent и забирает две вещи: сам блок rgb2dvi и определение шины TMDS. Второе выглядит необязательным, и формально так и есть — выводы соединятся по одному и без него. Но тогда каждая операция с блок-дизайном будет печатать критическое предупреждение о ненайденном определении шины, а предупреждения, к которым вы привыкли, — это те, которые вы пропустите в день, когда одно из них окажется важным.
Клонирование разряжённое намеренно: полная библиотека — это сотни мегабайт IP для плат, которых у нас нет.
13.5. Второй чужой блок: энкодер JPEG
git clone https://github.com/lcapossio/mjpegZero.git ip_repo/mjpegZero
Три детали, каждая из которых экономит недоумение.
Путь важен. Скрипт сборки ищет именно ip_repo/mjpegZero и при отсутствии честно останавливается с подсказкой. Клонировать в другое имя не надо.
Это не упакованный IP, а набор исходников. В проект они попадают как файлы, а в блок-дизайн — через нашу обвязку rtl/mjpeg_wrap.v как ссылку на модуль. Почему обвязка нужна и что она исправляет — часть VI.
Берётся не всё. В сборку идут только файлы rtl/*.v из этого репозитория. Каталог rtl/vendor/ содержит обёртки блочной памяти для других семейств ПЛИС, а rtl/eth/ — собственный сетевой тракт автора, который здесь бесполезен: физический уровень Ethernet этой платы висит на выводах процессорной системы и до программируемой логики не доходит вовсе.
Обе зависимости нужны только для полного камерного битстрима. Скелет из главы 14 собирается без них.
Глава 14. Скрипт как спецификация
14.1. Почему сборка скриптом, а не мышью
Блок-дизайн, собранный в графическом редакторе, невозможно воспроизвести. Через неделю вы не вспомните, какое из двадцати окон настройки приёмника стояло в режиме Fixed, а какое в None — а разница между ними, как покажет часть V, это разница между чистой картинкой и рваными полосами.
Поэтому scripts/build_camera.tcl — одновременно и сборка, и спецификация. В его шапке написано, почему видеотракт получил 142,857 МГц, почему память сконфигурирована явно и почему в процессорной системе включены блоки, которыми логика не пользуется. Графический редактор при этом никуда не девается: готовый проект в build/camera/ можно открыть и посмотреть схему глазами. Но источник истины — текст скрипта.
14.2. В репозитории два проекта
Собирается два разных дизайна двумя разными скриптами. Камерный — build_camera.tcl, он же make bit, и это собственно проект. Скелет процессорной системы — build_ps.tcl, он же make skeleton, и существует он ради одной задачи: доказать, что платформа под камерой исправна.
Скелет содержит процессорную систему, мастер-порт, порт в память, разрешённые прерывания и один блок GPIO на светодиодах — и ни одного камерного блока. Он собирается без чужого IP из главы 13 и работает без единой строки вашего Verilog.
Зачем он нужен, если всё равно нужен камерный битстрим: это регрессионная точка. Когда первый камерный блок начнёт вести себя странно, платформа под ним должна быть уже доказанно исправной. Отрицательный запас по удержанию на пустом скелете — это проблема тактирования, а не вашей логики, и узнать об этом лучше до того, как в дизайне появятся приёмник, три канала DMA и энкодер.
Скрипт скелета в конце печатает карту адресов и оба запаса по таймингам.
14.3. Кэш синтеза врёт — и важно знать, где именно
Классическая ловушка Vivado. Если ваш модуль включён в блок-дизайн как ссылка на модуль, инструмент синтезирует его отдельно, вне контекста, и кладёт результат в кэш в виде контрольной точки. Обычный повторный синтез имеет право переиспользовать эту контрольную точку.
Итог — худший класс ошибок из существующих: вы правите RTL, пересобираете, заливаете битстрим, и в кристалле работает старая логика. Ничего не предупреждает. В предыдущем проекте на этой плате так был потерян вечер на отладку состояния гонки, которое к тому моменту уже было исправлено в исходнике.
Теперь важное уточнение, которого в общих советах обычно нет: в этом репозитории две сборки ведут себя по-разному.
Камерный скрипт каждый раз создаёт проект заново, с нуля. Поэтому устаревшая контрольная точка ссылки на модуль пережить его не может: после правки rtl/mjpeg_wrap.v или rtl/rgb_to_yuyv.v достаточно обычного make bit. Цена — полная сборка каждый раз.
Скелет открывается инкрементально, и вот для него существует отдельный скрипт:
make rebuild
# vivado -mode batch -source scripts/rebuild_rtl.tcl
Он заново подтягивает исходники в блок-дизайн с принудительной проверкой, сбрасывает все внеконтекстные запуски синтеза, запускает их снова и в конце печатает время создания каждой контрольной точки:
REBUILD_RESET_RUN system_jpeg_0_synth_1
REBUILD_LAUNCH system_jpeg_0_synth_1
REBUILD_DCP ....dcp mtime=17:24:45
REBUILD_DONE
Это и есть проверка «прочитать обратно», только применительно к сборке: если время контрольной точки не новее вашей правки, обновление не сработало, и доверять битстриму нельзя. Второй способ, ещё честнее памяти, — сравнить контрольную сумму битстрима до и после правки.
14.4. Сборка, которая проверяет себя
Главное, что стоит унести из этой главы. Скрипт камерной сборки не просто собирает — он расставляет заслоны против молчаливых отказов. Их полезно знать, потому что по ним и читается лог.
|
Метка в логе |
Что проверяется |
Что предотвращает |
|---|---|---|
|
|
чужой IP на месте |
сорок минут сборки ради ошибки, которую видно сразу |
|
ошибка «IP not available for this part» |
блок есть в каталоге для этой части |
использование пустого объекта дальше по скрипту |
|
|
ширина GPIO из процессорной системы равна 1 |
см. ниже — самый поучительный заслон |
|
|
конфигурация памяти даёт ровно 1 ГБ |
неверная геометрия памяти, которая портит картинку, а не падает |
|
|
список выводов верхнего уровня |
ограничение, которое молча не применилось (15.2) |
|
|
карта адресов |
расхождение с программой, вешающее процессор |
|
|
что реально настроил умножитель частоты |
число в документации, взятое из головы |
|
|
три запаса по таймингам |
«сборка чистая», когда нарушена третья проверка |
Заслон с шириной GPIO стоит разобрать целиком, потому что он — идеальная иллюстрация правила 13.3.
Линия сброса камеры приходит из процессорной системы через механизм EMIO (часть II, глава 11.2), и её ширина должна быть равна одному биту. Настройка задаётся вместе с остальными параметрами процессорной системы — и не срабатывает. Причина: внутри одной команды присваивания ширина вычисляется в тот момент, когда сам механизм EMIO ещё выключен, поэтому остаётся значением по умолчанию — 64.
Дальше начинается домино. Шестьдесятчетырёхбитный порт EMIO создаёт 64 двунаправленных вывода верхнего уровня. Файл ограничений описывает один из них. Остальные 63 остаются без ограничений, и имплементация останавливается на проверке неограниченных выводов — но не сразу, а после двадцати минут размещения.
Лечится это тем, что ширина задаётся отдельной командой, после включения механизма, а результат читается обратно и сверяется. Три строки в скрипте вместо двадцати минут и недоумения.
14.5. Стадии сборки: не всегда нужен битстрим
У скрипта есть аргумент, выбирающий, где остановиться:
vivado -mode batch -source scripts/build_camera.tcl -tclargs bd # собрать и проверить блок-дизайн
make synth # плюс синтез
make bit # полностью, до битстрима
Стадия bd — секунды-минуты. Она отвечает на вопросы «все ли блоки есть», «приняты ли параметры», «сходится ли карта адресов», «как называются выводы» — то есть на всё, что проверяют заслоны из 14.4. Именно её стоит гонять, когда вы правите конфигурацию блоков.
Отдельно эта стадия пригодится в части VII: там понадобится изменить конфигурацию процессорной системы, не меняя логику, — и полная пересборка битстрима для этого не нужна, потому что настройки процессорной системы в логику не попадают. Две минуты вместо сорока.
Глава 15. Ограничения и связывание имён
15.1. Откуда берутся номера выводов
Ни один вывод в constraints/camera_pins.xdc не угадан: все скопированы из файла ограничений вендорского проекта и сверены со схемой платы, а затем механически проверены по базе устройства (scripts/query_pins.tcl — он же подтверждает, что тактовая пара попала на вывод, способный тактировать десериализатор, см. часть II, глава 12.2).
Из этого следует практическое правило: распиновку не «улучшают» по интуиции. Изменив номер вывода, вы расходитесь с медью на плате, и никакой инструмент об этом не скажет.
15.2. Молчаливый отказ: ограничение для вывода, которого нет
Второй представитель того же класса ошибок, что и неверное имя параметра.
Ограничение в файле XDC адресуется по имени вывода верхнего уровня. Если имени не существует — опечатка, переименовали порт в блок-дизайне, взяли имя из другой версии проекта — это не ошибка. Запрос вернёт пустой список, свойство применится в пустоту, а вывод отправится туда, куда его положит размещение.
Последствия зависят от того, какой это был вывод. Для светодиода — просто не работает. Для дифференциальной пары приёмника — вывод оказывается не на той паре, к которой подходит согласующая сеть, то есть у вас сохраняется полное ощущение правильно описанного интерфейса при физически неподключённом приёмнике.
Поэтому скрипт печатает полный список выводов и интерфейсов верхнего уровня:
CAM_PORTS_BEGIN
<имя> <направление> <старший>:<младший>
...
CAM_PORTS_END
Привычка: после изменения блок-дизайна сверить этот список с именами в файлах ограничений. Это ровно то же «прочитать обратно», что и в 13.3, только для другого молчаливого отказа.
15.3. Какой файл подключается в какой сборке
В репозитории несколько файлов ограничений, и они не склеены в один намеренно: разные сборки подключают разные наборы.
|
Файл |
Что описывает |
|---|---|
|
|
интерфейс камеры: пары, I²C, линия сброса модуля |
|
|
выход изображения |
|
|
генератор 50 МГц, кнопка, светодиоды |
|
|
то, что нужно сборке с процессорной системой |
Камерная сборка подключает первые два: остальное её не касается, потому что тактирование она берёт у процессорной системы, а не от генератора на плате. Скелет подключает свой набор. Если объединить всё в один файл, каждая сборка получит ограничения на выводы, которых в ней нет, — и вы приучитесь игнорировать предупреждения.
15.4. Что в файле ограничений нельзя нарушить
Электрические причины разобраны в части II, глава 9.3, здесь только сами строки и последствия, чтобы список был под рукой.
-
Дифференциальная терминация остаётся выключенной. Включает её не рука, а режим калибровки
Autoу приёмника — держитеNoneилиFixed. Последствие нарушения: имплементация требует другого напряжения банка и падает. -
Опорное напряжение 0,6 В объявляется внутри кристалла. Уберёте строку — дизайн не имплементируется, потому что стандарт низкоскоростных входов без опоры не определён.
-
Свойства конфигурационного напряжения (
CFGBVS VCCO,CONFIG_VOLTAGE 3.3) относятся ко всему кристаллу и держатся в камерном файле специально, чтобы сборка без файла платы всё равно их получила. -
Нумерация разъёма зеркальна — предупреждение живёт в шапке файла, потому что файл ограничений читают в отрыве от статьи. Механизм — в части II, глава 11.1.
Глава 16. Первый битстрим и что читать в отчётах
16.1. Сборка
make bit
Порядка сорока минут на первый прогон. В конце должны появиться:
CAM_BITSTREAM build/camera/camera.runs/impl_1/system_wrapper.bit
CAM_DONE
плюс три отчёта в reports/: тайминги, занятость, тактовые сигналы. И build/camera/ps7_init.tcl — файл инициализации процессорной системы, который понадобится в частях IV и VII. Это выходной продукт сборки, а не отдельная сущность: он генерируется из той же конфигурации процессорной системы, что стоит в блок-дизайне, и поэтому обязан ей соответствовать. Почему это критично и как он попадает в загрузчик — часть VII, глава 35.
Сохраните лог сборки целиком. По нему потом сверяются файл ограничений, описание устройств для Linux и адреса в программе.
16.2. Отчёт таймингов: три числа, а не одно
Самая частая ошибка чтения отчёта — посмотреть на заголовок. В нашем дизайне он выглядит пугающе:
Timing constraints are not met.
А содержательные числа при этом такие:
WNS +0.338 ns запас по установлению — положительный
WHS +0.015 ns запас по удержанию — положительный
WPWS -0.124 ns ширина импульса, 10 нарушенных точек
Нарушена третья проверка, и все десять точек лежат внутри сериализатора HDMI: развёртка 1080p60 требует последовательной частоты 742,5 МГц, а примитив на этом кристалле аттестован примерно до 680 МГц. Это свойство примитива, а не ошибка разводки, и на 1080p60 его не устранить (часть 0, глава 2.6).
Скрипт печатает все три числа отдельными строками (CAM_WNS, CAM_WHS, CAM_WPWS) именно поэтому: сборка, которая отчитывается только про установление и удержание, называет себя чистой в тот момент, когда лог говорит обратное.
Правило чтения: смотрите, какая именно проверка не сошлась и на каких путях, а не на итоговую фразу. Отрицательный запас по установлению в видеотракте и отрицательный запас по ширине импульса в сериализаторе — это два совершенно разных сообщения.
16.3. Отчёт занятости: сверять с портретом интерфейса
Числа занятости приведены в части 0, глава 2.4, и повторять их незачем. Полезнее сказать, что в этом отчёте смотреть в первый раз.
Раздел про примитивы ввода-вывода — это фактически портрет вашего физического интерфейса, и он должен совпасть с тем, что нарисовано в части II, глава 12.2: три дифференциальных входных буфера, два десериализатора, два элемента задержки, один блок калибровки, восемь сериализаторов на выходе. Если у вас, скажем, четыре десериализатора вместо двух — значит, приёмник остался с четырьмя линиями, то есть параметр не применился, и это надо чинить до всякой отладки на плате.
Второй ориентир — соотношение с предыдущей сборкой. Резкий скачок занятости после безобидной правки означает, что правка не безобидна.
16.4. Таблица тактовых сигналов: независимое подтверждение
Самый недооценённый отчёт. Он показывает, что инструмент на самом деле построил, и это можно сверить со своими ожиданиями:
clk_fpga_0 100.000 MHz
clk_out1_system_pixclk_0 148.500 MHz
PixelClkInX5 742.500 MHz
clk_fpga_1 142.857 MHz
clk_fpga_2 200.000 MHz
mipi_phy_if_clk_hs_p 204.040 MHz
rxbyteclkhs 51.010 MHz
Три вещи здесь стоят внимания.
142,857 вместо запрошенных 150. В скрипте запрошено ровно 150; инструмент выдал ближайшее достижимое. Это не ошибка и не небрежность — почему так вышло, разбирается в части VII, глава 35. Важно другое: число надо взять отсюда, а не из своего запроса, потому что именно с ним закрыты тайминги и именно его проверяет программа при старте.
204,04 МГц на тактовой паре камеры. Передача идёт по обоим фронтам, то есть это ровно 408 Мбит/с на линию — скорость, под которую мы собрали приёмник. Инструмент подтверждает её независимо от любых измерений на плате.
51,01 МГц байтовой частоты. Восьмая часть скорости линии, как и должно быть (часть II, глава 10.4).
16.5. Карта адресов и почему её нельзя «навести порядок»
CAM_ADDRESS_MAP_BEGIN
offset=0x43000000 range=64K (...)
...
CAM_ADDRESS_MAP_END
Адреса намеренно совпадают с вендорскими, и в окне, где у вендора стоял программируемый генератор частоты, у нас пусто. Соблазн «уплотнить» карту понятен и обходится дорого: все скрипты, написанные во время отладки на чужом битстриме, выучили эти числа, и любой из них после переезда адресов начнёт обращаться в пустоту. Чем это заканчивается на Zynq — зависанием процессора без сообщения — сказано в части 0, глава 3.3.
Практика: этот блок лога — эталон, с которым сверяются адреса в описании устройств для Linux и в программе. Три места, одно число.
16.6. Лабораторная 1: светодиоды как первое доказательство
Битстрим собран — но это ещё не значит, что процессор до логики дотягивается. Проверка, которая закрывает сразу пять вещей одним действием.
Из Linux, когда система уже поднялась (адрес — из карты камерного дизайна):
devmem 0x41200000 32 0x3 # оба светодиода включить
devmem 0x41200000 32 0x0 # выключить
Если светодиоды слушаются, одновременно доказаны: битстрим в логике, тактовая частота жива, преобразователи уровней между половинами кристалла включены, адрес совпадает с блок-дизайном, шина отвечает.
Если не слушаются — не идите в камеру. Типичные причины: битстрим не загружен, тактовая погашена, инициализация процессорной системы не выполнялась (а преобразователи уровней включает именно она), опечатка в адресе.
До операционной системы то же самое делается по JTAG, и там есть тонкость: скрипт заливки логики не выполняет инициализацию процессорной системы. Поэтому на «сырой» плате логика может быть прошита и при этом никуда не подключена. Признак поднятой процессорной системы — оба ядра в состоянии Running.
16.7. Разведка JTAG перед любой прошивкой
Последний шаг этой части, и он читающий, а не пишущий:
make probe # xsct scripts/jtag_probe.tcl
Отвечает на три вопроса: виден ли кабель, что на цепочке, действительно ли это XC7Z020. Программировать, не убедившись, что на цепочке ваш кристалл, — риск залить не туда, поэтому разведка вынесена в отдельный скрипт, который только читает.
Если цепочка видна, а список целей пуст — процессорная система подвисла:
make recover
И одно предупреждение на будущее, из опыта предыдущего проекта: перезаливка логики по JTAG под работающим Linux регулярно оставляет сеть нерабочей до перезагрузки. Рабочий цикл с операционной системой — положить битстрим на загрузочный раздел и перезагрузиться. JTAG остаётся для работы без операционной системы, и именно этим занимается следующая часть.
Дальше — часть IV: захват платы, живой сенсор, молчащий приёмник и тот порядок подъёма, без которого картинки не будет даже на идеально собранном битстриме.
Часть IV. Расследование на живой плате
Глава 17. Как забрать плату у самой себя
17.1. Зачем работать без операционной системы
Когда «камера не работает под Linux», одновременно подозреваемых десяток: загрузчик, битстрим, тактовые частоты, описание устройств, права доступа к памяти, настройка выводов шины I²C, сам сенсор. Симптом при этом один — чёрный экран.
Отладчик по JTAG убирает из этого списка всё, кроме железа: он даёт плоское физическое адресное пространство и те же регистры, которые потом будет писать программа. Правило, которое из этого выросло и которому подчинена вся серия: последовательность подъёма сначала доказывают на голом железе, а программа под Linux — перевод доказанного, а не новое изобретение.
17.2. Почему под живым Linux физические адреса не читаются
Первое, что хочется сделать с загруженной платой, — прочитать регистр отладчиком. Не получится:
MMU section translation fault
Команда чтения памяти в отладчике идёт через блок управления памятью работающего ядра, то есть через его таблицы страниц. Физический адрес, который вы вводите, для ядра — виртуальный, и означает он совсем не то, что вы имели в виду.
Отсюда процедура: сброс всей системы, остановка процессора и собственное исполнение инициализации процессорной системы. Плоское адресное пространство появляется именно так — не потому, что «отладчик сильнее», а потому, что трансляции адресов в этот момент ещё нет.
17.3. Остановить процессор за десятки миллисекунд, а не за секунду
Симптом. После захвата платы чтение регистра тактовых частот в процессорной системе (
0xF8000170) работает прекрасно. Запись в блок GPIO по адресу0x41200004падает с ошибкой трансляции адреса.Ложная гипотеза. Логика не отвечает: битстрим не залит, преобразователи уровней выключены, шина мертва. Гипотеза выглядит железобетонной — ведь процессорная система читается, а логика нет, значит проблема в логике.
Улика. Работает не то, что в кристалле ближе, а то, что нужно загрузчику. Загрузчик отображает используемую им периферию один к одному, поэтому регистр частот виден. Окна программируемой логики ему не нужны, и в его таблицах страниц их нет.
Причина. Прежняя процедура захвата делала сброс, ждала 800 мс «чтобы всё устаканилось» и только потом останавливала процессор. Восьмисот миллисекунд хватает, чтобы загрузчик с карты успел стартовать и включить блок управления памятью. Процессор останавливался уже с чужими таблицами страниц.
Правило. Останавливать сразу и проверять, где остановились. На практике процессор замирает примерно через 60 мс, оставаясь в загрузочном ROM, — а в загрузочном ROM блок управления памятью выключен по определению. Выключить его потом из отладчика нельзя: нужный системный регистр на этой цели недоступен. То есть ранняя остановка — не одна из возможностей, а единственная.
Процедура в lib_takeover.tcl поэтому не ждёт вообще: она в цикле пытается выбрать ядро и остановить его, а затем печатает, сколько это заняло и на каком адресе процессор стоит. Если адрес оказался в области памяти DDR — значит, загрузчик всё-таки успел, и плоскому адресному пространству доверять нельзя.
17.4. Когда целей не видно вообще
Второй отказ того же уровня, и он выглядит как «плата умерла».
Прерванная сессия отладчика оставляет порт доступа в состоянии ошибки транзакции. В этом состоянии список целей не содержит ни одного ядра ARM — видны только сам порт доступа и ПЛИС. Скрипт, который ищет ядро по имени, падает с сообщением «цель не найдена», не объясняющим ничего.
Лечится сбросом системы, выполненным с выбранным портом доступа. Тот же сброс с выбранной целью-ПЛИС — а это первое, что приходит в голову, когда ядер не видно, — не помогает. Разница нигде не написана и стоила отдельного вечера.
17.5. Первое, что делается после захвата: чтение частот обратно
Правило из части III, глава 13.3 — «записал, прочитай обратно» — здесь получает своё главное применение.
После захвата скрипт исполняет ps7_init.tcl, экспортированный из нашего дизайна, и сразу читает три частоты назад:
LIVE_PS7_INIT_OK ps7_init.tcl
LIVE_CLOCKS iopll=1000.0 fclk0=100.000 fclk1=142.857 fclk2=200.000
Если опорная частота приёмника отличается от 200 МГц больше чем на мегагерц, скрипт останавливается, не пытаясь ничего поднять. Причина в части II, глава 12.1: без годной опоры калибровка входных задержек не завершается, и приём отказывает так же, как от плохого шлейфа. Проверка стоит четыре строки и снимает целый класс диагнозов.
Почему нельзя взять вендорский файл инициализации: он настраивает две частоты из трёх и не трогает третью, ту самую опорную. Подробный разбор этой мины — в части VII, глава 35, где она же сработала второй раз, уже через загрузчик.
Глава 18. Угадайка: чужой битстрим и чужая программа
18.1. Приём, который стоит запомнить отдельно
В части 0 сказано, что в комплекте платы лежит готовый битстрим, исполняемый файл и инициализация процессорной системы. Теперь о том, как этим пользоваться, потому что приём применим далеко за пределами этого проекта.
Задача первого дня — не «собрать свой дизайн», а сократить число неизвестных. У нас их два: цела ли плата и правильно ли мы всё делаем. Чужой заведомо работающий битстрим убирает второе: если на нём приёмник ведёт себя так же, как у нас, дело не в наших руках.
Штатное приложение при этом устроено удачным для нас образом: оно настраивает генератор пиксельной частоты, тайминг-контроллер, оба канала DMA, демозаик и гамму на 1920×1080, а инициализацию сенсора делает последней и её провал игнорирует. Наш модуль — другой сенсор, инициализация не проходит, но приложение доходит до конца и оставляет полностью настроенный конвейер, которому не хватает только пикселей.
Отсюда рабочая схема первого этапа: чужая программа настраивает конвейер, свой код поднимает сенсор. Оба канала DMA у вендора работают через один и тот же буфер, поэтому захваченный кадр немедленно оказывается на экране — для эталона это удобно, а почему для продукта это источник разрывов, разбирает часть V, глава 26.
18.2. Что угадайка дала в первые минуты
Ещё до первой транзакции по шине I²C приёмник показал стоп-состояние на всех трёх парах. Что из этого следует — разобрано в части II, глава 11.5: питание модуля, его собственный передатчик и правильная ориентация шлейфа. Одно чтение регистра, четыре доказательства.
Дальше сканирование обеих шин I²C дало ровно то, что нужно:
шина 0: устройство 0x36, регистры 0x300A/0x300B = 0x56 0x47
шина 1: устройства 0x50 и 0x37
Первая строка — живой OV5647 (сверка идентификатора — часть I, глава 6.5). Вторая — канал данных подключённого монитора, то есть и вторая половина цепочки на месте. Обе шины у процессора выведены в логику.
Попутно выяснилась деталь, которая потом определила устройство программы: линия сброса модуля — не блок GPIO в логике, а единственная линия процессорной системы, выведенная в логику, то есть для программы это линия GPIO номер 54. Штатное приложение дёргает её низким уровнем примерно на секунду.
18.3. Где угадайка кончается
Чужой битстрим доказал плату и дал карту регистров. Но он собран под скорость линии 1000 Мбит/с, потому что вендорскому модулю нужно столько, а наш сенсор отдаёт 408. Со всеми последствиями, посчитанными в части II, глава 10.2.
Именно здесь эталон перестаёт помогать и начинается расследование.
Глава 19. Расследование первое: приёмник, который молчит
19.1. Симптом
Сенсор поднят, таблица режима залита, приёмник настроен. И:
D-PHY: около 20 000 высокоскоростных посылок в секунду на каждой линии
CSI-2: packet count = 0
CSI-2 ISR: чисто, ни одной ошибки
Нижний этаж приёмника считает посылки. Верхний не видит ни одного пакета и не жалуется.
19.2. Почему ложные гипотезы были соблазнительны
Первое, что приходит в голову при «нет пакетов» — физика: шлейф, разъём, целостность сигнала, входные задержки. Гипотеза подкреплялась тем, что скорость линии у нас нестандартная для этой платы, а сеть согласования — не compliance-решение. То есть «мы на пределе возможностей платы» звучало не как отговорка, а как самое вероятное объяснение.
Прелесть этой гипотезы в том, что она порождает бесконечную работу: перебирать задержки, менять кабель, шевелить разъём. И ни одно из действий не опровергает её саму.
19.3. Улика
Опровергло гипотезу сочетание двух наблюдений, каждое из которых по отдельности ничего не значит.
Первое: счётчики внутри нижнего этажа приёмника растут. Их надо смотреть отдельно от регистров верхнего этажа — они лежат в окне D-PHY (0x43C0101C и 0x43C01020, старшие полслова). Значит, байты по проводам идут, и физика работает.
Второе, и оно решающее: верхний этаж не сообщает ни одной ошибки. Как разобрано в части II, глава 10.5, приёмник, который получает байты и не может их разобрать, обязан жаловаться на начало передачи. Молчание означает, что байтов ему не дают вовсе.
Между двумя этажами сидит десериализатор, и единственная причина, по которой он не отдаёт байты при исправной физике, — он не нашёл синхробайт.
19.4. Причина и порядок, который единственно работает
Причина — окно установления, посчитанное в части II: приёмник, собранный под чужую скорость, начинает слушать после того, как синхробайт прошёл.
Но само значение оказалось только половиной ответа. Вторая половина — порядок, и он неочевиден:
Ядро CSI-2 нельзя выключать и включать заново. Каждое измерение, сделанное после такого цикла, давало ноль пакетов. Единственный успешный запуск отличался тем, что ядро оставалось включённым, а перезапускался только сенсор.
Судя по поведению, ядро фиксирует состояние линий в момент включения и не умеет подхватить уже поднятую линию. Отсюда процедура: значение окна установления пишется во включённое и простаивающее ядро (регистры 0x43C01030 и 0x43C01048, по девять бит на линию), а сенсор поднимается под ним.
Это свойство ещё дважды всплывёт в серии: оно же объясняет, почему повторный запуск программы под Linux требует импульса сброса логики (часть VII, глава 37).
19.5. Сенсору нужен цикл питания между попытками
Побочная находка, стоившая целого перебора значений.
Программного сброса сенсора хватает на один-два подъёма. Потом модуль продолжает исправно отвечать по I²C — и не передаёт ничего. Перебор, сделанный в таком состоянии, показывает нули даже для заведомо рабочего значения, и вывод из него получается прямо противоположный правильному.
Поэтому в процедуре подъёма первым шагом идёт цикл питания модуля, а не программный сброс. И поэтому же случайный успех на второй попытке — не доказательство: между попытками надо приводить сенсор в одинаковое состояние.
19.6. Отдельно: ловушка самого измерения
Эту историю стоит рассказать, потому что она про метод, а не про железо.
Перебор значений окна установления делался так: выставить значение, поднять сенсор, посчитать принятые строки за секунду. Результат читался как «работает только значение по умолчанию, все остальные дают ноль».
Вывод был артефактом. Пока канал захвата не запущен, никто ниже приёмника не забирает пиксели: блоки обработки не выставляют готовность, буфер строк в приёмнике заполняется, и примерно через полсекунды приём останавливается насовсем. То есть первое значение перебора измерялось честно, а все последующие измеряли уже мёртвый приёмник.
Отсюда строка в текущем скрипте: канал захвата взводится на каждом подъёме, до старта сенсора. И общее правило, которое стоит унести: если измерение делается в цикле, убедитесь, что после первой итерации система возвращается в исходное состояние. Иначе вы измеряете не параметр, а последствия первой итерации.
Глава 20. Расследование второе: поток есть, кадров нет
20.1. Домино, которое начинается с канала захвата
После починки окна установления поток пошёл по-настоящему:
data_type = 0x2B RAW10
bytes_per_line = 2400 ровно 1920 пикселей по 10 бит
Держался он доли секунды и замирал, а в регистре ошибок копились ошибки начала передачи и кадровой синхронизации. Это снова выглядело как маргинальная физика.
Причина оказалась в порядке взведения канала захвата, и работает она как домино. Канал, взведённый под уже идущим потоком, начинает посреди строки, немедленно поднимает признаки неверных границ кадра и останавливается. Остановился сток — данные никто не забирает — переполняется буфер строк в приёмнике — приём встаёт. Те самые доли секунды.
Дополнительная деталь, стоившая ложного диагноза: остановленный по ошибке канал не оживает повторной установкой бита запуска. Ему нужен сброс. Без него регистр состояния упорно читается как остановленный, сколько бы раз конфигурация ни переписывалась, и создаётся полное впечатление мёртвого блока.
20.2. Мнимая потеря сорока процентов строк
Симптом. Приём непрерывный, ошибок нет, но строк доходит 18 600 в секунду вместо ожидаемых 33 000.
Ложная гипотеза. Линия теряет часть посылок. Дальше — входные задержки, кабель, скорость.
Улика. Арифметика. 1080 активных строк при 17,2 кадра в секунду — это ровно 18 600 строк в секунду. То есть ни одна строка не потеряна, просто кадров меньше, чем мы думали.
Причина. Вертикальный полный размер кадра остался равным значению по умолчанию — 1968 строк вместо 1104, и это даёт 17,2 кадра в секунду. Почему его нет в таблице режима, разобрано в части I, глава 6.2: драйвер программирует его отдельно.
Правило. После записи правильного значения счётчик показал 33 655 строк в секунду против расчётных 33 068 — то есть линия чистая, а все подозрения на входные задержки были не по адресу. Прежде чем чинить физику, посчитайте, сколько строк вы обязаны получать.
Скрипт jtag_ov_timing.tcl печатает цепочку тактирования сенсора, скорость линии, кадровую частоту и ожидаемое число строк — именно для того, чтобы этот расчёт занимал секунды, а не выводился заново каждый раз.
20.3. Один бит, из-за которого не было кадров
Приём при этом всё равно не давал картинки, и картина была совершенно обескураживающей:
флаг «кадр принят» у ядра CSI-2 — не появляется ни разу
демозаик и гамма — ни один кадр не досчитан до конца
канал захвата — running, ошибок нет
метка 0xDEADBEEF в кадровом буфере — цела
То есть транспорт исправен, а изображения нет. Последняя строка — та самая проверка из части I, глава 8.7: работающий канал записи обязан затереть метку.
Причина — в одном бите регистра 0x4800 сенсора. Наш подъём писал туда 0x04 («отпустить шину»), чего достаточно для непрерывной тактовой линии. Штатный драйвер пишет 0x34: плюс гашение тактовой между посылками и плюс разрешение строчной синхронизации.
Проверка по отдельности дала неожиданно симметричный результат:
|
Значение |
Что взведено |
Флаг «кадр принят» |
|---|---|---|
|
|
ничего |
нет |
|
|
строчная синхронизация |
есть |
|
|
гашение тактовой |
есть |
|
|
и то и другое |
есть |
Достаточно любого из двух битов. Ноль получается только когда не взведён ни один. Итоговая конфигурация — штатные 0x34.
И следствие, которое переворачивает выводы предыдущей главы: время установления надо искать заново под выбранный режим тактовой линии. При непрерывной тактовой работало единственное значение, ни шагом в сторону. При гашеной есть широкое чистое плато от 80 нс и выше, включающее и вендорские 145, и на этом плато приёмник не сообщает ни одной ошибки заголовка, синхронизации или контрольной суммы.
То есть ощущение «мы держимся на самом краю возможностей платы» относилось не к плате, а к неудачно выбранному режиму тактовой линии. Расчёт из части II, глава 10.2 предсказывал плато 100…170 нс — и оно там и оказалось, стоило перестать мешать себе режимом клока.
20.4. Счётчик строк в регистре приёмника — не высота кадра
Мелкая на вид ловушка, стоившая полного тупика.
В регистре информации о канале младшая половина честно показывает 2400 байт на строку. Старшая читается десятками тысяч и следует счётчику пакетов, не сбрасываясь на каждом кадре. Канал захвата, взведённый на 32 899 строк, ждёт кадр, который никогда не кончится, и не досчитывает ни одного — при том что все остальные индикаторы здоровы.
Высоту кадра берут у сенсора, а не у приёмника.
20.5. Правило, которое обобщает оба расследования
Три ловушки этой главы — незаданный размер кадра, счётчик строк и (в следующей главе) биты регистра состояния — устроены одинаково: сравнивались величины, чья семантика не была проверена. Похожее имя, правдоподобное значение, неверный смысл.
Формулировка на будущее: сравнивать имеет смысл только те величины, чью семантику вы проверили. Не «называется line count, значит высота», а «прочитал и убедился, что при смене высоты оно меняется».
Глава 21. Порядок подъёма как результат расследования
21.1. Что осталось жёстким
Правила, которые вывелись из расследований и действуют независимо от версии дизайна.
Стоки раньше источников. Блоки обработки запускаются до сенсора, иначе приём остановится через полсекунды (принцип — в части I, глава 8.3, цена нарушения — в 19.6).
Ветка показа поднимается раньше камеры. Она от камеры не зависит, поэтому тёмный монитор после этого шага однозначно указывает на видеовыход. Побочный эффект, который не надо считать поломкой: канал показа — ведомый в схеме согласования, и пока захват не запущен, ему нечего повторять, так что на мониторе несколько секунд будет шум из неинициализированной памяти.
Ядро приёмника не циклируют. Значение окна установления пишется во включённое простаивающее ядро (19.4).
Сенсор приводят в одинаковое состояние циклом питания (19.5).
Адреса пишутся до бита запуска, а геометрия — последней. После сброса канала адресные регистры обнуляются. Если поставить бит запуска раньше адресов, между двумя записями по JTAG проходит миллисекунда-другая, и канал успевает начать писать по физическому адресу ноль. Правильный порядок: адреса и геометрия, потом бит запуска, и самой последней — запись вертикального размера, которая канал и взводит.
Заполняются все адресные регистры, а не первый. В циклическом режиме канал обходит все кадровые ячейки. Вендорское приложение заполняло только первую, и на четвёртой ячейке канал уходил в нулевой адрес, получал ошибку от шины и останавливался — картинка успевала появиться на экране и застыть. Наши скрипты заполняют все шестнадцать адресных регистров, даже когда используются три буфера.
21.2. Что перестало быть проблемой в своём дизайне
А вот это — самый ценный вывод главы, и он про то, как правильно закрывать такие проблемы.
На вендорском битстриме канал захвата нельзя было взвести ни до потока (нет границы кадра — он начнёт посреди первого, обрезанного), ни под потоком (начнёт посреди строки). Оставалось единственное окно — кадровый гасящий интервал, признаком начала которого служит флаг «кадр принят». Причём ширина окна зависит от вертикального размера кадра: при значении по умолчанию это около 14 мс, чего хватает на одну транзакцию JTAG, а при плотно подогнанном — меньше миллисекунды, и все записи приходилось готовить заранее.
Программа, которая успевает попасть в миллисекундное окно, — плохое решение. Оно работает, но зависит от скорости отладчика, от загрузки хоста и от случайностей.
В своём дизайне проблема снята при сборке: канал захвата собран с кадровой синхронизацией и сбросом по её сигналу (c_use_s2mm_fsync, c_flush_on_fsync). Канал, начавший на частичном кадре, теперь не останавливается с ошибкой, а отбрасывает этот кадр на следующей границе. То есть взводить его можно спокойно, пока линии ещё припаркованы, и вертикальный размер кадра задавать там же — до того, как поток пошёл.
Правило: если ваша процедура вынуждена попадать в узкое временное окно, поищите способ убрать окно при сборке. Опция в конфигурации блока надёжнее любой отточенной последовательности команд.
21.3. Диагностическая ошибка, которая мешала всё это увидеть
Отдельная история, потому что она про чтение регистров, а не про железо.
Биты регистра состояния канала DMA расшифровывались по памяти. В действительности (и это проверяется по исходнику драйвера ядра) бит 12 — признак завершённого кадра, а бит 15 — поздний конец строки. В голове же были бит 8 как признак кадра и бит 12 как поздний конец строки.
Последствия: каждый успешно завершённый кадр читался как ошибка длины строки, а флаг завершения кадра не смотрелся вовсе. Из этого выросла целая ложная теория про слишком длинные строки и перебор их длин, а настоящая ошибка шины прятался за словом, которого в регистре не было.
Мораль: биты берите из драйвера или из документации на блок, а не из памяти.
Туда же — два свойства этого регистра, каждое из которых даёт ложное «сломано»:
-
флаги липкие. Канал захвата видит первый кадр неполным и защёлкивает признак неверной границы. Биты держатся до явной очистки, и несколько прогонов подряд читаются как «конвейер сломан», хотя счётчик кадров в том же регистре растёт;
-
порог счётчика кадров лежит в битах 15:8, и значение 16 кадров — это полсекунды при тридцати кадрах в секунду, что легко переживает короткое окно измерения. Исправный канал при этом выглядит застрявшим.
И ещё одно, из той же серии: флаг завершения кадра выставляется только если в регистре управления разрешено соответствующее прерывание. Без этого разрешения полностью рабочий конвейер выглядит намертво застрявшим.
21.4. Итоговая процедура
Всё расследование сворачивается в такую последовательность. Именно в этом порядке её выполняет jtag_camera_live.tcl, и именно её потом повторяет программа под Linux.
-
Захватить плату: сброс, ранняя остановка процессора, наша инициализация процессорной системы.
-
Прочитать три частоты обратно и остановиться, если опорная не 200 МГц.
-
Залить битстрим, снять сброс логики.
-
Зажечь светодиоды — доказательство, что процессор дотягивается до логики.
-
Поднять ветку показа: тайминг-контроллер, затем канал показа.
-
Запустить демозаик и гамму — стоки раньше источника.
-
Указать каналу захвата на кадровые буферы.
-
Подключиться к шине I²C, сделать цикл питания модуля, проверить идентификатор сенсора.
-
Залить таблицу режима.
-
Взвести канал захвата и задать вертикальный размер кадра, пока линии припаркованы.
-
Разрешить передачу (
0x34) — поток пошёл. -
Измерить: строки в секунду у приёмника, кадры в памяти, состояние каналов.
Шаги 10 и 11 стоят рядом не случайно: между «сток готов» и «источник заговорил» не должно быть ничего.
21.5. Лабораторная 2: живое видео без операционной системы
make live
# xsct scripts/jtag_camera_live.tcl
Ожидаемый результат: монитор показывает живое 1080p, а в логе — прирост строк около 33 000 в секунду, порядка 30,5–30,8 кадра в секунду, отсутствие ошибок у приёмника за секунду измерения и метка в кадровом буфере, которая затирается.
Четыре диагностических режима того же скрипта появились из конкретных тупиков следующей части, но запускать их удобно уже сейчас:
xsct scripts/jtag_camera_live.tcl - - - - colors # залить экран одним компонентом
xsct scripts/jtag_camera_live.tcl - - - - lut # подменить гамму константой
xsct scripts/jtag_camera_live.tcl - - - - measure # пила по кадру для четырёх фаз
xsct scripts/jtag_camera_live.tcl - - - - cycle # те же фазы по двенадцать секунд
Общее у них то, ради чего они и написаны: каждый превращает вопрос «почему картинка не такая» в вопрос с заранее известным правильным ответом. Экран обязан стать ровно одного цвета; кадр обязан стать ровно серым; пила обязана упасть. Пока такого ответа нет, смотреть на картинку бесполезно — она одинаково выглядит при десятке разных причин.
Если своего битстрима ещё нет, доступен путь главы 18: чужой битстрим плюс своя инициализация сенсора. Только не смешивайте вендорскую инициализацию процессорной системы со своим битстримом — из-за третьей частоты (17.5).
21.6. Если сенсор не поднялся с первого раза
Такое бывает и при полностью верных регистрах: приёмник остаётся глухим, ноль пакетов и ноль ошибок. Надёжно лечится только перезагрузкой битстрима.
Поэтому запуск стоит оборачивать в цикл до появления прироста счётчика пакетов, а не считать первую неудачу диагнозом. И поэтому же измерять надо счётчиком, а не картинкой: сцена перед камерой статична, два одинаковых снимка буфера ничего не доказывают, а прирост счётчика за секунду и затёртая метка — доказывают.
21.7. Что этот этап дал, кроме картинки
Формально этап закончился живым видео на чужом битстриме. Фактически он дал то, без чего дальше было бы нечем работать: карту регистров, набор скриптов, процедуру подъёма и — главное — понимание того, какие индикаторы чему соответствуют.
Список скриптов, написанных на этом этапе, есть в ../camera_plan.md; самые полезные из них — jtag_ov_timing.tcl (расчёт того, что вы обязаны получить), jtag_hs_sweep.tcl (перебор окна установления по числу принятых строк) и jtag_fb_check.tcl (есть ли в кадровом буфере живые пиксели).
Дальше — часть V: свой битстрим вместо чужого, и четыре болезни, каждая из которых выглядит как «камера шумит», а находится совсем в другом месте — в калибровке задержек, в порядке байт, в согласовании каналов и в конфигурации микросхемы памяти.
Заключение к первой части статьи
На этом месте разумно остановиться и провести черту. До конечной цели проекта еще далеко: у нас пока нет автономной загрузки, JPEG-сжатия, Linux-программы и сетевого потока. Но самая неопределенная часть работы уже пройдена – путь от контактов разъема камеры до живого кадра перестал быть набором предположений и превратился в последовательность проверяемых состояний.
За первые четыре части мы успели пройти почти весь путь вниз по стеку и обратно.
Сначала определили архитектуру: процессор не должен таскать несжатые кадры и участвовать в обработке каждого пикселя. Его задача начинается там, где объем данных уже достаточно мал. Из этого решения выросли кадровые буферы, автономный видеотракт и возможность отлаживать камеру вообще без операционной системы.
Затем пришлось разобраться с тем, что происходит значительно ниже привычного уровня AXI и регистров: с двумя электрическими режимами D-PHY, пассивной согласующей сетью, напряжением банка, входными задержками, синхробайтом, окном установления и тем, почему 408 Мбит/с на линию превращаются в 51,01 МГц байтового тактового сигнала. После этого сообщения вида “пакетов нет” перестали быть одним общим симптомом: стало понятно, какие счетчики относятся к физическому уровню, какие – к декодированию пакетов и какие ошибки должны появляться при разных классах отказов.
Следующим объектом отладки оказался сам инструмент. Выяснилось, что Vivado умеет совершенно спокойно принять неверное имя параметра, применить ограничение к пустому набору объектов или использовать не то состояние проекта. Поэтому сборка постепенно превратилась из последовательности команд в спецификацию с самопроверкой: параметры читаются обратно, карта адресов печатается, порты сверяются, частоты проверяются по отчету, а тайминги рассматриваются по конкретным классам нарушений, а не по одной итоговой строке.
И только после этого появилась возможность идти на живую плату.
Именно там большая часть предварительных представлений оказалась проверена на прочность. Приемник с растущими счетчиками D-PHY и нулем пакетов оказался не неисправным физическим интерфейсом, а приемником, начинающим слушать после прошедшего синхробайта. “Потеря сорока процентов строк” оказалась правильным приемом при неправильной кадровой частоте сенсора. Полностью исправный поток без единого принятого кадра свелся к одному регистру транспортного режима OV5647. А канал DMA, который казался причиной нескольких разных отказов, в действительности просто показывал последствия неправильного порядка запуска.
В результате появился порядок подъема, в котором каждый следующий шаг имеет смысл только после доказательства предыдущего:
-
захватить процессор и выполнить известную инициализацию PS;
-
проверить реальные тактовые частоты;
-
загрузить логику и доказать доступ к ней через AXI;
-
отдельно поднять тракт отображения;
-
запустить обработчики до источника данных;
-
подготовить кадровые буферы и канал захвата;
-
проверить сенсор по I2C и только после этого загрузить его режим;
-
разрешить передачу;
-
проверить результат счетчиками, состоянием DMA и содержимым памяти.
Конечный результат этой части поэтому лучше формулировать не как “мы получили картинку”. Картинку можно получить случайно и потом потерять после первой же правки. Здесь получено другое: процедура, позволяющая объяснить, почему картинка появилась, и локализовать уровень отказа, если она исчезла.
К этому моменту известны электрическая конфигурация интерфейса, скорость линии, ожидаемая частота строк, карта регистров, требования к тактированию, порядок сбросов, последовательность запуска и несколько независимых способов доказать прохождение данных. Есть и живое изображение 1920×1080, полученное без участия Linux. Это уже достаточно прочный фундамент, чтобы перестать исследовать саму возможность работы камеры и перейти к следующей задаче – собрать весь тракт под собственным управлением.
Во второй части именно этим и займемся.
Сначала заменим оставшиеся элементы эталонного решения своим битстримом и разберем четыре совершенно разных дефекта, каждый из которых внешне выглядит как поврежденное изображение. Затем подключим JPEG-энкодер, причем основная работа снова окажется не внутри чужого ядра, а на его границах. После этого перенесем доказанную последовательность подъема в загрузчик и Linux, разберемся с владением физической памятью и сбросами при старте системы, а в конце выведем сжатые кадры в сеть и посмотрим, где реально теряются кадры между сенсором, энкодером и клиентом.
То есть дальше проект становится меньше похож на исследование неизвестного интерфейса и больше – на системную интеграцию. Но метод останется тем же: один шаг, одно проверяемое утверждение и заранее известный признак того, что оно действительно выполняется.
Размещайте облачную инфраструктуру и масштабируйте сервисы с надежным облачным провайдером Beget.Эксклюзивно для читателей Хабра мы даем бонус 10% при первом пополнении.
Автор: andreyzaostrovnykh


