nabbla (nabbla1) wrote,
nabbla
nabbla1

Category:

Рихтуем картинку с QuatCore (часть 1)

Вчера мы получили нечто, похожее на правду, но не самого высокого качества. И наиболее очевидный изъян - циклический сдвиг по горизонтали. В первую очередь надо устранить именно его.



Для этого добавляем ровно одну новую строку (она выделена зелёным), а другие две меняем местами (выделены рыжим):

			;начинаются полезные строки
			Acc	UsefulRows
	@@UsefulLoop:	ACQ	WholeRow
			ACQ	HSync
			NOP	GPUH
			SUB	1
			JGE	@@UsefulLoop


Теперь процессор не может "накидать заданий" впрок: мы посылаем задание "обработать всю строку",затем "дождаться строчного синхроимпульса", после чего мы ждём результатов обработки строки, и только после этого переходим на следующую итерацию. Накопившееся в начале задания по "пустым строкам" тоже здесь "сгорят", поскольку по окончании обработки задания с ожиданием синхроимпульса мы спускаем в унитаз (flush) выходной буфер, и строка "NOP GPUH" выполнится только когда закончится первая полезная строка.

Но гладко было на бумаге - особого улучшения добавление этой строки не дало.


Ушёл мусор с последних строк, доказывая, что запись кадра в память и его чтение (с первоначальным обнулением регистра) стали разнесены во времени. Но основной сдвиг практически на полстроки так и остался...

То есть, где-то во всём этом процессе идёт "неправомерное" обращение к статической памяти с инкрементом регистра ER (External memory Register).

Во время передачи кадра по UART (процесс небыстрый, 921600 бод = 92 160 байт/с, на картинку 1024х720х8 бит уходит 8 секунд!) я заметил, что мерцает светодиод, который я вывел от кадрового синхроимпульса с делением в 25 раз, т.е при нормальной работе камеры он должен был мигать с частотой 1 Гц. Но поскольку после одного-единственного кадра мы отключаем АЦП, он вообще мигать не должен. А он как-то нестабильно мерцает.

Это детектор синхроимпульсов начинает молоть данные "повторно", те что из статической памяти идут в QuatCore. Меня такая активность несколько испугала, и я добавил ему вход nEna (разрешение работы "с инверсией"), который соединил с AdcDisable. Светодиод мерцать перестал, но каких-то других последствий я не обнаружил, сдвиг никуда не делся.

Ещё я попробовал добавить всё-таки самый обычный "длинный цикл", чтобы выдержать паузу между получением изображения и его отправкой - тоже ничего не изменилось. Даже перетащил строку NOP IN (по сути "нажмите любую клавишу..." - ждёт, пока чего-нибудь пошлют по UART) в серединку, так что он сначала записывал кадр, потом ждал "ввода от пользователя" - и только тогда передавал, так что интервал и вовсе стал измеряться в секундах! Но и это не помогло.

"Сдвиг накапливается где-то ещё" - догадался Штирлиц!

Чтобы проверить это, я добавил две команды между первым циклом ("по пустым строкам") и вторым ("по полезным строкам"):
			;пропустить верхние "пустые" строки
			j	TopRows
	@@TopRowLoop:	ACQ	HSync
			jLOOP	@@TopRowLoop
				
				
			ERL	0
			ERH	0
			;начинаются полезные строки
			Acc	UsefulRows
	@@UsefulLoop:	ACQ	WholeRow
			ACQ	HSync
			NOP	GPUH
			SUB	1
			JGE	@@UsefulLoop


И после этого удалось получить изображение, указанное выше.

Выходит, что лишние данные записывались на заданиях по обнаружению кадрового синхроимпульса и 28 подряд идущих строчных, хотя они не должны были вообще выдать хоть что-либо "наружу"!</b>

Причём записано было 538..539 лишних байт, но что интересно - это количество почти не меняется от записи к записи! (Если бы много мусора сыпалось на этапе ожидания кадрового синхроимпульса, то из-за случайного момента начала работы у нас всё время получался бы разный сдвиг).

Причём это практически ПОЛОВИНА строки. Пора вспомнить, как выглядит кадровая развёртка в сигнале CVI, который мы применяем:


У нас идут "укороченные" строки, как раз половинной длительности. Их назначение в прогрессивной развёртке не очень ясно, но почему-то вот так.

Далее, нужно вспомнить, как работает наш селектор синхроимпульсов. Его выделение кадрового синхроимпульса основано на "интеграторе", который в цифровом виде сведён тупо к счётчику. Когда достаточное количество времени у нас сигнал ниже порога, "зажигается" кадровый синхросигнал:


(синий - "комбинированный" синхросигнал, жёлтый - кадровый, см. селектор синхроимпульсов на ПЛИС)

И наконец, из них "вырезаются" импульсы длиной в 1 такт, которые мы используем.

Весь этот экскурс в историю - чтобы прояснить один факт: сначала приходит строчный синхроимпульс, спустя несколько тактов: кадровый, а только потом примерно спустя полстроки - следующий синхроимпульс.

Увы, наш генератор тестового изображения выдавал кадровый и строчный синхроимпульс строго одновременно, и там тоже шла одна ошибочная запись, но это я счёл меньшей из своих бед:


Чтобы понять, что происходит "в реальности", чуть задержим выдачу кадрового синхроимпульса, на 5 тактов. Вот что мы получаем:


Сволочь! Ну как так-то!?

Осталось понять, что же здесь происходит, почему такое вообще могло случиться? Вот код, отвечающий за формирование WriteFromADC:

always @(posedge clk)
	FrontPorch <= (SyncOut[0] & Hsync)? 1'b1 : start? 1'b0 : FrontPorch;

assign WriteFromADC = ~ (SyncOut[0] | SyncOut[1] | FrontPorch);


Когда мы помещаем задание "дождаться кадрового синхроимпульса", у нас SyncOut[1]=1 ("нужен кадровый"), и SyncOut[0]=0 ("не нужен строчный"). Пока не пришёл кадровый синхроимпульс, SyncOut[1]=1 держит WriteFromADC = 0, как и должно быть.

Наконец, приходит VSync=1. Он не формирует сигнал frontPorch, он так и остаётся нулевым. Зато он сбрасывает к следующему такту SyncOut[1], из-за чего на этом такте формируется WriteFromADC ("мы больше не ждём кадрового импульса, не ждём строчного импульса, не обрабатываем "полочку" после строчного импульса, значит это полезная область изображения!").

И по-хорошему, сейчас задание на VSync должно было завершиться, ведь "мы его дождались". Только вот незадача, мы хотели дождаться координаты X=0, но сейчас X-координата уже прошла ноль! И не будет нулевой, пока не придёт строчный синхроимпульс, а это будет через "половинную строку"!

Да, теперь понятно! Осталось понять, а что же с этим делать? Мы хотим, чтобы в принципе ни на один такт WriteFromADC не переключился на единицу в этом месте. Наверное, самое простое - это "смягчить" условие для формирования FrontPorch. Вообще, непонятно, зачем мы были настолько разборчивы - "только по приходу строчного импульса, и только когда мы его действительно ждали!" Так что заменим строку на такую:

FrontPorch <= (Hsync | Vsync)? 1'b1 : start? 1'b0 : FrontPorch;


Попробуем ещё раз "на железе", выкинув лишние строки ERH 0 / ERL 0.

ДА! Выдаёт без сдвига, и в этот раз без "костыля". Но показать особо нечего - картинка та же самая :)


Кстати, в этот раз сама "сцена" почти чёрно-белая: белый стол со стенкой, серый держатель для паяльника (переделанный из огромного реостата), чёрные провода и адаптер, но в центре композиции белый и красный провода, перемотанные ЗЕЛЁНОЙ изолентой! И вот это самое место всё у нас "заштриховано" - в кои-то веки проявились цветоразностные компоненты, промодулированные на поднесущей - и вперёд. Тогда как в остальном картинка худо-бедно чистая.

Разумеется, контрастность и уровень чёрного никуда не годятся (это совсем "сырое" изображение напрямую с АЦП), и ещё я заметил, что примерно на пиксель гуляет синхронизация по горизонтали. Об этом тоже надо подумать...

PS. В этом и заключался мой "хитрый план" - реализовать "отладочную функцию" получения картинки в качестве приятного дополнения к штатной функциональности, и тогда вместо "специализированных модулей для видеозахвата", которые придётся мучительно отлаживать, я буду мучительно отлаживать ШТАТНОЕ ЖЕЛЕЗО! Да, на его реализацию ушло почти полгода, но всё равно это было необходимо. Мне кажется, самое страшное уже позади...
Tags: ПЛИС, программки, работа, странные девайсы
Subscribe

Recent Posts from This Journal

  • Огарь крупным планом

    Прям как на пьедестале, гордый такой, здоровенный, песенку спел, потом спустился водички попить, небось горло пересохло. Только на видео я заметил,…

  • Я создал монстра!

    Вот нормальная счастливая пара разъёмов ОНЦ-БС-1-10/14-Р12-2-В и ОНЦ-БС-1-10/14-В1-2-В: У розетки кроме основного выступа, отмечающего "верх",…

  • Нахождение двух самых отдалённых точек

    Пока компьютер долго и упорно мучал симуляцию, я пытался написать на ассемблере алгоритм захвата на ближней дистанции. А сейчас на этом коде можно…

  • Слишком общительный счётчик

    Вчера я чуть поторопился отсинтезировать проект,параметры не поменял: RomWidth = 8 вместо 7, RamWidth = 9 вместо 8, и ещё EnableByteAccess=1, чтобы…

  • Балансируем конвейер QuatCore

    В пятницу у нас всё замечательно сработало на симуляции, первые 16 миллисекунд полёт нормальный. А вот прошить весь проект на ПЛИС и попробовать "в…

  • Огари разговаривают

    Сегодня по пути на работу встретил огарей прямо в Лосином острове, на берегу Яузы. Эти были на удивление бесстрашны, занимались своими делами, не…

  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your IP address will be recorded 

  • 1 comment