Provided by: avr-libc_1.8.0-4.1_all bug

NAME

       assembler - avr-libc and assembler programs

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 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, 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 (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]
       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

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

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

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

       Example:

               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.