Соглашение о вызовах
Любой Си компилятор имеет соглашение о вызовах, которое определяет какие параметры передаются функциям в регистрах, а какие через стек, какие регистры могут быть использованы вызываемой функцией в качестве рабочих, а какие должны быть сохранены.
Например, в руководстве на компилятор 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
Поповоду асма - структурировать замучитесь :)!!!
RSS feed for comments to this post