ATmega32 Bare Metal Boot

Bare metal boot in AVR assembly without avr-libc. Demonstrates interrupt vectors, enabling the stack, the watchdog timer, and blinking an LED connected to PORTB, pin 0.

On boot, the LED will cycle three times quickly, then begin blinking. After approximately 2.2 seconds, it will be reset by the Watchdog Timer because wdr is not called.

To build and program:

$ avr-as -mmcu=avr5 -o blink.o blink.s
$ avr-ld blink.0
$ avr-objcopy -j .text -O binary a.out blink.bin
$ sudo avrdude -c usbasp -p m32 -P usb -U flash:w:blink.bin:r

For more information, see:

;   Interrupt Handlers
jmp boot            ;   RESET
jmp ignore_int      ;   INT0
jmp ignore_int      ;   INT1
jmp ignore_int      ;   INT2
jmp ignore_int      ;   TIMER2 COMP
jmp ignore_int      ;   TIMER2 OVF
jmp ignore_int      ;   TIMER1 CAPT
jmp ignore_int      ;   TIMER1 COMPA
jmp ignore_int      ;   TIMER1 COMPB
jmp ignore_int      ;   TIMER1 OVF
jmp ignore_int      ;   TIMER0 COMP
jmp ignore_int      ;   TIMER0 OVF
jmp ignore_int      ;   SPI, STC
jmp ignore_int      ;   USART, RXC
jmp ignore_int      ;   USART, UDRE
jmp ignore_int      ;   USART, TXC
jmp ignore_int      ;   ADC
jmp ignore_int      ;   EE_RDY
jmp ignore_int      ;   ANA_COMP
jmp ignore_int      ;   TWI
jmp ignore_int      ;   SPM_RDY

;   On RESET
boot:
    enable_stack:
        ldi r16, 0x08
        out 0x3E, r16
        ldi r16, 0x5F
        out 0x3D, r16
    call boot_finish
    call start

;   Dummy interrupt handler (should be the 1st thing after `boot`.)
ignore_int:
    reti

;   Additional boot chores post-stack-initialization, but before `start`
boot_finish:
    call enable_led
    call flash_led
    call enable_watchdog
    ret

;   Main program
start:
    main_loop:
        call toggle_led
        call delay
        rjmp main_loop
    ;   For completeness, you can return to `boot`, which drops through to `ignore_int`, calls 
    ;   `reti` and resets the processor. This is never executed.
    ret

;  LED "device driver" (reserves r26) for LED on PORTB, pin 0
enable_led:
    push r16
    ldi r16, 0x01
    out 0x17, r16
    clr r26
    pop r16
    ret

toggle_led:
    cpi r26, 0x01
    breq call_off
    call led_on
    ret
    call_off:
        call led_off
        ret

led_on:
    ldi r26, 0x01
    out 0x18, r26
    ret

led_off:
    clr r26 
    out 0x18, r26
    ret

;   Library functions
flash_led:
    push r16
    push r17
    push r18
    ldi r16, 0x06
    loop_a:
        call toggle_led
        clr r17
        loop_b:
            clr r18
            loop_c:
                dec r18
                brne loop_c
            dec r17
            brne loop_b
        dec r16
        brne loop_a
    pop r18
    pop r17
    pop r16
    ret

;   Enables the Watchdog Timer with a roughly 2.2 second timeout
enable_watchdog:
    push r16
    ldi r16, 0x0F
    out 0x21, r16
    pop r16
    ret

delay_small:
    push r16
    clr r16
    delay_small_while:
        cpi r16, 0x10
        breq end_delay_small_while
        inc r16
        rjmp delay_small_while
    end_delay_small_while:
    pop r16
    ret

delay:
    push r16
    push r17
    clr r16
    while_a:
        cpi r16, 0x20
        breq end_while_a 
        inc r16
        clr r17
        while_b:
            cpi r17, 0xFF
            breq end_while_b
            inc r17
            call delay_small
            rjmp while_b
        end_while_b:
        rjmp while_a
    end_while_a:
    pop r17
    pop r16
    ret

Sections