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 910
#include<avr/interrupt.h>#include<avr/pgmspace.h>#include"uart.h"#include<string.h>/* --------- private data --------------- */staticvolatilechar_uart_buffer[128];staticuart_state_t_gps_uart_state;staticchar_gps_line_buffer[100];staticuint8_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
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.
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.
#include<string.h>#include"uart.h"#define likely(x) __builtin_expect(!!(x),1)#define unlikely(x) __builtin_expect(!!(x),0)/* --------- private prototypes --------- */staticvoiduart_end_transmission(volatileuart_state_t*t);/* ----------- implementation ----------- */voiduart_init(USART_t*u,DMA_CH_t*dma_ch,volatileuart_state_t*s,uart_rx_callback_trx_callback,volatilechar*buffer,uint8_tbuffer_size){u->CTRLB=USART_TXEN_bm|USART_RXEN_bm;//enable receiver and transmitter//baud settingu->BAUDCTRLA=12;//BSEL = 12 9600bpsu->BAUDCTRLB=4<<USART_BSCALE_gp;//BSCALE = 4 @32MHzs->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 sourceswitch((uint16_t)u){//pointer has to be cast the ugly way #ifdef USARTC0case(uint16_t)&USARTC0:s->dma_trigger_source=DMA_CH_TRIGSRC_USARTC0_DRE_gc;break;#endif#ifdef USARTC1case(uint16_t)&USARTC1:s->dma_trigger_source=DMA_CH_TRIGSRC_USARTC1_DRE_gc;break;#endif#ifdef USARTD0case(uint16_t)&USARTD0:s->dma_trigger_source=DMA_CH_TRIGSRC_USARTD0_DRE_gc;break;#endif#ifdef USARTD1case(uint16_t)&USARTD1:s->dma_trigger_source=DMA_CH_TRIGSRC_USARTD1_DRE_gc;break;#endif#ifdef USARTE0case(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 immediatelyu->CTRLA=USART_RXCINTLVL_LO_gc;}voiduart_task(volatileuart_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((constchar*)t->buffer);}}}booluart_getc(volatileuart_state_t*t,char*target){if(t->buffer_tail!=t->buffer_head){uint8_tcurrent_head=t->buffer_head;t->buffer_head=(t->buffer_head+1)%t->buffer_size;//UART_BUFFER_SIZE;*target=t->buffer[current_head];returntrue;}returnfalse;}//this function transmits data that is already in the buffervoiduart_send(volatileuart_state_t*s){s->buffer[s->buffer_size-1]='\0';//enforce that the string is always null-terminatedif(s->dma_ch){//globally enable DMA, no double buffering, round-robinDMA.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) fixeds->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 triggers->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 ISRs->usart->DATA=s->buffer[0];//transmit first characters->buffer_index=1;//ISRs will take care of the rests->usart->CTRLA=USART_DREINTLVL_LO_gc;//enable DRE interrupt, disable RX interrupt}}char*uart_get_buffer(volatileuart_state_t*s){s->usart->CTRLA=0;//disable all interrupts (stop receiving)s->buffer_tail=0;s->buffer_head=0;return(char*)s->buffer;}inlinevoiduart_rx_isr(volatileuart_state_t*t){t->buffer[t->buffer_index]=t->usart->DATA;if(unlikely(t->buffer_index>=t->buffer_size-1)){//rx buffer overflow protectiont->buffer_index=0;}if(unlikely(t->buffer[t->buffer_index]==LINE_SEPARATOR)){//we received newline, let's terminate the commandt->buffer_index++;t->buffer[t->buffer_index]='\0';t->line_received=true;//now the line will be processed by uart_taskt->buffer_index=0;//hack}else{t->buffer_index++;}}inlinevoiduart_rx_isr_ringbuffer(volatileuart_state_t*t){uint8_tnext=(t->buffer_tail+1)%t->buffer_size;t->buffer[next]=t->usart->DATA;t->buffer_tail=next;}inlinevoiduart_dre_isr(volatileuart_state_t*t){//data register ready for more data interruptif(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 transmituart_end_transmission(t);}}inlinevoiduart_dma_isr(volatileuart_state_t*t){t->dma_ch->CTRLA=0;//disable channelt->dma_ch->CTRLB=0;//disable channel interruptsuart_end_transmission(t);}inlinestaticvoiduart_end_transmission(volatileuart_state_t*t){//prepare buffer for receptiont->buffer_index=0;t->buffer_tail=0;t->buffer_head=0;t->usart->CTRLA=USART_RXCINTLVL_LO_gc;//disable DRE interrupt, enable RX interrupt}