nabbla (nabbla1) wrote,
nabbla
nabbla1

Categories:

Программа для регулирования яркости на QuatCore

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

	@@FrameLoop:	Acc	IN
			SUB	0x2E
			JGE	@@GetPicture	 ;получится -1, если "-" или -3 если "+"
			;здесь управляем ИК подсветкой				
			SUB	-1	;в случае "-" получим 0, в случае "+" получим -2
			JGE	@@Dimmer
			;здесь мы прибавляем яркость, если она ещё не максимальна
			kLOOPup	@@ShowBright	;прибавляет единицу, если есть "куда прибавлять", и только в этом случае прыгает
			;если и так "на упоре" - попадаем своим ходом
	@@ShowBright:	IR	k
			Acc	'0'
			ADD	k
			OUT	Acc	;циферка отобразится через UART
			JMP	@@FrameLoop
	
			;здесь убавляем яркость, если она не достигла нуля
	@@Dimmer:	kLOOP	@@ShowBright
			JMP	@@ShowBright
				
; ------------------------- здесь прохода нет (только прыжками!) ---------------------------------------------------------------
				
				
			;чтобы экранное меню успело появиться, выжидаем аж 12 кадров (и 13-й начинаем обрабатывать)
	@@GetPicture:	j	12


Добавилось 10 строк, довольно-таки дурацких, но почему бы и нет...


Как видно, мы получаем один байт с UART, и сначала вычитаем 0x2E (символ "."). Отрицательный результат будет означать: мы нажали "+" или "-", с помощью которых у нас регулируется яркость. В противном случае, это одна из команд для получения изображения (либо просто I - Image, либо U/D/L/R/E/u/d/l/r/e для управления экранным меню, с пересылкой изображения этого самого меню), и тогда мы прыгнем дальше в @@GetPicture

Далее, нам нужно отличить "+" от "-", для чего приходится сделать "финт ушами" - ВЫЧЕСТЬ число -1. Кажется, легче было бы прибавить 1, но команда ADD не меняет регистр знака! Поэтому вот так.

Эта программа у нас совсем простая, в ней оказываются свободными регистры i,k, поэтому недолго думая мы применяем регистр k чтобы хранить текущее значение яркости.

Далее, мы хотим делать инкремент и декремент "с насыщением" - если дойдём до нуля, то ниже нуля опускаться не должны, равно как не должны и подниматься выше 7. Чтобы не опуститься ниже нуля, хорошо подходит команда kLOOP - только если k>0, она вычитает единичку и осуществляет прыжок по метке. В противном случае ноль так и остаётся нулём, и мы никуда не прыгаем. В данном случае прыжок оказывается "холостым" - что мы прыгаем, что нет - попадаем в одно и то же место.

Чтобы не подняться выше 7, сейчас я уменьшил ширину регистра k до 3 бит (0..7). При таком "раскладе", команда kLOOPup делает ровно то, что нам нужно: только при k>7 прибавляет единичку и осуществляет прыжок, в противном случае ничего не делает.

Наконец, мы попадаем на метку @@ShowBright, где отправляем значение k в новоиспечённый модуль QuatCorePFM, а потом хоть сколько-нибудь user-friendly отображаем текущую яркость по UART: туда придёт символ от "0" до "7".

Программа успешно компилируется, похоже, с конфигом всё в порядке. Запускаем:



Да, яркость регулируется, и если слишком много раз посылаешь "-", она доходит до нуля, да так в нуле и остаётся. А вот если слишком долго посылать "+", доходит до 7, а в следующий раз всё-таки "переворачивается" в ноль, и дальше по второму кругу.

Это подтверждает, что регистр k в данный момент действительно 3-битный, но команда kLOOPup не работает как ожидалось. Собственно, мы её ни разу и не использовали! И не проектировали - она как бы сама по себе получилась.

Вот 16 команд, "завязанных" на регистры i,j,k, Inv:


Я старался, чтобы биты из адресов команд требовали минимального декодирования, управляя практически "напрямую". Старшие 4 бита определяют, что мы возимся именно с этим конкретным модулем, QuatCorePCregisters:

wire isOurOp = (DestAddr[7:4] == 4'b1010)&(~stall); //excludes non-PC ops and unrelated JMP ops


Затем, проверялось на команду ijk:

wire isIJK = ijkEnabled & DestAddr[2] & DestAddr[1] & DestAddr[0]; //good command, but probably pretty annoying for HW (lots of MUXes!)

Эта команда должна была записать одновременно регистры i,j,k и Inv из соответствующих полей 16-битного слова. И такая же команда была на чтение, что позволило бы очень легко сохранить все эти регистры в стек и потом прочитать их оттуда. Но в своё время мне не понравилось, сколько лишних ЛЭ это добавляет, и я ввёл локальный параметр ijkEnabled, чтобы решать - "нужна ли нам эта команда?". А если теперь посмотреть на код, обнаруживается: он малость устарел, команда ijk будет декодироваться и вместе с командой Jnik (прыжок если i не равно k). Не забыть бы!

Но пока ijkEnabled = 0, так что isIJK всегда нулевой.

Далее, вводится "провод" isWrite:
wire isWrite = isOurOp & (~DestAddr[3]); //excludes also LOOP ops, but still leaves increments


который срабатывает на 8 "верхних" командах, которые в некотором роде "команды записи" в отличие от нижних 8, которые "команды условного перехода". Ладно, пока логика прослеживается...

Далее, вводятся сигналы, непосредственно управляющие синхронной загрузкой в соответствующие регистры:

wire WriteI = isWrite & (((DestAddr[1:0] == 2'b00)&(~(DestAddr[2]&ippEnabled)))|isIJK);

wire WriteJ = isWrite & (((DestAddr[1:0] == 2'b01)&(~(DestAddr[2]&jppEnabled)))|isIJK);

wire WriteK = isWrite & (((DestAddr[1:0] == 2'b10)&(~(DestAddr[2]&kppEnabled)))|isIJK);

wire WriteInv = isOurOp & (DestAddr[1:0] == 2'b11);


У нас определены ещё 3 локальных параметра: ippEnabled, jppEnabled и kppEnabled, которые определяют, используем ли мы команды i++,j++ и k++ соответственно. В данный момент, к примеру, я выставил ippEnabled=jppEnabled=0, kppEnabled=1.

Как видно, если мы не хотим инкремент, и если заведомо isIJK=0, то вся правая часть выражения успешно уходит, и мы начинаем декодировать требуемую команду только по 7 битам из 8: в isWrite мы уже проверили 5, а здесь проверяем ещё 2 младших. Соответственно, верхнюю строку от нижней мы отличить не сможем.

Для kppEnabled=1 получится проверка всех 8 бит - и сигнал WriteK сработает только на команде "k" и нигде ещё!

Для записи в Inv мы всегда проверяем только 7 бит, поскольку в команде ijk запись в Inv также производится!

Здесь всё верно.

Далее идут сигналы для счёта регистров i,j,k, а также для выбора направления счёта, ведь это реверсивные счётчики:
wire CountI = isOurOp & ((DestAddr[3] & (~iZ))|(DestAddr[2]&ippEnabled)) & (DestAddr[1:0] == 2'b00);
wire Iup = (DestAddr[2]&ippEnabled);

wire CountJ = isOurOp & ((DestAddr[3] & (~jZ))|(DestAddr[2]&jppEnabled)) & (DestAddr[1:0] == 2'b01);
wire Jup = (DestAddr[2]&jppEnabled);

wire CountK = isOurOp & ((DestAddr[3] & (~kZ))|(DestAddr[2]&kppEnabled)) & (DestAddr[1:0] == 2'b10);
wire Kup = (DestAddr[2]&kppEnabled);


Проще всего разобраться с выбором направления счёта. Если команды инкремента в принципе запрещены (к примеру, ippEnabled=0), то счёт всегда будет вестись "вниз", в ходе команды iLOOP. Если же инкремент возможен, до направление счёта определяется битом DestAddr[2], т.е команды из первой и третьей строки будут считать "вниз", а из второй и четвёртой - "вверх". Это соответствует нашей логике, и ровно такое поведение мы наблюдаем.

Похоже, вся проблема в сигналах CountI, CountJ и т.д. Если инкремент в одном из регистров запрещён, как в нашем случае ippEnabled=0, то счёт будет разрешён строго когда отсутствует сигнал cout с этого регистра, т.е когда значение ненулевое. При этом DestAddr[2] проверен не будет, т.е команды iLOOP и iLOOPup на самом деле отработают абсолютно одинаково. (собственно, ippEnabled=0 отключает команды i++ и iLOOPup). Здесь есть некая сермяжная правда.

А вот если инкремент разрешён, как у нас kppEnabled=1, то, как обнаруживается, счёт разрешается и в k++, и с тем же успехом в kLOOPup, и там проверка на "выход на упор" не производится. И в этом есть определённая "сермяжная правда": при такой реализации, любой цикл, будь то kLOOP или kLOOPup, закончится на k=0. Вот только в kLOOP регистр k будет уменьшаться на единицу, когда он с единицы уменьшится до нуля - начнётся последняя итерация, и уже из неё мы выйдем, при этом так и останется k=0. А в случае kLOOPup, k будет увеличиваться на единицу, последняя итерация начнётся с максимальным значением k, а по выходу из неё к k прибавится очередная единица, тем самым вернув к нулю.

В общем, как мы скажем - так и будет правильно! Сейчас удобнее сделать, чтобы на kLOOPup регистр "застрял" на максимальном значении, давайте так и сделаем:
//вариант где цикл с iLOOPup завершается на i=0
//wire CountI = isOurOp & ((DestAddr[3] & (~iZ))|(DestAddr[2]&ippEnabled)) & (DestAddr[1:0] == 2'b00);
//вариант где цикл с iLOOPup завершается на максимально возможном i
wire CountI = isOurOp & ((DestAddr[3] & (~iZ))|(~DestAddr[3]&DestAddr[2]&ippEnabled)) & (DestAddr[1:0] == 2'b00);

//вариант где цикл с jLOOPup завершается на j=0
//wire CountJ = isOurOp & ((DestAddr[3] & (~jZ))|(DestAddr[2]&jppEnabled)) & (DestAddr[1:0] == 2'b01);
//вариант где цикл с jLOOPup завершается на максимально возможном j
wire CountJ = isOurOp & ((DestAddr[3] & (~jZ))|(~DestAddr[3]&DestAddr[2]&jppEnabled)) & (DestAddr[1:0] == 2'b01);

//вариант где цикл с kLOOPup завершается на k=0
//wire CountK = isOurOp & ((DestAddr[3] & (~kZ))|(DestAddr[2]&kppEnabled)) & (DestAddr[1:0] == 2'b10);
//вариант где цикл с kLOOPup завершается на максимально возможном k
wire CountK = isOurOp & ((DestAddr[3] & (~kZ))|(~DestAddr[3]&DestAddr[2]&kppEnabled)) & (DestAddr[1:0] == 2'b10);


Как ни странно, такой код не добавляет нам ЛЭ. Синтез проходит без проблем, 1480 ЛЭ после Analysis&Synthesis, 1508 ЛЭ после Place&Route.

И теперь всё работает так, как мы ожидали:



Ну и поглядим, какие же токи через ИК светодиоды получаются при этом 3-битном ЧИМ генераторе :)


Уже гораздо лучше! Синяя кривая - когда я дожидался, пока ток выйдет на "тепловое равновесие". Красная - это после 5 минут на максимальной яркости как можно быстрее переключал яркости и записывал проходящий ток, не давая времени остыть. Как и ожидалось, здесь с ростом температуры растёт ток, причём на больших яркостях стабильность высокая (львиная доля напряжения падает на токозадающем резисторе), а по мере её уменьшения, зависимость от температуры усиливается.

Возможно, меня даже такая штука устроит. А если хочется поточнее - надо всё-таки заменить 3-битный ЧИМ (PFM) на 4..8 битный ШИМ (сколько бит - надо определять экспериментально) - и подбирать хорошие значения.

UPD. Может, несколько нагляднее сказать, во сколько раз изменяется ток на каждой "позиции". Мы бы хотели, чтобы падал в 2 раза на каждом уменьшении яркости. В действительности таблица такова:
1,2
2,25
2,67
3,69
4,58
3,86
2,83

Это куда лучше, чем прошлые 1,2 - 5 - 100 - ∞, но всё-таки слишком крупные скачки, хотелось бы поменьше...
Tags: ПЛИС, освещение, программки, работа, странные девайсы
Subscribe

  • Мышки плакали, кололись,

    но продолжали смотреть Доктора Кто... Что-то не то, всё-таки. Какая-то бессмыссленность происходящего, простые сюжеты. Расизм - это плохо, экология…

  • И ещё о 13-й докторе

    В воскресенье вышла первая серия, посмотрел это дело. Да в общем, нормально, вполне себе "Доктор Кто". Вот она, компаньон моей мечты - ЯЯЯЯЗЬ!…

  • Великая Октябрьская резня бензопилой

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

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

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

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

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

  • Покрышки с взрывным характером

    Продолжаю кататься на велосипеде на работу и назад, а также время от времени в РКК Энергию. С 17 мая (когда решил записывать, сколько проехал,…

  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your IP address will be recorded 

  • 0 comments