Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: Выкроить 4 такта в коде прерывания надо
Форум разработчиков электроники ELECTRONIX.ru > Микроконтроллеры (MCs) > AVR
Dikoy
Мучаю исходник 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;
Уже приводит к искажению картинки из-за увеличившегося времени выполнения прерывания.

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

Вот текст прерывания:
CODE
//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********

}


Ассемблер:

CODE
//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>:
Сергей Борщ
QUOTE (Dikoy @ Apr 4 2018, 12:25) *
Ассемблер:
Не вижу умножения или других действий с R0. Можно его не сохранять на стеке.
Dikoy
Возможно... Только как это сделать в 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
Dikoy
А есть для авр ассемблера что-то для быстрого подсчёта тактов? Например, от сих и до сих. А то на бумажке считать несколько уныло...
zombi
Цитата(Dikoy @ Apr 4 2018, 14:57) *
А есть для авр ассемблера что-то для быстрого подсчёта тактов? Например, от сих и до сих. А то на бумажке считать несколько уныло...

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

bb-offtopic.gif формирование синхросигналов VGA на СИ - это жесть biggrin.gif
prottoss
Цитата(Dikoy @ Apr 4 2018, 17:57) *
А есть для авр ассемблера что-то для быстрого подсчёта тактов? Например, от сих и до сих. А то на бумажке считать несколько уныло...

симулятор AVRStudio

Цитата(zombi @ Apr 4 2018, 20:15) *
Думаю должна быть возможность загрузить в студию прошивку.
Установить PC на нужный адрес "от сих" точку останова на "до сих" запустить эмуляцию и смотреть сколько тактов потребовалось.

Проще прям набить на ассемблере исследуемый код и замерить выполнение
Dikoy
Цитата(zombi @ Apr 4 2018, 17:15) *
bb-offtopic.gif формирование синхросигналов VGA на СИ - это жесть biggrin.gif

Жесть, это на ардуине https://forum.arduino.cc/index.php?topic=102181.0
Dikoy
В общем, победил.
Код сравнения выполнялся 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;


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

CODE

//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 дня отладки... Но если у кого будет желание - уверен так можно.
zombi
Цитата(Dikoy @ Apr 4 2018, 18:08) *
Жесть, это на ардуине https://forum.arduino.cc/index.php?topic=102181.0

Да хоть на чём угодно.
Но я не понимаю или не вижу:
где и как происходит выравнивание фронта синхросигнала в зависимости от времени выполнения прерываемой команды?
Dikoy
Цитата(zombi @ Apr 4 2018, 20:19) *
где и как происходит выравнивание фронта синхросигнала в зависимости от времени выполнения прерываемой команды?

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

****
Кстати о содомии. То же самое там происходит и с портом В. Но там всё проще. Меняем ногодрыг на 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;
zombi
Цитата(Dikoy @ Apr 4 2018, 20:51) *
Оооо! Там такая содомия в коде творится!

Да это то понятно.
Вы наверное относитесь к людям которые не ищут простых и/или лёгких путей. biggrin.gif
Но я немного о другом спрашивал.
Прерывание таймера может произойти во время выполнения команды длительностью 1,2 или 3 такта.
Это где-то учитывается?
demiurg_spb
Может Вам пригодится...
Для многих АВРок есть возможность тоглить (инвертировать) состояние GPIO путём записи в регистр PINx.

Пример инверсии нулевого и седьмого бита в порту Б:
Код
PINB = (1<<7)|(1<<0);

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

#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
V_G
Цитата(zombi @ Apr 5 2018, 04:43) *
Прерывание таймера может произойти во время выполнения команды длительностью 1,2 или 3 такта.
Это где-то учитывается?

Сильно подозреваю, что если вывод информации в строке жестко привязан к импульсу строчной синхронизации, то небольшое гуляние самого импульса синхронизации никак не скажется на качестве картинки
Dikoy
Цитата(zombi @ Apr 4 2018, 21:43) *
Да это то понятно.
Вы наверное относитесь к людям которые не ищут простых и/или лёгких путей. biggrin.gif
Но я немного о другом спрашивал.
Прерывание таймера может произойти во время выполнения команды длительностью 1,2 или 3 такта.
Это где-то учитывается?

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

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