Перейти к содержанию
    

Замена обработчика прерывания в WinAVR

Господа, Вы занимаетесь просто какой-то х.р..й ... :) :) :)

Вот это обертка в асм файле 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МЦ... на фсе...

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Господа, Вы занимаетесь просто какой-то х.р..й ... :) :) :)

Кому х.р.я, а кому - сын ошибок трудных. :)

Т.е. получается, что __attribute__((interrupt)) и заставляет все call-used регистры сохранять внутри функции?

Кстати Ваш ужас ...

Как приятно иногда сказать: знатоки, блин. :lol:

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Т.е. получается, что __attribute__((interrupt)) и заставляет все call-used регистры сохранять внутри функции?
Справедливости ради, такой подход имеет один побочный эфект,

прерывания будут разрешены еще до окончательного выхода из нашего

главного обработчика, НО, если прерывания идут с таким темпом что это

может вызвать переполнение стека, при других вариантах обработки мы просто

будем пропускать прерывания, так что такой вариант ИМХО вполне адекватный...

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

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

ИМХО в реале накладные расходы на if будут меньше push r30, icall и т.д.

Как приятно иногда сказать: знатоки, блин.
Не совсем понял иронию.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Не знаю, можно ли все, что я тут напишу, реализовать в 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);
  
}

 

Так меньше всего стека уйдет.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Что Ваш вариант в 20 тактов, что через icall в 21.

Тогда как банальный if(test) - 5 или 6 тактов - lds, and, breq.

ИМХО для 2 функций ни к чему. Навороты хороши не тогда, когда в комлекте ещё тормоза.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Что Ваш вариант в 20 тактов, что через icall в 21.

 

Дык я про то, что в моем варианте лишний стек не используется. По скорости это конечно тормоза.

 

ИМХО для 2 функций ни к чему.

 

А если их 22?

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

правильнее __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()

 

Просьба потестить эти примитивы и высказать мнения о применимости. Заранее спасибо.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Следовательно, как компромисс, можно просто заюзать все неиспользуемые вектора.
Конечно там нужен 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);
}

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Вызов функции по указателю внутри обработчика ведет к сохранению ВСЕХ регистров в стеке. И восстановлению их при выходе. Не быстрый обработчик получается :(

а что если "раздеть" обработчик (__naked__), а на подставляемые функции наоборот, навесить сохранение контекста?

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

а что если "раздеть" обработчик (__naked__), а на подставляемые функции наоборот, навесить сохранение контекста?

Не, там надо подставляемую функцию, к которой будут по указателю обращаться, делать (__naked__), а в нее заворачивать уже то, что нужно. С проктологической точки зрения - нормально :) двойной вложенный вызов. А с другой стороны оптимизация может выкинуть лишний call/ret.. надо попробовать

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Не, там надо подставляемую функцию, к которой будут по указателю обращаться, делать (__naked__),
И потерять резервирование места под локальные переменные.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Ну хоть как-то. Не очень хорошо конечно, но что делать. Может есть способ лучше?

Правда, в моем случае, быстродействия достаточно.

На мой взгляд, это отличный способ. Почему я так думаю - потому что:

1. Сохранение и восстановление всех регистров это всего лишь 128 тактов вход-выход в обработчик.

2. Как правило, необходимость в подмене обработчика прерывания происходит тогда, когда кардинально что-то меняется в работе программы, например - замена протокола обмена. В таких случаях логично предположить, что подменяемые обработчики "тяжеловесны" и по времени выполнения занимают гораздо больше 128 тактов.

3. Не надо гнаться за скоростью там где ее хватает, тем более в ущерб переносимости и читаемости кода.

 

Из чего делаем вывод - всего за 128 тактов получаем простой, прозрачный (легко читаемый), относительно безопасный, и прекрасно портируемый код на любую платформу.

Хорошо это, не очень хорошо, или совсем плохо - судить Вам ;>

 

 

 

Правда есть еще один тупой и топорный способ, о котором почему-то забыли упомянуть, хотя он обладает еще таким свойством как "абсолютная" безопасность :) :

ISR()
{
    prefix stuff

    if (...)
      handler1();
    else if (..)
      handler2();
    else
      common_handler();

   postfix stuff
}

при "сказочной" тупизне и кажущейся неоптимальности, вот какими плюсами сие обладает:

- Никакого гемороя с ассемблером.

- Тривиальная простота.

- Абсолютная безопастность - есть уверенность, что из обработчика выйдем всегда, и всегда выйдем правильно, нет риска неверного указателя в RAM.

- Возможность не только "подменять" обработчик, но и "параллельно" выполнять несколько обработчиков (например, по одному интерфейсу обслуживать несколько протоколов обмена).

- Легко портируемый.

- Нативно оптимизируемый - если все функции handler1, 2, и т.п. объявить в одном файле с обработчиком как static, то результирующий код получится эффективным - т.к. оптимизатор сделает свое дело сам.

 

 

 

Все шаманства с ассемблером и оптимизации "ради оптимизации" - это Зло. Лучше делать упор на функциональность, портируемость, читаемость. А вот когда действительно не будет хватать производительности и нет возможности поставить более шустрый кварц / камень, тогда (и только тогда) можно прибегнуть к извращениям на асм'е.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Есть у меня еще в стадии тестирования субвекторизация.
Есть несколько менее переносимый, зато более эффективный способ. В том смысле, что переписать эту субвекторизацию на другой асм другого процессора+компилятора можно, как можно и посмотреть реализацию структуры 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)

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

посмотреть реализацию структуры jump_buf и сделать переключатель

Да-а... На разных платформах количество переключаемого контекста настолько разное... У AVR- многовато :(

 

#define WherePC(a) { a = &&WPC_LABEL(WPC,__LINE__); WPC_LABEL(WPC,__LINE__):; }

................................

static void *next_state = &&start;

goto *next_state;

Спасибо!

Т.е. задача-то решается по-прежнему макросами(для совместимости), но адрес метки в gcc имхо самое не-кривое решение. :beer:

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Присоединяйтесь к обсуждению

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

Гость
К сожалению, ваш контент содержит запрещённые слова. Пожалуйста, отредактируйте контент, чтобы удалить выделенные ниже слова.
Ответить в этой теме...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

×
×
  • Создать...