table of contents
assembler(3avr) | avr-libc | assembler(3avr) |
NAME¶
assembler - avr-libc and assembler programsIntroduction¶
There might be several reasons to write code for AVR microcontrollers using plain assembler source code. Among them are:- •
- Code for devices that do not have RAM and are thus not supported by the C compiler.
- •
- Code for very time-critical applications.
- •
- Special tweaks that cannot be done in C.
- •
- Use of the C preprocessor and thus the ability to use the same symbolic constants that are available to C programs, as well as a flexible macro concept that can use any valid C identifier as a macro (whereas the assembler's macro concept is basically targeted to use a macro in place of an assembler instruction).
- •
- Use of the runtime framework like automatically assigning interrupt vectors. For devices that have RAM, initializing the RAM variables can also be utilized.
Invoking the compiler¶
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:- •
- There is basically only one program to be called directly, avr-gcc, regardless of the actual source language used.
- •
- The invokation of the C preprocessor will be automatic, and will include the appropriate options to locate required include files in the filesystem.
- •
- The invokation of the linker will be automatic, and will include the appropriate options to locate additional libraries as well as the application start-up code (crt XXX.o) and linker script.
Example program¶
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] ; 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 .endNote [1]
#define work 16Note [3]
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. Note [5]
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. Note [9]
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. Note [10]
Pseudo-ops and operators¶
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:- •
- .byte allocates single byte constants
- •
- .ascii allocates a non-terminated string of characters
- •
- .asciz allocates a \0-terminated string of characters (C string)
- •
- .data switches to the .data section (initialized RAM variables)
- •
- .text switches to the .text section (code and ROM constants)
- •
- .set declares a symbol as a constant expression (identical to .equ)
- •
- .global (or .globl) declares a public symbol that is visible to the linker (e. g. function entry point, global variable)
- •
- .extern declares a symbol to be externally defined; this is effectively a comment only, as gas treats all undefined symbols it encounters as globally undefined anyway
- •
- lo8 Takes the least significant 8 bits of a 16-bit integer
- •
- hi8 Takes the most significant 8 bits of a 16-bit integer
- •
- pm 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 somethingThis passes the address of function somefunc as the first parameter to function something.
Wed Jun 4 2014 | Version 1.8.0svn |