IAR Embedded Workbench IDE, использование ассемблера в C-проекте Печать
Добавил(а) microsin   

Обычно необходимость использовать ассемблер бывает в обработчиках прерываний. Рассмотрим по шагам, как это делается.

1. Для обработчиков прерываний лучше создать отдельный файл - выберите в меню File\New\File, вставьте в новый файл модуля текст:

#pragma vector=TIMER0_OVF_vect
__interrupt void TIMER0_OVF_routineTIMER0_OVF_routine(void)
{
  OCR0 = ampl1;
  if (idx < 64)
     ampl1 = SINUS[idx];
  else if (idx < 128)
     ampl1 = SINUS[127-idx];
  else if (idx < 192)
     ampl1 = 255 - SINUS[idx-128];
  else
     ampl1 = 255 - SINUS[255-idx];
  idx = fi1 >> 8;
  fi1 += freq1;    
}

Сохраните файл как int.c.

2. Правая кнопка на корневой папке проекта в браузере "Workspace", Add\Add Files... выберите файл int.c.

3. Правая кнопка на int.c, Options..., поставьте галку на Override inherited settings. Дальше перейдите на закладку List, поставьте галки на Output assembler file и на Include source. После перекомпиляции проекта появится ассемблерный файл Debug\List\int.s90. Разберем его содержимое.

4. В начале файла находится заголовок, где указана командная строка для компилятора C:\Program Files\IAR Embedded Workbench 4.0\avr\bin\iccavr.exe:
int.c
--cpu=m16
-mt
-o c:\MyDoc\FLOPPI\ANOTHER\avr\BLDC\Debug\Obj\
-lB c:\MyDoc\FLOPPI\ANOTHER\avr\BLDC\Debug\List\
--initializers_in_flash
-z2
--no_cse
--no_inline
--no_code_motion
--no_cross_call
--no_clustering
--no_tbaa
--debug
-e
-I "C:\Program Files\IAR AVR Embedded Workbench 4.0\avr\INC\"
-I "C:\Program Files\IAR AVR Embedded Workbench 4.0\avr\INC\CLIB\"
--eeprom_size 512

5. Рассмотрим опции компилятора поподробнее. Это поможет разобраться в происходящем и далее компилировать c-файлы отдельно, без IDE.

int.c это понятно, входной файл
--cpu=m16 тип используемого микроконтроллера
-mt memory model (t=tiny)
-o c:\MyDoc\FLOPPI\ANOTHER\avr\BLDC\Debug\Obj\ указывает, куда поместить объектный файл int.r90
-lB c:\MyDoc\FLOPPI\ANOTHER\avr\BLDC\Debug\List\ указывает, куда поместить list-файл. Это и есть наш текст на ассемблере. Буква B включает добавление в комментариях строк кода C исходника
--initializers_in_flash Aggregate initializers are placed in flash memory
-z2 Оптимизация по size на уровне 2 (отладка)
--no_cse Disable common sub-expression elimination
--no_inline Disable function inlining
--no_code_motion Disable code motion
--no_cross_call Disable cross call optimization
--no_clustering Disable variable clustering
--no_tbaa Disable type based alias analysis
--debug Insert debug info in object file
-e Enable IAR C/C++ language extensions
-I "C:\Program Files\IAR AVR Embedded Workbench 4.0\avr\INC\" Add #include search directory
-I "C:\Program Files\IAR AVR Embedded Workbench 4.0\avr\INC\CLIB\" Add #include search directory
--eeprom_size 512 The size of the inbuilt eeprom area

6. Для того, чтобы разобраться в ассемблерном файле, понадобится datasheet на процессор ATmega16, описание сегментов памяти (CSTACK, RSTACK, ..), которое можно найти в апноуте AVR032 [2], а также описание системы команд 8-битных AVR (8-bit AVR Instruction Set).

Итак, получился из исходника int.c такой выходной текст ассемблера:

       NAME int     ;дает имя программному модулю для дальнейших ссылок на него линкером

       RSEG CSTACK:DATA:NOROOT(0) ;начало перемещаемого (RSEG) сегмента CSTACK (Data stack),
                                  ; тип сегмента DATA, NOROOT означает,
                                  ; что линкер может отбросить сегмент в случае отсутствия на него ссылок.
                                  ; (0) означает величину выравнивания по адресу, равную 1
                                  ; (2 в степени 0 равно 1), то есть фактически никакого выравнивания нет.
       RSEG RSTACK:DATA:NOROOT(0) ;то же самое, сегмент RSTACK (return stack)

       EXTERN ?need_segment_init  ;импортирует внешний символ ?need_segment_init

       PUBWEAK `?`  ;опубликовывает символ `?`.
                                  ; PUBWEAK отличается от PUBLIC тем, что позволяет публикацию
                                  ;того же символа в разных модулях
       PUBWEAK `??TIMER0_OVF_routineTIMER0_OVF_routine??INTVEC `
       PUBLIC TIMER0_OVF_routineTIMER0_OVF_routine      ;публикует имя обработчика прерывания таймера
       PUBWEAK _A_OCR0            ;публикует имя переменной _A_OCR0
                                  ; (регистр OCR0 - Timer/Counter0 Output Compare Register)
       PUBWEAK __?EEARH           ;публикует имя регистра адреса EEPROM
       PUBWEAK __?EEARL           ;публикует имя регистра адреса EEPROM
       PUBWEAK __?EECR            ; ..          EEPROM Control Register
       PUBWEAK __?EEDR            ; ..          EEPROM Data Register
       PUBLIC ampl1               ;тут публикуются переменные (зачем?)
       PUBLIC fi1                 ;
       PUBLIC freq1               ;
       PUBLIC idx                 ;

TIMER0_OVF_routineTIMER0_OVF_routine SYMBOL "TIMER0_OVF_routineTIMER0_OVF_routine"
`??TIMER0_OVF_routineTIMER0_OVF_routine??INTVEC ` SYMBOL "??INTVEC 36", TIMER0_OVF_routineTIMER0_OVF_routine

       EXTERN SINUS

// c:\MyDoc\FLOPPI\ANOTHER\avr\BLDC\int.c
//   1 #include

       ASEGN ABSOLUTE:DATA:NOROOT,05cH;начинает абсолютный сегмент данных ABSOLUTE, адрес начала 0x5C
// union volatile __io _A_OCR0
_A_OCR0:
       DS 1;здесь определяется регистр OCR0 (зачем таким образом, непонятно).
//   2 #include
//   3 #include "settings.h"
//   4
;далее идет описание используемых переменных и выделение для них памяти
       RSEG TINY_Z:DATA:NOROOT(0)
       REQUIRE `?`
//   5 uint fi1;
fi1:
       DS 2

       RSEG TINY_Z:DATA:NOROOT(0)
       REQUIRE `?`
//   6 uchar idx;
idx:
       DS 1

       RSEG TINY_Z:DATA:NOROOT(0)
       REQUIRE `?`
//   7 uchar ampl1;
ampl1:
       DS 1

       RSEG TINY_Z:DATA:NOROOT(0)
       REQUIRE `?`
//   8 uint freq1;
freq1:
       DS 2
//   9
//   10 extern const char SINUS [64];
//   11
//   12 #pragma vector=TIMER0_OVF_vect

       RSEG CODE:CODE:NOROOT(1)   ;начало сегмента кода, выравнивание (1) означает,
                                  ; что адрес сегмента выравнивается по четным адресам
                                  ; (2 в степени 1). Такое выравнивание нужно потому, что
                                  ; каждая команда кода программы занимает 2 байта.
//   13 __interrupt void TIMER0_OVF_routineTIMER0_OVF_routine(void)
TIMER0_OVF_routineTIMER0_OVF_routine:
//   14 {
       ST-Y, R31          ;Запись в стек данных (CSTACK) переменных. Для этого используется индексный
       ST-Y, R30          ; регистр Y (R28 Low byte, R29 High byte)
       ST-Y, R20          ;Хорошо бы от этого избавиться...
       ST-Y, R19          ;
       ST-Y, R18          ;
       ST-Y, R17          ;
       ST-Y, R16          ;
       INR20, 0x3F        ;сохраняем регистр статуса SREG (0x3F)
//   15    OCR0 = ampl1;
       LDSR16, ampl1      ;тут все понятно - выводим в регистр OCR0 переменную ampl1
       OUT0x3C, R16       ;
//   16    if (idx < 64)
       LDSR16, idx                                    ;тут тоже все понятно
       CPIR16, 64                                     ;
       BRCC??TIMER0_OVF_routineTIMER0_OVF_routine_0   ;
//   17      ampl1 = SINUS[idx];
       LDSR16, idx
       MOVR30, R16
       SUBIR30, (-(SINUS) & 0xFF) ;вычитаем константу из регистра R30 (Z-register Low Byte)
       LDIR31, 0                  ;загрузить в R31 константу 0 (Z-register High Byte)
       LDR16, Z                   ;загрузить R16 непосредственно (по адресу в Z)
       STSampl1, R16              ;записать в ячейку ОЗУ ampl1 регистр R16
       RJMP??TIMER0_OVF_routineTIMER0_OVF_routine_1
//   18    else if (idx < 128)
??TIMER0_OVF_routineTIMER0_OVF_routine_0:
       LDSR16, idx
       CPIR16, 128
       BRCC??TIMER0_OVF_routineTIMER0_OVF_routine_2
//   19      ampl1 = SINUS[127-idx];
       LDSR16, idx
       NEGR16
       MOVR30, R16
       SUBIR30, LOW((-(SINUS + 127) & 0xFF))
       LDIR31, 0
       LDR16, Z
       STSampl1, R16
       RJMP??TIMER0_OVF_routineTIMER0_OVF_routine_1
//   20    else if (idx < 192)
??TIMER0_OVF_routineTIMER0_OVF_routine_2:
       LDSR16, idx
       CPIR16, 192
       BRCC??TIMER0_OVF_routineTIMER0_OVF_routine_3
//   21      ampl1 = 255 - SINUS[idx-128];
       LDIR16, 255
       LDSR17, idx
       MOVR30, R17
       SUBIR30, LOW((-(SINUS - 128) & 0xFF))
       LDIR31, 0
       LDR17, Z
       SUBR16, R17
       STSampl1, R16
       RJMP??TIMER0_OVF_routineTIMER0_OVF_routine_1
//   22    else
//   23      ampl1 = 255 - SINUS[255-idx];
??TIMER0_OVF_routineTIMER0_OVF_routine_3:
       LDIR16, 255
       LDSR17, idx
       NEGR17
       MOVR30, R17
       SUBIR30, LOW((-(SINUS - 1) & 0xFF))
       LDIR31, 0
       LDR17, Z
       SUBR16, R17
       STSampl1, R16
//   24    idx = fi1 >> 8;
??TIMER0_OVF_routineTIMER0_OVF_routine_1:
       LDIR30, fi1                ;загрузка адреса fi1 в регистр Z
       LDIR31, 0                  ;
       LDDR17, Z+1                ;загрузить R17 ячейкой Z+1
       MOVR16, R17                ;R16=R17 типа делает сдвиг
       STSidx, R16                ;записать в ячейку idx регистр R16
//   25    fi1 += freq1;
       LDIR30, freq1              ;загрузка адреса freq1 в регистр Z
       LDIR31, 0                  ;
       LDR16, Z                   ;загрузка переменной freq1 в R17:R16
       LDDR17, Z+1                ;
       LDIR30, fi1                ;загрузка адреса fi1 в регистр Z
       LDIR31, 0                  ;
       LDR18, Z                   ;загрузка переменной fi1 в R19:R18
       LDDR19, Z+1                ;
       ADDR18, R16                ;R19:R18 = R19:R18 + R17:R16
       ADCR19, R17                ;
       STZ, R18                   ;Запись R19:R18 в переменную fi1
       STDZ+1, R19
//   26 }
       OUT0x3F, R20               ;восстанавливаем регистр статуса SREG (0x3F)
       LDR16, Y+                  ;восстановим регистры, использовавшиеся для промежуточных
       LDR17, Y+                  ; вычислений
       LDR18, Y+                  ;
       LDR19, Y+                  ;
       LDR20, Y+                  ;восстановим R20 (использовался как хранилище SREG)
       LDR30, Y+                  ;восстановим регистр Z
       LDR31, Y+                  ;
       RETI

       ASEGN ABSOLUTE:DATA:NOROOT,01cH ;далее зачем-то идет описание регистров EEPROM
__?EECR:                          ; (зачем, непонятно -все равно они не используются)

       ASEGN ABSOLUTE:DATA:NOROOT,01dH
__?EEDR:

       ASEGN ABSOLUTE:DATA:NOROOT,01eH
__?EEARL:

       ASEGN ABSOLUTE:DATA:NOROOT,01fH
__?EEARH:

       COMMON INTVEC:CODE:ROOT(1) ;общий (COMMON) сегмент кода, находящийся
                                  ; гарантированно, выравнивание по четным байтам (1)
       ORG 36                     ; за векторами прерываний, с 36 байта
`??TIMER0_OVF_routineTIMER0_OVF_routine??INTVEC `:
       JMPTIMER0_OVF_routineTIMER0_OVF_routine

       RSEG INITTAB:CODE:NOROOT(0)
`?`:
       DWSFE(TINY_Z) - SFB(TINY_Z)
       DWSFB(TINY_Z)
       DW0
       REQUIRE ?need_segment_init

       END

7. Теперь осталось заменить файл int.c на полученный файл int.s90, что совсем просто. Для этого копируем файл Debug\List\int.s90 в корневую папку проекта (там, где у нас int.c) - имеется в виду не дерево каталогов IDE (Workspace), а именно файловая папка. Затем из дерева каталогов IDE (теперь имеется в виду уже не файловая система, а именно IDE) удаляется int.c (правая кнопка\Remove) и добавляется int.s90 (правая кнопка на корневой папке проекта в браузере IDE\Add\Add Files..., тип файлов выбираем Assembler Files (*.s*;*.msa;*.asm), затем выбираем файл int.s90 и жмем Open).

8. Есть смысл также упомянуть на так называемые inline-операторы, которые позволяют вставлять ассемблерные инструкции сразу в код C, например:

asm ("sei");     //разрешить прерывания

или управление ножкой порта в обработчиках прерывания:

#pragma vector=TIM1_OVF_vect
__interrupt void TIMER1_OVF_routine(void)
{
   asm("CBI 0x18, 2");    //asm("CBI PORTB, SERVO"), сбросить SERVO в 0
}

#pragma vector=TIM0_COMPA_vect
__interrupt void TIMER1_COMPA_routine(void)
{
   asm("SBI 0x18, 2");    //asm("SBI PORTB, SERVO"), установить SERVO в 1
}

[Ссылки]

1AVR-GCC: руководство по встраиванию кода на ассемблере.
2AVR032: командные файлы линкера для компилятора IAR ICCA90