LPC176x UART Driver
In my last post (here), I claimed that FIFOs are often used in UART drivers. Here I will show a UART driver that utilizes dual FIFOs, one for transmit and one for receive. A universal asynchronous receiver/transmitter (UART) is a device that receives and transmits data without a known clock relationship to the connecting device. This allows each device to send data whenever it wants. This is in stark contrast to the SPI and I2C buses where the slave device can’t send data without the master first initiating a bus transfer. UARTs are very versatile and are in wide use. They are most commonly found in RS-232 ports on PCs.
The basic structure behind a UART driver is a negotiation process between the asynchronous hardware and the user’s code. FIFOs are used to aide this process. For transmitting data, it is desirable for the user to drop the data off at any time and forget about the actual serial transmission. This is where the FIFO comes in. The UART driver just takes the data and puts it in a FIFO and returns to the user. In another thread (driven by interrupts) the driver sends all the data in the FIFO as fast as it can. The receive path is very similar. The driver, again in an interrupt driven thread, transfers all received data into a FIFO. The user periodically checks if there is any new data and pulls it out at its own speed.
UARTs are often used for printing ASCII to a debug console. Most of the UARTs I have made have only been used for this purpose. For this reason it is very important to have a good method for converting numbers (integer and floating-point) to a sequence of ASCII characters. Of course, you could use a sprintf-like function, however, these are very slow. Even the embedded versions of these libraries produce terribly inefficient code (I dare you to follow the call stack of a printf function). I’m not a big fan of Arduinos, but I must say that the Arduino serial printing functions are very nice. There are no format strings to parse. Instead, the user just calls a sequence of print functions to produce the desired ASCII. My UART driver has an integrated printing library similar to the functions found in the Arduino library. This may be better off separated from the actual driver, however, I feel it fits fine into this code. You’ll notice a lot of similarity between my print functions and the Arduino serial library.
Header File
/************************************************************************ Copyright (c) 2011, Nic McDonald All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ************************************************************************* Information: File Name : uart3.h Author(s) : Nic McDonald Hardware : LPCXpresso LPC1768 Purpose : UART 3 Driver ************************************************************************* Modification History: Revision Date Author Description of Revision 1.00 05/30/2011 NGM initial ************************************************************************* Assumptions: All print functions assume the UART is enabled. Calling these functions while the UART is disabled produced undefined behavior. ************************************************************************/ #ifndef _UART3_H_ #define _UART3_H_ /* includes */ #include <stdint.h> /* defines */ #define SW_FIFO_SIZE 512 #define UART3_DISABLED 0x00 #define UART3_OPERATIONAL 0x01 #define UART3_OVERFLOW 0x02 #define UART3_PARITY_ERROR 0x03 #define UART3_FRAMING_ERROR 0x04 #define UART3_BREAK_DETECTED 0x05 #define UART3_CHAR_TIMEOUT 0x06 /* typedefs */ /* functions */ void uart3_enable(uint32_t baudrate); void uart3_disable(void); void uart3_printByte(uint8_t c); void uart3_printBytes(uint8_t* buf, uint32_t len); void uart3_printString(char* buf); // must be null terminated void uart3_printInt32(int32_t n, uint8_t base); void uart3_printUint32(uint32_t n, uint8_t base); void uart3_printDouble(double n, uint8_t frac_digits); uint32_t uart3_available(void); uint8_t uart3_peek(void); uint8_t uart3_read(void); uint8_t uart3_txStatus(void); uint8_t uart3_rxStatus(void); #endif /* _UART3_H_ */
Source File
/************************************************************************
Copyright (c) 2011, Nic McDonald
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*************************************************************************
Information:
File Name : uart3.c
Author(s) : Nic McDonald
Hardware : LPCXpresso LPC1768
Purpose : UART 3 Driver
*************************************************************************
Modification History:
Revision Date Author Description of Revision
1.00 05/30/2011 NGM initial
*************************************************************************
Theory of Operation:
This provides a simple UART driver with accompanying print functions
for converting integer and floating point numbers to bytes.
************************************************************************/
#include "uart3.h"
#include "fifo.h"
#include "LPC17xx.h"
/* local defines */
#define RX_TRIGGER_ONE 0x0
#define RX_TRIGGER_FOUR 0x1
#define RX_TRIGGER_EIGHT 0x2
#define RX_TRIGGER_FOURTEEN 0x3
#define RX_TRIGGER_LEVEL RX_TRIGGER_FOURTEEN
#define RLS_INTERRUPT 0x03
#define RDA_INTERRUPT 0x02
#define CTI_INTERRUPT 0x06
#define THRE_INTERRUPT 0x01
#define LSR_RDR (1<<0)
#define LSR_OE (1<<1)
#define LSR_PE (1<<2)
#define LSR_FE (1<<3)
#define LSR_BI (1<<4)
#define LSR_THRE (1<<5)
#define LSR_TEMT (1<<6)
#define LSR_RXFE (1<<7)
/* local persistent variables */
static uint8_t uart3_tx_sts = UART3_DISABLED;
static uint8_t uart3_rx_sts = UART3_DISABLED;
static uint8_t uart3_txBuffer[SW_FIFO_SIZE];
static uint8_t uart3_rxBuffer[SW_FIFO_SIZE];
static FIFO txFifo;
static FIFO rxFifo;
/* private function declarations */
static inline void uart3_interruptsOn(void);
static inline void uart3_interruptsOff(void);
uint32_t rdaInterrupts = 0;
uint32_t ctiInterrupts = 0;
/* public functions */
void uart3_enable(uint32_t baudrate) {
uint32_t fdiv, pclk;
// initial the SW FIFOs
fifo_init(&txFifo, SW_FIFO_SIZE, (uint8_t*)uart3_txBuffer);
fifo_init(&rxFifo, SW_FIFO_SIZE, (uint8_t*)uart3_rxBuffer);
// set pin function to RxD3 and TxD3
LPC_PINCON->PINSEL0 &= ~0x0000000F;
LPC_PINCON->PINSEL0 |= 0x0000000A;
// give power to PCUART3
LPC_SC->PCONP |= (1 << 25);
// set peripheral clock selection for UART3
LPC_SC->PCLKSEL1 &= ~(3 << 18); // clear bits
LPC_SC->PCLKSEL1 |= (1 << 18); // set to "01" (full speed)
pclk = SystemCoreClock;
// set to 8 databits, no parity, and 1 stop bit
LPC_UART3->LCR = 0x03;
// enable 'Divisor Latch Access" (must disable later)
LPC_UART3->LCR |= (1 << 7);
// do baudrate calculation
fdiv = (pclk / (16 * baudrate));
LPC_UART3->DLM = (fdiv >> 8) & 0xFF;
LPC_UART3->DLL = (fdiv) & 0xFF;
// disable 'Divisor Latch Access"
LPC_UART3->LCR &= ~(1 << 7);
// set the number of bytes received before a RDA interrupt
LPC_UART3->FCR |= (RX_TRIGGER_LEVEL << 6);
// enable Rx and Tx FIFOs and clear FIFOs
LPC_UART3->FCR |= 0x01;
// clear Rx and Tx FIFOs
LPC_UART3->FCR |= 0x06;
// add the interrupt handler into the interrupt vector
NVIC_EnableIRQ(UART3_IRQn);
// set the priority of the interrupt
NVIC_SetPriority(UART3_IRQn, 30); // '0' is highest
// turn on UART3 interrupts
uart3_interruptsOn();
// set to operational status
uart3_tx_sts = UART3_OPERATIONAL;
uart3_rx_sts = UART3_OPERATIONAL;
}
void uart3_disable(void) {
// disable interrupt
NVIC_DisableIRQ(UART3_IRQn);
// turn off all interrupt sources
uart3_interruptsOff();
// clear software FIFOs
fifo_clear(&txFifo);
fifo_clear(&rxFifo);
// set to disabled status
uart3_tx_sts = UART3_DISABLED;
uart3_rx_sts = UART3_DISABLED;
}
void uart3_printByte(uint8_t b) {
uint8_t thr_empty;
// turn off UART3 interrupts while accessing shared resources
uart3_interruptsOff();
// determine if the THR register is empty
thr_empty = (LPC_UART3->LSR & LSR_THRE);
// both checks MUST be here. there is a slight chance that
// the THR is empty but chars still reside in the SW Tx FIFO
if (thr_empty && fifo_isEmpty(&txFifo)) {
LPC_UART3->THR = b;
}
else {
// turn UART3 interrupts back on to allow Sw Tx FIFO emptying
uart3_interruptsOn();
// wait for one slot available in the SW Tx FIFO
while (fifo_isFull(&txFifo));
// turn interrupts back off
uart3_interruptsOff();
// add character to SW Tx FIFO
fifo_put(&txFifo, b); // <- this is the only case of txFifo putting
}
// turn UART3 interrupts back on
uart3_interruptsOn();
}
void uart3_printBytes(uint8_t* buf, uint32_t len) {
// transfer all bytes to HW Tx FIFO
while ( len != 0 ) {
// send next byte
uart3_printByte(*buf);
// update the buf ptr and length
buf++;
len--;
}
}
void uart3_printString(char* buf) {
while ( *buf != '\0' ) {
// send next byte
uart3_printByte((uint8_t)*buf);
// update the buf ptr
buf++;
}
}
void uart3_printInt32(int32_t n, uint8_t base) {
uint32_t i = 0;
// print '-' for negative numbers, also negate
if (n < 0) {
uart3_printByte((uint8_t)'-');
n = ((~n) + 1);
}
// cast to unsigned and print using uint32_t printer
i = n;
uart3_printUint32(i, base);
}
void uart3_printUint32(uint32_t n, uint8_t base) {
uint32_t i = 0;
uint8_t buf[8 * sizeof(uint32_t)]; // binary is the largest
// check for zero case, print and bail out if so
if (n == 0) {
uart3_printByte((uint8_t)'0');
return;
}
while (n > 0) {
buf[i] = n % base;
i++;
n /= base;
}
for (; i > 0; i--) {
if (buf[i - 1] < 10)
uart3_printByte((uint8_t)('0' + buf[i - 1]));
else
uart3_printByte((uint8_t)('A' + buf[i - 1] - 10));
}
}
void uart3_printDouble(double n, uint8_t frac_digits) {
uint8_t i;
uint32_t i32;
double rounding, remainder;
// test for negatives
if (n < 0.0) {
uart3_printByte((uint8_t)'-');
n = -n;
}
// round correctly so that print(1.999, 2) prints as "2.00"
rounding = 0.5;
for (i=0; i<frac_digits; i++)
rounding /= 10.0;
n += rounding;
// extract the integer part of the number and print it
i32 = (uint32_t)n;
remainder = n - (double)i32;
uart3_printUint32(i32, 10);
// print the decimal point, but only if there are digits beyond
if (frac_digits > 0)
uart3_printByte((uint8_t)'.');
// extract digits from the remainder one at a time
while (frac_digits-- > 0) {
remainder *= 10.0;
i32 = (uint32_t)remainder;
uart3_printUint32(i32, 10);
remainder -= i32;
}
}
uint32_t uart3_available(void) {
uint32_t avail;
uart3_interruptsOff();
avail = fifo_available(&rxFifo);
uart3_interruptsOn();
return avail;
}
uint8_t uart3_peek(void) {
uint8_t ret;
uart3_interruptsOff();
ret = fifo_peek(&rxFifo);
uart3_interruptsOn();
return ret;
}
uint8_t uart3_read(void) {
uint8_t ret;
uart3_interruptsOff();
ret = fifo_get(&rxFifo);
uart3_interruptsOn();
return ret;
}
uint8_t uart3_txStatus(void) {
return uart3_tx_sts;
}
uint8_t uart3_rxStatus(void) {
return uart3_rx_sts;
}
/* private functions */
void UART3_IRQHandler(void) {
uint8_t intId; // interrupt identification
uint8_t lsrReg; // line status register
// get the interrupt identification from the IIR register
intId = ((LPC_UART3->IIR) >> 1) & 0x7;
// RLS (receive line status) interrupt
if ( intId == RLS_INTERRUPT ) {
// get line status register value (clears interrupt)
lsrReg = LPC_UART3->LSR;
// determine type of error and set Rx status accordingly
if (lsrReg & LSR_OE)
uart3_rx_sts = UART3_OVERFLOW; // won't happen when using SW fifo
else if (lsrReg & LSR_PE)
uart3_rx_sts = UART3_PARITY_ERROR;
else if (lsrReg & LSR_FE)
uart3_rx_sts = UART3_FRAMING_ERROR;
else if (lsrReg & LSR_BI)
uart3_rx_sts = UART3_BREAK_DETECTED;
}
// RDA (receive data available) interrupt
else if ( intId == RDA_INTERRUPT ) {
// this interrupt occurs when the number of bytes in the
// HW Rx FIFO are greater than or equal to the trigger level
// (FCR[7:6])
// read out bytes
// clears interrupt when HW Rx FIFO is below trigger level FCR[7:6]
// the number of loops should be the trigger level (or +1)
while ((LPC_UART3->LSR) & 0x1)
fifo_put(&rxFifo, LPC_UART3->RBR);
rdaInterrupts++;
}
// CTI (character timeout indicator) interrupt
else if ( intId == CTI_INTERRUPT ) {
// this interrupt occurs when the HW Rx FIFO contains at least one
// char and nothing has been received in 3.5 to 4.5 char times.
// read out all remaining bytes
while ((LPC_UART3->LSR) & 0x1)
fifo_put(&rxFifo, LPC_UART3->RBR);
ctiInterrupts++;
}
// THRE (transmit holding register empty) interrupt
else if ( intId == THRE_INTERRUPT ) {
uint8_t i;
// transfer 16 bytes if available, if not, transfer all you can
for (i=0; ((i<16) && (!fifo_isEmpty(&txFifo))); i++)
LPC_UART3->THR = fifo_get(&txFifo);
}
}
static inline void uart3_interruptsOn(void) {
LPC_UART3->IER = 0x07; // RBR, THRE, RLS
}
static inline void uart3_interruptsOff(void) {
LPC_UART3->IER = 0x00; // !RBR, !THRE, !RLS
}
Handling FIFOs
The LPC176x UART design has hardware FIFOs built-in. Having these hardware FIFOs makes the UART hardware very efficient. However, handling the data flow between the hardware FIFOs, the software FIFOs, and the user can be very tricky. There are many situations that must be considered. The main issue is synchronization (the lack of such will cause data corruption). A correct UART driver design must always send the data in-order. Issues will occur if the driver mistakenly assumes that the software FIFO is empty and adds data directly to the hardware FIFO. If you look at the ‘print_byte()’ function, it has a lot of checks to ensure this does not happen. Throughout the code, the driver is constantly turning on and off the UART interrupts. This is because the interrupts can trigger at any time. While accessing shared memory, the interrupt code must be stalled. This is a tricky concept and is the basis for many embedded system software errors.











