Макет: наконец настроили экспозицию!

Dec 10, 2020 03:22

Добрался, наконец, до меню камеры, и отрегулировал худо-бедно.

Было:



Стало:



Покорячиться пришлось знатно, но к концу дня все "непонятности" сложились в достаточно стройную картину...


Изначально, чтобы зайти в это меню камеры, использовался небольшой "джойстик" на кабеле, выходящем из камеры. Но кабель этот я отсоединил, чтобы не мешался, предварительно проведя реверс-инжиниринг. С кнопочек на отладочной плате ПЛИС всё работало замечательно.

Но потом и кнопки отладочной платы стали недоступны, скрытые внутри корпуса, так что единственный вариант - управлять через RS485 с компьютера. Когда-то у меня и такой вариант вполне успешно работал, но сейчас почему-то "с нахрапу" не желал - не реагирует, и всё тут!

Так оно было устроено:



Модуль PWM мы уже описывали, и даже осциллограммы показывали.

Видно, что модуль, берущий управление с кнопок, сейчас отключён, а вместо него поставлен другой, напрямую берущий символы с приёмника UART. Вот его код:

//we receive symbol by UART:
//"L" = Left
//"R" = Right
//"U" = Up
//"D" = Down
//"E" = Enter
//we should convert it to 5-bit number for PWM,
//and put it for long enough duration so camera react properly
//after some experimentation, we found out:
//63 ms is enough!
//32 ms is too fast: it doesn't work correctly

//also:
//"l" = left for 5 sec = CVBS
//"r" = right for 5 sec = TVI
//"u" = up for 5 sec = AHD
//"d" = down for 5 sec = CVI

module JoystickControlByUART (input clk, input ce, input [7:0] D, input Byte_ce, output qe, output reg [4:0] Q = 1'b0);

reg OneOfOurBytes = 1'b0;
reg OneOfOtherOurBytes = 1'b0; //for long presses
reg Byte_ce_z = 1'b0;

wire ena0, ena1;

DelayLine DL ( .clk (clk),
.start (OneOfOurBytes & Byte_ce_z),
.ce (ce),
.sclr (1'b0),
.working (ena0),
.finished ());
defparam
DL.Duration = 131072;

DelayLine longDL (.clk (clk),
.start (OneOfOtherOurBytes & Byte_ce_z),
.ce (ce),
.sclr (1'b0),
.working (ena1),
.finished ());
defparam
longDL.Duration = 4194304; // 2^22 to get 5 seconds!

assign qe = ena0 | ena1;

always @(posedge clk) begin
OneOfOurBytes <= (D == "L") | (D == "R") | (D == "U") | (D == "D") | (D == "E");
OneOfOtherOurBytes <= (D == "l") | (D == "r") | (D == "u") | (D == "d");
Byte_ce_z <= Byte_ce;
if (Byte_ce)
Q <= ((D == "L") | (D == "l"))? 5'b10100 :
((D == "R") | (D == "r"))? 5'b11011 :
((D == "U") | (D == "u"))? 5'b11000 :
((D == "D") | (D == "d"))? 5'b11111 :
5'b10000 ;

end

endmodule

Особого усердия сюда не вкладывал, собирал "из подручных средств". В том плане, что не стал продумывать счётчик "с нуля", который мог бы отсчитывать короткие интервалы ("одиночное нажатие") или длинные, пятисекундные, для переключения формата выходного сигнала (CVBS/TVI/CVI/AHD).

По комментариям видно, что в своё время разбирался, какую длительность импульса надо сделать, чтобы камера воспринимала эти нажатия. Оказалось: 32 мс слишком мало, а вот 64 мс уже достаточно.

А тут включаю - и НЕ РАБОТАЕТ И ВСЁ ТУТ!

Стал вспоминать, что я там мог поменять? Одно изменение вспомнил: изначально ШИМ полностью "захватывал управление" проводом джойстика, но позже я добавил Z-состояние, чтобы можно было и настоящий джойстик туда подсоединить и управлять с него

А разница кое-какая есть: если мы оставляем нашу RC-цепочку 360 Ом и 0,1 мкФ "болтаться в воздухе" - она очень долго заряжается назад до 3,3 вольт! Может, именно это сбивает камеру с толку? Она ожидает чётких, ступенчатых переключений!

Ладно, это легко исправить, уберём Z-состояние нафиг:

//keeps Q disconnected while qen = 0,
//form PWM on it when qen = 1, D = 0 is largest output ("1" everywhere except 1 low pulse), D = 2^(PWMwidth ) - 1 is lowest output (just 0).

//also, we divide input frequency (set by ce pulses) by 2^PWMwidth and output this on ceo. (why waste so good counter :))

module PWM (input clk, input ce, input qen, input [PWMwidth - 1 : 0] D, output ceo, inout Q);

parameter PWMwidth = 3;

wire [PWMwidth - 1 : 0] cnt;
wire TC; //Terminal Count

assign ceo = TC & ce;

reg sQ = 1'b0;

//assign Q = qen? sQ : 1'bz; //puts into Z state if qen = 0
assign Q = qen? sQ : 1'b1; //we're alone there, let's better turn off quickly! (we think, long trails of capacitor charging up leads to mistakes)

lpm_counter counter (
.clock (clk),
.cnt_en (ce),
.q (cnt),
.cout (TC) );
defparam
counter.lpm_direction = "UP",
counter.lpm_port_updown = "PORT_UNUSED",
counter.lpm_type = "LPM_COUNTER",
counter.lpm_width = PWMwidth;

always @(posedge clk) if (ce) begin
// sQ <= TC? 1'b1 : (D == cnt)? 1'b0 : sQ;
sQ <= TC? 1'b0 : (D == cnt)? 1'b1 : sQ;
end
endmodule

И впору посмотреть, а сигнал-то на камеру идёт, или в новом кабеле он пропал куда-то?




Идёт, и вполне расчётной длительности 84 мс. Поначалу я работал с тактовой частотой 4 МГц, и поэтому после делений частоты на степени двойки получались интервалы либо 32 мс, либо 64 мс. Но теперь, с тактовой частотой 25 МГц, деление сначала на 32 внутри ШИМ, и затем ещё в 65536 раз в модуле JoystickControlByUART, получаем как раз-таки 84 мс - этого должно было хватить, раз когда-то мне 64 мс хватало!

И хвостов больше нет, но не работает, хоть ты тресни.

Второе отличие: раньше я ковырялся со стандартным CVBS-сигналом, а сейчас перешёл на сигнал высокой чёткости TVI. Может быть, в разных режимах чуть по-разному обрабатывается этот джойстик? Напрашивается, например, опрос на обратном ходе кадровой развёртки, когда процессор относительно свободен. Вот и получается: в CVBS у нас опрос идёт 50 Гц, и он, к примеру, ждёт два повторяющихся значения напряжения на входе с джойстика. А теперь вместо чресстрочной развёртки у нас прогрессивная 25 Гц, и опрос стал вдвое реже, и даже наших 84 мс уже не хватает.

Ладно, это легко проверяется - увеличиваем длительность импульса с 65536 импульсов ce (они приходят каждый 32-й такт) до 131072, то есть вдвое.

Синтезируем, запускаем - и видимо ХОТЬ ЧТО-ТО. МЕНЮ НАКОНЕЦ-ТО ВЫЗЫВАЕТСЯ, НО ОЧЕНЬ ГЛЮЧНО. Срабатывает через раз, кнопки путает, но хоть какой-то прогресс есть.

Подумал: длительности импульсов всё равно не хватает, и увеличил её ещё вдвое. Улучшений не заметил.

Наконец, изрядно поковырявшись с этим меню, понял закономерность: он ПРАВИЛЬНО РЕАГИРУЕТ НА КНОПКУ, НО ВЫДАЁТ КАДР С ПРОШЛЫМ МЕНЮ!

И после небольших размышлений понимаешь: так и должно быть, уж слишком мы быстрые! Мы, "озадачив" ШИМ-контроллер, сразу же начинаем оцифровывать очередной кадр, заминка может составить не более 40 мс (если кадровый синхроимпульс ушёл буквально у нас перед носом). А тем временем, камере нужно дождаться всей длительности нажатия кнопки в 168 мс (это свыше 4 кадров), а потом ещё пару раз замерить напряжение и убедиться, что кнопку уже отпустили (ещё, допустим, 2 кадра) - а потом ещё и не факт, что ТУТ ЖЕ это меню появится на экране, может и ещё сколько-то кадров пропустить!

Так что я решил и в программу добавить пропуск 12 кадров (где-то полсекунды) после "нажатия джойстика", чтобы меню успело появиться - и только после этого начать оцифровку и передачу картинки.

Ввёл такой вот код:

@@WaitMenu: j 12
ACQ VSync ;дождаться кадрового синхроимпульса
jLOOP @@WaitMenu

Компилирую, синтезирую, запускаю, запрашиваю изображение - и получаю ошибку "нет сигнала". Ну здрассте, приехали!

Раз уж осциллограф был наготове, проверил, что сигнал всё-таки идёт, прерывание не должно было прийти! И включив со старой прошивкой, убедился - с аппаратной частью всё в порядке.

По счастью, "сторожевой пёс" у нас стоял несколько обособленно, и ошибку нашёл довольно быстро. Дурацкая ошибка:

module VideoSignalWatchDog (input clk, input ce, input VSync, input Disable, output NMI);

parameter CounterWidth = 16;

lpm_counter WDT ( .clock (clk),
.clk_en (ce),
.sclr (VSync | Disable | NMI),
.cout (NMI));
defparam
WDT.lpm_type = "LPM_COUNTER",
WDT.lpm_direction = "UP",
WDT.lpm_port_updown = "PORT_UNUSED",
WDT.lpm_width = CounterWidth;

endmodule

И к сожалению, не первая, уже ошибался так несколько раз, и ещё ошибусь. Я вместо входа cnt_en (count enable), который разрешает или запрещает СЧЁТ, но при этом сбрасывать и устанавливать счётчик можно в любой момент, применил куда более суровый clk_en (clock enable), так что и сброс счётчика можно сделать только при clk_en=1.

Вот и вышло: лишь с вероятностью 1/32 у нас VSync (продолжительностью в 1 такт) попадёт аккурат на clk_en=1, и счётчик будет сброшен. Нам, очевидно, не повезло. Чего-то вообще эти "сторожевые псы" разбушевались в последнее время...

А ранее эта ошибка маскировалась тем фактом, что disable=0 занимало всего 1 кадр, поэтому счётчик по-любому не успевал дойти до упора. А сейчас вот дошёл-таки...

Ну ладно, с кем не бывает, исправили. Но теперь мы просто получили зависание, без какого-либо сообщения об ошибке!

Решил, что видеопроцессору почему-то не нравится, когда у нас друг за другом идут команды ACQ VSync ("дождаться кадрового синхроимпульса"), дескать, ему и строчные ловить необходимо в серединке, иначе он в какое-то нерасчётное состояние попадает. Сидел, громко думал, внёс какое-то изменение, запустил на синтез. Пока оно синтезировалось, сообразил, что дело не в этом.

А в том самом коде на 3 строчки:

@@WaitMenu: j 12
ACQ VSync ;дождаться кадрового синхроимпульса
jLOOP @@WaitMenu

Да как так-то, я метку поставил не туда! На каждой итерации присваивается j=12, посылаем задание "дождаться кадрового синхроимпульса", вычитаем единичку и проверяем, равен ли j нулю. И если нет - прыгаем в начало, и снова присваиваем 12. И чего же это оно зависло???

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

Зашёл в подменю AE (Automatic Exposure вестимо) и первым делом посмотрел, какие там можно выбрать режимы (exposure mode). По умолчанию стояло globe, я так понимаю, самая классика жанра - он пытается держать среднюю яркость кадра на определённом уровне.

Далее идёт center:



Разницы не заметил вообще, но по логике вещей средняя яркость берётся только по центральной части кадра, в предположении, что именно эта область интересует нас сильнее всего. Далее идёт некий BLC с дополнительным параметром LV:



Нагуглил, что это Back Light Compensation - позволяет хоть что-то вытянуть при съёмке "против света". Но у меня, на самом деле, задача обратная. Я должен убедить камеру, что эти яркие пятна - это не просто источники света, по какому-то недоразумению попавшие в кадр, а и есть САМАЯ ИНФОРМАТИВНАЯ ЧАСТЬ ИЗОБРАЖЕНИЯ! Тут же видим, что всё лишь ухудшилось, совсем слилось в гигантское пятно.

И ещё один режим, FLC:



Это означает Front Light Compensation, но нахрена его компенсировать - не очень ясно. Здесь тоже шибко хорошо не становится.

Так что я решил остановиться на режиме Globe, но выкрутить на минимум яркость.

Получается уже приемлемо:


(повторение картинки в начале поста)

То, что пятна какие-то "изрезанные" - это я так хорошо вырезал кружочки из светоотражающей плёнки, так что мелкие детали на самом деле передаются очень хорошо!

Не факт, что на любой дальности будет получаться хорошо, но надо же хоть с чего-то начинать.

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

Главная интрига - успею ли до конца года?

PS. Ещё есть подменю Video Settings, там может оказаться полезным WDR (Wide Dynamic Range), который попытается выправить разные части изображения по яркости, чтобы "всё влезло", и там же старая добрая контрастность.

странные девайсы, ПЛИС, программки, работа

Previous post Next post
Up