/* Copyright (c) 2002,2004,2005 Joerg Wunsch
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE. */
/* $Id: assembler.dox,v 1.8 2005/11/04 22:55:15 joerg_wunsch Exp $ */
/** \page assembler avr-libc and assembler programs
\section ass_intro Introduction
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.
Usually, all but the first could probably be done easily using the
\ref inline_asm "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:
- 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,
\ref sec_dot_init "initializing the RAM variables" can also
be utilized.
\section ass_tools 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 (\c 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,
\c 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
(crtXXX.o) and linker script.
Note that the invokation of the C preprocessor will be automatic when
the filename provided for the assembler file ends in \c .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.
Alternatively, the language can explicitly be specified using the
-x assembler-with-cpp option.
\section ass_example 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.
\code
#include ; 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
\endcode
\par Note [1]
As in C programs, this includes the central processor-specific file
containing the IO port definitions for the device. Note that not all
include files can be included into assembler sources.
\par Note [2]
Assignment of registers to symbolic names used locally. Another
option would be to use a C preprocessor macro instead:
\code #define work 16 \endcode
\par Note [3]
Our bit number for the square wave output. Note that the right-hand
side consists of a CPP macro which will be substituted by its value (6
in this case) before actually being passed to the assembler.
\par Note [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 \c 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 \c TCCNT0 register, we therefore have to account
for the number of clock cycles required for interrupt acknowledge and
for the instructions to reload \c TCCNT0 (4 clock cycles for interrupt
acknowledge, 2 cycles for the jump from the interrupt vector, 2 cycles
for the 2 instructions that reload \c TCCNT0). This is what the
constant \c fuzz is for.
\par Note [5]
External functions need to be declared to be \c .global. \c main is
the application entry point that will be jumped to from the
ininitalization routine in \c crts1200.o.
\par Note [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 \c 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.
\par Note [7]
Interrupt functions can get the \ref avr_signames "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 \c .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 \c avr-objdump or
\c avr-nm, a name like __vector_N should appear,
with \e N being a small integer number.)
\par Note [8]
As explained in the section about
\ref avr_sfr_notes "special function registers",
the actual IO port address should be obtained using the macro
\c _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 \c in / \c out instructions anyway.)
Since the operation to reload \c TCCNT0 is time-critical, it is even
performed before saving \c SREG. Obviously, this requires that the
instructions involved would not change any of the flag bits in \c SREG.
\anchor ass_isr
\par Note [9]
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 \c SREG.
(Note that this serves as an example here only since actually, all the
following instructions would not modify \c 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 \ref faq_reg_usage "register usage guidelines"
imposed by the C compiler. Also, any register modified inside the
interrupt sevice needs to be saved, usually on the stack.
\par Note [10]
As explained in \ref avr_interrupts "Interrupts", a global "catch-all" interrupt
handler that gets all unassigned interrupt vectors can be installed
using the name \c __vector_default. This must be \c .global, and
obviously, should end in a \c reti instruction. (By default, a jump
to location 0 would be implied instead.)
\section ass_pseudoops 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 \c 0x for
hexadecimal constants), expressions use a C-like syntax.
Some common pseudo-ops include:
- \c .byte allocates single byte constants
- \c .ascii allocates a non-terminated string of characters
- \c .asciz allocates a \\0-terminated string of characters (C string)
- \c .data switches to the .data section (initialized RAM variables)
- \c .text switches to the .text section (code and ROM constants)
- \c .set declares a symbol as a constant expression (identical to
\c .equ)
- \c .global (or \c .globl) declares a public symbol that is visible
to the linker (e. g. function entry point, global variable)
- \c .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
Note that \c .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:
- \c lo8 Takes the least significant 8 bits of a 16-bit integer
- \c hi8 Takes the most significant 8 bits of a 16-bit integer
- \c 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 \c IJMP or \c ICALL
instruction), and can also handle relocatable symbols on the
right-hand side.
Example:
\verbatim
ldi r24, lo8(pm(somefunc))
ldi r25, hi8(pm(somefunc))
call something
\endverbatim
This passes the address of function \c somefunc as the first parameter
to function \c something.
*/