Search code examples
assemblytimeravr

AVR assembly on arduino uno, timer0 configuration?


I'm having a look at fiddling the registers in assembly programming for the AVR family, on an Arduino UNO board, with its standard bootloader (avra+avrdude).

I'm having trouble with the Timer0. I've reduced the problem to a short program wich is supposed to

  • put a red LED on (on B5 = pin 13)
  • configure the timer0 for normal mode, prescale 1024
  • set the counter to 0
  • enter a loop, and escapes when counter is over 200
  • and then puts a green Led on (on B4).

Symptom: green led never turns on. With some other values than 200, turns on after a random duration (seconds).

Here is the code :

    .include "./m328Pdef.inc"

    .EQU ROUGE = 0b0100000   ; red
    .EQU VERT  = 0b0010000   ; green


main:   
    ldi r16,ROUGE+VERT      ; pins activated
    out DDRB,r16
    ldi r16,ROUGE           ; red on
    out portB,r16

    ;; configure timer
    lds r16,TCCR0B
    andi r16,0b11111000
    ori r16,0b00000101      ; prescale 1024
    sts TCCR0B,r16

    ldi r16,0       ; count is  0
    sts TCNT0,r16

loop:   
    lds r16,TCNT0            
    cpi r16,100
    brlo loop

    ldi r16,VERT     ; green on
    out PortB,r16

z:
    nop
    rjmp z

The same programs seems to work correctly with the Timer1 and its associated registers.

What's wrong? Some interference with the bootloader?

EDIT the hex file :

:020000020000FC
:1000000000E304B900E205B900912500087F05600E
:100010000093250000E0009326000091260004369E
:0A002000E0F300E105B90000FECF97
:00000001FF

Compiled by : avra bug0.asm

Upload :

avrdude -q -V -D -p atmega328p -C /etc/avrdude.conf \
    -c arduino -b 115200 -P /dev/ttyACM0 \
    -U flash:w:bug0.hex:i

Solution

  • The problem is that constants: DDRB, PORTB, TCCR0B, TCNT0, etc. evaluate to I/O addresses of given SFRs (it's OK to use them with in/out) but you use TCCR0B and TCNT0 with lds/sts which expect their operands to containt data space addresses.

    The solution is to either use in/out with TCCR0B and TCNT0 -- it's OK, because these registers belong to I/O registers (as opposed to extended I/O registers which must be accessed using their data space addresses):

        .include "./m328Pdef.inc"
    
        .EQU ROUGE = 0b0100000   ; red
        .EQU VERT  = 0b0010000   ; green
    
    
    main:
        ldi r16,ROUGE+VERT      ; pins activated
        out DDRB,r16
        ldi r16,ROUGE           ; red on
        out portB,r16
    
        ;; configure timer
        in r16,TCCR0B
        andi r16,0b11111000
        ori r16,0b00000101      ; prescale 1024
        out TCCR0B,r16
    
        ldi r16,0       ; count is  0
        out TCNT0,r16
    
    loop:
        in r16,TCNT0
        cpi r16,100
        brlo loop
    
        ldi r16,VERT     ; green on
        out PortB,r16
    
    z:
        nop
        rjmp z
    

    or to make this I/O addresses into data space addresses:

        .include "./m328Pdef.inc"
    
        .EQU ROUGE = 0b0100000   ; red
        .EQU VERT  = 0b0010000   ; green
    
    
    main:
        ldi r16,ROUGE+VERT      ; pins activated
        out DDRB,r16
        ldi r16,ROUGE           ; red on
        out portB,r16
    
        ;; configure timer
        lds r16,TCCR0B+0x20
        andi r16,0b11111000
        ori r16,0b00000101      ; prescale 1024
        sts TCCR0B+0x20,r16
    
        ldi r16,0       ; count is  0
        sts TCNT0+0x20,r16
    
    loop:
        lds r16,TCNT0+0x20
        cpi r16,100
        brlo loop
    
        ldi r16,VERT     ; green on
        out PortB,r16
    
    z:
        nop
        rjmp z
    

    For information on why you have to add 0x20 to these constants, see chapter 8.3 SRAM Data Memory on page 19 (especially the figure) of m328p datasheet and this bug report.


    Why the original code worked with timer1?

    Timer1 registers are located in extended I/O space and avra probably substitutes their names with extended I/O space addresses (as they doesn't even have I/O space addresses).


    Why the original code behaved erraticaly with timer0?

    One can write lds r16,TCNT0 as lds r16,PINC+0x20, reading the state of floating pins yields (more or less) random result.


    To see what avra did with your code I used the command avr-objdump -b ihex -m avr5 -D *.hex, it shows what is actually written to the microcontroller.

    For SFR addresses, see chapter 36. Register Summary of the aforementioned atmega328p datasheet.

    You may need to install avr-gcc or avr-binutils to be able to use this command.