Вызов функции из прерывания

01/03/2010 - 23:56

Соглашение о вызовах

   Любой Си компилятор имеет соглашение о вызовах, которое определяет какие параметры передаются функциям в регистрах, а какие через стек,  какие регистры могут быть использованы вызываемой функцией в качестве рабочих, а какие должны быть сохранены.
   Например, в  руководстве на компилятор  IAR C/C++ для AVR говорится:

   "Рабочие регистры

   Любая функция может разрушать содержимое рабочих регистров. Если функции требуется значения регистров после вызова другой функции, она должна их сохранить перед ее вызовом.  Следующие 14 регистров могут быть использованы в качестве рабочих: R0–R3, R16–R23  и R30–R31

   Зарезервированные регистры

  Зарезервированные регистры должны сохранять свое значение до и после вызова функции. Любая функция может использовать зарезервированный регистр для других  целей, но предварительно она должна  сохранить его значение (положить в стек), а перед выходом  восстановить (извлечь из стека). Следующие регистры являются зарезервированными: R4–R15 и R24–R27."

   Таким образом, для обычной функции, компилятор IAR C/C++  будет использовать 14 рабочих регистров там, где это возможно. А если 14 рабочих регистров не будет хватать, он задействует зарезервированные регистры.

Подпрограмма обработки прерывания (Interrupt Service Routine - ISR)

   Прерывания происходят в неизвестном времени/месте, поэтому внутри обработчика прерывания, компилятор обычно не может знать  какие регистры содержат информацию, которая будет использоваться после возвращения из обработчика. Таким образом регистры, используемые в пределах  обработчика прерывания,  должны быть сохранены. Хороший компилятор, конечно, будет  использовать регистры экономичным/оптимальным путем, чтобы минимизировать накладные расходы по сохранению и восстановлению  регистров.

Обработчик прерывания вызывающий функцию

   Если  в обработчике прерывания используется регистр, то предыдущее содержимое этого регистра должно быть сохранено. Если  обработчик прерывания вызывает функцию, то компилятору необходимо соблюдать соглашение о вызовах, чтобы уберечь значения нужных регистров от уничтожения. Таким образом он  должен сохранить все 14 регистров прежде чем вызовет функцию. Обычно так и происходит, даже если вызываемая функция не использует никаких регистров.

Как избежать увеличения кода

   Очевидная вещь которую нужно сделать чтобы избежать увеличения кода – отказаться от вызова функции внутри ISR. Используя препроцессор,  можно получить запись, которая выглядит как вызов функции, однако это не рекомендуется, так как в этом случае не будет ни проверки типов, ни других преимуществ нормального вызова.
   Если у вас есть весомые  причины для вызова функций из ISR, вам нужно  обеспечить компилятор информацией, чтобы он смог сгенерировать хороший код. Вообще  нужно проверять, что происходить в каждом конкретном случае, но вы можете попробовать определить вызываемую функцию 1)статической и 2)в этом же файле где и ISR. С 2) компилятор будет  точно знать какие регистры используются вызываемой функцией при компилировании ISR, а с 1) вы указываете компилятору, что эта функция – вызывается из обработчика прерывания только в этом файле. Если у компилятора есть прагма поощряющая встраивание кода, вы можете попробовать и это.  При использовании IAR Embedded Workbench для  AVR ни ключевое слово static, ни прагма inline не требуются (прим. – непонятный момент, это не всегда так)
   С этой информацией, возможно, компилятор встроит код функции и оптимизирует накладные расходы по сохранению регистров.
   Вам нужно включить соответствующую оптимизацию и посмотреть на сгенерированный код, чтобы понять, делает ли компилятор то, что вы хотите.  

По материалам фирмы IAR Systems

Приложение

Пример №1
Обработчик прерывания и вызываемая функция определены в разных файлах.

//test.h
#include <ioavr.h>
unsigned char TestFunc(void);

//test.c
#include "test.h"
unsigned char TestFunc(void)
{
   unsigned char tmp = PORTB;
   tmp++;
   return tmp;
}

//timer.h
#include <ioavr.h>

//timer.c
#include "timer.h"
#pragma vector = TIMER0_OVF_vect
__interrupt void Timer0Ovf(void)
{
   PORTB = TestFunc();
}

Одинаковые результаты  при максимальных уровнях оптимизации по скорости и по размеру – 70 байтов кода на обработчик прерывания и 6 байтов на функцию TestFunc. В прерывании сохраняются все рабочие регистры, функция вызывается.

      5          __interrupt void Timer0Ovf(void)
   \                     Timer0Ovf:
      6          {
   \   00000000   938A               ST      -Y, R24
   \   00000002   93FA               ST      -Y, R31
   \   00000004   93EA               ST      -Y, R30
   \   00000006   923A               ST      -Y, R3
   \   00000008   922A               ST      -Y, R2
   \   0000000A   921A               ST      -Y, R1
   \   0000000C   920A               ST      -Y, R0
   \   0000000E   937A               ST      -Y, R23
   \   00000010   936A               ST      -Y, R22
   \   00000012   935A               ST      -Y, R21
   \   00000014   934A               ST      -Y, R20
   \   00000016   933A               ST      -Y, R19
   \   00000018   932A               ST      -Y, R18
   \   0000001A   931A               ST      -Y, R17
   \   0000001C   930A               ST      -Y, R16
   \   0000001E   B78F               IN      R24, 0x3F
      7            PORTB = TestFunc();
   \   00000020   ....               RCALL   TestFunc
   \   00000022   BB08               OUT     0x18, R16
      8          }
   \   00000024   BF8F               OUT     0x3F, R24
   \   00000026   9109               LD      R16, Y+
   \   00000028   9119               LD      R17, Y+
   \   0000002A   9129               LD      R18, Y+
   \   0000002C   9139               LD      R19, Y+
   \   0000002E   9149               LD      R20, Y+
   \   00000030   9159               LD      R21, Y+
   \   00000032   9169               LD      R22, Y+
   \   00000034   9179               LD      R23, Y+
   \   00000036   9009               LD      R0, Y+
   \   00000038   9019               LD      R1, Y+
   \   0000003A   9029               LD      R2, Y+
   \   0000003C   9039               LD      R3, Y+
   \   0000003E   91E9               LD      R30, Y+
   \   00000040   91F9               LD      R31, Y+
   \   00000042   9189               LD      R24, Y+
   \   00000044   9518               RETI


Пример №2
Функции определены в одном файле. Ключевые слова не используются.

//test.h
#include <ioavr.h>
unsigned char TestFunc(void);

//test.c
#include "test.h"

unsigned char TestFunc(void)
{
  unsigned char tmp = PORTB;
  tmp++;
  return tmp;
}

#pragma vector = TIMER0_OVF_vect
__interrupt void Timer0Ovf(void)
{
  PORTB = TestFunc();
}

При максимальном уровне оптимизации по размеру – 70 байтов кода на обработчик прерывания и 6 байтов на функцию TestFunc. В прерывании сохраняются все рабочие регистры, функция вызывается.

     12          __interrupt void Timer0Ovf(void)
   \                     Timer0Ovf:
     13          {
   \   00000000   938A               ST      -Y, R24
   \   00000002   93FA               ST      -Y, R31
   \   00000004   93EA               ST      -Y, R30
   \   00000006   923A               ST      -Y, R3
   \   00000008   922A               ST      -Y, R2
   \   0000000A   921A               ST      -Y, R1
   \   0000000C   920A               ST      -Y, R0
   \   0000000E   937A               ST      -Y, R23
   \   00000010   936A               ST      -Y, R22
   \   00000012   935A               ST      -Y, R21
   \   00000014   934A               ST      -Y, R20
   \   00000016   933A               ST      -Y, R19
   \   00000018   932A               ST      -Y, R18
   \   0000001A   931A               ST      -Y, R17
   \   0000001C   930A               ST      -Y, R16
   \   0000001E   B78F               IN      R24, 0x3F
     14            PORTB = TestFunc();
   \   00000020   ....               RCALL   TestFunc
   \   00000022   BB08               OUT     0x18, R16
     15          }
   \   00000024   BF8F               OUT     0x3F, R24
   \   00000026   9109               LD      R16, Y+
   \   00000028   9119               LD      R17, Y+
   \   0000002A   9129               LD      R18, Y+
   \   0000002C   9139               LD      R19, Y+
   \   0000002E   9149               LD      R20, Y+
   \   00000030   9159               LD      R21, Y+
   \   00000032   9169               LD      R22, Y+
   \   00000034   9179               LD      R23, Y+
   \   00000036   9009               LD      R0, Y+
   \   00000038   9019               LD      R1, Y+
   \   0000003A   9029               LD      R2, Y+
   \   0000003C   9039               LD      R3, Y+
   \   0000003E   91E9               LD      R30, Y+
   \   00000040   91F9               LD      R31, Y+
   \   00000042   9189               LD      R24, Y+
   \   00000044   9518               RETI

При максимальном уровне оптимизации по скорости - 20 байтов кода на обработчик прерывания и 6 байтов на функцию TestFunc. В прерывании сохраняются только один используемый функцией регистр, тело функции встроено.

   12          __interrupt void Timer0Ovf(void)
   \                     Timer0Ovf:
     13          {
   \   00000000   931A               ST      -Y, R17
   \   00000002   930A               ST      -Y, R16
   \   00000004   B71F               IN      R17, 0x3F
     14            PORTB = TestFunc();
   \   00000006   B308               IN      R16, 0x18
   \   00000008   9503               INC     R16
   \   0000000A   BB08               OUT     0x18, R16
     15          }
   \   0000000C   BF1F               OUT     0x3F, R17
   \   0000000E   9109               LD      R16, Y+
   \   00000010   9119               LD      R17, Y+
   \   00000012   9518               RETI

Пример №3
Функции определены в одном файле. Используется ключевое слово static и прагма inline

//test.h
#include <ioavr.h>
unsigned char TestFunc(void);

//test.c
#include "test.h"

#pragma inline=forced
static unsigned char TestFunc(void)
{
  unsigned char tmp = PORTB;
  tmp++;
  return tmp;
}

#pragma vector = TIMER0_OVF_vect
__interrupt void Timer0Ovf(void)
{
  PORTB = TestFunc();
}

   Одинаковые результаты при максимальных уровнях оптимизации по скорости и по размеру – 20 байтов кода на обработчик и функцию TestFunc. В прерывании сохраняются только один используемый функцией регистр, тело функции встроено.

     12          __interrupt void Timer0Ovf(void)
   \                     Timer0Ovf:
     13          {
   \   00000000   931A               ST      -Y, R17
   \   00000002   930A               ST      -Y, R16
   \   00000004   B71F               IN      R17, 0x3F
     14            PORTB = TestFunc();
   \   00000006   B308               IN      R16, 0x18
   \   00000008   9503               INC     R16
   \   0000000A   BB08               OUT     0x18, R16
     15          }
   \   0000000C   BF1F               OUT     0x3F, R17
   \   0000000E   9109               LD      R16, Y+
   \   00000010   9119               LD      R17, Y+
   \   00000012   9518               RETI

Comments   

# Guest 2010-03-02 09:36
Еще один метод борьбы с компилятором - писать обработчики прерываний на асме. Но это уж когда совсем припрет.
# Guest 2010-03-14 11:33
А еще можно писать всю программу на асме :lol: .
# Silverio 2010-03-15 11:25
Огромное спасибо за информацию !!!
Поповоду асма - структурировать замучитесь :)!!!

У вас недостаточно прав для комментирования.