Skip to content

Latest commit

Β 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

AVR Serial UART β€” A Hardware UART Library

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.


Table of Contents πŸ“‹


Features ✨

  • Interrupt-driven communication for both transmission and reception.
  • Buffered TX/RX operation using software ring buffers.
  • Non-blocking API for uart_transmit() and uart_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.
  • stdio integration for standard C input/output functions.
  • Designed for both C and C++ AVR projects.
  • Suitable for integration with the AVR-CMake-Template library structure.

Prerequisites ❗

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, and avr-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.


Installation πŸ› οΈ

Recommended: Git Submodule

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/uart

Your 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 --recursive

Why use a submodule?

Using 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.


Alternative: Git Clone

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/uart

This method is simpler, but the application repository does not automatically track which version of the UART library it is using.


Quick Start πŸš€

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.


Usage Example πŸ’‘

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.


stdio Integration πŸ–¨οΈ

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

Newline Behavior

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

Blocking Behavior of stdio

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().


API Reference πŸ“–

void uart_init(uint32_t baud_rate, uint32_t f_cpu)

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 through sei().


uint8_t uart_available(void)

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.


bool uart_receive(uint8_t *data)

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.


bool uart_transmit(uint8_t data)

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.


Hardware Configuration βš™οΈ

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.


Important Notes ⚠️

Buffer Capacity

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.

Global Interrupts

Calling:

uart_init(...);

automatically enables global interrupts.

Take this into consideration when initializing other interrupt-driven peripherals.

USART Resource Usage

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.

stdio Performance

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.


Roadmap πŸ“Œ

  • 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.

License πŸ“œ

This project is licensed under the MIT License β€” free for both personal and commercial use.

About

Lightweight, interrupt-driven UART library for AVR microcontrollers with buffered, non-blocking TX/RX communication and avr-libc stdio support. Designed for bare-metal C/C++ projects and seamless integration with the AVR-CMake-Template.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Contributors

Languages