UART is a type of serial interface, as opposed to a parallel interface. A parallel interface can work at higher speeds but the disadvantage is that it needs multiple input/output lines. Other examples of serial interfaces are SPI and I2C.
Features
- Custom Baud rate
- Asynchronous or synchronous modes
- Supports serial frames with 5, 6, 7, 8, or 9 data bits
- Odd, even or no parity
- Error detection
- Multi-processor communication mode used to address multiple devices on the same serial bus
- Double speed asynchronous communication mode
Contents
- USART Characteristics
- Library Structure
- Using UART library
- Initialization function
- Send a byte
- Send a string
- Send bytes
- Send integer number
- Send float number
- Send hex numbers
- Check for new received data
- Wait for transmission complete
- Read a byte
- Read bytes
- Read bytes until
- Disable UART
- Flush
- Redirect received data
- Enable Start Frame Detector
- Enable One Wire Mode
- Receiver error flags
- Enable the Multi-processor Communication mode (MPCM)
- Disable the Multi-processor Communication mode (MPCM)
- Send UART address
- Multi-processor Communication mode (MPCM) Example
- Code example
- Download
USART Characteristics
USART can be set to work in the following modes: Normal asynchronous, Double Speed asynchronous, Master synchronous, Slave synchronous mode and Multi-Processor Communication Mode.
Asynchronous and synchronous serial
Asynchronous means that data is transferred without support from an external clock signal since the clock is generated internally and is synchronized to the incoming serial frames.
Synchronous mode requires an extra clock line but has the advantage that it can work at higher speed rates that asynchronous mode.
In Master synchronous the main device generates and outputs the clock on XCKn pin while in Slave synchronous mode the device uses the clock generated by the Master.
Multi-Processor Communication Mode
The Multi-Processor Communication mode enables several slave MCUs to receive data from a master MCU. This is done by first decoding an address frame to find out which MCU has been addressed. If a particular slave MCU has been addressed, it will receive the following data frames as normal, while the other slave MCUs will ignore the received frames until another address frame is received.
Frame formats
A serial frame is composed of a character of data bits with synchronization bits (start and stop bits), and optionally a parity bit for error checking.
A frame starts with the start bit, followed by the data bits (from 5 up to 9 data bits in total): first the least significant data bit, then the next data bits ending with the most significant bit. If enabled, the parity bit is inserted after the data bits, before the one or two stop bits. When a complete frame is transmitted, it can be directly followed by a new frame, or the communication line can be set to an idle (high) state. The figure below illustrates the possible combinations of the frame formats. Bits inside brackets are optional.
IDLE: No transfers on the communication line (Rx or Tx). An IDLE line
must be high.
St: Start bit, always low.
(n): Data bits (0 to 8).
P: Parity bit. Can be odd or even.
Sp: Stop bit, always high.
The Stop bit can be followed by a Start bit (low) for another frame, or Idle (high). All devices connected to the UART bus must use the same frame format and baud rate.
Baud Rate
The baud rate specifies how fast data is sent over a serial line. It's usually expressed in units of bits-per-second (bps). Most commonly used baud rates are 9600 and 115200 bps.
Wiring
Some AVRs have two UARTs with pins named RXDn (Receiver) and TXDn (Transmitter). n represents the UART number - 0 or 1.
Lets say you want to interface UART 0 with a device that has this pins: RX and
TX. Then you connect RXD0 to TX and TXD0 to RX. Also they must share a common
ground.
In Synchronous mode you also need the XCKn pin for clock.
For ATmega328PB the TX/RX pins are as follows:
TXD0 - PD1, RXD0 - PD0, XCK0 - PD4 for UART 0
TXD1 - PB3, RXD1 - PB4, XCK1 - PB5 for UART 1
For other types of microcontrollers check the Pin Configurations chapter of the datasheet.
Library Structure
When a microcontroller has two USART peripherals, both of them can be used with a single library, by using the appropriate structure object as a function argument. There are two structure objects defined, named: uart0 and uart1.
The file uart.h defines some user settings that can be modified accordingly.
TX and RX buffers
Transmitted and Received data are buffered in two circular arrays that by
default have a size of 32 bytes each. The size can be from 1 to 255 bytes
except when using 9-bit mode - in this case the buffer size must be 32 bytes.
Having a larger array can be a bit faster. When using higher baud rates it is
recommended a bigger buffer to avoid loosing incoming data and to give more
time for the microcontroller to process the data.
#define UART_TX_BUFFER_SIZE 32 #define UART_RX_BUFFER_SIZE 32
CPU clock frequency - F_CPU
#define F_CPU 16000000UL
F_CPU defines the processor clock frequency in Hertz and is used to calculate the baud rate.
F_CPU is best to be defined inside project properties in Microchip Studio (or your particular IDE) or a Makefile if custom Makefiles are used. See https://www.programming-electronics-diy.xyz/2024/01/defining-fcpu.html.
9-bit USART mode
#define UART_USE_9BITS 0
When using UART in 9 bit mode, the UART_USE_9BITS must be set to 1. For all other character sizes set this to 0.
Exclude UART1
#define UART_USE_UART1 1
Set this to 0 if the device has UART1 but is not used, to reduce code size.
Using UART library
TX pin
The transmission pin must be set as an output by the user, for the UART to work.
Initialization function
Sets the baud rate, frame format, enables UART interrupts and global
interrupts. The function can be used at any point to change baud rate or frame
parameters. It can also be used in case of any communications issues.
void UART_begin(UARTstruct_t* uart, float baudrate, uint8_t mode, uint8_t parity, uint8_t bits)
Parameters
UARTstruct_t* uart
Pointer to an uart structure object. E.g.: &uart0, &uart1.
uint32_t baudrate
Bits per second. E.g: 9600, 115200.
uint8_t mode
UART mode: Asynchronous, Synchronous Master/Slave mode. When using synchronous mode, the Data Direction Register for the XCKn pin controls whether the clock source is internal (Master mode) or external (Slave mode). The XCKn pin is only active when using synchronous mode.
Available constants:
UART_ASYNC - Asynchronous Normal mode. Asynchronous Double Speed mode will be selected automatically when baud rate is above 57600.
UART_SYNC - Synchronous Master/Slave mode.
uint8_t parity
Even or odd parity can be selected for error checking. Available constants:
UART_NO_PARITY, UART_EVEN_PARITY, UART_ODD_PARITY
uint8_t bits
Number of bits in the frame, 5, 6, 7, 8 or 9. Most common format is having 8 bits but if you have small numbers and speed is important, using 5, 6 or 7 bits can provide a bit more speed. The maximum number that can be transmitted is 2^n where n is the number of bits. 9 bits is usually only used in Multi-processor Communication mode where the 9'th bit indicates an address or data frame.
UART_5_BIT, UART_6_BIT, UART_7_BIT, UART_8_BIT, UART_9_BIT.
Usage:
UART_begin(&uart0, 115200, UART_ASYNC, UART_NO_PARITY, UART_8_BIT);
Send a byte
Puts a byte of data in the transmit buffer and enables the Data Register Empty Interrupt.
uint8_t UART_send(UARTstruct_t* uart, char_size_t data)
Parameters
data
The data byte. By default data type is 8-bit. When UART_USE_9BITS is 1, the data type is 16-bit to accommodate for the 9th bit.
Return: 0 on success, 1 on failure.
Usage:
// Send 'A' ASCII character UART_send(&uart0, 'A'); // Send '3' ASCII symbol UART_send(&uart0, '3'); // Send number of the '3' ASCII symbol UART_send(&uart0, 51);
Send a string
Send a null terminated string.
uint8_t UART_sendString(UARTstruct_t* uart, char* s)
Parameters
char* s
A string of characters.
Return: 0 on success, 1 on failure.
Usage:
UART_sendString(&uart0, "Sent from UART0"); // Or using the '\n' character to start a new line in a serial terminal. UART_sendString(&uart0, "Sent from UART0\n");
Send bytes
Send a series of bytes in a buffer.
uint8_t UART_sendBytes(UARTstruct_t* uart, char_size_t* buff, uint8_t length)
Parameters
uint8_t* buff
An array of bytes.
uint8_t length
Length of the array.
Return: 0 on success, 1 on failure.
Usage:
const uint8_t bufferSize = 20; uint8_t buff[bufferSize]; // bufferSize argument can be smaller to // send only first few bytes UART_sendBytes(&uart0, buff, bufferSize);
Send integer number
Convert an integer number into a string array and send it over UART.
void UART_sendInt(UARTstruct_t* uart, INT_SIZE number)
Parameters
INT_SIZE number
INT_SIZE is defined in the "utils.h" file, and can be int32_t or int64_t
Send float number
Convert a float number into a string array and send it over UART.
void UART_sendFloat(UARTstruct_t* uart, float number, uint8_t decimals)
Parameters
float number
A float number.
uint8_t decimals
Number of digits after the dot.
Send hex numbers
Convert a 1- 2- or 4-byte integer number into hexadecimal value and send it over UART.
void UART_sendHex8(UARTstruct_t* uart, uint8_t value) void UART_sendHex16(UARTstruct_t* uart, uint16_t value) void UART_sendHex32(UARTstruct_t* uart, uint32_t value)
Check for new received data
Returns true if new data is available and false otherwise.
bool UART_available(UARTstruct_t* uart)
Wait for transmission complete
Waits in a while loop until all bytes in the buffer have been transmitted. Can be used before putting the microcontroller to sleep to ensure all data has been transmitted. Baud rate and F_CPU are used to calculate the time it takes for the last byte to be transmitted after the interrupt is disabled but the last byte is still transmitted.
void UART_isSending(UARTstruct_t* uart, float fcpu, float baudrate)
Parameters
fcpu
CPU clock frequency in Hertz.
baudrate
UART baudrate used in initialization function.
Read a byte
Returns the next received byte or 0 if no new data is available. Should be
used only if UART_available() returns true.
char_size_t UART_read(UARTstruct_t* uart)
Read bytes
Read received bytes into the provided buffer. The function terminates if the
specified length has been read, or it times out (around 0.5s).
uint8_t UART_readBytes(UARTstruct_t* uart, char_size_t* buff, uint8_t length)
Parameters
uint8_t* buff
An array buffer where to put incoming data.
uint8_t length
Length of the array.
Return: the number of characters read.
Usage:
const uint8_t bufferSizeRX = 20; uint8_t buff[bufferSizeRX]; if(UART_available(&uart0)){ // Read serial data UART_readBytes(&uart0, buff, bufferSizeRX); // Now 'buff' contains received data }
Read bytes until
Read received bytes into the provided buffer. The function terminates if the specified length has been read, the termination character has been found or it times out (around 0.5s). The termination character is not included in the buffer.
uint8_t UART_readBytesUntil(UARTstruct_t* uart, char character, char_size_t* buff, uint8_t length)
Parameters
char character
The termination character. When this character is encountered, the function terminates.
uint8_t* buff
An array buffer where to put incoming data.
uint8_t length
Length of the array.
Return: the number of characters read.
Disable UART
Disable UART transmitter, receiver and interrupts. According to the datasheet:
"When the transmitter is disabled, it will no longer override the TxDn pin, and the pin direction is set as input automatically by hardware, even if it was configured as output by the user."
void UART_end(UARTstruct_t* uart)
Flush
If the UART buffer has to be flushed during normal operation, due to for
instance an error condition, this reads the UDRn I/O location until the RXCn Flag is
cleared.
void UART_flush(UARTstruct_t* uart)
Redirect received data
Redirects received data to a user defined function.
void UART_setRXhandler(UARTstruct_t* uart, void (*rx_func)(uint8_t c))
Usage:
void rxHandler(uint8_t c){ // This function is called inside the RX interrupt // and it should not take long time. // 'c' is the received byte. } UART_setRXhandler(&uart0, &rxHandler);
Enable Start Frame Detector
The Start Frame detector is able to wake-up the system from Idle or Standby Sleep modes when a high (IDLE) to low (START) transition is detected on the RxD line.
Not all microcontrollers have this feature. If the micro is programmable via UPDI then most likely supports this function.
void UART_enableStartFrameDetection(UARTstruct_t* uart)
Enable One Wire Mode
In this mode only the TX pin is needed since the TX pin is connected to the RX pin internally. Also open-drain mode is enabled. If the receiver is enabled when transmitting it will receive what the transmitter is sending. This can be used to check that no one else is trying to transmit since received data will not be the same as the transmitted data. For more information about USART in one wire mode, see the application note from Microchip linked in the download section.
Internal pull-up resistor for TX pin needs to be enabled by the user since the pin number will vary depending of the microcontroller.
PORTA.PIN6CTRL |= PORT_PULLUPEN_bm; // pin 6 on port A
Not all microcontrollers have this feature. If the micro is programmable via UPDI then most likely supports this function.
void UART_enableOneWireMode(UARTstruct_t* uart)
The tricky part is to discard data from own transmission. A proper implementation depends on the application, however I provide this code example as an idea.
char_size_t buff[10] = {0}; bool sent_bytes_discarded = true; // Enable pin internal pull-up resistor PORTA.PIN6CTRL |= PORT_PULLUPEN_bm; // Enable 1-wire mode UART_enableOneWireMode(&uart0); // Send 10 characters of data UART_sendString(&uart0, "Send data\n"); sent_bytes_discarded = false; // set flag // Wait to receive data from own transmission if(UART_available(&uart0)){ // Read and discard 10 bytes transmitted previously UART_readBytes(&uart0, buff, 10); sent_bytes_discarded = true; // clear flag }
// Read data from other devices only after the bytes
// from own transmission have been discarded if(UART_available(&uart0) && sent_bytes_discarded == true){ UART_readBytes(&uart0, buff, 5); // number of bytes to read is just an example }
Receiver error flags
After each byte is received, the error bits in the USART register are saved in
a variable. The following functions can be used to check if an error
occurred and the type of error. At the end, the error flag must be cleared.
if(UART_isError(&uart0)){ if(UART_frameError(&uart0)){ UART_sendString(&uart0, "Frame error"); } if(UART_bufferOverflowError(&uart0)){ UART_sendString(&uart0, "Buffer overflow"); } if(UART_parityError(&uart0)){ UART_sendString(&uart0, "Parity error"); } UART_clearErrorFlags(&uart0); }
Frame Error
The Frame Error Flag indicates the state of the first stop bit of the next readable frame stored in the receive buffer. The FE Flag is zero when the stop bit was correctly read as '1', and the FE Flag will be one when the stop bit was incorrect (zero). This flag can be used for detecting out-of-sync conditions, detecting break conditions and protocol handling.
Parity Error
The Parity Error Flag indicates that the next frame in the receive buffer had a Parity Error when received.
Buffer Overflow
This flag is set if a Buffer Overflow condition is detected. A Buffer Overflow occurs when the receive buffer is full (two characters), it is a new character waiting in the Receive Shift register, and a new Start bit is detected.
Enable the Multi-processor Communication mode (MPCM)
After this function sets
the MPCM bit, frames that do not contain an address will be ignored by the
UART. Frames containing an address have the 9'th bit set to 1. The Transmitter is unaffected by the MPCM setting.
void UART_mpcmEnable(UARTstruct_t* uart)
Disable the Multi-processor Communication mode (MPCM)
Disables the Multi-processor Communication mode (MPCM). After this function
sets the MPCM bit to 0, data frames can be received. This should be used after
a valid address has been received after using UART_mpcmEnable().
void UART_mpcmDisable(UARTstruct_t* uart)
Send UART address
Select a device by sending an address frame over UART.
void UART_mpcmSelectDevice(UARTstruct_t* uart, uint8_t address)
Parameters
uint8_t address
Address of the device to select.
Usage:
// In MPCM mode the character size should be selected as 9-bits UART_begin(&uart0, 115200, UART_ASYNC, UART_NO_PARITY, UART_9_BIT); /* Subscriber (MCU) with address 0x01 */ // Enable 'waiting for address mode'. // Incoming data that doesn't have the 9th bit set to 1 // will be ignored by the USART peripheral. // Data cannot be received, only address data. uint8_t sub1_addr = 0x01; UART_mpcmEnable(&uart0); /* Subscriber (another MCU) with address 0x02 */ uint8_t sub2_addr = 0x02; UART_mpcmEnable(&uart0); /* Subscriber (yet another MCU) with address 0x03 */ // Subscribers can share the same address // if they are interested in the same content. uint8_t sub3_addr = 0x03; UART_mpcmEnable(&uart0); /* Influencer (MCU) */ // Influencer decides to interact with subscriber // with address 0x02 because they give support on Patron. // The function sends 0x02 as usual, except this time // the 9th bit is set to 1 to indicate that the data contains an address. UART_mpcmSelectDevice(&uart0, 0x02); // Subscribers receive the address in a while loop // and check if they are subscribed to the topic. // Example of subscriber 2 char_size_t data = 0; if(UART_available(&uart0)){ data = UART_read(&uart0); // Variable 'data' contains the address and also the 9th bit, like so 0x0102. // 0x02 is the address and 0x01 is the 9th bit. // This is because the 9-bit mode can be used without MPCM mode by this library. // The subscriber address is ORed with 0x0100 to set the 9th bit. // This is not necessary when the address is stored with the 9th bit set // but then the data type should be uint16_t instead of uint8_t. if(data == (sub2_addr | 0x0100)){ // Received address matches device address. // Disable MPCM to be able to receive data. UART_mpcmDisable(&uart0); } // A variable could be used based on which the application // could decide when to enable or disable MPCM. For example // bool enable MPCM = true or false after n bytes have been received. // To check if data is address: (data & 0x0100) checks if 9th bit is 1. }
Code example
// #define F_CPU 16000000 // defined inside uart.h #include "uart.h" #include <util/delay.h>
int main(void){ const uint8_t bufferSize = 20; char_size_t buff[bufferSize]; uint8_t bytes_read = 0; UART_begin(&uart0, 115200, UART_ASYNC, UART_NO_PARITY, UART_8_BIT); UART_begin(&uart1, 115200, UART_ASYNC, UART_NO_PARITY, UART_8_BIT); UART_sendString(&uart1, "I'm UART 1\r"); UART_sendString(&uart0, "Hi there!\r"); _delay_ms(2000); UART_sendString(&uart0, "Is this thing working?\r"); while(1){ if(UART_available(&uart0)){ // Read serial data in the 'buff' array bytes_read = UART_readBytes(&uart0, buff, bufferSize);
UART_sendString(&uart0, "Received ");
UART_sendInt(&uart0, bytes_read); UART_sendString(&uart0, " bytes\r"); if(bytes_read == bufferSize){ // Print received data to a serial terminal UART_sendBytes(&uart0, buff, bytes_read); UART_send(&uart0, '\r'); } // Check for reception errors if(UART_isError(&uart0)){ if(UART_frameError(&uart0)){ UART_sendString(&uart0, "Frame error"); } if(UART_bufferOverflowError(&uart0)){ UART_sendString(&uart0, "Buffer overflow"); } if(UART_parityError(&uart0)){ UART_sendString(&uart0, "Parity error"); } UART_clearErrorFlags(&uart0); } // end if UART_isError() } // end if UART_available() } }
Download
Changelog and license can be found at the beginning of the header files | ||
uart v2.2 | ||
uart.h | ||
uart.c | ||
utils v1.0 | ||
utils.h | Used by sendInt() and sendFloat() | |
utils.c | ||
External Links | ||
Termite terminal | Useful serial terminal to use with UART | |
Microchip - AN2658 - USART in One-Wire Mode | Microchip - USART in One-Wire Mode in PDF format | |
Microchip - AN2451 - Getting Started with Core Independent Peripherals on AVR | On modern AVR (UPDI devices) USART can be used as an Event Generator for the Event System where the received data can be filtered without the use of the CPU. This PDF file from Microchip describes how to use the Event System, LUTs, CCL and Sequential Logic. | |
Changelog | ||
v2.2 |
31-01-2024: - included a function used to wait in a while loop until all bytes in the buffer have been transmitted. |
|
v2.1 | - UART1 can now be excluded by a preprocessor directive to save space. | |
v2.0 |
- added support for modern AVR (UPDI devices). Tested on tinyAVR402. - all USART peripherals can now be controlled by a single library by the use of structures. - removed functions dedicated to 9-bit UART. Now 9-bit is supported by interrupts. |
No comments:
Post a Comment