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, http://atmel.com/dyn/resources/prod_documents/doc2466.pdf
- описание сегментов памяти (CSTACK, RSTACK, ..) можно найти в файле http://www.atmel.com/dyn/resources/prod_documents/doc1079.pdf (AVR032: Linker Command Files for the IAR ICCA90 Compiler).
- AVR IAR Assembler Reference Guide, C:\Program Files\IAR Embedded Workbench 4.0\avr\doc\EWAVR_AssemblerReference.pdf
Итак, получился из исходника 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 `?<Segment init: TINY_Z>` ;опубликовывает символ `?<Segment init: TINY_Z>`.
; 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 <iom16.h>
ASEGN ABSOLUTE:DATA:NOROOT,05cH;начинает абсолютный сегмент данных ABSOLUTE, адрес начала 0x5C
// union <unnamed> volatile __io _A_OCR0
_A_OCR0:
DS 1;здесь определяется регистр OCR0 (зачем таким образом, непонятно).
// 2 #include <intrinsics.h>
// 3 #include "settings.h"
// 4
;далее идет описание используемых переменных и выделение для них памяти
RSEG TINY_Z:DATA:NOROOT(0)
REQUIRE `?<Segment init: TINY_Z>`
// 5 uint fi1;
fi1:
DS 2
RSEG TINY_Z:DATA:NOROOT(0)
REQUIRE `?<Segment init: TINY_Z>`
// 6 uchar idx;
idx:
DS 1
RSEG TINY_Z:DATA:NOROOT(0)
REQUIRE `?<Segment init: TINY_Z>`
// 7 uchar ampl1;
ampl1:
DS 1
RSEG TINY_Z:DATA:NOROOT(0)
REQUIRE `?<Segment init: TINY_Z>`
// 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)
`?<Segment init: TINY_Z>`:
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
}
|
Комментарии
2009-02-1304:26:49 Страница 98 IAR C/C++ Compiler Reference Guide.
microsin: это для тех, кто хорошо читает по-английски.
2009-02-1211:40:08 Все пучком.
Следует заметить, что для С++ нужны дополнительные пуговички :)
microsin: очень заинтересовали - что за такие "пуговички"?..