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
A simple blink example
.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
Timer and interrupts (Blinks led every second)
.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