In this discussion we will go through ARM Cortex-M3 LP1768 UART programming Tutorial. It is also applicable to for other MCUs of LPC17xx family like LPC1769. If you are new to UART, you can go through the basic UART tutorial which I’ve posted @ Basic Uart Tutorial.
UART basics revisited
Uart uses TxD(Transmit) Pin for sending Data and RxD(Receive) Pin to get data. UART sends & receives data in form of chunks or packets. These chunks or packets are also referred to as ‘Frames’. While sending Data, LSB(i.e. Data Bit 0) is transmitted First and MSB is transmitted Last. The structure of a UART Packet/Frame using 1 stop bit is as shown below:
Here is an example of single Packet/Frame using 8 data bits, even parity and 1 stop bit:
LPC1768 UART Block
Now, Lets start with the main Tutorial. ARM Cortex-M3 LPC176x has 4 UART blocks which are UART0 to UART3. UART0/2/3 are identical. UART1 additionally supports Full modem control handshaking & RS-485. Refer Datasheet for more info. After reset UART0 & UART1 are enabled by default. TxD and RxD pins for these blocks are mapped on multiple pins.
Pins: | TxD | RxD |
UART0 | P0.2 | P0.3 |
UART1 | P0.15/P2.0 | P0.16/P2.1 |
UART2 | P0.10/P2.8 | P0.11/P2.9 |
UART3 | P0.0/P0.25/P4.28 | P0.1/P0.26/P4.29 |
All UART blocks internally have a 16-byte FIFO (First In First Out) structure to hold data. Each byte in this FIFO represents a character which was sent or received in order. All blocks also contain 2 registers each, for data access and assembly as given below:
- Tx has THR(Transmit Holding Register) and TSR(Transmit Shift Register) – When Tx Data is written to THR, it is then transferred to TSR which assembles the Transmit data.
- Similarly Rx has RSR(Receive Shift Register) and RBR(Receive Buffer Register) – When Data is Received at the Rx Pin, it is first assembled in RSR and then transferred into Rx FIFO which can be then accessed using RBR.
LPC176x UART Registers used in programming
Before we can use these pins to transfer data, first we need to configure and initialize the UART block in our LPC176x microcontroller. But before doing that, lets go through some of the important registers:
Data Related Registers
1) UxRBR – Receiver Buffer Register: This register contains the top most byte(8-bit data chunk) in the Rx FIFO i.e the oldest received data in FIFO. Before reading from UxRBR, the DLAB(Divisor Latch Access) bit in U0LCR register must be 0. As mentioned in user manual “The right approach for fetching the valid pair of received byte and its status bits is first to read the content of the U0LSR register, and then to read a byte from the U0RBR.”
2) UxTHR – Transmit Holding Register: U0THR contains the top most byte in Tx FIFO and in this case its the newest(latest) transmitted data. As in the case with U0RBR , we must set DLAB=0 to access UxTHR for write operation.
Baud Rate Setup related registers
1) UxDLL and UxDLM – Divisor Latch registers: Both of them hold 8-bit values. These register together form a 16-bit divisor value which is used in baud rate generation. UxDLM holds the upper 8-bits and U0DLL holds the lower 8-bits and the formation is “[U0DLM:U0DLL]“. Since these form a divisor value and division by zero is invalid, the starting value for U0DLL is 0x01 (and not 0x00). Please keep this in mind while doing baud-rate calculations. In order to access and use these registers properly, DLAB bit in UxLCR must be first set to 1.
2) UxFDR – Fractional Divider Register : It is used to set the prescale value for baud rate generation. The input clock is the PCLK and output is the desired clock defined by this register. This register actually holds to different 4-bit values (a divisor and a multiplier) for prescaling which are:
- Bit [3 to 0] – DIVADDVAL: This is the prescale divisor value. If this value if 0 then fractional baud rate generator wont have any effect on Uart Baud rate.
- Bit [7 to 4] – MULVAL: This is prescale multiplier value. Even if fractional baud rate generator is not used the value in this register must be more than or equal to 1 else UART0 will not operate properly.
- Other Bits reserved.
Remark from User-manual:” If the fractional divider is active (DIVADDVAL > 0) and DLM = 0, the value of the DLL register must be 2 or greater!”
Control and Status Registers
1) UxFCR – FIFO Control Register: Used to control Rx/Tx FIFO operations.
- Bit 0 – FIFO Enable: 1 to Enable both Rx and Tx FIFOs and 0 to disable.
- Bit 1 – Rx FIFO Reset: Writing a 1 will clear and reset Rx FIFO.
- Bit 2 – Tx FIFO Reset: Writing a 1 will clear and reset Tx FIFO.
- Bits [7 to 6]: Used to determine that how many Rx FIFO characters must be written before an interrupt is activated.
- Others bits are reserved.
2) UxLCR – Line Control Register : Used to configure the UART block (i.e the data format used in transmission).
- Bit [1 to 0] – Word Length Select: Used to select the length of an individual data chunk. [00] for 5 bit character length. Similarly [01] , [10] , [11] for 6 , 7 , 8 bit character lengths respectively.
- Bit 2 – Stop bit select: 0 for using 1 stop bit and 1 for using 2 stop bits.
- Bit 3 – Parity Enable: 0 to disabled Partiy generation & checking and 1 to enable it.
- Bit [5 to 4] – Parity Select: [00] to Odd-parity , [01] for Even-parity , [10] for forced “1”(Mark) parity and [11] for forced “0”(Space) parity.
- Bit 6 – Break Control: 0 to disable break transmission and 1 to enable it. TxD pin will be forced to logic 0 when this bit is 1!
- Bit 7 – Divisior Latch Access bit: 0 to disable access to divisor latches and 1 to enable access.
3) UxLSR – Line Status Register: used to read the status of Rx and Tx blocks. Bits 1 to 4 get cleared after reading UxLSR.
- Bit 0 – Receiver Data Ready(RDR): 0 means UxRBR is empty(i.e Rx FIFO is empty) and 1 means UxRBR contains valid data.
- Bit 1 – Overrun Error(OE): 1 means Overrun has occured, 0 otherwise. Overrun is the condition when RSR(Receive Shift Register) has new character assembled but the RBR FIFO is full and the new assembled character was eventually lost since no data is written into FIFO when its full.
- Bit 2 – Parity Error(PE): 1 means a parity error has occured else not. When the value of the parity bit in the recieved character is in wrong state then a parity error occurs.
- Bit 3 – Framing Error(FE): 1 means that a framing error has taken place else not. Framing error occurs when the stop bit of a received character is zero.
- Bit 4 – Break Interrupt: 1 means that it has occured else not. A Break Interrupt occurs when the RxD line is pulled low (i.e all 0s) i.e held in spacing state for 1 full character after which Rx Block goes into Idle state. Rx Block gets back to active state when RxD pin is pulled high (i.e all 1s) i.e held in marking state for 1 full character.
- Bit 5 – Transmit Holding Register Empty(THRE): 0 means UxTHR has valid data and 1 means its empty.
- Bit 6 – Transmitter Empty(TEMT): 0 means UxTHR and/or UxRSR has valid data and 1 means that both UxTHR and UxRSR are empty.
- Bit 7 – Error in RX FIFO(RXFE): 0 means that UxRBR has no Rx Errors or Rx FIFO is disabled(i.e 0th bit in U0FCR is 0) and 1 means that UxRBR has at-least one error. Note: This bit is cleared only if UxLSR is read and there are no other subsequent errors in Rx FIFO, else this bit will stay 1
4) UxTER – Transmit Enable Register: This register is used to enable UART transmission. When bit-7 (i.e TXEN) is set to 1 Tx block will be enabled and will keep on transmitting data as soon as its ready. If bit-7 is set to 0 then Tx will stop transmission. Other bits are reserved.
Interrupt Related Registers
1) UxIER – Interrupt Enable Register: Set a bit to 0 to disable and 1 to enable the corresponding interrupt. Other bits are reserved.
- Bit 0 – RBR Interrupt Enable
- Bit 1 – THRE Interrupt Enable
- Bit 2 – RX Line Status Interrupt Enable
- Bit 8 – ATEOInt (End of Auto Baud Interrupt) Enable
- Bit 9 – ATBOInt (Auto Baud Time-Out Interrupt) Enable
2) UxIIR – Interrupt Identification Register: This register is organized as follows:
- Bit 0 – Interrupt Pending : 0 means at-least one interrupt is pending, 1 means no interrupts are pending. Note: This bit is ACTIVE LOW!
- Bits [3 to 1] – Interrupt Identification : [011] implies Receive Line Status(RLS) , [010] implies Receive Data Available(RDA) , 110 implies Character Time-out Indicator(CTI) , [001] implies THRE Interrupt.
- Bits [7 to 6] – FIFO Enable.
- Bit 8 – ABEOInt : 1 means Auto Baud Interrupt has successfully ended and 0 otherwise.
- Bit 9 – ABTOInt : 1 means Auto Baud Interrupt has Timed-out.
- All others bits are reserved.
UART Baud Rate Calculations
The main formula for calculating baud rate is given as:
Where DIVADDVAL & MULVAL are part of “Fractional Rate Divider” or “Baud-Prescaler” which is used in Baud-Rate generation. This “Fractional Divider” is only active when DIVADDVAL > 0. This formula is pretty common for LPC ARM micro-controllers.
which can be further simplified to :
X
with following conditions strictly applied:
- 0 < MULVAL <= 15
- 0 <= DIVADDVAL <= 14 - if DIVADDVAL > 0 & DLM = 0 then, DLL must be >= 2
- DIVADDVAL < MULVAL
Where PCLK is the Peripheral Clock value in Hz , DLM and DLL are the divisor registers which we saw earlier and finally DIVADDVAL and MULVAL are part of the Fractional baudrate generator register.
As it can been seen this formula has 2 prominent parts which are: A Base value and a Fraction Part(Prescaler) i.e:
This Fraction Part i.e. the Fractional Divider or the “Baud Rate Prescaler” can be used to scale down or keep the base value as it is (when disabled). Hence, its very useful for fine-tuning and getting the baudrate as accurate as possible.
Now we know the formula, how we do actually start ?
1) The Dirty and Simplest Method:
The quickest and also the dirtiest(accuracy wise) method without using any algorithm or fine-tuning is to set DLM=0 and disable the Fractional Divider. In this case MULVAL=1 and DIVADDVAL=0 which makes the Fraction Part(FP) = 1. Now we are left with only 1 unknown in the equation which is UxDLL and hence we simplify the equation and solve for UxDLL.
In my opinion, if possible, you must stay away from above method as it works only for particular bauds & specific PCLK value and moreover computed UxDLL might get out of range i.e. > 255 in which case you have to start increasing DLM and recompute a new value for DLL.
2) A better Method/Algorithm (Recommended):
In these method we again start with DLM=0 , DIVADDVAL=0(i.e. Fractional divider disabled) and MULVAL=1 and get an initial value for DLM. If you are lucky you will get a very close baudrate to the desired one. If not then we perform some fine-tuning using DLM, MULVAL and DIVADDVAL and get a new value for DLM. There is on one single method to perform fine-tuning and one can also make an algorithm for computing the best match given the desired baudrate and PCLK. The fine-tuning method or Algorithm which I have given below is a basic one suitable for beginners(in my opinion though). A more invloved Iterative Algorithm is given on Page 324(Rev 4.1) of the user manual.
Example: PCLK = 25 Mhz and Required Baud Rate is 115200 bauds.
Lets start with DLM = 0 , DIVADDVAL = 0 and MULVAL = 1
We have PCLK = 25 Mhz = 25 x 106 Hz
So the equation now gets simplified and we can find DLL.
We get UxDLL = 13.56, since it must be an integer we round it to 14. With UxDLL = 14 our actual baud rate will be = 111607.14 with a error of 3592.86(magnitude) which gives a relative error of 3.11%. Since its greater than 1.1% error specification we cannot use this. Now all we have to do is to get this error as low as possible. This can be done by multiplying it by a suitable fraction defined using MULVAL and DIVADDVAL – as given in equation. But since MULVAL & DIVADDVAL can be maximum 15 & 14 receptively, we don’t have much control over the Fraction Part(FP) value.
First lets compute the Required Fraction Part(FP) value given by FPrequired = [Desired Baud / Actual Baud Rate]. In our case its FPrequired = 1.032. Note that this is the required “Baud Prescaler” value that needs to be multiplied by Base value we got above to bring back it to ~115200. Now we have a problem – the max value for the Fractional part is 1(When Fractional Divider is disabled), but in this case we need an upscaling value which is not possible. Only downscaling is possible(when Fractional Divider is Enabled). So what do we do now? Well, a few approaches can be used. One of the simple approach is to decrease the computed DLL value so that our final baudrate overshoots i.e. we get a positive error. So, then we can try to scale it down using Fractional Part (i.e. MULVAL and DIVADDVAL) to get near to desired baud rate.
Lets set UxDLL = 12. We now get new baud rate = 130208.33 with error = +15008.33 from 115200. Now, we have FPrequired = 0.8847. We must try to get Fractional Part of equation as close as possible to 0.8847. The closest possible Fraction to 0.8847 is FP = 0.8823 which is when MULVAL=15 and DIVADDVAL=2. So, multiplying that with computed baud we get: [Base] x [Fraction Part(FP)] = 130208.33 x 0.8823 = 114882.8 ~ 114882 which is pretty bang on with an error of 318(magnitude) i.e. 0.27% Relative Error that is well with in 1.1% spec. Hence, we can use the following settings:
PCLK = 25 MHz
U0DLL = 12
U0DLM = 0
MULVAL = 15
DIVADDVAL = 2
How to Configure and Initialize UART
Once you know how UART communication works, configuring and initializing UART is pretty straight forward. In Source Code Examples for this tutorial we will use UART0 with following configuration:
- BaudRate = 115200 (with PCLK=25Mhz)
- Data Length = 8 bits
- No Parity Bit
- and 1 Stop Bit
Note: Its your responsibility to make it sure that configuration is same on both the communicating ends.
As seen in example above: in order to get 115200(114882 actually) bauds at 25Mhz PCLK we must use the following settings for baud generation :
Now, lets write a function “InitUART0()” which we can use initialize and configure UART0:
#define MULVAL 14
#define DIVADDVAL 2
#define Ux_FIFO_EN (1<<0)
#define Rx_FIFO_RST (1<<1)
#define Tx_FIFO_RST (1<<2)
#define DLAB_BIT (1<<7)
void InitUART0(void)
{
LPC_PINCON->PINSEL0 |= (1<<4) | (1<<6); //Select TXD0 and RXD0 function for P0.2 & P0.3!
//LPC_SC->PCONP |= 1<<3; //Power up UART0 block. By Default it is enabled after RESET.
LPC_UART0->LCR = 3 | DLAB_BIT ; /* 8 bits, no Parity, 1 Stop bit & DLAB set to 1 */
LPC_UART0->DLL = 12;
LPC_UART0->DLM = 0;
//LPC_UART0->IER |= ..; //Edit this if want you to use UART interrupts
LPC_UART0->FCR |= Ux_FIFO_EN | Rx_FIFO_RST | Tx_FIFO_RST;
LPC_UART0->FDR = (MULVAL<<4) | DIVADDVAL; /* MULVAL=15(bits - 7:4) , DIVADDVAL=2(bits - 3:0) */
LPC_UART0->LCR &= ~(DLAB_BIT);
//Now since we have applied DLL and DLM we now lock or freeze those valuse by diabling DLAB i.e DLAB=0
//Baud= ~115200(114882). Now we can perform UART communication!
}
Once this is done you are now ready to transfer data using U0RBR and U0THR registers.
Connections between MCU and PC or Laptop
Here we can have 2 possible scenarios.
- Your PC/Laptop(older ones) already has a serial port. In this case you will need a RS232 to TTL converter.
- Your PC/Laptop doesn’t have a serial port and you are using USB to Serial converter(FTDI ones, etc..).
Please refer to the connection diagrams & terminal software configuration(and COM ports) as given in Basic Uart Tutorial.
Here is a configuration screenshot for terminal software PuTTYtel which we will be using for examples :
Note: You can get PuTTYtel from Here. Direct download link: Here. You can also use other similar terminal software as well.
LPC1768 UART Programming Examples
Now its time to actually use UART in real life! Lets do some communication between your LPC1768(or similar MCU like LPC1769) MCU and PC/Laptop. Before we get into actual examples for LPC1768, first lets define 2 functions which will be used to Read and Write Data from UART block. The code is based on CMSIS which is the Base library for ARM Cortex Micrcontrollers.
U0Read() – Read Data from UART0:
#define RDR (1<<0) //Receiver Data Ready
char U0Read(void)
{
while(!(LPC_UART0->LSR & RDR)); //wait until any data arrives in Rx FIFO
return LPC_UART0->RBR;
}
U0Write() – Write Data to UART0:
#define THRE (1<<5) //Transmit Holding Register Empty
void U0Write(char data)
{
while(!(LPC_UART0->LSR & THRE)); //wait till the THR is empty
//now we can write to the Tx FIFO
LPC_UART0->THR = data;
}
LPC1768 UART Example 1
This demo will print “Hello from LPC1768!” on a new line in Serial Terminal repeatedly. The code given below uses Carriage-Return + Line-Feed (i.e. CR+LF) as New-Line(Enter or “\n”) character. If you are using Terminal on Linux or MacOS to interfacing LPC176x then only use Line-Feed(LF) character for New-Line.
/*(C) Umang Gajera - www.ocfreaks.com
More Embedded tutorials @ www.ocfreaks.com/cat/embedded
LPC1768 Basic UART Example 1 Source Code.
License : GPL.*/
#include <lpc17xx.h>
#define THRE (1<<5) //Transmit Holding Register Empty
#define MULVAL 15
#define DIVADDVAL 2
#define Ux_FIFO_EN (1<<0)
#define Rx_FIFO_RST (1<<1)
#define Tx_FIFO_RST (1<<2)
#define DLAB_BIT (1<<7)
#define LINE_FEED 0x0A //LF, For Linux, MAC and Windows Terminals
#define CARRIAGE_RETURN 0x0D //CR, For Windows Terminals (CR+LF).
void initUART0(void);
void U0Write(char data);
int main(void)
{
//SystemInit(); //This already gets called by CMSIS Startup Code.
char msg[] = { 'H','e','l','l','o',' ','f','r','o','m',' ','L','P','C','1','7','6','8','\0' };
int count=0;
initUART0();
while(1)
{
while( msg[count]!='\0' )
{
U0Write(msg[count]);
count++;
}
//Send NEW Line Character(s) i.e. "\n"
U0Write(CARRIAGE_RETURN); //Comment this for Linux or MacOS
U0Write(LINE_FEED); //Windows uses CR+LF for newline.
count=0; // reset counter
}
//return 0; //This won't execute normally
}
void U0Write(char txData)
{
while(!(LPC_UART0->LSR & THRE)); //wait until THR is empty
//now we can write to Tx FIFO
LPC_UART0->THR = txData;
}
void initUART0(void)
{
/*Assuming CCLK = 100Mhz and PCLK = 25Mhz!*/
LPC_PINCON->PINSEL0 |= (1<<4) | (1<<6); //Select TXD0 and RXD0 function for P0.2 & P0.3!
//LPC_SC->PCONP |= 1<<3; //Power up UART0 block. By Default it is enabled after RESET.
LPC_UART0->LCR = 3 | DLAB_BIT ; /* 8 bits, no Parity, 1 Stop bit & DLAB set to 1 */
LPC_UART0->DLL = 12;
LPC_UART0->DLM = 0;
//LPC_UART0->IER |= ..; //Edit this if want you to use UART interrupts
LPC_UART0->FCR |= Ux_FIFO_EN | Rx_FIFO_RST | Tx_FIFO_RST;
LPC_UART0->FDR = (MULVAL<<4) | DIVADDVAL; /* MULVAL=15(bits - 7:4) , DIVADDVAL=2(bits - 3:0) */
LPC_UART0->LCR &= ~(DLAB_BIT);
//Now since we have applied DLL and DLM we now lock or freeze those valuse by diabling DLAB i.e DLAB=0
//Baud= ~115200(114882). Now we can perform UART communication!
}
LPC1768 UART Example 2
In this example you will see the characters that you type on your keyboard i.e. we echo back the characters(data) received by MCU. Serial Terminals only displays the character that it receives via serial Port. When you type a character it is directly sent to the other side via serial port. Hence, in order for us to see the character which we typed, we need to send back the same character. This is what the program below does. Whenever MCU receives a character from the RXD0 pin, it will send the same character back to PC via the TXD0 Pin and it gets displayed in Terminal.
Here is a snippet of Example 2: (Full source code given is in KEIL ARM project files – attached below)
/*(C) Umang Gajera - www.ocfreaks.com
More Embedded tutorials @ www.ocfreaks.com/cat/embedded/
LPC1768 Basic UART Example 2 Source Code.
License: GPL.*/
#include <lpc17xx.h>
#define RDR (1<<0) //Receiver Data Ready
#define THRE (1<<5) //Transmit Holding Register Empty
#define MULVAL 15
#define DIVADDVAL 2
#define Ux_FIFO_EN (1<<0)
#define Rx_FIFO_RST (1<<1)
#define Tx_FIFO_RST (1<<2)
#define DLAB_BIT (1<<7)
#define LINE_FEED 0x0A //LF, For Linux, MAC and Windows Terminals
#define CARRIAGE_RETURN 0x0D //CR, For Windows Terminals (CR+LF).
#define ENTER CARRIAGE_RETURN //Ascii value/code for Enter is 0x0D i.e. CR
void initUART0(void);
char U0Read(void);
void U0Write(char data);
int main(void)
{
initUART0();
char data = 0;
while(1)
{
data = U0Read(); //Read Data from Rx
if(data == ENTER) //Check if user pressed Enter key
{
//Send NEW Line Character(s) i.e. "\n"
U0Write(CARRIAGE_RETURN); //Comment this for Linux or MacOS
U0Write(LINE_FEED); //Windows uses CR+LF for newline.
}
else
{
U0Write(data); //Tx Read Data back
}
}
//return 0; //Normally this won't execute
}
char U0Read(void)
{
while(!(LPC_UART0->LSR & RDR)); //wait until data arrives in Rx FIFO
return LPC_UART0->RBR;
}