Можно найти несколько причин написания исходного кода, используя ассемблер. Среди них:
Обычно все пункты, кроме первого, могут быть сделаны, используя встроенный ассемблер.
Поскольку avr-libc прежде всего преследуется поддержка программирования микроконтроллеров AVR на языках C и C++, в них ограничена поддержка прямого использования ассемблера. Выгоды от этого:
Для цели, описанной в этом документе, ассемблер и компоновщик обычно не вызываются вручную, а скорее использование внешнего интерфейса C компилятора (avr-gcc) это в свою очередь вызовет ассемблер и компоновщик, как требуется.
Этот подход имеет следующие преимущества:
crt
XXX.
o
) и
сценария
линкера.Обратите внимание, что invokation препроцессора C будет автоматический, когда имя файла предусмотрело концы файла ассемблера в .S (заглавная буква "s"). Это даже обратилось бы к операционным системам, что использование filesystems без учета регистра, так как фактическое решение сделано основанным на регистре имени файла, данного на командной строки, не основанным на фактическом имени файла от файловой системы.
Альтернативно, язык может явно быть определен, используя опцию –x, assembler-with-cpp.
Следующий пример показывает простой генератор прямоугольных импульсов на 100 кГц, используя AT90S1200, синхронизированный кварцем на 10.7 МГЦ . Вывод PD6 – выход прямоугольных импульсов.
#include <avr/io.h> ; Note [1]
work = 16 ; Note [2]
tmp = 17
inttmp = 19
intsav = 0
SQUARE = PD6 ; Note [3]
; Note [4]:
tmconst= 10700000 / 200000 ; 100 kHz => 200000 edges/s
fuzz= 8 ; # clocks in ISR until TCNT0 is set
.section .text
.global main ; Note [5]
main:
rcall ioinit
1:
rjmp 1b ; Note [6]
.global TIMER0_OVF_vect ; Note [7]
TIMER0_OVF_vect:
ldi inttmp, 256 - tmconst + fuzz
out _SFR_IO_ADDR(TCNT0), inttmp ; Note [8]
in intsav, _SFR_IO_ADDR(SREG) ; Note [9]
sbic _SFR_IO_ADDR(PORTD), SQUARE
rjmp 1f
sbi _SFR_IO_ADDR(PORTD), SQUARE
rjmp 2f
1: cbi _SFR_IO_ADDR(PORTD), SQUARE
2:
out _SFR_IO_ADDR(SREG), intsav
reti
ioinit:
sbi _SFR_IO_ADDR(DDRD), SQUARE
ldi work, _BV(TOIE0)
out _SFR_IO_ADDR(TIMSK), work
ldi work, _BV(CS00) ; tmr0: CK/1
out _SFR_IO_ADDR(TCCR0), work
ldi work, 256 - tmconst
out _SFR_IO_ADDR(TCNT0), work
sei
ret
.global __vector_default ; Note [10]
__vector_default:
reti
.end
Примечание [1]
Как в программах C, это включает центральный определенный процессором файл, содержащий определения порта IO для устройства. Обратите внимание, что не все файлы для включения могут быть включены в источники ассемблера.
Примечание [2]
Назначение регистраторов к символическим именам, используемым в местном масштабе. Другая опция должна была бы использовать макрокоманду C препроцессора вместо этого:
#define work 16
Примечание [3]
Наш разрядный номер для выхода прямоугольных импульсов. Обратите внимание, что правая сторона состоит из макрокоманды CPP, которой будет заменять ее значение (6 в этом случае) перед тем, чтобы фактически быть переданным к ассемблеру.
Примечание [4]
The assembler uses integer
operations in the host-defined integer size (32 bits or longer) when evaluating
expressions. This is in contrast to the C compiler that uses the C type int
by default in order to calculate
constant integer expressions.
In order to get a 100 kHz output, we need to toggle the PD6 line 200000 times
per second. Since we use timer 0 without any prescaling options in order to get
the desired frequency and accuracy, we already run into serious timing
considerations: while accepting and processing the timer overflow interrupt,
the timer already continues to count. When pre-loading the TCCNT0
register, we therefore have to
account for the number of clock cycles required for interrupt acknowledge and
for the instructions to reload TCCNT0
(4 clock cycles for interrupt acknowledge, 2
cycles for the jump from the interrupt vector, 2 cycles for the 2 instructions
that reload TCCNT0
).
This is what the constant fuzz
is for.
Примечание [5]
External functions need to be
declared to be .global. main
is the application entry point that will be jumped to from the
ininitalization routine in crts1200.o
.
Примечание [6]
The main loop is just a single jump
back to itself. Square wave generation itself is completely handled by the
timer 0 overflow interrupt service. A sleep
instruction (using idle mode) could
be used as well, but probably would not conserve much energy anyway since the
interrupt service is executed quite frequently.
Примечание [7]
Interrupt functions can get the usual
names that are
also available to C programs. The linker will then put them into the
appropriate interrupt vector slots. Note that they must be declared .global in
order to be acceptable for this purpose. This will only work if <
avr/io.h
>
has been included. Note that the
assembler or linker have no chance to check the correct spelling of an
interrupt function, so it should be double-checked. (When analyzing the resulting
object file using avr-objdump
or avr-nm
, a name like __vector_
N should
appear, with N being a small integer number.)
Примечание [8]
As explained in the section about special function registers, the actual IO port address should
be obtained using the macro _SFR_IO_ADDR
. (The AT90S1200 does not have RAM thus the
memory-mapped approach to access the IO registers is not available. It would be
slower than using in
/ out
instructions anyway.)
Since the operation to reload TCCNT0
is time-critical, it is even performed before
saving SREG
.
Obviously, this requires that the instructions involved would not change any of
the flag bits in SREG
.
Interrupt routines must not clobber
the global CPU state. Thus, it is usually necessary to save at least the state
of the flag bits in SREG
. (Note that this serves as an example here only since actually, all the
following instructions would not modify SREG
either, but that's not commonly the
case.)
Also, it must be made sure that registers used inside the interrupt routine do
not conflict with those used outside. In the case of a RAM-less device like the
AT90S1200, this can only be done by agreeing on a set of registers to be used
exclusively inside the interrupt routine; there would not be any other chance
to "save" a register anywhere.
If the interrupt routine is to be linked together with C modules, care must be
taken to follow the register usage guidelines imposed by the C compiler. Also, any register
modified inside the interrupt sevice needs to be saved, usually on the stack.
Примечание [10]
As explained in Interrupts, a global "catch-all"
interrupt handler that gets all unassigned interrupt vectors can be installed
using the name __vector_default
. This must be .global, and obviously, should
end in a reti
instruction. (By default, a jump to location 0 would be implied instead.)
The available pseudo-ops in the
assembler are described in the GNU assembler (gas) manual. The manual can be
found online as part of the current binutils release under http://sources.redhat.com/binutils/.
As gas comes from a Unix origin, its
pseudo-op and overall assembler syntax is slightly different than the one being
used by other assemblers. Numeric constants follow the C notation (prefix 0x
for hexadecimal constants),
expressions use a C-like syntax.
Some common pseudo-ops include:
Note that .org is available in gas
as well, but is a fairly pointless pseudo-op in an assembler environment that
uses relocatable object files, as it is the linker that determines the final
position of some object in ROM or RAM.
Along with the architecture-independent standard operators, there are some AVR-specific operators available which are unfortunately not yet described in the official documentation. The most notable operators are:
lo8
Takes the least significant 8
bits of a 16-bit integerhi8
Takes the most significant 8
bits of a 16-bit integerpm
Takes a program-memory (ROM) address,
and converts it into a RAM address. This implies a division by 2 as the
AVR handles ROM addresses as 16-bit words (e.g. in an IJMP
or ICALL
instruction), and can also
handle relocatable symbols on the right-hand side.Пример:
ldi r24, lo8(pm(somefunc))
ldi r25, hi8(pm(somefunc))
call something
Это
передает
адрес
функции somefunc
как
первый
параметр в
функцию something
.