nabbla (nabbla1) wrote,
nabbla
nabbla1

Обнаружение точек "вживую"

Если помните, я очень долго писал и отлаживал алгоритм обнаружения точек, используя "генератор тестового изображения". Сначала проверял на симуляции, потом перебрался на "железо", но ещё без реального сигнала.

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


Попросту поглядим в фотошопе на изображение из предыдущего поста. И окажется, что порог нужно ставить 255 (!!!):


"штрих" наверху - это кромка плоского зеркала. Где это зеркало правильно ставить - до сих пор вопрос открытый. Оно нужно для "отладочных целей" - для нахождения нормали к плоскости мишени, чтобы знать, каковы реальные параметры сближения. В принципе, его можно чем-то закрывать во время работы макета. Повесить занавесочку бархатную :)

Пятна слева снизу - это лежит 6 винтов, держащих крышку макета. Их можно завинтить и надеяться, что по закону подлости не придётся тут же их отвинчивать.

Вспоминаем, как работало наше обнаружение точек, вот файл ProcessFrame.asm:

ProcessFrame proc			
			j		1	;отныне и во веки веков!	
			;ждём кадрового синхроимпульса
			ACQ		VSync		;"застревает" до тех пор, пока кадровый импульс не придёт
			;пропускаем "пустые строки" вверху изображения (нужно только для аналоговой камеры, в цифровой такой фигни нет вроде бы...)
			k		9
			Z		D1												
@@topRows:		ACQ		HSync
			kLOOP		@@topRows
			;теперь k=0, пусть так и остаётся, наши [X+k], [Y+k], [Z+k] будут означать [X],[Y],[Z], очень надо для списков				
			;теперь начинаются информативные строки изображения, сейчас их 720, позже может стать 1024. 
			C		[Z+k]			;ожидаемый диаметр точки, всё-таки штука весьма нужная, лучше держать локальной перем.												
			[SP+2j]		0		;в [SP+2] храним номер строки. начнём всё-таки с максимума... 
						
			JMP		@@FinalRange	;чтобы СНАЧАЛА отправить запрос, а потом уже смотреть результат, так почему-то удобнее					
			;можем заменить на JNO, переполнения здесь случаться не должно

			;НАЧАЛО ЦИКЛА ПО СТРОКАМ
@@newRow:		[SP+2j]		Acc
			X		ActivePoints	;начинаем цикл по всем активным точкам
			
			;даже если список пуст, первую половину цикла мы пройдём, она касается отрезка, предшествующего точке из списка
@@ActPointsStart:	[SP+2j+1]	GPUL			;яркость самой яркой точки на отрезке
			[SP+1]		GPUH			;соотв. координата
			Acc		Threshold		;поставили задом наперёд, чтобы избежать Hazard'а	
			SUB		[SP+2j+1]		;вычитаем порог, положит. значение свидетельствует о новом найденном "пятне"						
			JL		@@MidCycle		;пятна нет, не торопимся заказывать отрезок
			;пятно есть. Надо проверить: не сливается ли оно с пятном слева или с пятном справа
			;X сейчас указывает на точку, расположенную слева (возможно, фиктивную, нам не важно)
			;коорд. текущей точки лежит в Acc, её яркость в C
			Z		X		;Чтобы в Merge всегда использовать Z, независимо от того, левая или правая точка оказались
			i		1
@@checkCycle:		Acc		[SP+1]
			SUB		[Z+2j+k]
			ABS		Acc
			[SP]		Acc		;модуль разности - это и будет новый "диаметр" точки, если сработает слияние
			DIV2S		C		;не будем мудрить пока что, всё "в лоб"
			DIV2S		[Z+1]						
			JL		@@DoMerge
			Z		[X+k]
			iLOOP		@@checkCycle
			;если попали сюда, значит слияния не было, и точку всё-таки надо добавить!
											
			;[SP+2j+1]=[SP+3] указывает яркость этой точки, [SP+2j]=[SP+2]: Y-координату, [SP+1]: X-координата
			;а куда вставить, указывает рег. X
			;в Acc содержится модуль разности между правой точкой и текущей, за вычетом двух радиусов
			Y		Heap									
			;добавит в X новую, пока что пустую точку.
			[SP++]		@@AfterListOp
						
;взять первый элемент из списка Y и перецепить его в начало списка X
;значения X,Y остаются неизменными,
;при вызове должно быть k=0, затираем регистр Z, Acc и флаг знака
;адрес возврата хранится в стеке, как положено
	ListOp proc			
			ZAcc		RoundZero
			Z		[Y+k]
			SUB		[Y+k]
			[Y+k]		[Z+k]
			JGE		@@OutOfMem									
			[Z+k]		[X+k]
			[X+k]		Z
			NOP		0
			JMP		[--SP]
	@@OutOfMem:	[--SP]		Call(OutOfMemory)	;SP возвращается на место, но выпрыгиваем в другое место, на обработку ошибки
	ListOp endp						
											
						;если добрались досюда, значит памяти хватило. X содержит адрес указателя на новую точку, а вот Z содержит адрес самой точки!
						;наполним её содержимым!						
@@AfterListOp:		X		Z		;для дальнейшей работы (после MidCycle)... Чтобы мы не взялись за только что добавленную точку!
			[Z+1]		C		;гориз. размер точки (будет расти при слияниях, а поначалу D, т.к мы сюда включаем и )																														
			[SP++]		@@MidCycle		;после вызова AcqQueue возвращаемся
											;сразу на метку @@MidCycle

;"заказать" видеопроцессору два отрезка на обработку,
;первый - на поиск новой точки (т.е здесь мы пока не ожидаем ничего найти),
;второй - на уточнение координат существующей
;в [SP+1], теперь уже [SP], лежит X-координата
;в [SP+2j]=[SP+2], теперь уже [SP+1] - Y-координата точки,
;в [SP+2j+1]=[SP+3], теперь уже [SP+2j]=[SP+2] - яркость точки
;при вызове AcqAndUpdate обновим значения, при вызове AcqNoUpd - не будем

;в [X+1] лежит радиус точки,
;в [X+2j+k]=[X+2] - X-координата
;в [X+2j+1]=[X+3] - Y-координата
;в [X+4j+k]=[X+4] - яркость
	AcqAndUpdate proc	
			i		2
			NOP		0	;избежать Hazard, ведь i участвует в следующей строке
	@@updCycle:	[X+2j+i]	[SP+i]
			iLOOP		@@updCycle
	AcqNoUpd:	Acc		[X+2j+k]
			DIV2S		[X+1]	;теперь в аккмуляторе у нас X - Ceil(D/2)
			ACQ		Acc		;первый отрезок
			ADD		[X+1]	;а вот теперь X + Floor(D/2)
			ACQ		Acc		;второй отрезок
			JMP		[--SP]
	AcqAndUpdate endp	
										
			;а вот и слияние. В простейшем случае увеличиваем размер точки, и на этом считаем работу выполненной...
@@DoMerge:		[Z+1]		[SP]	;пока так			
			;теперь надо посмотреть - а есть ли точка? Или мы уже прошли последний отрезок между точками?
			;но у нас в конце списка правая фиктивная точка - её надо игнорировать!			
@@MidCycle:		Y		X
			X		[X+k]								
			ZAcc		RoundZero
			SUB		[X+k]
			JGE		@@FinalRange	;наткнулись на NULL, значит, все точки обработали...						
			;запрашиваем отрезок, где ожидаем точку. Первой придёт яркость, как всегда
			[SP+2j+1]	GPUL		;яркость
			Acc		GPUL
			SUB		[X+4j+k]						
			[SP+1]		GPUH		;пока не забыли - сразу второе слово запрашиваем (коорд точки), чтобы не сбить весь FIFO 
			[SP++]		@@ActPointsStart	;нам по-любому придётся вернуться именно сюда!
			JL		AcqAndUpdate	;отсюда "выпрыгнем" сразу на метку @@ActPointsStart						
			;ветвь исполнения, если старая точка оказывалась ярче.
			;Нужно сообразить: не пора ли ей на покой, то бишь в список AllPoints?
			;в кои-то веки работаем с Y-координатой!
			Acc		[X+2j+1]	;координата точки (из списка)
			DIV2A		[X+1]		;прибавить половинку её диаметра
			SUB		[SP+1]	;вычесть текущий номер строки
			JGE		AcqNoUpd	;здесь ОБЯЗАН быть именно JGE, т.к флаг знака является одним из "аргументов" для AcqQueue
			;пора эту точку перетаскивать в другой список... 
			X		AllPoints
			CALL		ListOp
			X		Y		;теперь [X] ссылается уже не на обработанную и удалённую точку, а на следующую, так что всё верно
			JMP		[--SP]	;ура...					
			;до PixelsProcessed проскрипели, теперь последний участок запросить на обработку, и синхроимпульс для кучи
@@FinalRange:		ACQ		WholeRow
			ACQ		HSync
			Acc		[SP+2j]
			SUB		ImgHeight
			ADD		ImgHeightP1
			JL		@@newRow
			;усё, отстрелялись...
					
			;надо ещё все оставшиеся в ActivePoints точки "перецепить" в AllPoints
			Y		ActivePoints	;это заведомо фиктивная, и указывает либо на другую фиктивную, то ли на реальную
			X		AllPoints
			Y		[Y+k]			;это уже может быть реальная точка, а может быть правой фиктивной
@@Transfer:		ZAcc		RoundZero
			SUB		[Y+k]			;если [Y+k]=NULL, значит пора закругляться
			[SP++]		@@Transfer
			JL		ListOp
			NOP		[--SP]		;раз не было вызова - возвращаем стек в исходное состояние												
		ProcessFrame endp


Страшная штуковина... И в ней затесалась одна "магическая постоянная", 9, в самом начале. Это количество строк "выше экрана", которые мы пропускаем. Так было в "модельном изображении", но в реальности надо пропустить 29 строк, и в более новой ImageTransfer.asm (где картинка сохраняется в статическую память, а потом передаётся на компьютер) введена константа TopRows:

	TopRows	EQU	28	;столько синхроимпульсов нужно пропустить, прежде чем начнутся "полезные" строки (минус 1, поскольку считаем до нуля)


Пригодится!

Остальные константы в ProcessFrame.asm не введены, они сидят в VideoProcessing.asm - файле, в который ProcessFrame подключается в виде отдельной "библиотеки". Вот он, VideoProcessing.asm:

;громко думаем, как организовать видеоообработку
;наилучшее качество получим с сигнала CVI, т.к у него длительность видимой строки максимальная, 43 мкс, и 10,3 мкс на обратный ход
;у нас есть не более 308 тактов для подготовки к следующей строке!

;в 1205ХВ014 логика будет существенно отличаться: сканирование "в два луча", всего 8 тактов на обратный ход
;но давайте решать проблемы по мере их поступления. Потренируемся на кошках, то бишь на аналоговых камерах высокой чёткости...

%include "QuatCoreConsts.inc"
%include "Win1251.inc"
.rodata
	; VSync			EQU	0x8000	;ожидание кадрового синхроимпульса, значение для регистра произвольное
	; HSync			EQU	0x4000	;ожидание строчного синхроимпульса, а затем ещё интервал front porch (между импульсом и началом картинки)
	; WholeRow		EQU	1023	;для обработки всей строки целиком	
	; ImgHeight		EQU	720	;720p пока что 
	VSync			EQU	0x8000	;ожидание кадрового синхроимпульса, значение для регистра произвольное
	HSync			EQU	0x40EE	;ожидание строчного синхроимпульса, а затем ещё интервал back porch (между импульсом и началом картинки) в 240 тактов (239+1 такт на сброс счётчика + 2 такта задержки на ПЗУ)
	WholeRow		EQU	31	;для проверки на симуляторе
	ImgHeight		EQU	32	;вообще, 32, но вводим дополнительные строки, чтобы перенести все точки из ActivePoints в AllPoints
	ImgHeightP1		EQU	33	;не обучили свой компилятор обрабатывать "constexp"
	Threshold		EQU	0xDFF	;минимальная яркость пятен, сейчас поставили 1/8 от "насыщения" (4096), но с инверсией, так надо!
	Nil			EQU	-32768	;традиционно это 0, но у нас нет флага нуля! Поэтому куда удобнее гигантское отриц. значение, всяко у нас 65535 слов памяти не наберётся, надеемся что 256..512 слов вполне хватит
.code	
	main proc
	
		%include "SetClock.asm"
			
				SP		Stack	;инициализация стека.
		
		%include "ProcessFrame.asm"
		
				;давайте передадим параметры обнаруженных точек по UART
				;пока тупо по младшему байту
				SIO		UART
.rodata
	PointsStr	db	'Обнаруженные точки:',0x0D,0x0A,0x00
.code
				X		PointsStr
				CALL		print
				
				Z		AllPoints	;В списке AllPoints нет фиктивных точек. Но может вообще никаких не быть!
				;Хотя сам AllPoints - это "заголовок", с него надо двинуться вперёд, если получится
				i		0		;номер точки (для отображения в терминале, поэтому нумерация с 1)
				JMP		@@moveOn	;перемещение на очередную точку
				;к началу цикла мы переместились на одну из найденных
		@@outLoop:	X		NumSign		
				Acc		i
				CALL		CallThemAll
				;назвали номер точки, теперь выдаём её параметры
.rodata
	NumSign		db	'№',0x00
	XStr		db	'X:',0x00
	YStr		db	'Y:',0x00
	LumStr		db	'Яркость:',0x00
	SizeStr		db	'Диаметр:',0x00		
	OutOfMemStr	db	'Out of heap memory',0x0D,0x0A,0x00	
.code	
;в [Z+1] лежит радиус точки,
;в [Z+2j+k]=[Z+2] - X-координата
;в [Z+2j+1]=[Z+3] - Y-координата
;в [Z+4j+k]=[Z+4] - яркость
				X		XStr
				Acc		[Z+2j+k]		;X-координата
				CALL 		CallThemAll
				
				X		YStr
				Acc		[Z+2j+1]	;Y-координата
				CALL 		CallThemAll
				
				X		LumStr
				Acc		0xFFF		;инверсия 12 младших бит через вычитание
				SUB		[Z+4j+k]	;Яркость
				CALL 		CallThemAll
				
				X		SizeStr
				Acc		[Z+1]	;размер точки
				CALL 		CallThemAll				
				
				
				;продвигаемся на одну позицию, если только указатель не nil
		@@moveOn:	ZAcc		RoundZero
				SUB		[Z+k]
				Z		[Z+k]
				i++		0
				JL		@@outLoop


				JMP		@@endless
	OutOfMemory:		X		OutOfMemStr
				CALL		print
	@@endless: 		JMP 		@@endless
	main endp	

		
	;после 3 вызовов этой процедуры начинается экономия :)
	CallThemAll	proc
		CALL	print
		CALL	BetterBin2Bcd
		OUT 13
		OUT 10
		JMP [--SP]
	CallThemAll endp
	
	%include "Print.asm"
	%include "Bin2Bcd.asm"
	
.data
	;расчётный диаметр пятна. Вообще, должен грубо устанавливаться исходя из экспозиции кадра, затем корректироваться по вычисленной дальности
	;но пока, для отладки, сразу зададим 3
	D1		dw	6
	;ДИВАН
	;в дальнейшем будет ещё R2 - радиус МДД, когда работаем в ближней зоне (они больше не используются для измерения, но нужно проверять их наличие)

	ActivePoints	dw	APHeadDummyX
	AllPoints	dw	Nil	
	APHeadDummyX	dw	Nil	;когда будем сверять с X-координатой ПРЕДЫДУЩЕЙ ТОЧКИ, это позволит не вводить лишних проверок!
	Heap		dw	Elem0	
	APTailDummyX	dw	32767
	;возможно, и оставшиеся значения Y, Lum, Path могут пригодится, а пока пущай будут чем угодно
	
	;область для списков точек, для начала она вся объединена в список Heap
	Elem0:
	Elem0Next	dw	Elem1
	Elem0X		dw	?
	Elem0Y		dw	?
	Elem0L		dw	?
	Elem0P		dw	?
	Elem1		dw	Elem2,?,?,?,?
	Elem2		dw	Elem3,?,?,?,?
	Elem3		dw	Elem4,?,?,?,?
	Elem4		dw	Elem5,?,?,?,?
	Elem5		dw	Elem6,?,?,?,?
	Elem6		dw	Elem7,?,?,?,?
	Elem7		dw	Elem8,?,?,?,?
	Elem8		dw	Elem9,?,?,?,?
	Elem9		dw	ElemA,?,?,?,?
	ElemA		dw	ElemB,?,?,?,?
	ElemB		dw	ElemC,?,?,?,?
	ElemC		dw	ElemD,?,?,?,?
	ElemD		dw	ElemE,?,?,?,?
	ElemE		dw	ElemF,?,?,?,?
	ElemF		dw	Elem10,?,?,?,?
	Elem10		dw	Elem11,?,?,?,?
	Elem11		dw	Elem12,?,?,?,?
	Elem12		dw	Elem13,?,?,?,?
	Elem13		dw	Elem14,?,?,?,?
	Elem14		dw	Elem15,?,?,?,?
	Elem15		dw	Elem16,?,?,?,?
	Elem16		dw	Elem17,?,?,?,?
	Elem17		dw	Elem18,?,?,?,?
	Elem18		dw	Elem19,?,?,?,?
	Elem19		dw	Elem1A,?,?,?,?
	Elem1A		dw	Elem1B,?,?,?,?
	Elem1B		dw	Elem1C,?,?,?,?
	Elem1C		dw	Elem1D,?,?,?,?
	Elem1D		dw	Elem1E,?,?,?,?
	Elem1E		dw	Elem1F,?,?,?,?
	Elem1F		dw	-32768,?,?,?,?
	
	Stack	dw	?,?,?,?

(пришлось убрать цвета - ЖЖ возмущается слишком большой записью)

Пора редактировать константы.

VSync остаётся какой и была, 0x8000, то есть только флажок "ожидать кадровый синхроимпульс", а сколько после этого тактов прождать - совершенно пофиг!

HSync - видим закомментированное значение 0x4000, то есть и здесь лишь флажок "ожидать строчный синхроимпульс", но оно переправлено на 0x40EE (пропустить 238 тактов!), тогда как в ImageTransfer имеем 0x4106 (пропустить 262 такта), и это уже "боевое" значение для стандарта CVI, с условием, что мы подрезаем наше поле зрения с 1280х720 до 1024х720. Пусть оно и будет.

WholeRow - было значение 1023, которое закомментировали и написали 31, очевидно, для "тестового изображения", чтобы совсем не подохнуть на симуляции. А в ImageTransfer у нас тоже 1023, так и поставим.

ImgHeight - было 720, переправили на 32, тогда как в ImageTransfer стоит константа UsefulRows = 719. Но там мы традиционно начинаем с 719 и уменьшаем до нуля, и по признаку отрицательного числа решаем выйти из цикла. Тут у нас был Over-Engineering, когда мы начинали с нуля, и в конце каждой итерации вычитали 720 и прибавляли 721, и по флагу знака решали, выходить ли нам из цикла.

Это было вынужденное решение, поскольку мы собирались взять ещё сотню-другую "лишних строк" внизу кадра, чтобы "волшебным образом" все точки, которые остались в списке ActivePoints, были бы перенесены в список AllPoints. Сделай мы счёт "сверху вниз" - и у нас получалась бы уродская координата Y от 200 до 919, или вроде того, поэтому решили - пусть всё-таки будет от 0 до 719, как и полагается. Хотя, мы-таки ошиблись на единицу и получили в итоге от 1 до 720.

Ладно, не будем пока "окончательно вылизывать" - не удивлюсь, если этот алгоритм ещё очень кардинально поменяется! Так что пусть будет ImgHeight=720, и ещё ImgHeightP1=721.

Threshold - стоит значение 0xDFF, что является 1/8 от насыщения при использовании 12-битного АЦП, и при условии, что к нам приходит инвертированное значение яркости. Почему так вышло - см "нахождение второй суммы", если вкратце: это результат скрещивания ежа с ужом, то бишь сумматора с "обнаружителем максимума".

Но сейчас у нас 8-битный АЦП, и порог мы хотим 255. Поэтому выставим Threshold = 0x000 - ВНЕЗАПНО. Впрочем, давайте на всякий случай поставим 0x001, то есть порог 254 - нужно будет проверить, какое условие мы проверяем, "больше", "больше или равно", или в разных местах по-разному?

D1 (в сегменте .data внизу файла) - предполагаемый диаметр точек. У меня в компьютерной модели работало примерно так: сначала ставилась максимальная экспозиция (там её можно регулировать самостоятельно), и если в кадре где-то шло насыщение - экспозицию снижали вдвое. Наконец, по величине экспозиции ГРУБО определялась дальность, и по ней - размер пятен, с которыми мы будем иметь дело.

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

Может оказаться, однако, что этого и не надо! В конце концов, мы почти довели наш алгоритм обнаружения до того, что каждому пятну он будет сам присваивать радиус. То есть, может мы вообще уберём переменную D1, заменив её на константу, и всегда будем начинать с 6 (минимально возможное значение).

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

Также нам надо написать (скопипастить) обработчик прерываний и инициализировать ЖК-экранчик
Исходно в файле VideoProcessing.asm ничего этого нет. Но ничего страшного: для инициализации ЖК мы просто подключим файл LCD4wire.asm.

Ну а обработчик прерываний скопируем из ImageProcessing. Хотя и для него, если подумать, можно было отдельный файлик создать.

Так теперь выглядит VideoProcessing.asm:

;громко думаем, как организовать видеоообработку
;наилучшее качество получим с сигнала CVI, т.к у него длительность видимой строки максимальная, 43 мкс, и 10,3 мкс на обратный ход
;у нас есть не более 308 тактов для подготовки к следующей строке!

;в 1205ХВ014 логика будет существенно отличаться: сканирование "в два луча", всего 8 тактов на обратный ход
;но давайте решать проблемы по мере их поступления. Потренируемся на кошках, то бишь на аналоговых камерах высокой чёткости...

%include "QuatCoreConsts.inc"
%include "Win1251.inc"
.rodata
	
	; ImgHeight		EQU	720		;720p пока что 
	VSync			EQU	0x8000	;ожидание кадрового синхроимпульса, значение для регистра произвольное
	HSync			EQU	0x4106	;формат CVI, 9,5 мкс от синхроимпульса до начала строки, и ещё 1 мкс прибавляем, т.к хотим 1024 вместо 1280 (а вообще при 25 МГц полезная строка занимает 1075 пикселей)
	WholeRow		EQU	1023		;для обработки всей строки целиком		
	ImgHeight		EQU	720		;вообще, 32, но вводим дополнительные строки, чтобы перенести все точки из ActivePoints в AllPoints
	ImgHeightP1		EQU	721		;не обучили свой компилятор обрабатывать "constexp"
	Threshold		EQU	0x01		;минимальная яркость пятен, сейчас поставили 1/8 от "насыщения" (4096), но с инверсией, так надо!
	Nil			EQU	-32768	;традиционно это 0, но у нас нет флага нуля! Поэтому куда удобнее гигантское отриц. значение, всяко у нас 65535 слов памяти не наберётся, надеемся что 256..512 слов вполне хватит
.code	
	main proc
	
		%include "SetClock.asm"
			
				SP		Stack	;инициализация стека.

.rodata
	CommonError	dw	0x101,0x106,'Ошибка: ',0
	PointsStr	db	'Обнаруженные точки:',0x0D,0x0A,0x00				
	NumSign		db	'№',0x00
	XStr		db	'X:',0x00
	YStr		db	'Y:',0x00
	LumStr		db	'Яркость:',0x00
	SizeStr		db	'Диаметр:',0x00		
	OutOfMemStr	db	'Out of heap memory',0x0D,0x0A,0x00		
	NoVideoSignal	db	'нет сигнала',0
	UnderflowInt	db	'исчерпание  ','заданий GPU',0
	OverflowInt	db	'переполнение','результатов GPU',0	
		
		%include "LCD4wire.asm"				
					
		%include "ProcessFrame.asm"
		
				;давайте передадим параметры обнаруженных точек по UART
				;пока тупо по младшему байту
				SIO		UART

.code
				X		PointsStr
				CALL		print
				
				Z		AllPoints	;В списке AllPoints нет фиктивных точек. Но может вообще никаких не быть!
				;Хотя сам AllPoints - это "заголовок", с него надо двинуться вперёд, если получится
				i		0		;номер точки (для отображения в терминале, поэтому нумерация с 1)
				JMP		@@moveOn	;перемещение на очередную точку
				;к началу цикла мы переместились на одну из найденных
		@@outLoop:	X		NumSign		
				Acc		i
				CALL		CallThemAll
				;назвали номер точки, теперь выдаём её параметры

;в [Z+1] лежит радиус точки,
;в [Z+2j+k]=[Z+2] - X-координата
;в [Z+2j+1]=[Z+3] - Y-координата
;в [Z+4j+k]=[Z+4] - яркость
				X		XStr
				Acc		[Z+2j+k]		;X-координата
				CALL 		CallThemAll
				
				X		YStr
				Acc		[Z+2j+1]	;Y-координата
				CALL 		CallThemAll
				
				X		LumStr
				Acc		0xFFF		;инверсия 12 младших бит через вычитание
				SUB		[Z+4j+k]	;Яркость
				CALL 		CallThemAll
				
				X		SizeStr
				Acc		[Z+1]	;размер точки
				CALL 		CallThemAll				
				
				
				;продвигаемся на одну позицию, если только указатель не nil
		@@moveOn:	ZAcc		RoundZero
				SUB		[Z+k]
				Z		[Z+k]
				i++		0
				JL		@@outLoop


				JMP		@@endless
	OutOfMemory:		X		OutOfMemStr
				CALL		print
	@@endless: 		JMP 		@@endless
	main endp	
	
	;обработка прерываний
	GPU_WDT:	C	NoVideoSignal
			JMP	IntHandler
	GPU_UFLO:	C	UnderflowInt
			JMP	IntHandler
	GPU_OFLO:	C	OverflowInt
	IntHandler:	SIO	LCD
			X	CommonError
			CALL	print
			X	C
			CALL	print
	@@endless:	JMP	@@endless	
	

		
	;после 3 вызовов этой процедуры начинается экономия :)
	CallThemAll	proc
		CALL	print
		CALL	BetterBin2Bcd
		OUT 13
		OUT 10
		JMP [--SP]
	CallThemAll endp
	
	%include "Print.asm"
	%include "Bin2Bcd.asm"
	
.data
	;расчётный диаметр пятна. Вообще, должен грубо устанавливаться исходя из экспозиции кадра, затем корректироваться по вычисленной дальности
	;но пока, для отладки, сразу зададим 3
	D1		dw	110
	;ДИВАН
	;в дальнейшем будет ещё R2 - радиус МДД, когда работаем в ближней зоне (они больше не используются для измерения, но нужно проверять их наличие)

	ActivePoints	dw	APHeadDummyX
	AllPoints	dw	Nil	
	APHeadDummyX	dw	Nil	;когда будем сверять с X-координатой ПРЕДЫДУЩЕЙ ТОЧКИ, это позволит не вводить лишних проверок!
	Heap		dw	Elem0	
	APTailDummyX	dw	32767
	;возможно, и оставшиеся значения Y, Lum, Path могут пригодится, а пока пущай будут чем угодно
	
	;область для списков точек, для начала она вся объединена в список Heap
	Elem0:
	Elem0Next	dw	Elem1
	Elem0X		dw	?
	Elem0Y		dw	?
	Elem0L		dw	?
	Elem0P		dw	?
	Elem1		dw	Elem2,?,?,?,?
	Elem2		dw	Elem3,?,?,?,?
	Elem3		dw	Elem4,?,?,?,?
	Elem4		dw	Elem5,?,?,?,?
	Elem5		dw	Elem6,?,?,?,?
	Elem6		dw	Elem7,?,?,?,?
	Elem7		dw	Elem8,?,?,?,?
	Elem8		dw	Elem9,?,?,?,?
	Elem9		dw	ElemA,?,?,?,?
	ElemA		dw	ElemB,?,?,?,?
	ElemB		dw	ElemC,?,?,?,?
	ElemC		dw	ElemD,?,?,?,?
	ElemD		dw	ElemE,?,?,?,?
	ElemE		dw	ElemF,?,?,?,?
	ElemF		dw	Elem10,?,?,?,?
	Elem10		dw	Elem11,?,?,?,?
	Elem11		dw	Elem12,?,?,?,?
	Elem12		dw	Elem13,?,?,?,?
	Elem13		dw	Elem14,?,?,?,?
	Elem14		dw	Elem15,?,?,?,?
	Elem15		dw	Elem16,?,?,?,?
	Elem16		dw	Elem17,?,?,?,?
	Elem17		dw	Elem18,?,?,?,?
	Elem18		dw	Elem19,?,?,?,?
	Elem19		dw	Elem1A,?,?,?,?
	Elem1A		dw	Elem1B,?,?,?,?
	Elem1B		dw	Elem1C,?,?,?,?
	Elem1C		dw	Elem1D,?,?,?,?
	Elem1D		dw	Elem1E,?,?,?,?
	Elem1E		dw	Elem1F,?,?,?,?
	Elem1F		dw	-32768,?,?,?,?
	
	Stack	dw	?,?,?,?

(пришлось обойтись без подкрашивания цветом, ЖЖ жалуется на слишком большую запись)

По-моему, это самая крупная из наших программ на данный момент!

Получилось 186 слов кода и 323 слова данных, что уже требует 8-битной адресации ПЗУ и 9-битной адресации ОЗУ.

Кроме того, используется 5 адресов процедур и 45 "непосредственных значений".


Синтез проходит без проблем: 1588 ЛЭ, или 16% от доступных на 5576ХС4Т, и ещё 12 288 бит памяти, или 13% от доступных. С таймингами проблем нет: предельная частота 27 МГц.

Одна маленькая проблемка: при запуске вообще не подаёт никаких признаков жизни - экранчик не инициализирует, там пустота, и никаких сообщений на компьютер не посылает. С нахрапу не получилось - придётся сейчас уйти глубоко в отладку.

Продолжение следует.
Tags: ПЛИС, программки, работа, странные девайсы
Subscribe

Recent Posts from This Journal

  • Я создал монстра!

    Вот нормальная счастливая пара разъёмов ОНЦ-БС-1-10/14-Р12-2-В и ОНЦ-БС-1-10/14-В1-2-В: У розетки кроме основного выступа, отмечающего "верх",…

  • Нахождение двух самых отдалённых точек

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

  • Слишком общительный счётчик

    Вчера я чуть поторопился отсинтезировать проект,параметры не поменял: RomWidth = 8 вместо 7, RamWidth = 9 вместо 8, и ещё EnableByteAccess=1, чтобы…

  • Балансируем конвейер QuatCore

    В пятницу у нас всё замечательно сработало на симуляции, первые 16 миллисекунд полёт нормальный. А вот прошить весь проект на ПЛИС и попробовать "в…

  • Огари разговаривают

    Сегодня по пути на работу встретил огарей прямо в Лосином острове, на берегу Яузы. Эти были на удивление бесстрашны, занимались своими делами, не…

  • Ковыряемся с сантехникой

    Наконец-то закрыл сколько-нибудь пристойно трубы, подводящие к смесителю, в квартире в Москве: А в воскресенье побывал на даче, там очередная…

  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your IP address will be recorded 

  • 0 comments