Man Linux: Main Page and Category List


       assembler - .TH "assembler" 3 "Thu Aug 12 2010" "Version 1.6.8" "avr-


       assembler - .SH "Introduction"

       There might be several reasons to write code for AVR microcontrollers
       using plain assembler source code. Among them are:

       o Code for devices that do not have RAM and are thus not supported by
         the C compiler.

       o Code for very time-critical applications.

       o Special tweaks that cannot be done in C.

       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:

       o 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).

       o 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

       This approach has the following advantages:

       o There is basically only one program to be called directly, avr-gcc,
         regardless of the actual source language used.

       o The invokation of the C preprocessor will be automatic, and will
         include the appropriate options to locate required include files in
         the filesystem.

       o 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 .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.

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]
               rcall   ioinit
               rjmp    1b                              ; Note [6]

               .global TIMER0_OVF_vect                 ; Note [7]
               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

               out     _SFR_IO_ADDR(SREG), intsav

               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



               .global __vector_default                ; Note [10]


       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.

       Note [2]

       Assignment of registers to symbolic names used locally. Another option
       would be to use a C preprocessor macro instead:

        #define work 16

       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.

       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 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.

       Note [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.

       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 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.

       Note [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.)

       Note [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.

       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 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.

       Note [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

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

       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

       o lo8 Takes the least significant 8 bits of a 16-bit integer

       o hi8 Takes the most significant 8 bits of a 16-bit integer

       o 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 something

       This passes the address of function somefunc as the first parameter to
       function something.