HomeLoginRegisterCreate pageChangelogMenu

Wiki

Wiki - Avr assembly (Arduino uno)

Avr assembly (Arduino uno)

by janick7/24/2022


Avr assembly (Arduino uno)

Compiling and uploading

Avr assembly can be compiled and uploaded using make, avrdude and avra using this makefile

%.hex: %.asm
        avra -fI $<
        rm *.eep.hex *.obj *.cof

all: $(patsubst %.asm,%.hex,$(wildcard *.asm))

DEVICE=/dev/ttyACM0

upload: ${program}.hex
        avrdude -c arduino -p m328p -P $(DEVICE) -b 115200 -U flash:w:$<
 
.PHONY: all upload 

Pinout of the chip

R (1).jpg

.device ATmega328P

.equ PORTB = 0x05
.equ DDRB  = 0x04

.org 0x0000 ; Execution starts here
    jmp main

main:
        sbi DDRB, 5 ; Set pin B5 as output (Arduino pin 13)

loop:
        sbi PORTB, 5 ; Set pin B5 to high
        call delay_1000ms ; wait 1s
        cbi PORTB, 5 ; Set pin B5 to low
        call delay_1000ms ; wait 1s
        rjmp loop ; loop

delay_1000ms:
        ldi r18, 0xFF
        ldi r24, 0xD3
        ldi r25, 0x30
        ; 0xff + 0xd3 + 0x30 = 514

inner_loop:
        subi r18, 0x01 ; Substract 1
        sbci r24, 0x00 ; Substract c (1) if previous sub overflowed
        sbci r25, 0x00 ; Substract c (1) if previous sub overflowed
        brne inner_loop ; branch not equal (in this case if != 0)
        ret

More examples

Reading and writing from pins and pullup functionality

.device ATmega328P

.equ PORTB = 0x05
.equ DDRB  = 0x04

.equ PORTD = 0x0b
.equ DDRD  = 0x0a
.equ PIND  = 0x09

.org 0x0000 ; Execution starts here
    rjmp main

main:
    ldi r16, 0xff
    out DDRB, r16 ; set PB0 - PB7 as output (data direction bit is 1)

    ldi r16, 0x00
    out DDRD, r16 ; set PD0 - PD7 as input (data direction bit is 0)

    ldi r16, 0xff
    out PORTD, r16 ; set PD0 - PD7 pullup (since pd* is set as input we set pullup using PORTD)

loop:
    in r16, PIND ; read PD0 - PD7
    out PORTB, r16 ; write PD0 - PD7 to PB0 - PB7

    rjmp loop

Reading and writing from specific pins

.device ATmega328P

.equ PORTB = 0x05
.equ DDRB  = 0x04

.equ PORTD = 0x0b
.equ DDRD  = 0x0a
.equ PIND  = 0x09

.org 0x0000 ; Execution starts here
    rjmp main

main:
    ldi r16, 0xff
    out DDRB, r16 ; set PB0 - PB7 as output (data direction bit is 1)

    ldi r16, 0x00
    out DDRD, r16 ; set PD0 - PD7 as input (data direction bit is 0)

    ldi r16, 0xff
    out PORTD, r16 ; set PD0 - PD7 pullup (since pd* is set as input we set pullup using PORTD)

loop:
    sbic PIND, 5 ; \"skip if bit cleared\" skip next instruction if bit cleared (bit is cleared if pin is pulled to gnd)
    rjmp set_pin

    cbi PORTB, 5 ; clear bit 5 in PORTB (PB5)
    rjmp loop

set_pin:
    sbi PORTB, 5 ; set bit 5 n PORTB (PB5)
    rjmp loop

Simple bit logic

.device ATmega328P

.equ PORTB = 0x05
.equ DDRB  = 0x04

.org 0x0000 ; Execution starts here
    rjmp main

main:
    ldi r16, 0xff
    out DDRB, r16 ; set PB0 - PB7 as output (data direction bit is 1)

    ldi r16, 0b10101010
    ldi r17, 0b01010101

    ; and r16, r17
    ; andi r16, 0b01010101 ; and using constant (works with most logic instruction (instr + i))

    ; or r16, r17
    ; com r16 ; com == not
    eor r16, r17 ; eor == xor

    out PORTB, r16

loop:
    rjmp loop

Stack and subprograms

.device ATmega328P

.equ PORTB  = 0x05
.equ DDRB   = 0x04

.equ LED_B  = 5

.equ RAMEND =  0x8ff
.equ SPL    = 0x3d
.equ SPH    = 0x3e

.org 0x0000 ; Execution starts here
    rjmp main

main:
    ldi r16, low(RAMEND)
    out SPL, r16 ; setup stack ptr low
    ldi r16, high(RAMEND)
    out SPH, r16 ; setu stack ptr high

    rcall setup ; call setup

loop_intrnl:
    rcall loop ; call loop
    jmp loop_intrnl

setup:
    sbi DDRB, LED_B ; set PB5 output

    sbi PORTB, LED_B ; set PB5 high

    ret ; return

loop:
    ret ; return
.device ATmega328P

.equ PORTB  = 0x05
.equ DDRB   = 0x04

.equ LED_B  = 5

.equ RAMEND =  0x8ff
.equ SPL    = 0x3d
.equ SPH    = 0x3e

.equ TCNT_O = 49910

.equ TCNT1L = 0x84
.equ TCNT1H = 0x85

.equ TCCR1A = 0x80
.equ TCCR1B = 0x81

.equ TIMSK1 = 0x6f

.equ SREG   = 0x3f

.equ SMCR   = 0x33

.org 0x0000 ; Execution starts here
    jmp main

.org 0x001A ; interrupt handler for timer 1 overflow
    jmp timer1_ovf_vect

timer1_ovf_vect:
    push r16
    push r17
    push r18

    in r16, SREG ; save SREG since it can happen that we interrupt the cpu at a point where the vaule in SREG is still needed

    in r17, PORTB
    ldi r18, 0b00100000
    eor r17, r18
    out PORTB, r17 ; flip PB5

    call set_tcnt ; reset timer counter

    out SREG, r16 ; restore sreg
    pop r18
    pop r17
    pop r16
    reti ; return from interrupt

main:
    ldi r16, low(RAMEND)
    out SPL, r16 ; setup stack ptr low
    ldi r16, high(RAMEND)
    out SPH, r16 ; setu stack ptr high

    rcall setup ; call setup

loop_intrnl:
    rcall loop ; call loop
    jmp loop_intrnl

setup:
    sbi DDRB, LED_B ; set PB5 output


    call set_tcnt ; set timer counter


    ldi r16, 0b00000101

    ldi r30, low(TCCR1B) ; set ZL
    ldi r31, high(TCCR1B) ; set ZH

    st Z, r16 ; set preescaler to 1024


    ldi r16, 0

    ldi r30, low(TCCR1A) ; set ZL
    ldi r31, high(TCCR1A) ; set ZH

    st Z, r16 ; disable all timer features with makes it a overflow timer


    ldi r16, 0b00000001

    ldi r30, low(TIMSK1) ; set ZL
    ldi r31, high(TIMSK1) ; set ZH

    st Z, r16 ;  // unmask timer overflow interrupt 1

    ldi r16, 0b00000001
    out SMCR, r16 ; set sleep mode to idle and set enable bit so that we can use the sleep insturction

    sei ; enable interrupts

    ret ; return

set_tcnt:
    push r16
    push r30
    push r31

    ldi r16, low(TCNT_O)

    ldi r30, low(TCNT1L) ; set ZL
    ldi r31, high(TCNT1L) ; set ZH

    st Z, r16 ; set low part of timer counter


    ldi r16, high(TCNT_O)

    ldi r30, low(TCNT1H) ; set ZL
    ldi r31, high(TCNT1H) ; set ZH

    st Z, r16 ; set high part of timer counter

    pop r31
    pop r30
    pop r16

    ret

loop:
    sleep ; enter sleep mode
    ret ; return

Simple echo console with 9600 baud in USART0

.device ATmega328P

.equ PORTB  = 0x05
.equ DDRB   = 0x04

.equ LED_B  = 5

.equ RAMEND =  0x8ff
.equ SPL    = 0x3d
.equ SPH    = 0x3e

.equ UBRR0H = 0xc5
.equ UBRR0L = 0xc4

.equ UCSR0A = 0xc0
.equ UCSR0B = 0xc1
.equ UCSR0C = 0xc2

.equ UDR0   = 0xc6

.equ BAUD   = 9600
.equ F_CPU  = 16000000
.equ UBRR_V = (((F_CPU) + 8 * (BAUD)) / (16 * (BAUD)) - 1)

.org 0x0000 ; Execution starts here
    rjmp main

USART0_init:
    push r16
    push r30
    push r31

    ldi r16, low(UBRR_V)

    ldi r30, low(UBRR0L) ; set ZL
    ldi r31, high(UBRR0L) ; set ZH
    st Z, r16 ; set low part of baud rate reg

    ldi r16, high(UBRR_V)

    ldi r30, low(UBRR0H) ; set ZL
    ldi r31, high(UBRR0H) ; set ZH
    st Z, r16 ; set high part of baud rate reg


    ldi r16, 0b00011000

    ldi r30, low(UCSR0B) ; set ZL
    ldi r31, high(UCSR0B) ; set ZH
    st Z, r16 ; enable transmitter and receiver

    ldi r16, 0b00001110

    ldi r30, low(UCSR0C) ; set ZL
    ldi r31, high(UCSR0C) ; set ZH
    st Z, r16 ; set frame format: 8data, 2stop bit

    pop r31
    pop r30
    pop r16
    ret

; char to transmit is in r22
USART0_transmit:
    push r16
    push r30
    push r31

USART0_transmit_loop:
    ldi r30, low(UCSR0A) ; set ZL
    ldi r31, high(UCSR0A) ; set ZH
    ld r16, Z

    sbrs r16, 5 ; wait for empty transmit buffer
    rjmp USART0_transmit_loop

    ldi r30, low(UDR0) ; set ZL
    ldi r31, high(UDR0) ; set ZH
    st Z, r22 ; put data into buffer, sends the data

    pop r31
    pop r30
    pop r16

    ret

; char is in r22
USART0_receive:
    push r16
    push r30
    push r31

USART0_receive_loop:
    ldi r30, low(UCSR0A) ; set ZL
    ldi r31, high(UCSR0A) ; set ZH
    ld r16, Z

    sbrs r16, 7 ; wait for data to be received
    rjmp USART0_receive_loop

    ldi r30, low(UDR0) ; set ZL
    ldi r31, high(UDR0) ; set ZH
    ld r22, Z ; get and return received data from buffer

    pop r31
    pop r30
    pop r16

    ret

main:
    ldi r16, low(RAMEND)
    out SPL, r16 ; setup stack ptr low
    ldi r16, high(RAMEND)
    out SPH, r16 ; setu stack ptr high

    rcall setup ; call setup

loop_intrnl:
    rcall loop ; call loop
    jmp loop_intrnl

setup:
    sbi DDRB, LED_B ; set PB5 output

    call USART0_init

    ; print hi on serial terminal
    ldi r22, 'H'
    call USART0_transmit
    ldi r22, 'i'
    call USART0_transmit

    sbi PORTB, LED_B ; set PB5 to high

    ret ; return

loop:
    ; simple echo example
    call USART0_receive ; receive char. char is now in r22
    call USART0_transmit ; transmit char in r22
    ret ; return