Добрался, наконец, до меню камеры, и отрегулировал худо-бедно.
Было:
Стало:
Покорячиться пришлось знатно, но к концу дня все "непонятности" сложились в достаточно стройную картину...
Изначально, чтобы зайти в это меню камеры, использовался небольшой "джойстик" на кабеле, выходящем из камеры. Но кабель этот я отсоединил, чтобы не мешался, предварительно
проведя реверс-инжиниринг. С кнопочек на отладочной плате ПЛИС всё работало замечательно.
Но потом и кнопки отладочной платы стали недоступны, скрытые внутри корпуса, так что единственный вариант - управлять через 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), который попытается выправить разные части изображения по яркости, чтобы "всё влезло", и там же старая добрая контрастность.