
И оно даже ожидаемо в какой-то мере, пятна у меня вот ни разу не круглые! Проблема связана с нашей тактовой частотой 25 МГц. В данной ПЛИС, 5576ХС4Т, нет ни одного ФАПЧ (PLL), какую частоту извне подали (на два Dedicated, то бишь "специально обученных" вывода) - такую и используй! А ethernet-контроллер, который мне пока служит в качестве генератора тактовой частоты, имеет только фиксированные значения, после 25 МГц идёт сразу 33,33 МГц - на такой частоте QuatCore пока не потянет... К другому выводу подключён генератор тактовой частоты на 80 МГц - это совсем дохрена.
А на 25 МГц выходит, что пиксели берутся "прямоугольные" - по-хорошему должно быть 1280 пикселей на строке, проходящие за 43 мкс, т.е каждый пиксель за 33,6 нс, или 29,8 МГц. А у нас вместо этого 25 МГц, то есть картинка ужимается по горизонтали на 19%. (поставь мы 33,33 МГц - было бы растяжение на 12% - получше, но всё равно). Вот как выглядит наша картинка, если растянуть её как надо:

Эти "прямоугольные пиксели" несколько нарушают работу нашего алгоритма, ищущего на самом деле КВАДРАТЫ. Можно было бы его немножко подрихтовать, но не очень хочется - это же у нас лишь отладка, потом перейдём на нормальную матрицу со вполне себе квадратными пикселями! Хотя, если подумать, изменения не столь страшны, нужно разность по координате Y умножить на 1,19, прежде чем все действия предпринимать - и тогда алгоритм переквалифицируется на прямоугольники с соотношением 5:4
Поэтому пока хотелось ещё один простой способ опробовать, благо он точно в дальнейшем понадобится - НАСТРОИТЬ ЯРКОСТЬ ПОДСВЕТКИ, чтобы чересчур эти пятна не пересвечивались, тогда их диаметр должен сколько-нибудь уменьшиться, и отличить их друг от друга будет проще!
Думаю, регулировать яркость с шагом "в два раза" вполне подойдёт, и попавшая под хвост шлея требует и здесь реализовать модулёк на верилоге "с нуля", а не путём соединения стандартных компонентов (реверсивный счётчик + дешифратор + генератор ШИМ)...
Мне представляется самым логичным сделать фиксированную длительность импульса (который включает подсветку), 1 такт, или 40 нс, и регулировать длительность паузы. Она может быть нулевой (импульсы идут друг за другом, "сливаясь" просто в лог. "1"), или иметь длительность в 1 такт (1/2 яркости), 3 такта (1/4 яркости), 7 тактов (1/8) и так далее. Тем самым, я могу пока что заложиться на довольно большой диапазон регулирования, скажем 1 к 256 или даже к 1024 и знать, что пульсации получатся минимально возможными. По сути, это ФИМ - Фазо-Импульсная Модуляция, где длительность импульса фиксированна, а меняется длительность паузы.
Если бы я применил классический ШИМ, как здесь, и установил бы его разрядность в 10 бит, то при задании половинной яркости вышло бы 512 тактов включённого осветителя (20,48 мкс) и затем 512 тактов выключенного. Учитывая длительность строки в 53 мкс, мы уже можем увидеть очень неприятный муар. Здесь же мы получим 40 нс включённого осветителя и 40 нс выключенного, что скорее всего "усреднится" ещё в драйвере. Думаю, именно поэтому ФИМ не особо жалуют в импульсных источниках питания - там менять частоту преобразования в широких пределах ой как не хочется - на чересчур высокой частоте становятся недопустимо большими потери на переключении транзисторов. Но здесь источник тока для светодиодов всё равно линейный, рассчитанный на гашение всего лишнего напряжения, и я хочу, чтобы он таковым и оставался. Когда выйдем на минимальную яркость в 1/1024, там оба варианта будут работать одинаково - импульсы 40 нс, между которыми 40,92 мкс "тишины". Тут очень вероятно, что кадр будет освещён неравномерно. Впрочем, если экспозиция будет приближаться к максимальным 1/25 секунды (но со скользящем затвором), то даже здесь ничего шибко страшного случиться не должно, всё усреднится.
И ещё интересной была бы возможность после минимальной яркости ввести ещё одну "градацию" - полностью выключенная подсветка! В нашей ШИМ получалось, что из 2N уровней только один будет соответствовать постоянному значению на выходе, и мы должны были выбрать, будет ли это 0 вольт, или всё-таки +3,3. Здесь же выбирать не хочется, хочется ВСЕГО И СРАЗУ :)
После неких размышлений, прихожу к такой схеме. На выходе стоит счётчик "вверх", с входом параллельной загрузки (data), разрешением параллельной загрузки (sload) и синхронным сбросом (sclr):
parameter PWMwidth = 8; lpm_counter counter ( .clock (clk), .data (D), .sload (PWM), .sclr (IRdisable), .cout (PWM), .q (Q) ); defparam counter.lpm_direction = "UP", counter.lpm_port_updown = "PORT_UNUSED", counter.lpm_type = "LPM_COUNTER", counter.lpm_width = PWMwidth;
Здесь Q носит отладочную роль, увидеть, как всё это работает. И видим ровно одно "соединение", cout идёт не только на выход, но и на sload.
Входа у нас два: D и IRdisable. Если выставить IRdisable=1, то счётчик будет сбрасываться на каждом такте, и на выходе всегда будет ноль. Если IRdisable=0, то светодиоды будут гореть.
Ярче всего они будут гореть, если на вход D поставить все единицы - тогда уже сразу после параллельной загрузки у нас получится cout=1, который заставит на следующий такт загрузить то же самое значение, что так и будет поддерживать cout=1 неограниченно долго. Для 8-битного варианта получаем,
IRdisable D 0 1111_1111 - максимальная яркость
Чтобы яркость уменьшить вдвое, нужно подавать 1111_1110, тогда у нас будет уходить один такт, чтобы досчитать до 1111_1111, при котором появляется cout=1, после чего снова будет загружаться это значение - и так далее.
Чтобы уменьшить яркость вчетверо, нужно 1111_1100, и так далее. Заполним список:
IRdisable D 0 1111_1111 - максимальная яркость 0 1111_1110 - 1/2 0 1111_1100 - 1/4 0 1111_1000 - 1/8 0 1111_0000 - 1/16 0 1110_0000 - 1/32 0 1100_0000 - 1/64 0 1000_0000 - 1/128 0 0000_0000 - 1/256 1 0000_0000 - подсветка выключена
Давайте проверим это дело на практике, правда, есть одна подлянка - при первом включении как бы запускается минимальная яркость 1/256 (стартует счётчик с нуля), и пока он хоть один раз эти 256 тактов не отсчитает - на заданную ему яркость он не выйдет!
Вот работа на максимальной яркости:

Да, как и обещалось - по истечении 10,24 мкс (40 нс · 512) выдаётся непрерывная лог. "1".
Теперь поглядим 1/2 яркости, т.е когда на входе 0xFE = 254:

И 1/4 яркости, когда на входе 0xFC = 252:

Ну и 1/8, когда на входе 0xF8 = 248, и на этом хватит!

Присутствуют "комбинаторные выбросы", наверное стоит "для красоты" поставить на выходе защёлку, чтобы не излучать помеху по чём зря! Это "бесплатно", поскольку в выходном буфере каждой ножки и так есть защёлка, которую ни для чего другого не применишь, насколько я понимаю.
Хотя Quartus почему-то не спешит её использовать:

Не очень понимаю, почему, вот тут товарищ подробно разбирался, правда, у него была куда более достойная мотивация - тайминги уменьшить :) А у меня тупо одного ЛЭ жалко...
Ну и ладно, зато сигнал покрасивее, без выбросов:

И теперь хочется, чтобы у нас было два входных сигнала, brighter и dimmer, которые будут соответствующим образом менять значения IRdisable и D. Тут нам вполне подойдёт сдвиговый регистр, нечто похожее мы делали в буфере FIFO на ЛЭ, а потом и в параметризуемом FIFO на ЛЭ.
Снова начнём с "частного случая" из 4 бит для управления счётчиком, и дополнительный, пятый - для IRdisable:
wire SRena = brighter | dimmer; always @(posedge clk) if (SRena) begin D[0] <= dimmer? 1'b0 : D[1]; D[1] <= dimmer? D[0] : D[2]; D[2] <= dimmer? D[1] : D[3]; D[3] <= dimmer? D[2] : (~IRdisable); IRdisable <= dimmer? (~D[3]) : 1'b0; end
Проверим это дело:

Неплохо! По импульсам brighter яркость растёт, а достигнув максимума - так в нём и остаётся, по dimmer она падает, а дойдя до нуля - так и остаётся в нуле, хорошее предсказуемое поведение.
Немножко забавно, что стартует он на минимальной яркости, но всё же при включённой подсветке, хотя я и постарался D[] инициализировать единичками. Почему-то Quartus к этим начальным значениям зачастую относится наплевательски! Именно поэтому у меня при запуске QuatCore всегда вместо кода 0x89 (NOP) выскакивает 0x00 (OUT), из-за чего по UART передаётся одинокий нолик. Впрочем, я уже к нему привык и воспринимаю как одиночный "бип" при старте компьютера :)
Ну и осталось расширить это дело под произвольное количество бит. Здесь, по счастью, циклы не нужны:
wire SRena = brighter | dimmer; always @(posedge clk) if (SRena) begin D[0] <= dimmer? 1'b0 : D[1]; D[PWMwidth-2:1] <= dimmer? D[PWMwidth-3:0] : D[PWMwidth-1:2]; D[PWMwidth-1] <= dimmer? D[PWMwidth-2] : (~IRdisable); IRdisable <= dimmer? (~D[PWMwidth-1]) : 1'b0; end
И наконец, глянем, как оно работает на 8-битном регистре, т.е с глубиной регулирования до 1/256 яркости (и затем выключение):

ОТЛИЧНО! Мне поначалу страшно не нравилось, что IRdisable не совсем "вписывается" в этот сдвиговый регистр, там две инверсии стоит, нельзя ли было как-то по-другому счётчик определить, чтобы всё совсем вышло шикарно? Похоже, что нет: мы могли бы инвертировать значения регистра D, если заставить счётчик считать не вверх, а вниз, но тогда непрерывная подача sclr=1 (то есть сигнала IRdisable) приведёт не к выключению, а к максимальной яркости, т.к cout при счёте "вниз" зажигается как раз когда досчитаем до нуля! Можно было бы IRdisable подавать не на sclr, а на sset, но одновременное использование sset (загрузка фиксированного значения) и sload (загрузка с шины) нежелательно, число ЛЭ для счётчика сразу удваивается, по сути "спереди" ставится что-то вроде мультиплексора. Поэтому именно так, как описано у меня, и не иначе!
Ещё из интересного: пока я опеределял D как output (чтобы посмотреть, что там творится), то даже указав там
output reg [3:0] D = 4'b1111
синтезатор не захотел инициализировать его нулями.
А вот когда я перестал выдавать его на выход, и написал просто в теле модуля:
reg [PWMwidth-1:0] D = {PWMwidth{1'b1}};
он, похоже, принял это к сведению, но число ЛЭ возросло сразу на 8. Похоже, что регистр D был реализован как "инвертированный", но это заставило поставить ещё 8 инверторов перед поступлением на счётчик, чтобы оно работало "как задумано". А если регистр объявлен непосредственно на выходе, то инвертор уже ставить "некуда" - и тогда квартус игнорирует инициализацию.
Довольно необычное поведение, и не факт что вообще задокументированное...
Разумеется, я из жадности убрал эту инициализацию, пущай стартует с нуля, это в общем-то довольно разумный подход, дать минимальную яркость. В таком виде и при 8-битном регистре (регулировка 1:256) этот модуль занимает 22 ЛЭ, что я считаю неплохим результатом. 8 ЛЭ были необходимы для счётчика, ещё 9 ЛЭ - для сдвигового регистра, ещё 1 ЛЭ - защёлка на выходе, и ещё 1 ЛЭ - делает ИЛИ между brighter и dimmer, итого 19 ЛЭ. Ещё 3 где-то "затесались", так бывает.
Вот код модуля целиком:
module IR_PWM (input clk, input brighter, input dimmer, output reg PWM = 1'b0); parameter PWMwidth = 8; reg [PWMwidth-1:0] D = 1'b0; //{PWMwidth{1'b1}}; reg IRdisable = 1'b0; wire SRena = brighter | dimmer; always @(posedge clk) if (SRena) begin D[0] <= dimmer? 1'b0 : D[1]; D[PWMwidth-2:1] <= dimmer? D[PWMwidth-3:0] : D[PWMwidth-1:2]; D[PWMwidth-1] <= dimmer? D[PWMwidth-2] : (~IRdisable); IRdisable <= dimmer? (~D[PWMwidth-1]) : 1'b0; end wire c_PWM; lpm_counter counter ( .clock (clk), .data (D), .sload (c_PWM), .sclr (IRdisable), .cout (c_PWM) ); defparam counter.lpm_direction = "UP", counter.lpm_port_updown = "PORT_UNUSED", counter.lpm_type = "LPM_COUNTER", counter.lpm_width = PWMwidth; always @(posedge clk) PWM <= c_PWM; endmodule
Ну и теперь дурацкий модулёк, сделанный на "отвяжись", который будет давать импульс "brighter" по приходу с UART символа "+" и "dimmer" по символу "-":
module IRcontrolByUart (input [7:0] D, input ByteCE, output dimmer, output brighter); assign dimmer = ByteCE & (D == "-"); assign brighter = ByteCE & (D == "+"); endmodule
Возможно, имеет смысл всё это управление отдельными символами (U,D,L,R,E, u,d,l,r,e для джойстика и +,- для яркости) собрать в один модуль, и там часть кода объединить, но не факт, что это чего-то даст, и уж это точно код отладочный.
Наконец, соединяем всё это вместе:

Затем берём старую прошивку, не VideoProcessing, где реализован алгоритм обнаружения точек, а ImageTransfer, где просто передаётся картинка и приняты специальные меры, чтобы при вызове экранного меню мы его на переданном изображении уже видели (см. наконец настроили экспозицию)
Синтезируется всё это безобразие в 1525 ЛЭ, или 1553 ЛЭ после Fitter'а. Опять "чисто по рандому" выполз Critical Warning: Timing constraints not met, 24,88 МГц вместо требуемых 25 МГц.
Сейчас чуть-чуть поковыряем - и попробуем запустить...