| assembler(3avr) | assembler(3avr) |
assembler - AVR-LibC and Assembler Programs
There might be several reasons to write code for AVR microcontrollers using plain assembler source code. Among them are:
Usually, all but the first could probably be done easily using the inline assembler facility of the compiler.
Although AVR-LibC is primarily targeted to support programming AVR microcontrollers using the C (and C++) language, there's limited support for direct assembler usage as well. The benefits of it are:
For the purpose described in this document, the assembler and linker are usually not invoked manually, but rather using the C compiler frontend (avr-gcc) that in turn will call the assembler and linker as required.
This approach has the following advantages:
Note that the invokation of the C preprocessor will be automatic when the filename provided for the assembler file ends in .S (the capital letter 's'). This would even apply to operating systems that use case-insensitive filesystems since the actual decision is made based on the case of the filename suffix given on the command-line, not based on the actual filename from the file system.
As an alternative to using .S, the suffix .sx is recognized for this purpose (starting with GCC v4.3). This is primarily meant to be compatible with other compiler environments that have been providing this variant before in order to cope with operating systems where filenames are case-insensitive (and, with some versions of make that could not distinguish between .s and .S on such systems).
Alternatively, the language can explicitly be specified using the -x assembler-with-cpp option.
The following annotated example features a simple 100 kHz square wave generator using an AT90S1200 clocked with a 10.7 MHz crystal. Pin PD6 will be used for the square wave output.
#include <avr/io.h> // Note [1] work = 16 // Note [2] tmp = 17 inttmp = 19 intsav = 0 SQUARE = PD6 // Note [3] #define IO(x) _SFR_IO_ADDR(x) // Note [4]: // 100 kHz => 200000 edges/s tmconst = 10700000 / 200000 // # clocks in ISR until TCNT0 is set fuzz = 8 .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 IO(TCNT0), inttmp // Note [8]
in intsav, IO(SREG) // Note [9]
sbic IO(PORTD), SQUARE
rjmp 1f
sbi IO(PORTD), SQUARE
rjmp 2f 1: cbi IO(PORTD), SQUARE 2:
out IO(SREG), intsav
reti ioinit:
sbi IO(DDRD), SQUARE
ldi work, _BV(TOIE0)
out IO(TIMSK), work
ldi work, _BV(CS00) // tmr0: CK/1
out IO(TCCR0), work
ldi work, 256 - tmconst
out IO(TCNT0), work
sei
ret .global __vector_default // Note [10] __vector_default:
reti
#define work 16
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.
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.
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.
The directives available in the assembler are described in the GNU assembler (gas) manual at Assembler Directives.
As gas comes from a Unix origin, its directives 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, 0b for binary constants), expressions use a C-like syntax.
Some common directives include:
Section Ops Description .section
name,'flags',@typ Put the following objects into
named section name. Set section
flags flags and section type to
typ .pushsection ...
.popsection Like .section, but also pushes the current section and
subsection onto the section stack. The current section and subsection can be
restored with .popsection. .subsection int Put the
following code into subsection number int which is some
integer. Subsections are located in order of increasing index within their
input section. The default after switching to a new section by means of
.section or .pushsection is subsection 0. .text
.data
.bss Put the following code into the .text section, .data section or
.bss section, respectively. The assembler knows the right section flags
and section type, for example the .text directive is basically the same
like .section .text,'ax',@progbits. The directives support an optional
subsection argument, see .subsection above.
Symbol Ops Description .global sym
.globl sym Globalize symbol sym so that it can be
referred to in other modules. When a symbol is used without prior
declaration or definition, the symbol is implicitly global. The
.global directive can also by used to refer to that symbol, so that
the linker pulls in code that defines the symbol (provided such a symbol
definition exists). For example, code that puts objects in the .data
section and that assumes that the startup code initializes
that area, would use .global __do_copy_data. .weak
syms Declare symbols syms as weak
symbols, where syms is a comma-separated list of symbols. This
applies only when the symbols are also defined in the same module. When the
linker encounters a weak symbol that is also defined as .global in a
different module, then the linker will use the latter without raising a
diagnostic about multiple symbol definitions. .type
sym,@kind Set the type of symbol sym to
kind. Commonly used symbol types are @function for
function symbols like main and @object for data symbols. This
has an affect for disassemblers, debuggers and tools that show function /
object properties. .size sym,size Set the size
associated with symbol sym to expression size.
The linker works on the level of sections, it does not even know what
functions are. This directive serves book-keeping, and may be useful for
debuggers, disassemblers or tools that show which function / object consumes
how much memory. .set sym, expr
.equ sym, expr
sym = expr Set the value of symbol sym to the value of
expression expr. When a global symbol is set multiple times,
the value stored in the object file is the last value stored into the
symbol. .extern Ignored for compatibility with other assemblers.
.org Advance the location pointer to a specified offset
relative to the beginning of the input section.
The location counter cannot be moved backwards.
This is a fairly pointless directive in an assembler environment that uses
relocatable object files. The linker determines the final location of the
objects. See the FAQ on how to relocate code to a fixed
address.
Data Ops Description Alias .byte list Allocate bytes specified by a list of comma-separated expressions. .2byte list Similar to .byte, but for 16-bit values. .word .4byte list Similar to .byte, but for 32-bit values. .long .8byte list Similar to .byte, but for 64-bit values. .qword .ascii 'string' Allocate a string of characters without \0 termination. .asciz 'string' Allocate a \0 terminated string. .float list Allocate IEEE-754 single 32-bit floating-point values specified in the comma-separated list. .double list Same, but for IEEE-754 double 64-bit floats. .space num[,val] Allocate num bytes with value val where val is optional and defaults to zero. .skip .zero num Insert num zero bytes. Alignment Ops Description Alias .balign val Align the following code to val bytes, where val is an absolute expression that evaluates to a power of 2. .align .p2align expo Align the following code to 2expo bytes.
Moreover, there is the .macro directive, which starts an assembler macro. The GNU assembler implements a powerful macro processor which even supports recursive macro definitions. For an example, see the gas documentation for .macro. A gas .macro can further be combined with C preprocessor directives. For some real-world examples, see the AVR-LibC sources macros.inc and asmdef.h.
There are some AVR-specific operators available like lo8(), hi8(), pm(), gs() etc. For an overview see the documentation of the operand modifiers in the inline assembly Cookbook.
Example:
ldi r24, lo8(gs(somefunc))
ldi r25, hi8(gs(somefunc))
call something
subi r24, lo8(-(my_var))
sbci r25, hi8(-(my_var))
This passes the address of function somefunc as the first parameter to function something, and adds the address of variable my_var to the 16-bit return value of something.
| Version 2.2.1 | AVR-LibC |