Jump to content

    

Выкроить 4 такта в коде прерывания надо

Мучаю исходник http://www.vga-avr.narod.ru/main_rus.html "Простой VGA/Видео адаптер"

В проекте есть прерывание, в котором выполняется горизонтальная и вертикальная синхронизации, а также настройки указателей отрисовки.

Прерывание это нормировано по тактам, даже в ветвлениях if-ов нопами выровнено время выполнения. Я не знаю зачем автор так сделал, но если добавить хоть 1 nop в любое место, то вся синхронизация падает.

 

Проблема в том, что автор использует для выдачи сигналов синхронизации команды PORTD = 4; и PORTD = 0;

Что, естественно, приводит к невозможности использовать порт D для чего бы то ни было ещё.

Вторая проблема - PORTD = 4 на самом деле означает не только установку третьего бита, но и сброс второго. Третий бит это вертикальная синхронизация, второй - горизонтальная. И происходить эта операция должна одновременно.

 

Я пробовал менять на

PORTD |= (1<<3);

PORTD &= ~(1<<2);

Но всё тут же падает и из-за не одновременности вывода сигналов, и из-за возросшего времени выполнения.

Не помогает также и замена на ассемблерные cbi/sbi.

 

Единственный вариант, который мне видится, это

 

char temp;

temp = PORTD;

temp |= (1<<3);

temp &= ~(1<<2);

PORTD = temp;

 

Но даже

char temp;

temp = PORTD;

Уже приводит к искажению картинки из-за увеличившегося времени выполнения прерывания.

 

В общем, с этим надо что-то делать... Либо соптимизировать прерывание и выкроить такты, либо управлять портом как-то иначе, например, через указатель, но сомневаюсь что по числу тактов будет выигрыш.

 

Вот текст прерывания:

//Global definitions for VGA render
#define vga_field_line_count 525 //standart VGA quantity lines
#define vga_symbols_per_row 20   //symbols quantity per horizontal
#define vga_row_count 20		 //symbols quantity per vertical
#define vga_symbol_height 24	 //rendered symbol height
#define TIMER_LIMIT 0xC3		   //set count, One VGA line 31.77us

//All VGA sincronize made here..
SIGNAL(SIG_OVERFLOW0) {

TCNT0 = TIMER_LIMIT; //reload counter value   0xC3  //set count, One VGA line 31.77us
	//******Syncronization Handler********

		//Count number of lines
		if (++linecount == vga_field_line_count) {
			linecount = 0;
			//clear pointers for render display buffer
			raw_render = 0; 
			y_line_render = 0;
		}

// Вертикальный и горизонтальный синхроимпульсы должны быть одновременно, поэтому дёргать битами порта раздельно по cbi/sbi не получится.
// кроме того, тело прерывания крайне чувствительно ко времени исполнения, и добавление любой команды сбивает синхронизацию.

		//Make Vsync length 2 VGA lines 
		if ((linecount == 10 )||(linecount == 11 )) {
			//Make here vertical syncronization & HSYNC syncro level on
			PORTD = 0; //vsync_on
		} else 	{
			//.. & HSYNC syncro level on
			PORTD = 4;// vsync_off


		}

		video_enable_flg = true;


		if (linecount < 45) {
			video_enable_flg = false;
				//Add to avoid flickering at top display
				NOP; // 15 nops
				NOP;
				NOP;
				NOP;
				NOP;
				NOP;
				NOP;
				NOP;
				NOP;
				NOP;
				NOP;
				NOP;
				NOP;
				NOP;
				NOP;

		}  else  {
			 //Forming current string for rendering
			 if (++y_line_render == vga_symbol_height) {
			  raw_render++;
			  y_line_render = 0;
			 } else {
				NOP; // 8 nops
				NOP;
				NOP;
				NOP;
				NOP;
				NOP;
				NOP;
				NOP;
			 }

		}

		hsync_off; //HSYNC syncro level off sbi(PORTD,3) 

	//******Syncronization Handler********

}

 

Ассемблер:

 

//All VGA sincronize made here..
SIGNAL(SIG_OVERFLOW0) {
 de4:	1f 92	   	push	r1
 de6:	0f 92	   	push	r0
 de8:	0f b6	   	in	r0, 0x3f; 63
 dea:	0f 92	   	push	r0
 dec:	11 24	   	eor	r1, r1
 dee:	2f 93	   	push	r18
 df0:	8f 93	   	push	r24
 df2:	9f 93	   	push	r25
//unsigned char port_buffer;

TCNT0 = TIMER_LIMIT; //reload counter value
 df4:	83 ec	   	ldi	r24, 0xC3; 195
 df6:	82 bf	   	out	0x32, r24; 50
	//******Syncronization Handler********

		//Count number of lines
		if (++linecount == vga_field_line_count) {
 df8:	80 91 77 00 	lds	r24, 0x0077
 dfc:	90 91 78 00 	lds	r25, 0x0078
 e00:	01 96	   	adiw	r24, 0x01; 1
 e02:	90 93 78 00 	sts	0x0078, r25
 e06:	80 93 77 00 	sts	0x0077, r24
 e0a:	80 91 77 00 	lds	r24, 0x0077
 e0e:	90 91 78 00 	lds	r25, 0x0078
 e12:	8d 50	   	subi	r24, 0x0D; 13
 e14:	92 40	   	sbci	r25, 0x02; 2
 e16:	41 f4	   	brne	.+16; 0xe28 <__vector_11+0x44>
			linecount = 0;
 e18:	10 92 78 00 	sts	0x0078, r1
 e1c:	10 92 77 00 	sts	0x0077, r1
			//clear pointers for render display buffer
			raw_render = 0; 
 e20:	10 92 73 03 	sts	0x0373, r1
			y_line_render = 0;
 e24:	10 92 79 00 	sts	0x0079, r1
// Вертикальный и горизонтальный синхроимпульсы должны быть одновременно, поэтому дёргать битами порта раздельно по cbi/sbi не получится.
// кроме того, тело прерывания крайне чувствительно ко времени исполнения, и добавление любой команды сбивает синхронизацию.
// я не смог победить этот глюк и использовал костыль с промежуточной переменной. 

		//Make Vsync length 2 VGA lines 
		if ((linecount == 10 )||(linecount == 11 )) {
 e28:	80 91 77 00 	lds	r24, 0x0077
 e2c:	90 91 78 00 	lds	r25, 0x0078
 e30:	0a 97	   	sbiw	r24, 0x0a; 10
 e32:	31 f0	   	breq	.+12; 0xe40 <__vector_11+0x5c>
 e34:	80 91 77 00 	lds	r24, 0x0077
 e38:	90 91 78 00 	lds	r25, 0x0078
 e3c:	0b 97	   	sbiw	r24, 0x0b; 11
 e3e:	11 f4	   	brne	.+4	; 0xe44 <__vector_11+0x60>
			//Make here vertical syncronization & HSYNC syncro level on
			PORTD = 0; //vsync_on
 e40:	12 ba	   	out	0x12, r1; 18
 e42:	02 c0	   	rjmp	.+4	; 0xe48 <__vector_11+0x64>
		} else 	{
			//.. & HSYNC syncro level on
			PORTD = 4;// (PORTD | 0x04); //vsync_off
 e44:	84 e0	   	ldi	r24, 0x04; 4
 e46:	82 bb	   	out	0x12, r24; 18


		}

		video_enable_flg = true;
 e48:	81 e0	   	ldi	r24, 0x01; 1
 e4a:	80 93 74 03 	sts	0x0374, r24


		if (linecount < 45) {
 e4e:	80 91 77 00 	lds	r24, 0x0077
 e52:	90 91 78 00 	lds	r25, 0x0078
 e56:	8d 97	   	sbiw	r24, 0x2d; 45
 e58:	90 f4	   	brcc	.+36; 0xe7e <__vector_11+0x9a>
			video_enable_flg = false;
 e5a:	10 92 74 03 	sts	0x0374, r1
...
				NOP;
				NOP;
				NOP;
				NOP;
				NOP;
				NOP;
 e7a:	00 00	   	nop
 e7c:	19 c0	   	rjmp	.+50; 0xeb0 <__vector_11+0xcc>

		}  else  {
			 //Forming current string for rendering
			 if (++y_line_render == vga_symbol_height) {
 e7e:	80 91 79 00 	lds	r24, 0x0079
 e82:	8f 5f	   	subi	r24, 0xFF; 255
 e84:	80 93 79 00 	sts	0x0079, r24
 e88:	80 91 79 00 	lds	r24, 0x0079
 e8c:	88 31	   	cpi	r24, 0x18; 24
 e8e:	41 f4	   	brne	.+16; 0xea0 <__vector_11+0xbc>
			  raw_render++;
 e90:	80 91 73 03 	lds	r24, 0x0373
 e94:	8f 5f	   	subi	r24, 0xFF; 255
 e96:	80 93 73 03 	sts	0x0373, r24
			  y_line_render = 0;
 e9a:	10 92 79 00 	sts	0x0079, r1
 e9e:	08 c0	   	rjmp	.+16; 0xeb0 <__vector_11+0xcc>
...
				NOP;
			 }

		}

		hsync_off; //HSYNC syncro level off sbi(PORTD,3) 
 eb0:	93 9a	   	sbi	0x12, 3; 18

	//******Syncronization Handler********

}
 eb2:	9f 91	   	pop	r25
 eb4:	8f 91	   	pop	r24
 eb6:	2f 91	   	pop	r18
 eb8:	0f 90	   	pop	r0
 eba:	0f be	   	out	0x3f, r0; 63
 ebc:	0f 90	   	pop	r0
 ebe:	1f 90	   	pop	r1
 ec0:	18 95	   	reti

00000ec2 <display_Mega>:

Edited by Dikoy
[codebox] для длинного кода. [code]-для короткого!!!

Share this post


Link to post
Share on other sites

Возможно... Только как это сделать в WINAVR? Запретить сохранять регистр?

 

Ещё мысль:

 

if (linecount < 45) с его нопами можно использовать для

if ((linecount == 10 )||(linecount == 11 )) {
//Make here vertical syncronization & HSYNC syncro level on
PORTD = 0; //vsync_on
} else {
//.. & HSYNC syncro level on
PORTD = 4;// vsync_off

 

Ибо 10 и 11 меньше 45, тогда как минимум 15 нопов экономится.

 

И не будет ли быстрее записать этот код как

 

if (linecount == 10 ) PORTD = 0; //vsync_on
if (linecount == 12 ) PORTD = 4;// vsync_off

Share this post


Link to post
Share on other sites

А есть для авр ассемблера что-то для быстрого подсчёта тактов? Например, от сих и до сих. А то на бумажке считать несколько уныло...

Share this post


Link to post
Share on other sites
А есть для авр ассемблера что-то для быстрого подсчёта тактов? Например, от сих и до сих. А то на бумажке считать несколько уныло...

Думаю должна быть возможность загрузить в студию прошивку.

Установить PC на нужный адрес "от сих" точку останова на "до сих" запустить эмуляцию и смотреть сколько тактов потребовалось.

 

:bb-offtopic: формирование синхросигналов VGA на СИ - это жесть :biggrin:

Share this post


Link to post
Share on other sites
А есть для авр ассемблера что-то для быстрого подсчёта тактов? Например, от сих и до сих. А то на бумажке считать несколько уныло...

симулятор AVRStudio

 

Думаю должна быть возможность загрузить в студию прошивку.

Установить PC на нужный адрес "от сих" точку останова на "до сих" запустить эмуляцию и смотреть сколько тактов потребовалось.

Проще прям набить на ассемблере исследуемый код и замерить выполнение

Share this post


Link to post
Share on other sites

В общем, победил.

Код сравнения выполнялся 3+3+2+1+3+3+2+1+1+2+1+1 = 23 (24, 25) циклов.

//Make Vsync length 2 VGA lines 
            if ((linecount == 10 )||(linecount == 11 )) {
                //Make here vertical syncronization & HSYNC syncro level on
                PORTD = 0; //vsync_on
            } else     {
                //.. & HSYNC syncro level on
                PORTD = 4;// vsync_off
            }

 

Переписал его так и уложился в тот же тактаж:

 

hsync_on;     // cbi(PORTD,3)
if (linecount == 10 ) cbi(PORTD,2); //PORTD = 0;
if (linecount == 12 ) sbi(PORTD,2); //PORTD = 4;

 

Этого хватило. Работает, порты отпустило. Сейчас тело прерывания выглядит вот так. Вдруг кто ещё будет это чудовище повторять.

 

 
//All VGA sincronize made here..
SIGNAL(SIG_OVERFLOW0) {

TCNT0 = TIMER_LIMIT; //reload counter value
	//******Syncronization Handler********

		video_enable_flg = true;

		//Count number of lines
		if (++linecount == vga_field_line_count) { // == 525
			linecount = 0;
			//clear pointers for render display buffer
			raw_render = 0; 
			y_line_render = 0;
		}

hsync_on;	 // cbi(PORTD,3)					
if (linecount == 10 ) cbi(PORTD,2); //PORTD = 0;
if (linecount == 12 ) sbi(PORTD,2); //PORTD = 4;


		if (linecount < 45) {

				//Add to avoid flickering at top display
				NOP; // 15 nops
				NOP;
				NOP;
				NOP;
				NOP;
				NOP;
				NOP;
				NOP;
				NOP;
				NOP;
				NOP;
				NOP;
				NOP;
				NOP;
				NOP;

				video_enable_flg = false;		

		}  else  {
			 //video_enable_flg = true;
			 //Forming current string for rendering
			 if (++y_line_render == vga_symbol_height) {
			  raw_render++;
			  y_line_render = 0;
			 } else {
				NOP; // 8 nops
				NOP;
				NOP;
				NOP;
				NOP;
				NOP;
				NOP;
				NOP;
			 }
		//
		}

		hsync_off; //HSYNC syncro level off sbi(PORTD,3) 

	//******Syncronization Handler********

}

 

В принципе, остаюсь при мнении что перенос синхронизации за if (linecount < 45) позволит ещё сократить время выполнения прерывания, как минимум на 15 тактов, но это потребует подбора нопов в противовесе (после else) а мне лень. Проект и так из "давай быстро прилепим вывод на монитор к стенду" перерос в 2 дня отладки... Но если у кого будет желание - уверен так можно.

Edited by Dikoy

Share this post


Link to post
Share on other sites
Жесть, это на ардуине https://forum.arduino.cc/index.php?topic=102181.0

Да хоть на чём угодно.

Но я не понимаю или не вижу:

где и как происходит выравнивание фронта синхросигнала в зависимости от времени выполнения прерываемой команды?

Share this post


Link to post
Share on other sites
где и как происходит выравнивание фронта синхросигнала в зависимости от времени выполнения прерываемой команды?

Оооо! Там такая содомия в коде творится! В мейнлупе то же самое происходит - вывод строки сделан в цикле и не дай кришна убрать один ноп!

Если б я знал заранее что будет такая жесть, не начинал бы это дело. Написал бы своё на СТМ или использовал ЖК панель. Но попробовал скомпилированный код автора, работает, ок, думаю, допишу вывод своих данных, это не сложно... :rolleyes:

 

****

Кстати о содомии. То же самое там происходит и с портом В. Но там всё проще. Меняем ногодрыг на sbi/cbi:

 

#define video_off    cbi(DDRB,5)  // DDRB=0x90 
#define video_on     sbi(DDRB,5)  // DDRB=0xB0

 

Убираем по одному нопу после video_on; и до video_off;, т.к. команды sbi/cbi выполняются по 2 такта, а присвоение, преобразуемое в LDI - только 1 такт.

 

//Cycle for render line 
                i = vga_symbols_per_row;
                while(i--)
                {
                    SPDR = pgm_read_byte_near(_ptr1 + (* _ptr++)*vga_symbol_height/2);
                    video_on;
                    NOP;
                }
                //Delay for draw last symbol
                NOP;
                NOP;
                NOP;
                NOP;
                NOP;
                video_off;

Edited by Dikoy

Share this post


Link to post
Share on other sites
Оооо! Там такая содомия в коде творится!

Да это то понятно.

Вы наверное относитесь к людям которые не ищут простых и/или лёгких путей. :biggrin:

Но я немного о другом спрашивал.

Прерывание таймера может произойти во время выполнения команды длительностью 1,2 или 3 такта.

Это где-то учитывается?

Share this post


Link to post
Share on other sites

Может Вам пригодится...

Для многих АВРок есть возможность тоглить (инвертировать) состояние GPIO путём записи в регистр PINx.

 

Пример инверсии нулевого и седьмого бита в порту Б:

PINB = (1<<7)|(1<<0);

Вот список моделей для которых это можно

#if  defined(__AVR_AT90PWM1__)  \
 || defined(__AVR_AT90PWM2__)  \
 || defined(__AVR_AT90PWM2B__) \
 || defined(__AVR_AT90PWM3__)  \
 || defined(__AVR_AT90PWM3B__) \
 || defined(__AVR_AT90PWM216__) \
 || defined(__AVR_AT90PWM316__) \
 || defined(__AVR_AT90PWM81__)  \
 || defined(__AVR_AT90USB82__)  \
 || defined(__AVR_AT90USB162__) \
 || defined(__AVR_ATmega8U2__)  \
 || defined(__AVR_ATmega16U2__) \
 || defined(__AVR_ATmega32U2__) \
 || defined(__AVR_ATmega16U4__) \
 || defined(__AVR_ATmega32U4__) \
 || defined(__AVR_ATmega32U6__) \
 || defined(__AVR_AT90USB646__) \
 || defined(__AVR_AT90USB647__) \
 || defined(__AVR_AT90USB1286__) \
 || defined(__AVR_AT90USB1287__) \
 || defined(__AVR_AT90CAN32__)  \
 || defined(__AVR_AT90CAN64__)  \
 || defined(__AVR_AT90CAN128__) \
 || defined(__AVR_ATmega16M1__) \
 || defined(__AVR_ATmega32M1__) \
 || defined(__AVR_ATmega64M1__) \
 || defined(__AVR_ATmega32C1__) \
 || defined(__AVR_ATmega64C1__) \
 || defined(__AVR_ATmega48__)  || defined(__AVR_ATmega48A__) \
 || defined(__AVR_ATmega88__)  || defined(__AVR_ATmega88A__) \
 || defined(__AVR_ATmega168__) || defined(__AVR_ATmega168A__) \
 || defined(__AVR_ATmega48P__) || defined(__AVR_ATmega48PA__) \
 || defined(__AVR_ATmega88P__) || defined(__AVR_ATmega88PA__) \
 || defined(__AVR_ATmega168P__) || defined(__AVR_ATmega168PA__) \
 || defined(__AVR_ATmega328__)  \
 || defined(__AVR_ATmega328P__) \
 || defined(__AVR_ATmega640__)  \
 || defined(__AVR_ATmega1280__) \
 || defined(__AVR_ATmega1281__) \
 || defined(__AVR_ATmega2560__) \
 || defined(__AVR_ATmega2561__) \
 || defined(__AVR_ATmega164A__) \
 || defined(__AVR_ATmega164P__) || defined(__AVR_ATmega164PA__) \
 || defined(__AVR_ATmega324A__) \
 || defined(__AVR_ATmega324P__) || defined(__AVR_ATmega324PA__) \
 || defined(__AVR_ATmega644__)  || defined(__AVR_ATmega644A__) \
 || defined(__AVR_ATmega644P__) || defined(__AVR_ATmega644PA__) \
 || defined(__AVR_ATmega1284P__) \
 || defined(__AVR_ATmega165__)  || defined(__AVR_ATmega165A__) \
 || defined(__AVR_ATmega165P__) || defined(__AVR_ATmega165PA__) \
 || defined(__AVR_ATmega325__)  || defined(__AVR_ATmega325A__)  \
 || defined(__AVR_ATmega325P__) \
 || defined(__AVR_ATmega3250__) \
 || defined(__AVR_ATmega3250P__) \
 || defined(__AVR_ATmega645__)  || defined(__AVR_ATmega645A__) \
 || defined(__AVR_ATmega645P__) \
 || defined(__AVR_ATmega6450__) || defined(__AVR_ATmega6450A__) \
 || defined(__AVR_ATmega6450P__) \
 || defined(__AVR_ATmega169__)  || defined(__AVR_ATmega169A__) \
 || defined(__AVR_ATmega169P__) || defined(__AVR_ATmega169PA__) \
 || defined(__AVR_ATmega329__)  || defined(__AVR_ATmega329A__) \
 || defined(__AVR_ATmega329P__) || defined(__AVR_ATmega329PA__) \
 || defined(__AVR_ATmega3290__) \
 || defined(__AVR_ATmega3290P__) \
 || defined(__AVR_ATmega649__)   || defined(__AVR_ATmega649A__) \
 || defined(__AVR_ATmega6490__)  || defined(__AVR_ATmega6490A__) \
 || defined(__AVR_ATmega6490P__) \
 || defined(__AVR_ATmega649P__)  \
 || defined(__AVR_ATmega406__) \
 || defined(__AVR_ATmega8HVA__) \
 || defined(__AVR_ATmega16HVA__) \
 || defined(__AVR_ATmega16HVA2__) \
 || defined(__AVR_ATmega16HVB__) \
 || defined(__AVR_ATmega32HVB__) \
 || defined(__AVR_ATmega64HVE__) \
 || defined(__AVR_ATtiny261__) || defined(__AVR_ATtiny261A__) \
 || defined(__AVR_ATtiny461__) || defined(__AVR_ATtiny461A__) \
 || defined(__AVR_ATtiny861__) || defined(__AVR_ATtiny861A__) \
 || defined(__AVR_ATtiny2313__) || defined(__AVR_ATtiny2313A__) \
 || defined(__AVR_ATtiny4313__) \
 || defined(__AVR_ATtiny13__) || defined(__AVR_ATtiny13A__) \
 || defined(__AVR_ATtiny25__) \
 || defined(__AVR_ATtiny45__) \
 || defined(__AVR_ATtiny85__) \
 || defined(__AVR_ATtiny24__) || defined(__AVR_ATtiny24A__) \
 || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny44A__) \
 || defined(__AVR_ATtiny84__) \
 || defined(__AVR_ATtiny43U__) \
 || defined(__AVR_ATtiny48__) || defined(__AVR_ATtiny88__) \
 || defined(__AVR_ATtiny87__) || defined(__AVR_ATtiny167__)
#  define PIN_TOGGLE_BY_PIN_WRITE 1
#else
#  define PIN_TOGGLE_BY_PIN_WRITE 0
#endif

Share this post


Link to post
Share on other sites
Прерывание таймера может произойти во время выполнения команды длительностью 1,2 или 3 такта.

Это где-то учитывается?

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

 

Share this post


Link to post
Share on other sites
Да это то понятно.

Вы наверное относитесь к людям которые не ищут простых и/или лёгких путей. :biggrin:

Но я немного о другом спрашивал.

Прерывание таймера может произойти во время выполнения команды длительностью 1,2 или 3 такта.

Это где-то учитывается?

Здесь скорее "кроилово привело к попадалову" - желание сэкономить время и использовать готовый проект как базу привело к бОльшим трудозатратам, чем сделать то же на СТМ.

Но не суть. Работает. То, что мне нужно - делает. Прибор одноразовый, для одного конкретного стенда и то для отладки. Поэтому допустимо немного припустить планку. Так-то железка годная, только писали её неправильно. Там ещё выжать можно и скорости, и ресурса.

 

Нигде это не учитывается. Но предыдущий оратор прав - колебания начала отрисовки на пару тактов ни на что не влияют. Картинка довольно чёткая. Даже на ЭЛТ мониторе выпуска 2001 года. На ЖК ЛОСе, современном, вообще очень чёткая картинка. Видимо, монитор хорошо подстраивается.

 

Edited by Dikoy

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now