nabbla (nabbla1) wrote,
nabbla
nabbla1

Category:

Алгоритм обнаружения, "барабанная дробь"

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


Как-то так. Яркость 3 вполне подходящая. Не забываем теперь добавить строку "IR 3" ближе к началу программы обнаружения точек.

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

Запуск - и...


"Ошибка: исчерпание заданий GPU"


Шо, опять???

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

(325; 171), радиус 3,
(330; 169), радиус 4,
(334; 170), радиус 0 (!)


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

; -------------------------------------------- через эту линию код не идёт (только перепрыгивает) ------------------------------------

				;НАЧАЛО ЦИКЛА ПО СТРОКАМ
	@@newRow:		[SP+2j]		Acc
				X		ActivePoints	;начинаем цикл по всем активным точкам
				
	@@ResetPending:		C		0			;уж явно не 0x8000			
	;даже если список пуст, первую половину цикла мы пройдём, она касается отрезка, предшествующего точке из списка
	@@ActPointsStart:	[SP+2j+1]	GPUL			;яркость самой яркой точки на отрезке
				[SP+1]		GPUH			;соотв. координата
				Acc		Threshold		;поставили задом наперёд, чтобы избежать Hazard'а	
				SUB		[SP+2j+1]		;вычитаем порог, положит. значение свидетельствует о новом найденном "пятне"						
				[SP]		@@MidCycle
				JL		TaskPending	;пятна нет, не торопимся заказывать отрезок
				;пятно есть. Надо проверить: не сливается ли оно с пятном слева или с пятном справа
				;X сейчас указывает на точку, расположенную слева (возможно, фиктивную, нам не важно)
				;коорд. текущей точки лежит в Acc, её яркость в C
				
				;первым делом, проверяем, не сливается ли с пятном слева			
				Acc		[X+2j+k]
				SUB		[SP+1]
				DIV2A		D1
				DIV2A		[X+1]
				Z		[X+k]
				JGE		@@LeftMerge
				
				
				;если дошли досюда, значит с пятном слева не сливаемся, надо проверить теперь на пятно справа						
				Acc		[Z+2j+k]
				SUB		[SP+1]
				DIV2S		D1p1
				DIV2S		[Z+1]
				JL		@@RightMerge
				
				;если попали сюда, значит слияния не было, и точку всё-таки надо добавить!
				;но сначала проверим, нет ли у нас "долга" за предыдущую точку?
				[SP]		@@NewBlob
						
	;по сути, процедура, хотя вызываем мы её, укладывая адрес возврата в [SP], БЕЗ ИНКРЕМЕНТА
	;в регистре С лежит некий флаг, подсказывающий, нужно ли нам выдавать задания на обработку отрезков,
	;которые мы не выдали сразу в страхе, что произойдёт слияние пятен, и надо будет дать несколько другое задание,
	;а команды AUP у нас нет
	;Регистр X указывает на то самое пятно, по которому надо выдать задания
	;самый быстрый способ ветвиться по содержанию C - это заносить туда либо -32768, либо любое другое значение
	;брать ABS и смотреть на переполнение
	;пусть -32768 будет означать "выполняй!"
	TaskPending proc
		ABS		C
		JNO		[SP]		;вот в чём прелесть стека без инкремента!
		;если дошли до сюда, заказываем отрезки на обработку
AcqNoCheck:	Acc		[X+2j+k]
		DIV2S		[X+1]	;теперь в аккмуляторе у нас X - Ceil(D/2)
		DIV2A		1	;чтобы всё-таки было X-Floor(D/2)
		ACQ		Acc		;первый отрезок
		ADD		[X+1]	;а вот теперь X + Floor(D/2)
		ACQ		Acc		;второй отрезок
		JMP		[SP]
	TaskPending endp	
					
; ------------------------------ Через эту линию код не проходит (только перепрыгивает) -----------------------------------------------------------------

											
				;[SP+2j+1]=[SP+3] указывает яркость этой точки, [SP+2j]=[SP+2]: Y-координату, [SP+1]: X-координата
				;а куда вставить, указывает рег. X
				;в Acc содержится модуль разности между правой точкой и текущей, за вычетом двух радиусов
	@@NewBlob:		Y		Heap									
				[SP]		@@AfterListOp
				NOP		0			;на следующей строке ListOp нам нужен и Y уже готовый, и доступ к памяти, так что Hazard по-любому будет какой-то...
				
				;добавит в X новую, пока что пустую точку.
				
						
;взять первый элемент из списка Y и перецепить его в начало списка X
;значения X,Y остаются неизменными,
;при вызове должно быть k=0, затираем регистр Z, Acc и флаг знака
;адрес возврата хранится в стеке, как положено
	ListOp proc			
		Z		[Y+k]
		ABS		[Y+k]
MergeBlobCase:	[Y+k]		[Z+k]
		JO		OutOfMemory									
		[Z+k]		[X+k]
		[X+k]		Z
		NOP		0		;избежать Memory hazard (одновременный доступ на чтение и запись)
		JMP		[SP]
	ListOp endp						

; -------------------------------- Через эту линию код не идёт (только перепрыгивает) ------------------------------------------------------------------------
													
	;обнаруженная точка примыкает к пятну слева от себя. Возможно, потребуется слияние и с пятном справа от себя
	;X указывает на пятно слева, Z - на пятно справа 
	@@LeftMerge:	Acc		[Z+2j+k]		;взяли X-координату правого пятна
			SUB		[X+2j+k]		;вычитаем X-координату левого
			DIV2S		[Z+1]			;вычитаем радиус правого пятна
			DIV2S		[X+1]			;и радиус левого пятна
			SUB		D1p1			;и минимально допустимый диаметр пятна
			JL		@@MergeBlobs	;делаем слияние ПЯТЕН
			;если дошли до этого места, значит пятна сливать не нужно, достаточно лишь уточнить параметры левого пятна
			;увы, аккумулятор совсем другим набит, нужно "с нуля" начинать
			Acc		[SP+1]	;Acc = x, т.е координата обнаруженной точки
			SUB		[X+2j+k]	;вычитаем X, т.е Acc = x - X - разность между обнаруженной точкой и центром левого пятна
			DIV2A		[X+1]		;прибавили D/2, получаем Acc = x-X+D/2 
			ADD		1
			[X+1]		Acc		;обновили диаметр левого пятна.
			Acc		1
			;теперь подкорректировать X-координату
			ADD		[SP+1]	;берём координату обнаруженной точки
			DIV2S		[X+1]		;вычитаем половинку диаметра
			[X+2j+k]	Acc
			JMP		AcqNoCheck	;если слева от нас есть РЕАЛЬНОЕ пятно (фиктивное мы трогать не можем, его X-координата "-32768", условие заведомо не выполнится),
			;значит проверять "есть ли за нами должок" не нужно - точно есть!						
						
; ---------------------------------- Через эту линию код не идёт (только перепрыгивает) ---------------------------------------------------------------------------
	@@MergeBlobs:	Acc		[X+1]
			ADD		[Z+1]
			[X+1]		Acc	;диаметр равен сумме диаметров
			ZAcc		RoundZero
			DIV2A		[X+2j+k]
			DIV2A		[Z+2j+k]
			[X+2j+k]	Acc	;среднее арифметическое от центров точек
			;теперь удаляем правое пятно
			Y		X
			X		Heap
			[SP]		CALL(MergeBlobCase)
			X		Y
			JMP		@@EndOfCycle
						
			;а "ручками" не быстрее ли выйдет?
			;Y		Heap - можно в начале цикла по строкам задать (переместить из "добавления новой точки)
			;[X+k]		[Z+k]
			;[Z+k]		[Y+k]
			;[Y+k]		Z
			;если б не Hazard'ы - то же самое по коду, и быстрее по тактам, но с ними - придётся ещё 2 NOP'а вставить, ну нафиг. 
; ---------------------------------- Через эту линию код не идёт (только перепрыгивает) ----------------------------------------------------------------------------												
						
	@@RightMerge:	ADD		[Z+1]	;в аккумуляторе лежало x-X-D/2-D1/2. Теперь прибавили D, получили x-X+D/2-D1/2
			DIV2A		D1p2	;прибавили D1/2, получили x-X+D/2 - новый диаметр пятна
			[Z+1]		Acc	;ага, записали.
			;теперь подкорректировать X-координату
			DIV2		Acc
			ADD		[SP+1]
			SUB		1
			[Z+2j+k]	Acc
	;а теперь с чистой совестью отправляем задание на обработку точки
			JMP		TaskPending

; --------------------------------- Через эту линию код не идёт (только перепрыгивает) ---------------------------------------------------------------------------

			;если добрались досюда, значит памяти хватило. X содержит адрес указателя на новую точку, а вот Z содержит адрес самой точки!
			;наполним её содержимым!						
			;на первую строку всегда попадаем в результате прыжка!
	@@AfterListOp:	[Z+2j+k]	[SP+1]	;X-координата точки
			X		Z		;для дальнейшей работы (после MidCycle)... Чтобы мы не взялись за только что добавленную точку!
			[Z+2j+1]	[SP+2j]
			[Z+1]		D1		;гориз. размер точки (будет расти при слияниях, а поначалу D, т.к мы сюда включаем и )
			;и теперь ещё нужно выдать задания на обработку, СРАЗУ.
			[SP]		CALL(AcqNoCheck)

	;теперь надо посмотреть - а есть ли точка? Или мы уже прошли последний отрезок между точками?
	;но у нас в конце списка правая фиктивная точка - её надо игнорировать!			
	;улучшенный вариант:
			%CheckHazards OFF
	@@MidCycle:	X		[X+k]
			Y		X
			%CheckHazards ON
			ABS		[X+k]
			JO		@@FinalRange
									
	;запрашиваем отрезок, где ожидаем точку. Первой придёт яркость, как всегда
	@@EndOfCycle:	[SP+2j+1]	GPUL			;яркость
			[SP+1]	GPUH			;пока не забыли - сразу второе слово запрашиваем (коорд точки), чтобы не сбить весь FIFO 
			C		Nil			;по умолчанию ставим метку "за нами должок!"				
			;входит ли этот отрезок в уже найденное пятно?
			Acc		[X+2j+1]
			DIV2A		[X+1]
			SUB		[SP+2j+k]
			JGE		@@ActPointsStart

			;возможно, мы ещё не очень далеко ушли от пятна, и как оказывается, пятно здесь продолжается
			SUB		nD1
			JL		@@RemoveBlob

			;да, мы ещё близко. Но превышает ли яркость порог?
			Acc		Threshold		;чем выше значение - тем ЧЕРНЕЕ
			SUB		[SP+2j+1]		;поэтому если получится значение меньше нуля - значит точка тусклая, расширять пятно не стоит
			JL		@@RemoveBlob
			;раз оба раза перехода не было, значит пора пятно сдвигать немного вниз!
			;просто прибавим единицу и к диаметру, и к Y-координате
			Acc		[X+2j+1]
			ADD		1
			[X+2j+1]	Acc
			NOP		0
			Acc		[X+1]
			ADD		1
			[X+1]		Acc
			JMP		@@ActPointsStart
				
; ------------------------------------------ Через эту линию код не идёт (только перепрыгивает) ------------------------------------------------------------------
										
			;пора эту точку перетаскивать в другой список... 
	@@RemoveBlob:	X		AllPoints
			[SP]		CALL(ListOp)
			X		Y			;теперь [X] ссылается уже не на обработанную и удалённую точку, а на предыдущую, так что всё верно
			JMP		@@ResetPending	;ура...					
			;до PixelsProcessed проскрипели, теперь последний участок запросить на обработку, и синхроимпульс для кучи
				
; ------------------------------------------ Через эту линию код не идёт (только перепрыгивает) ------------------------------------------------------------------												
				
	@@FinalRange:	ACQ		WholeRow
			ACQ		HSync
			Acc		[SP+2j]
			SUB		ImgHeight
			ADD		ImgHeightP1
				
		;на @@NewRow мы переопределим и Y, и X, и [SP], так что можно...
				;это приготовления уже к transfer, но чтобы избежать хазарда, пришлось сюда перенести. Вообще, это нам сжирает лишних 3600 тактов = 144 мкс. 
			Y		ActivePoints	;это заведомо фиктивная, и указывает либо на другую фиктивную, то ли на реальную
			X		AllPoints
			Y		[Y+k]			;это уже может быть реальная точка, а может быть правой фиктивной
			[SP]		@@Transfer																				
				
			JL		@@newRow
			;усё, отстрелялись...
			


	@@Transfer:	ABS		[Y+k]
			JL		ListOp		;если [Y+k]=NULL, значит пора закругляться							
				
ProcessFrame endp


На этот раз всё-таки запустим симуляцию в Quartus'е, чтобы хоть не забывать, как это делается. А то потом страшно становится, кажется легче "в уме" весь этот алгоритм проиграть, но это всё же не так надёжно и очень "затратно".

Помнится, мы во время такого тестирования брали только "ядро" QuatCore и видеопроцессор, оставляя всю "периферию" за кадром, и в этом было определённое удобство - не нужно было дожидаться всего обмена по SPI. А сейчас-то и вовсе у меня в программе ожидается приход "отмашки" по UART, так что придётся или сделать "имитатор UART" впридачу, или программу переписать (но это рискованно, когда программа для "железа" и для "симуляции" не совпадает - одна может работать как надо, а другая глючить), или вернуться к этим "старым добрым временам", когда периферия попросту не подсоединена. И тогда команды IN и OUT проходят за 1 такт, "вхолостую" :)

Да и прерывания нам здесь не шибко нужны - можно просто вывести отдельные "пины" OFLO и UFLO, и наблюдать, чтобы там единички не появлялись!

В общем, вот такая схема "ядро+видеопроцессор":


И теперь на него надо подавать "записанное" изображение:


Изображения 32х32 нам уже не хватит, давайте это будет 128х128. Эх, это уже 16 килобайт, на борту ПЛИС столько нет, тут бы лучше действительно сделать вид, что картинка приходит "извне", тогда никаких ограничений не будет!

А пока сделал изображение 128х128х1 бит, такое помещается "с запасом", а большей "битности" нам и не надо сейчас.

И всё-таки придётся в программу изменение внести - поменять количество пикселей в строке

;	WholeRow		EQU	1023		;для обработки всей строки целиком		
	WholeRow		EQU	127		;для симуляции


Иначе видеопроцессор "сойдёт с ума"...

Ну что ж, и попробуем всё это запустить.

Ах да, ещё обнуление статической памяти надо убрать, 737 килобайт (1024х720) как-никак, тяжело это для симулятора...

Ну вот, вроде заработало:

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

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

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

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

    Вчера я чуть поторопился отсинтезировать проект,параметры не поменял: 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