Provided by: avr-libc_2.0.0+Atmel3.7.0-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.

       As  an  alternative to using .S, the suffix .sx is recognized for this purpose (starting with GCC 4.3.0).
       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.

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.

Version 2.0.0                               Fri Nov 24 2023 23:59:10                             assembler(3avr)