Writing UART Routines
Universal Asynchronous Serial Transmission (UART) is one of the simplest ways to allow your microcontroller to communicate with a computer or another chip. Implementing UART in assembly is quite simple and can be done with the instructions we've seen so far. Here we will look at writing UART routines for the ATmega328P. The proccess is the same for all AVR chips, but some of the register names may need to be modified.
Initialization
A simple UART initialization subroutine is shown below
;**************************************************************
;* subroutine: initUART
;*
;* inputs: r17:r16 - baud rate prescale
;*
;* enables UART transmission with 8 data, 1 parity, no stop bit
;* at input baudrate
;*
;* registers modified: r16
;**************************************************************
initUART:
sts UBRR0L,r16 ; load baud prescale
sts UBRR0H,r17 ; to UBRR0
ldi r16,(1<<RXEN0)|(1<<TXEN0) ; enable transmitter
sts UCSR0B,r16 ; and receiver
ret ; return from subroutine
The desired baud prescale must be loaded into registers 16 and 17 before calling this subroutine. For example
.equ baud = 9600 ; baudrate
.equ bps = (F_CPU/16/baud) - 1 ; baud prescale
...
ldi r16,LOW(bps) ; load baud prescale
ldi r17,HIGH(bps) ; into r17:r16
rcall initUART ; call initUART subroutine
All constants are treated as 32-bit numbers by the assembler, so we do not have to worry about truncation with the above calculation.
Transmitting Characters
A subroutine for transmitting a single character is shown below
;**************************************************************
;* subroutine: putc
;*
;* inputs: r16 - character to transmit
;*
;* transmits single ASCII character via UART
;*
;* registers modified: r17
;**************************************************************
putc: lds r17,UCSR0A ; load UCSR0A into r17
sbrs r17,UDRE0 ; wait for empty transmit buffer
rjmp putc ; repeat loop
sts UDR0,r16 ; transmit character
ret ; return from subroutine
Using this subroutine simply requires loading a character into r16 before calling it. For example
ldi r16,'a' ; load char 'a' into r16
rcall putc ; transmit character
Transmitting Strings
More often than not, you will want to transmit an entire string rather than a single character. The easiest way to do this is with a subroutine that accepts a Program Memory address stored in the Z pointer that points to a null terminated string.
;**************************************************************
;* subroutine: puts
;*
;* inputs: ZH:ZL - Program Memory address of string to transmit
;*
;* transmits null terminated string via UART
;*
;* registers modified: r16,r17,r30,r31
;**************************************************************
puts: lpm r16,Z+ ; load character from pmem
cpi r16,$00 ; check if null
breq puts_end ; branch if null
puts_wait:
lds r17,UCSR0A ; load UCSR0A into r17
sbrs r17,UDRE0 ; wait for empty transmit buffer
rjmp puts_wait ; repeat loop
sts UDR0,r16 ; transmit character
rjmp puts ; repeat loop
puts_end:
ret ; return from subroutine
You should notice that this subroutine uses the labels and . It is good practice to include the subroutine name in labels that appear within the subroutine. This helps prevent them from conflicting with labels in the rest of your program.
To use this subroutine, you must define a null terminated string in Program Memory and load its address into the Z pointer. For example
ldi ZL,LOW(2*myStr) ; load Z pointer with
ldi ZH,HIGH(2*myStr) ; myStr address
rcall puts ; transmit string
...
myStr: .db "Hello",$00
Receiving Characters
Implementing a routine to receive a character over UART can be done as
;**************************************************************
;* subroutine: getc
;*
;* inputs: none
;*
;* outputs: r16 - character received
;*
;* receives single ASCII character via UART
;*
;* registers modified: r16, r17
;**************************************************************
getc: lds r17,UCSR0A ; load UCSR0A into r17
sbrs r17,UDRE0 ; wait for empty transmit buffer
rjmp getc ; repeat loop
lds UDR0,r16 ; get received character
ret ; return from subroutine
This subroutine has no inputs so it can be called without any setup. It will put the microcontroller in a loop that will continue until a character is received.
Receiving Strings
Implementing a subroutine to receive a string over UART can be done in many different ways. Here is an example of a subroutine that will receive characters and store them in a buffer until a carriage return is received.
;**************************************************************
;* subroutine: gets
;*
;* inputs: XH:XL - SRAM buffer address for rcv'd string
;*
;* outputs: none
;*
;* receives characters via UART and stores in data memory
;* until carriage return received
;*
;* registers modified: r16, r17, XL, XH
;**************************************************************
gets: lds r17,UCSR0A ; load UCSR0A into r17
sbrs r17,UDRE0 ; wait for empty transmit buffer
rjmp putc ; repeat loop
lds UDR0,r16 ; get received character
cpi r16,$0D ; check if rcv'd char is CR
breq gets_end ; branch if CR rcv'd
st X+,r16 ; store character to buffer
rjmp gets ; get another character
gets_end:
ret ; return from subroutine
Post a Comment