nabbla (nabbla1) wrote,
nabbla
nabbla1

Categories:

Макет видеоизмерителя: регулировка яркости подсветки

В четверг меня очень деморализовал тот факт, что при самую малость повёрнутой мишени у меня даже "на симуляции" на компьютере начали сливаться две точки:


И оно даже ожидаемо в какой-то мере, пятна у меня вот ни разу не круглые! Проблема связана с нашей тактовой частотой 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 МГц.


Сейчас чуть-чуть поковыряем - и попробуем запустить...
Tags: ПЛИС, освещение, программки, работа, странные девайсы
Subscribe

  • А всё-таки есть польза от ковариаций

    Вчера опробовал "сценарий", когда варьируем дальность от 1 метра до 11 метров. Получилось, что грамотное усреднение - это взять с огромными весами…

  • Так есть ли толк в ковариационной матрице?

    Задался этим вопросом применительно к своему прибору чуть более 2 недель назад. Рыл носом землю с попеременным успехом ( раз, два, три, четыре),…

  • Big Data, чтоб их ... (4)

    Наконец-то стряхнул пыль с компьютерной модели сближения, добавил в неё код, чтобы мы могли определить интересующие нас точки, и выписать…

  • Очередная несуразность в единицах измерения

    Когда-то я написал программу PhysUnitCalc - калькулятор, умеющий работать с размерностями. Мне казалось, что я наступил уже на все грабли, которые…

  • Big Data, чтоб их... (3)

    "В предыдущих сериях": мой прибор выдаёт 6 значений: 3 координаты и 3 угла, т.е все 6 степеней свободы твёрдого тела. Причём ошибки измерения этих 6…

  • Big Data, чтоб их... (2)

    Вчера получил упоротое уравнение, чтобы найти, с какими весами нужно брать результаты измерений, чтобы получить наименее шумную и при этом…

  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your IP address will be recorded 

  • 5 comments