This is a quite universal, non-blocking UART driver for XMEGA. It supports both transmission (with optional DMA) and reception. Receive side can deliver callbacks whenever a complete line (terminated with \n) is received or received bytes can be retrieved one-by-one from a ringbuffer (more useful for GPS units). This driver can support multiple hardware USARTs in a single application.
Usage example
This is a real world usage example of this driver with a GPS unit. Due to bursty GPS transmission (a couple of NMEA sentences at once) I had to use a ringbuffer instead of a callback every line, because the time spent analyzing a sentence was too long and some sentences were randomly lost.
Data
1 2 3 4 5 6 7 8 9 10 |
#include <avr/interrupt.h> #include <avr/pgmspace.h> #include "uart.h" #include <string.h> /* --------- private data --------------- */ static volatile char _uart_buffer[128]; static uart_state_t _gps_uart_state; static char _gps_line_buffer[100]; static uint8_t _gps_line_buffer_index = 0; |
In order to use the ringbuffer the application has to keep:
- uart_state_t structure
- a generic buffer (_gps_line_buffer) for the exclusive use by the driver – in this case this is used as a ringbuffer for reception or as a linear buffer for transmission
- optionally – another buffer to logically process the received bytes one by one
Initilization and interrupts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
void gps_uart_init(void){ //TODO: configure TX pin as an output uart_init(&USARTE0, &DMA.CH0, &_gps_uart_state, NULL /*line callback*/, _uart_buffer, sizeof(_uart_buffer)); char* buffer = uart_get_buffer(&_gps_uart_state); strcpy_P(buffer, PSTR("$PMTK300,5000,0,0,0,0*18\r\n")); //update rate = 5 seconds uart_send(&_gps_uart_state); } //uncomment this interrupt when not using DMA //ISR(USARTE0_DRE_vect){ // uart_dre_isr(&_gps_uart_state); //} ISR(USARTE0_RXC_vect){ uart_rx_isr_ringbuffer(&_gps_uart_state); } ISR(DMA_CH0_vect){ uart_dma_isr(&_gps_uart_state); } |
The application passes pointers to USART, optionally DMA channel. Line callback is not used in this case. Last arguments are the buffer and its size.
In order to reduce GPS refresh rate (I don’t need an update every second) a special command is sent to the GPS (buffer is taken using get_uart_buffer and filled with sprintf).
The application also has to declare interrupts as shown – in this case the USART RX interrupt (with ringbuffer isr functions) and DMA channel interrupt.
Using DMA to transmit single command is of course an overkill, but it gives the general usage idea of the driver.
Receiving data
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
void gps_uart_task(void){ char c = 0; while (uart_getc(&_gps_uart_state, &c)){ if (c == '\n'){ //whole line has arrived - fire callback _gps_line_buffer[_gps_line_buffer_index++] = c; //add the newline, otherwise parser will fail _gps_line_buffer[_gps_line_buffer_index] = '\0'; //TODO: process the line _gps_line_buffer_index = 0; continue; } _gps_line_buffer[_gps_line_buffer_index] = c; _gps_line_buffer_index++; if (unlikely(_gps_line_buffer_index > sizeof(_gps_line_buffer)-1)){ _gps_line_buffer_index = 0; } } } |
The gps_uart_task is called from the main loop. It tries to receive as many bytes from the UART RX buffer as possible until a newline is encountered.
If line callback mode is used, then a function taking a string as an argument has to be implemented and passed to uart_init. Main loop has to keep calling uart_task and the function called from RXC ISR has to be uart_rx_isr.
Driver
Most parts are similar to my SPI driver. Have a look for a detailed overview.
uart.h
All the functions have doxygen comments that should make their intended use understood.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
#ifndef UART_H_ #define UART_H_ #include <stdbool.h> #include <stdint.h> #include <avr/io.h> #define LINE_SEPARATOR '\n' typedef void (*uart_rx_callback_t)(const char *s); typedef struct { volatile char *buffer; //must be null-terminated, shared by RX and TX directions uint8_t buffer_size; uint8_t buffer_index; //used wit uart_task uint8_t buffer_head; //used with ringbuffer uint8_t buffer_tail; //used with ringbuffer bool line_received; uart_rx_callback_t rx_callback; USART_t* usart; DMA_CH_t* dma_ch; uint8_t dma_trigger_source; } uart_state_t; /** Initializes the driver with a particular buffer and USART. * @param t Pointer to USART struct, eg. &USARTD0 * @param dma_ch Pointer to DMA channel struct, eg. &DMA.CH0, NULL if DMA is not needed. * @param s Pointer to uart_state_t struct, allocated statically. * @param rx_callback Pointer to function that should be called whenever a complete line is received * @param buffer Pointer to array that will be used as the TX and RX buffer * @param buffer_size Size of buffer array. */ void uart_init(USART_t* t, DMA_CH_t* dma_ch, volatile uart_state_t* s, uart_rx_callback_t rx_callback, volatile char *buffer, uint8_t buffer_size); /** Line received callbacks are delivered from this function. It should be called * from main loop. Not used with RX ringbuffer! * @param t Pointer to uart_state_t struct, allocated statically. */ void uart_task(volatile uart_state_t* t); /** This function has to be called to obtain the buffer to fill with * data to be transmitted. It also stops RX operation of the driver. * DATA IN BUFFER MUST BE NULL TERMINATED. * After filling the buffer uart_send has to be called. * @param s Pointer to uart_state_t struct, allocated statically. * @return Pointer to buffer to be filled with data. The same as buffer argument of uart_init. */ char* uart_get_buffer(volatile uart_state_t *s); /** Starts sending data in buffer (from uart_get_buffer) * @param s Pointer to uart_state_t struct, allocated statically. */ void uart_send(volatile uart_state_t* s); /** Gets a single byte from receive ringbuffer. * @param t Pointer to uart_state_t struct, allocated statically. * @partam target Pointer where the byte from ringbuffer is to be put. * @return true if a byte was received, false otherwise */ bool uart_getc(volatile uart_state_t* t, char *target); /** Receive interrupt handler in line mode. To be called within ISR(USARTxn_RXC_vect) function. * @param t Pointer to uart_state_t struct, allocated statically. */ void uart_rx_isr(volatile uart_state_t* t); /** Receive interrupt handler in ringbuffer mode. To be called within ISR(USARTxn_RXC_vect) function. * @param t Pointer to uart_state_t struct, allocated statically. */ void uart_rx_isr_ringbuffer(volatile uart_state_t* t); /** Transmit interrupt handler. Used when no DMA channel * is specified. To be called within ISR(USARTxn_DRE_vect) function. * @param t Pointer to uart_state_t struct, allocated statically. */ void uart_dre_isr(volatile uart_state_t* t); /** Transmit interrupt handler. Used with a DMA channel * interrupt. To be called within ISR(DMA_CHx_vect) function. * @param t Pointer to uart_state_t struct, allocated statically. */ void uart_dma_isr(volatile uart_state_t* t); #endif |
uart.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 |
#include <string.h> #include "uart.h" #define likely(x) __builtin_expect(!!(x),1) #define unlikely(x) __builtin_expect(!!(x),0) /* --------- private prototypes --------- */ static void uart_end_transmission(volatile uart_state_t *t); /* ----------- implementation ----------- */ void uart_init(USART_t* u, DMA_CH_t *dma_ch, volatile uart_state_t* s, uart_rx_callback_t rx_callback, volatile char *buffer, uint8_t buffer_size){ u->CTRLB = USART_TXEN_bm | USART_RXEN_bm; //enable receiver and transmitter //baud setting u->BAUDCTRLA = 12; //BSEL = 12 9600bps u->BAUDCTRLB = 4 <<USART_BSCALE_gp; //BSCALE = 4 @32MHz s->buffer = buffer; s->buffer_size = buffer_size; s->rx_callback = rx_callback; s->usart = u; s->line_received = false; s->buffer_index = 0; s->buffer_head = 0; s->buffer_tail = 0; s->dma_ch = dma_ch; if (dma_ch){ //look for trigger source switch ((uint16_t)u){ //pointer has to be cast the ugly way #ifdef USARTC0 case (uint16_t)&USARTC0: s->dma_trigger_source = DMA_CH_TRIGSRC_USARTC0_DRE_gc; break; #endif #ifdef USARTC1 case (uint16_t)&USARTC1: s->dma_trigger_source = DMA_CH_TRIGSRC_USARTC1_DRE_gc; break; #endif #ifdef USARTD0 case (uint16_t)&USARTD0: s->dma_trigger_source = DMA_CH_TRIGSRC_USARTD0_DRE_gc; break; #endif #ifdef USARTD1 case (uint16_t)&USARTD1: s->dma_trigger_source = DMA_CH_TRIGSRC_USARTD1_DRE_gc; break; #endif #ifdef USARTE0 case (uint16_t)&USARTE0: s->dma_trigger_source = DMA_CH_TRIGSRC_USARTE0_DRE_gc; break; #endif } } //enable RXC interrupt - low priority //DRE interrupt must not be enabled, otherwise it will fire immediately u->CTRLA = USART_RXCINTLVL_LO_gc; } void uart_task(volatile uart_state_t *t){ if (t->line_received){ t->line_received = false; if (t->rx_callback){ t->buffer_index = 0; //MUST BE BEFORE CALLBACK! t->rx_callback((const char*)t->buffer); } } } bool uart_getc(volatile uart_state_t* t, char *target){ if (t->buffer_tail != t->buffer_head){ uint8_t current_head = t->buffer_head; t->buffer_head = (t->buffer_head + 1) % t->buffer_size; //UART_BUFFER_SIZE; *target = t->buffer[current_head]; return true; } return false; } //this function transmits data that is already in the buffer void uart_send(volatile uart_state_t* s){ s->buffer[s->buffer_size-1] = '\0'; //enforce that the string is always null-terminated if (s->dma_ch){ //globally enable DMA, no double buffering, round-robin DMA.CTRL = DMA_ENABLE_bm; /* ------ TX DMA channel setup ------ */ s->dma_ch->CTRLA = DMA_CH_RESET_bm; s->dma_ch->ADDRCTRL = DMA_CH_SRCDIR_INC_gc | DMA_CH_DESTDIR_FIXED_gc | DMA_CH_DESTRELOAD_TRANSACTION_gc; //source - increment, destination (USARTxx.DATA) fixed s->dma_ch->TRIGSRC = s->dma_trigger_source; s->dma_ch->DESTADDR0 = (((uint16_t)&(s->usart->DATA)) >> 0) & 0xFF; s->dma_ch->DESTADDR1 = (((uint16_t)&(s->usart->DATA)) >> 8) & 0xFF; s->dma_ch->DESTADDR2 = 0x00; s->dma_ch->SRCADDR0 = (((uint16_t)s->buffer) >> 0) & 0xFF; s->dma_ch->SRCADDR1 = (((uint16_t)s->buffer) >> 8) & 0xFF; s->dma_ch->SRCADDR2 = 0x00; //internal SRAM s->dma_ch->TRFCNT = strlen((char*)s->buffer); //transfer length //enable channel, USART DRE will trigger the transmission, single shot mode - one byte per trigger s->dma_ch->CTRLA = DMA_ENABLE_bm | DMA_CH_SINGLE_bm | DMA_CH_BURSTLEN_1BYTE_gc; s->dma_ch->CTRLB = DMA_CH_TRNINTLVL_LO_gc; //transfer complete interrupt enable /* ---------------------------------- */ } else { //DMA channel is not set, use ISR s->usart->DATA = s->buffer[0]; //transmit first character s->buffer_index = 1; //ISRs will take care of the rest s->usart->CTRLA = USART_DREINTLVL_LO_gc; //enable DRE interrupt, disable RX interrupt } } char* uart_get_buffer(volatile uart_state_t *s){ s->usart->CTRLA = 0; //disable all interrupts (stop receiving) s->buffer_tail = 0; s->buffer_head = 0; return (char*)s->buffer; } inline void uart_rx_isr(volatile uart_state_t *t){ t->buffer[t->buffer_index] = t->usart->DATA; if (unlikely(t->buffer_index >= t->buffer_size-1)){ //rx buffer overflow protection t->buffer_index = 0; } if (unlikely(t->buffer[t->buffer_index] == LINE_SEPARATOR)){ //we received newline, let's terminate the command t->buffer_index++; t->buffer[t->buffer_index] = '\0'; t->line_received = true; //now the line will be processed by uart_task t->buffer_index = 0; //hack } else { t->buffer_index++; } } inline void uart_rx_isr_ringbuffer(volatile uart_state_t *t){ uint8_t next = (t->buffer_tail + 1) % t->buffer_size; t->buffer[next] = t->usart->DATA; t->buffer_tail = next; } inline void uart_dre_isr(volatile uart_state_t *t){ //data register ready for more data interrupt if (t->buffer[t->buffer_index]){ //if we have more data to transmit (string must be null-terminated) t->usart->DATA = t->buffer[t->buffer_index]; t->buffer_index++; } else { //no more data to transmit uart_end_transmission(t); } } inline void uart_dma_isr(volatile uart_state_t* t){ t->dma_ch->CTRLA = 0; //disable channel t->dma_ch->CTRLB = 0; //disable channel interrupts uart_end_transmission(t); } inline static void uart_end_transmission(volatile uart_state_t *t){ //prepare buffer for reception t->buffer_index = 0; t->buffer_tail = 0; t->buffer_head = 0; t->usart->CTRLA = USART_RXCINTLVL_LO_gc; //disable DRE interrupt, enable RX interrupt } |