Обычно необходимость использовать ассемблер бывает в обработчиках прерываний. Рассмотрим по шагам, как это делается.
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 }
[Ссылки]
1. AVR-GCC: руководство по встраиванию кода на ассемблере. 2. AVR032: командные файлы линкера для компилятора IAR ICCA90. |
Комментарии
microsin: это для тех, кто хорошо читает по-английски.
RSS лента комментариев этой записи