Lightweight, interrupt-driven UART communication library for AVR microcontrollers. Designed for standard C and C++ bare-metal development workflows and intended for seamless integration with the AVR-CMake-Template.
The library provides buffered, non-blocking UART transmission and reception using software TX/RX ring buffers and the AVR USART peripheral. It also integrates with avr-libc stdio, allowing standard functions such as printf(), puts(), and getchar() to communicate through the UART interface.
- AVR Serial UART β A Hardware UART Library
- Interrupt-driven communication for both transmission and reception.
- Buffered TX/RX operation using software ring buffers.
- Non-blocking API for
uart_transmit()anduart_receive(). - Receive-buffer availability checking through
uart_available(). - Automatic baud-rate calculation from the requested baud rate and CPU clock frequency.
- Automatic selection of USART double-speed mode (
U2X0) when normal-speed mode produces excessive baud-rate error. - 8 data bits, 1 stop bit, no parity (8N1) frame configuration.
stdiointegration for standard C input/output functions.- Designed for both C and C++ AVR projects.
- Suitable for integration with the AVR-CMake-Template library structure.
This library is specifically built on top of and designed to integrate with the AVR-CMake-Template repository. For smooth development and compilation, ensure your project is built using that template as its base.
Your host environment must meet the base project toolchain requirements:
- Base Project Template: AVR-CMake-Template (verify your main application setup follows this structure).
- AVR Toolchain: Microchip AVR Toolchain (
avr-gcc,binutils-avr, andavr-libc). - Build System: CMake (v3.16+) and a build generator like Ninja or GNU [Make].
Note: For platform-specific toolchain installation steps (Windows/MSYS2 UCRT64, Linux/Debian/Ubuntu, or macOS/Homebrew), please refer directly to the AVR-CMake-Template Prerequisites section.
When this library is used as part of a project based on the AVR-CMake-Template, using a Git submodule is recommended.
A submodule keeps the library as an independent Git repository while allowing the application project to track the exact library revision it depends on.
From the root of your AVR application project:
git submodule add https://github.com/Arif-Rachmat-AVR/avr-uart.git Lib/uartYour project can then have a structure similar to:
Your-AVR-Project/
βββ CMakeLists.txt
βββ Lib/
β βββ uart/
β βββ CMakeLists.txt
β βββ include/
β β βββ uart.h
β βββ src/
β βββ uart.c
βββ src/
β βββ main.cpp
βββ ...
When cloning an existing project that contains this library as a submodule, use:
git clone --recurse-submodules <your-project-url>If the project has already been cloned without its submodules:
git submodule update --init --recursiveUsing a submodule is particularly useful for the AVR-CMake-Template workflow because:
- The library remains an independent repository.
- The application stores the exact library commit it uses.
- Updating the library can be done deliberately rather than implicitly.
- The library can be shared between multiple AVR projects.
- The complete application project can be cloned together with all of its dependencies.
For long-term projects, this is generally preferable to manually cloning libraries into the project directory.
For standalone projects or quick experiments, the library can also be cloned directly into the Lib directory:
git clone https://github.com/Arif-Rachmat-AVR/avr-uart.git Lib/uartThis method is simpler, but the application repository does not automatically track which version of the UART library it is using.
Include the UART header in your application:
#include "uart.h"
int main(void)
{
uart_init(9600, 16000000UL);
for (;;) {
uint8_t data;
if (uart_receive(&data)) {
uart_transmit(data);
}
}
}The example above initializes the UART at 9600 baud with a 16 MHz CPU clock, then continuously receives and echoes bytes.
uart_transmit() does not wait for the UART hardware to finish transmitting. Instead, the byte is placed into the TX buffer and transmitted automatically by the UART data-register-empty interrupt.
Similarly, received bytes are collected by the RX interrupt and stored in the RX buffer until the application retrieves them.
The following example demonstrates a simple command-style UART application:
#include <stdint.h>
#include "uart.h"
int main(void)
{
uart_init(115200, 16000000UL);
for (;;) {
uint8_t data;
if (uart_receive(&data)) {
/* Echo received data */
uart_transmit(data);
/* Example command */
if (data == '\r') {
uart_transmit('\n');
}
}
}
return 0;
}Because reception is interrupt-driven, the application does not need to continuously poll the hardware UART data register.
The library also redirects stdin and stdout to the UART stream.
This allows standard C I/O functions to communicate through UART:
#include <stdio.h>
#include "uart.h"
int main(void)
{
uart_init(115200, 16000000UL);
printf("Hello from AVR UART!\r\n");
for (;;) {
int c = getchar();
printf("Received: %c\r\n", c);
}
}This can be useful for:
- Debug output
- Serial command interfaces
- Logging
- Interactive configuration
- Simple terminal-based applications
The library automatically sends a carriage return ('\r') before a newline ('\n') when using the standard output stream.
Therefore:
printf("Hello\n");is transmitted as:
Hello\r\n
Although the underlying UART API is buffered and non-blocking, the stdio wrapper functions use a waiting loop when the software buffer is temporarily unable to accept more data or when no received character is available.
Therefore, code using printf() or getchar() should not be assumed to have the same non-blocking behavior as uart_transmit() and uart_receive().
Initializes the UART peripheral using the specified baud rate and CPU clock frequency.
uart_init(115200, 16000000UL);Parameters:
| Parameter | Description |
|---|---|
baud_rate |
Desired UART baud rate in bits per second |
f_cpu |
CPU clock frequency in hertz |
The function configures the UART for:
- 8 data bits
- 1 stop bit
- No parity
- Transmitter enabled
- Receiver enabled
- Receive-complete interrupt enabled
The function also calculates the required UBRR value and enables USART double-speed mode when the normal-speed configuration produces more than approximately 2% baud-rate error.
Note:
uart_init()enables global interrupts throughsei().
Returns the number of bytes currently waiting in the receive buffer.
if (uart_available() > 0) {
/* Data is available */
}Return value: Number of bytes currently stored in the RX buffer.
Attempts to retrieve one byte from the receive buffer.
uint8_t data;
if (uart_receive(&data)) {
/* Process received byte */
}Parameters:
| Parameter | Description |
|---|---|
data |
Pointer where the received byte will be stored |
Return value:
| Value | Meaning |
|---|---|
true |
A byte was successfully received |
false |
RX buffer is empty |
This function is non-blocking.
Attempts to queue one byte for transmission.
if (!uart_transmit('A')) {
/* TX buffer is full */
}Parameters:
| Parameter | Description |
|---|---|
data |
Byte to transmit |
Return value:
| Value | Meaning |
|---|---|
true |
Byte successfully added to TX buffer |
false |
TX buffer is full |
This function is non-blocking. Actual transmission is performed asynchronously by the UART Data Register Empty interrupt.
The current implementation uses USART0-compatible AVR registers and interrupt vectors.
| Parameter | Configuration |
|---|---|
| USART Peripheral | USART0 |
| Frame Format | 8N1 |
| Baud Rate | Configurable |
| CPU Frequency | Supplied to uart_init() |
| TX Handling | USART Data Register Empty interrupt |
| RX Handling | USART Receive Complete interrupt |
| TX Buffer | Software ring buffer |
| RX Buffer | Software ring buffer |
The implementation currently accesses registers and vectors such as:
UCSR0A
UCSR0B
UCSR0C
UBRR0H
UBRR0L
UDR0
USART_RX_vect
USART_UDRE_vect
Therefore, compatibility currently depends on the target AVR providing this USART0 register and interrupt interface.
The UART uses finite software buffers for both transmission and reception.
When the TX buffer is full, uart_transmit() returns false.
When the RX buffer is full, newly received bytes are discarded.
Applications that receive data at a high rate should periodically process the RX buffer to prevent overflow.
Calling:
uart_init(...);automatically enables global interrupts.
Take this into consideration when initializing other interrupt-driven peripherals.
The library owns the USART0 peripheral and its associated UART interrupt vectors.
Another library or application component must not attempt to configure the same USART peripheral or define the same interrupt handlers independently.
Functions such as printf() are convenient, but they may produce significantly more code and runtime overhead than directly using uart_transmit().
For size- or performance-sensitive firmware, the direct UART API is recommended.
- Support additional AVR USART instances such as USART1, USART2, etc.
- Expand compatibility across more AVR device families.
- Make UART peripheral selection configurable.
- Make TX/RX buffer sizes configurable through a dedicated configuration header.
- Add optional UART configuration for parity and stop-bit selection.
- Add configurable frame formats beyond 8N1.
- Improve baud-rate calculation and validation for a wider range of clock frequencies.
- Add optional error reporting for UART framing, parity, and overrun errors.
- Add hardware flow-control support where supported by the target MCU.
- Add automated tests and target-specific compatibility examples.
This project is licensed under the MIT License β free for both personal and commercial use.