Hi all,
I'm using an Arduino Uno as a devel platform for something that I will eventually burn directly onto a non-Arduino AVR. Currently I'm trying to make the Arduino send some data back to the Linux host via USB. I'm not using the Arduino IDE (I know about the Serial() library). I'm doing this in Assembler (because I want to), using avra and avrdude.
My code is below. It has a timer interrupt that fires the "ticker" routine at 50 Hz. Every 50 pulses it turns the Arduino built-in LED on or off, and it sends a string over the USART into my computer. I use "screen /dev/ttyACM0 9600" to monitor the outout.
Here's what I observe:
- The LED goes on and off as it should
- Every second the terminal receives in strings like '@y2@Hello, World: _'
- The digit counts from 0 to 7 as it should, but it is at pos. 2 of the string, not 13
So two things seem to be going wrong. One, the additional garbage around my output string. Probably this is not my program's fault but has to do with the USART's data getting mangled through two USB interfaces on the Arduino and my host. I'm kind of OK with that.
What I don't understand, though, is why the digit doesn't show up where it should in the string. It should replace the underscore but it doesn't. Also this can't have to do with the terminal's cursor being kicked around by stray control chars because the underscore is still visibile.
Bear with me, this is my very first assembler program in 30+ years of C and Python. I always wanted to learn assembler and now I'm doing it. I know there are easier ways to go about this.
.include "m328Pdef.inc"
.list
.def r = r16 ; scratch register
.def s = r18 ; scratch register
.def r_ticks = r17 ; decremented in ISR
.equ MAX_TICKS = 50 ; LED status toggle after this many
; ticks
.equ LED_BIT = 5 ; Pin5 on PORT B = Arduino Pin 13
.equ COUNT_LIMIT = 1249 ; results in 50Hz ticker freq
; A couple of helper macros to write constants into registers or I/O
.macro sts_i ; "store immediate"
ldi r, @1
sts @0, r
.endmacro
.macro out_i ; "out immediate"
ldi r, @1
out @0, r
.endmacro
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; MAIN PROGRAM
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
.cseg
.org 0x00
rjmp main
.org OC1Aaddr
rjmp ticker
.org URXCaddr
reti ; USART receiving not (yet) implemented
.org UTXCaddr
rjmp usart_isr_send
hello: .db "Hello World: _", 0x0d, 0x0a, 0 ; note the underscore @ pos 13
main:
out_i SPL, low(RAMEND)
out_i SPL, high(RAMEND)
cli ; disable Interrupts
; set timer control registers TCCR1A, TCCR1B
clr r
sts TCCR1A, r ; clear TTCR1A
sts_i TCCR1B, (1 << WGM12) | (1 << CS12) ; CTC mode, x256 prescaler
; set counter limit
sts_i OCR1AH, high(COUNT_LIMIT) ; set high and...
sts_i OCR1AL, low(COUNT_LIMIT) ; low byte of counter limite
; update interrupt mask
lds r, TIMSK1 ; load int mask
ori r, 1 << OCIE1A ; set int bit
sts TIMSK1, r ; store int mask
; init tick counter
ldi r_ticks, MAX_TICKS ; start ticks
; set LED bit in Port B to out
in r, DDRB
ori r, 1 << LED_BIT
out DDRB, r
sbi PORTB, LED_BIT
sei ; enable interrupts
rcall usart_init ; set up USART
; fill USART buffer
ldi ZL, low(hello)
ldi ZH, high(hello)
rcall usart_strcpy
loop:
rjmp loop
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; TICKER ISR
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
.dseg
counter: .byte 1
.cseg
ticker:
dec r_ticks ; decrement ticks
brne ticker_end ; go to end if > 0
ldi r_ticks, MAX_TICKS ; reset ticks
in r, PORTB ; read port
ldi s, 1 << LED_BIT ; "1" at LED bit
eor r, s ; toggle LED bit in r
out PORTB, r ; write LED port
lds r, counter
inc r
sts counter, r
andi r, 0x07 ; limit to 0..7
subi r, -48 ; '0' == 48
sts usart_buffer + 13, r ; copy ASCII code for digit to pos 13
rcall usart_send_buffer
ticker_end:
reti
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; USART stuff
; sends constant strings from program space, interrupt driven
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
.equ F_CPU = 16000000
.equ BAUD = 9600
.dseg
usart_p: .byte 2 ; pointer to next char to be sent
usart_buffer: .byte 20
.cseg
; copy string in program memory @ Z into buffer
usart_strcpy:
cli
ldi YL, low(usart_buffer) ; set Y to beginning of buffer
ldi YH, high(usart_buffer)
loop_strcpy:
lpm r, Z+ ; fetch char pointed to by Z into r, increment Z
st Y+, r ; store into buffer @ Y++
ori r, 0x00 ; NOP to set zero flag if r == 0
brne loop_strcpy ; r == 0 -> end of string
sei
ret
usart_init:
.equ prescaler = F_CPU / (16 * BAUD) - 1
cli
ldi r, high(prescaler)
sts UBRR0H, r
ldi r, low(prescaler)
sts UBRR0L, r
ldi r, (1 << RXEN0) | (1 << TXEN0) ; TX and RX enable
sts UCSR0B, r
; 8 data bits, no parity
ldi r, (1 << UCSZ01) | (1 << UCSZ00)\
| (0 << UPM00) | (0 << UPM01)
sts UCSR0C, r
sei
ret
; Send out current contents of usart_buffer
usart_send_buffer:
cli
ldi r, low(usart_buffer) ; set pointer to buffer start
sts usart_p, r
ldi r, high(usart_buffer)
sts usart_p + 1, r
lds r, UCSR0B
ori r, 1 << TXCIE0 ; enable transmit complete interrupt
sts UCSR0B, r
rcall usart_send ; send first char.
sei
ret
usart_send:
cli
lds ZL, usart_p ; load pointer to current char into Z
lds ZH, usart_p + 1
ld r, Z+ ; fetch char pointed to by Z into r, increment Z
ori r, 0x00 ; NOP to set zero flag if r == 0
breq finished ; r == 0 -> end of string
sts UDR0, r ; send char.
sts usart_p, ZL ; save Z into pointer
sts usart_p + 1, ZH
rjmp end
finished:
lds r, UCSR0B
andi r, ~(1 << TXCIE0) ; disable interrupt
sts UCSR0B, r
end:
sei
ret
usart_isr_send:
rcall usart_send
reti