|
|
  |
Замена обработчика прерывания в WinAVR |
|
|
|
Dec 16 2008, 18:44
|
дятел
    
Группа: Свой
Сообщений: 1 681
Регистрация: 13-05-06
Из: Питер
Пользователь №: 17 065

|
Господа, Вы занимаетесь просто какой-то х.р..й ...  Вот это обертка в асм файле isr.s : Код .extern curr_isr .global TIMER1_COMPA_vect; прерывание с переключаемым обработчиком TIMER1_COMPA_vect: push r30 push r31 lds r30, curr_isr lds r31, curr_isr + 1 icall pop r31 pop r30 reti а вот это сам файл с обработчиками и примером: Код #include <avr/io.h> #include <avr/interrupt.h>
volatile unsigned char tmp;
void INT0_vect(void) __attribute__((interrupt)); // свободный вектор прерывания void INT0_vect(void) { tmp = 0; // тело обработчика 0 }
void INT1_vect(void) __attribute__((interrupt)); // свободный вектор прерывания void INT1_vect(void) { tmp = 1; // тело обработчика 1 }
//Указатель на функцию обработчик void (* curr_isr)(void) = INT1_vect;
int main(void) { curr_isr = INT1_vect; asm("sei"); while (1); } Накладные расходы примерно 16МЦ... на фсе...
|
|
|
|
|
Dec 16 2008, 20:03
|
дятел
    
Группа: Свой
Сообщений: 1 681
Регистрация: 13-05-06
Из: Питер
Пользователь №: 17 065

|
Цитата(_Pasha @ Dec 16 2008, 22:50)  Т.е. получается, что __attribute__((interrupt)) и заставляет все call-used регистры сохранять внутри функции? Справедливости ради, такой подход имеет один побочный эфект, прерывания будут разрешены еще до окончательного выхода из нашего главного обработчика, НО, если прерывания идут с таким темпом что это может вызвать переполнение стека, при других вариантах обработки мы просто будем пропускать прерывания, так что такой вариант ИМХО вполне адекватный...
|
|
|
|
|
Dec 17 2008, 02:01
|
Местный
  
Группа: Участник
Сообщений: 205
Регистрация: 8-03-05
Пользователь №: 3 146

|
Цитата Справедливости ради, такой подход имеет один побочный эфект, прерывания будут разрешены еще до окончательного выхода из нашего главного обработчика С __attribute__((interrupt)) прерывания будут разрешены сразу после входа в INT0_vect (INT0_vect), первой командой будет sei. C __attribute__((signal)) такого не будет, ИМХО правильнее __attribute__((signal,used)). К тому же вариант singlskv не универсален - переключаются векторы INT0, INT1, а если они нужны? В случае "просто" функций - компилятор выдаст предупреждение "misspelled interrupt handler" - из-за этого данный вариант мне показался не подходящим. ИМХО в реале накладные расходы на if будут меньше push r30, icall и т.д. Цитата Как приятно иногда сказать: знатоки, блин. Не совсем понял иронию.
|
|
|
|
|
Dec 17 2008, 07:42
|

Йа моск ;)
     
Группа: Модераторы
Сообщений: 4 345
Регистрация: 7-07-05
Из: Kharkiv-city
Пользователь №: 6 610

|
Не знаю, можно ли все, что я тут напишу, реализовать в GCC, но в IAR'е я бы делал так Код static void (*IntProc_Pointer)(void); static UREG save_r16;
#pragma diag_suppress=Ta006 __interrupt void Proc1(void) { extern void foo(UINT16); foo(1234); }
__interrupt void Proc2(void) { extern void foo(UINT16); foo(4566); }
#pragma diag_default=Ta006
#pragma required=save_r16 #pragma required=IntProc_Pointer
#pragma vector=PCINT0_vect __raw __interrupt void Proc_JMP(void) { asm( " STS save_r16,R16\n" " LDS R16,IntProc_Pointer+0\n" //Возможно, необходимо поменять местами +0 и +1 " PUSH R16\n" " LDS R16,IntProc_Pointer+1\n" " PUSH R16\n" " LDS R16,save_r16\n" " RET\n" );
}
__monitor void Write_IntProc_Pointer(void(__interrupt *p)(void)) { IntProc_Pointer=(void(*)(void))p; }
void Set_Proc1(void) { Write_IntProc_Pointer(Proc1); }
void Set_Proc2(void) { Write_IntProc_Pointer(Proc2); } Так меньше всего стека уйдет.
--------------------
"Практика выше (теоретического) познания, ибо она имеет не только достоинство всеобщности, но и непосредственной действительности." - В.И. Ленин
|
|
|
|
|
Dec 17 2008, 08:19
|

Йа моск ;)
     
Группа: Модераторы
Сообщений: 4 345
Регистрация: 7-07-05
Из: Kharkiv-city
Пользователь №: 6 610

|
Цитата Что Ваш вариант в 20 тактов, что через icall в 21. Дык я про то, что в моем варианте лишний стек не используется. По скорости это конечно тормоза. Цитата ИМХО для 2 функций ни к чему. А если их 22?
--------------------
"Практика выше (теоретического) познания, ибо она имеет не только достоинство всеобщности, но и непосредственной действительности." - В.И. Ленин
|
|
|
|
|
Dec 17 2008, 10:17
|
;
     
Группа: Участник
Сообщений: 5 646
Регистрация: 1-08-07
Пользователь №: 29 509

|
Цитата(733259 @ Dec 17 2008, 06:01)  правильнее __attribute__((signal,used)). К тому же вариант singlskv не универсален - переключаются векторы INT0, INT1, а если они нужны? Следовательно, как компромисс, можно просто заюзать все неиспользуемые вектора. Есть у меня еще в стадии тестирования субвекторизация. Допустим, у нас есть функция, при должном внимании к построению которой можно добиться, что она состоит из набора независимых блоков, между которыми компилятор не будет осуществлять операции по сохранению используемых регистров Код void foo(void) { { block 1 } //........ { block N } } Сделаем необходимый инструментарий Код typedef uint16_t pc_type; // пока обошел указатели стороной
uint8_t wherePC(void* pcreg) __attribute__((naked)); void setPC(pc_type newPC) __attribute__((naked)); /*Обертка для субвекторов*/ #define SubVector(t_name) if(wherePC(&t_name)) WherePC заточено таким образом, чтобы возвращать 0 при вызове, а в параметре - адрес, с которого ее вызывали, исключая обработку возвращаемого значения. Код void setPC(pc_type newPC) { asm volatile("ijmp" : : "z" (newPC)); }
uint8_t wherePC(void* pcreg) { asm volatile ( "pop r25" "\n\t" "pop r24" "\n\t" "adiw r24,2" "\n\t" "st Z, r24" "\n\t" "std Z+1,r25" "\n\t" "movw r30,r24" "\n\t" "sbiw r30,2" "\n\t" "clr r24" "\n\t" "ijmp" "\n\t" ::"z" (pcreg)); } Используя SubVector(имя_переменной_указателя_на_блок) Код void foo(void) { static pc_type sv[3]; static uint8_t work = 0; if(work) {// код, использующий косв. переход на sv[n] set_PC(sv[1]); } else { SubVector(sv[0]) { block 1 } SubVector(sv[1]) { block 2 } SubVector(sv[2]) { block 3 } work = 1; } return; } В итоге при первом вызове получаем адреса блоков кода в переменных sv[], но сами блоки не выполняем, по аналогии с setjmp, а при последующих вызовах - переходим в кратчайшие сроки к независимому блоку. В данном контексте это должно обеспечить выигрыш по сравнению со switch() Просьба потестить эти примитивы и высказать мнения о применимости. Заранее спасибо.
|
|
|
|
|
Dec 17 2008, 11:09
|
дятел
    
Группа: Свой
Сообщений: 1 681
Регистрация: 13-05-06
Из: Питер
Пользователь №: 17 065

|
Цитата(_Pasha @ Dec 17 2008, 13:17)  Следовательно, как компромисс, можно просто заюзать все неиспользуемые вектора. Конечно там нужен signal а не interrupt. На самом деле можно заюзать и несуществующие вектора, и с учетом варианта обертки от Rst7 можно сделать вот так: обертка в асм файле isr.s : Код #include <avr/interrupt.h> .extern curr_isr .extern save_r16 .global TIMER1_COMPA_vect; прерывание с переключаемым обработчиком TIMER1_COMPA_vect: sts save_r16, r16 lds r16, curr_isr push r16 lds r16, curr_isr + 1 push r16 lds r16, save_r16 ret а вот это сам файл с обработчиками и примером: Код #include <avr/io.h> #include <avr/interrupt.h>
#define Isr1 __vector_Isr1 #define Isr2 __vector_Isr2
void Isr1(void) __attribute__((signal)); void Isr1(void) { // тело обработчика 0 }
void Isr2(void) __attribute__((signal)); void Isr2(void) { // тело обработчика 1 }
// указатель на функцию обработчик void (* curr_isr)(void) = Isr1; // для сохранения r16 в обработчике unsigned char save_r16;
// установить обработчик void Set_ISR(void (* p)(void)) { unsigned char sreg = SREG; cli(); curr_isr = p; SREG = sreg; }
int main(void) { sei();
Set_ISR(Isr1); Set_ISR(Isr2);
while (1); }
|
|
|
|
|
Dec 20 2008, 01:03
|

кекс
     
Группа: Свой
Сообщений: 3 825
Регистрация: 17-12-05
Из: Киев
Пользователь №: 12 326

|
Цитата(PhX @ Dec 16 2008, 11:19)  Ну хоть как-то. Не очень хорошо конечно, но что делать. Может есть способ лучше? Правда, в моем случае, быстродействия достаточно. На мой взгляд, это отличный способ. Почему я так думаю - потому что: 1. Сохранение и восстановление всех регистров это всего лишь 128 тактов вход-выход в обработчик. 2. Как правило, необходимость в подмене обработчика прерывания происходит тогда, когда кардинально что-то меняется в работе программы, например - замена протокола обмена. В таких случаях логично предположить, что подменяемые обработчики "тяжеловесны" и по времени выполнения занимают гораздо больше 128 тактов. 3. Не надо гнаться за скоростью там где ее хватает, тем более в ущерб переносимости и читаемости кода. Из чего делаем вывод - всего за 128 тактов получаем простой, прозрачный (легко читаемый), относительно безопасный, и прекрасно портируемый код на любую платформу. Хорошо это, не очень хорошо, или совсем плохо - судить Вам ;> Правда есть еще один тупой и топорный способ, о котором почему-то забыли упомянуть, хотя он обладает еще таким свойством как "абсолютная" безопасность  : Код ISR() { prefix stuff
if (...) handler1(); else if (..) handler2(); else common_handler();
postfix stuff } при "сказочной" тупизне и кажущейся неоптимальности, вот какими плюсами сие обладает: - Никакого гемороя с ассемблером. - Тривиальная простота. - Абсолютная безопастность - есть уверенность, что из обработчика выйдем всегда, и всегда выйдем правильно, нет риска неверного указателя в RAM. - Возможность не только "подменять" обработчик, но и "параллельно" выполнять несколько обработчиков (например, по одному интерфейсу обслуживать несколько протоколов обмена). - Легко портируемый. - Нативно оптимизируемый - если все функции handler1, 2, и т.п. объявить в одном файле с обработчиком как static, то результирующий код получится эффективным - т.к. оптимизатор сделает свое дело сам. Все шаманства с ассемблером и оптимизации "ради оптимизации" - это Зло. Лучше делать упор на функциональность, портируемость, читаемость. А вот когда действительно не будет хватать производительности и нет возможности поставить более шустрый кварц / камень, тогда (и только тогда) можно прибегнуть к извращениям на асм'е.
|
|
|
|
|
Dec 20 2008, 16:00
|

Нечётный пользователь.
     
Группа: Свой
Сообщений: 2 033
Регистрация: 26-05-05
Из: Бровари, Україна
Пользователь №: 5 417

|
Цитата(_Pasha @ Dec 17 2008, 12:17)  Есть у меня еще в стадии тестирования субвекторизация. Есть несколько менее переносимый, зато более эффективный способ. В том смысле, что переписать эту субвекторизацию на другой асм другого процессора+компилятора можно, как можно и посмотреть реализацию структуры jump_buf и сделать переключатель, как у Rst7, привязанный к процессору+компилятору, но переписываемый под любую другую компбинацию. А тут - привязка к конкретному инструменту. Но раз в примере всё равно использован gcc, то... в нём можно взять адрес метки. Во-первых, при этом можно WherePC сделать макросом, адрес вычисляется линкером: Код #define glue2(a,b) a##b #define WPC_LABEL(a,b) glue2(a,b)
#define WherePC(a) { a = &&WPC_LABEL(WPC,__LINE__); WPC_LABEL(WPC,__LINE__):; }
volatile unsigned char a;
void *p1; void *p2;
void foo() { ++a; WherePC(p1); ++a; WherePC(p2); ++a; } И результат компиляции: Код foo: lds r24,a subi r24,lo8(-(1)) sts a,r24 ldi r24,lo8(gs(.L2)) ldi r25,hi8(gs(.L2)) sts (p1)+1,r25 sts p1,r24 .L2: lds r24,a subi r24,lo8(-(1)) sts a,r24 ldi r24,lo8(gs(.L3)) ldi r25,hi8(gs(.L3)) sts (p2)+1,r25 sts p2,r24 .L3: lds r24,a subi r24,lo8(-(1)) sts a,r24 ret Во вторых, этот макрос при такой постановке вопроса не нужен вообще: http://forum.sources.ru/index.php?showtopi...p;#entry1929594В переводе на AVR-ский язык Код ISR(INT0_vect) { static void *next_state = &&start;
goto *next_state;
start: next_state = &&idle; return;
idle: if (PINB & 0x01) { PORTB |= 0x80; next_state = &&drain; } return;
drain: if (PINB & 0x02) { PORTB &= ~0x80; next_state = &&idle; } return; } И результат: Код .text .size foo, .-foo .global __vector_1 __vector_1: push __zero_reg__ push __tmp_reg__ in __tmp_reg__,__SREG__ push __tmp_reg__ clr __zero_reg__ push r24 push r25 push r30 push r31
lds r30,next_state.1519 lds r31,(next_state.1519)+1 ijmp .L6: .L13: ldi r24,lo8(gs(.L7)) ldi r25,hi8(gs(.L7)) sts (next_state.1519)+1,r25 sts next_state.1519,r24 rjmp .L12 .L7: sbis 35-0x20,0 rjmp .L12 sbi 37-0x20,7 ldi r24,lo8(gs(.L10)) ldi r25,hi8(gs(.L10)) sts (next_state.1519)+1,r25 sts next_state.1519,r24 rjmp .L12 .L10: sbis 35-0x20,1 rjmp .L12 cbi 37-0x20,7 rjmp .L13 .L12:
pop r31 pop r30 pop r25 pop r24 pop __tmp_reg__ out __SREG__,__tmp_reg__ pop __tmp_reg__ pop __zero_reg__ reti
.data next_state.1519: .word gs(.L6)
--------------------
Ну, я пошёл… Если что – звоните…
|
|
|
|
|
Dec 20 2008, 16:26
|
;
     
Группа: Участник
Сообщений: 5 646
Регистрация: 1-08-07
Пользователь №: 29 509

|
Цитата(ReAl @ Dec 20 2008, 20:00)  посмотреть реализацию структуры jump_buf и сделать переключатель Да-а... На разных платформах количество переключаемого контекста настолько разное... У AVR- многовато  Цитата #define WherePC(a) { a = &&WPC_LABEL(WPC,__LINE__); WPC_LABEL(WPC,__LINE__):; } ................................ static void *next_state = &&start; goto *next_state; Спасибо! Т.е. задача-то решается по-прежнему макросами(для совместимости), но адрес метки в gcc имхо самое не-кривое решение.
|
|
|
|
|
  |
1 чел. читают эту тему (гостей: 1, скрытых пользователей: 0)
Пользователей: 0
|
|
|