A lightweight, buffered UART library for AVR microcontrollers.
- FIFO Circular Buffers: Efficient transmit and receive buffers with configurable sizes
- Interrupt-Driven: Non-blocking transmission and reception via UART interrupts
- Pattern Matching: Register callback functions to trigger on specific character sequences
- STDIO Integration: Optional stdio-style input/output
- Flexible Configuration: Statically (compile-time) or dynamically (runtime) configurable UART settings
- Portable: Port abstraction layer for different AVR variants (currently ATmega328P)
#include <uart.h>
int main(void) {
uart_setup();
uart_send_byte('A');
char c = uart_recv_byte();
uart_send("Hello, World!\r\n", 16);
uart_send_int(42);
return 0;
}There are two ways to use the avr-uart library in your project:
Use the API headers and link to the precompiled static library:
- Copy
include/,port/directories to your project - Copy
lib/libuart.a(generated after building) to your project - Add include paths:
-Iinclude -Iport - Link with:
-L. -luart
# Build the library first
make build-lib
# In your project:
avr-gcc -mmcu=atmega328p -Iinclude -Iport your_main.c -L. -luart -o your_project.elfThis approach is simpler but you cannot customize the library features at compile time.
Note: To enable runtime UART configuration in the precompiled library, build with:
RUNTIMECONF=1 make build-libThen in your code:
#define AVR_UART_RUNTIME_CONFIG
#include <uart.h>
struct uart_config cfg = {
.baud_rate = 115200,
.char_size = 8,
.stop_bits = 1,
.parity = UART_PARITY_NONE
};
uart_setup(&cfg);Copy the source files into your project and compile along with your code:
- Copy
include/,src/, andport/directories to your AVR project - Add
src/uart.candsrc/match.c(if using pattern matching) to your build - Configure your build system to include the appropriate include paths
- Define feature flags (like
AVR_UART_MATCH,AVR_UART_STDIO, etc.) in your build
# In your project Makefile:
CFLAGS += -I$(UART_DIR)/include -I$(UART_DIR)/port -DAVR_UART_MATCH
SRCS += $(UART_DIR)/src/uart.c $(UART_DIR)/src/match.cThis approach allows you to customize library features at compile time.
Configure the UART module using compile-time options. For feature flags (like STDIO, pattern matching, etc.), see the next section.
Define these macros in uart_config.h or before including uart.h:
| Macro | Description | Default |
|---|---|---|
UART_BAUD_RATE |
Baud rate (e.g., 9600, 115200) | 9600 |
UART_CHAR_SIZE |
Data bits (5-8) | 8 |
UART_STOP_BITS |
Stop bits (1 or 2) | 1 |
UART_PARITY |
Parity (UART_PARITY_NONE/EVEN/ODD) | UART_PARITY_NONE |
UART_TX_BUFFER_LEN |
TX buffer size | 64 |
UART_RX_BUFFER_LEN |
RX buffer size | 64 |
UART_MAX_SEQ_LEN |
Max pattern match length | 8 |
UART_MATCH_MAX |
Max number of patterns | 8 |
Define AVR_UART_RUNTIME_CONFIG before including uart.h:
#define AVR_UART_RUNTIME_CONFIG
#include <uart.h>
int main(void) {
struct uart_config cfg = {
.baud_rate = 115200,
.char_size = 8,
.stop_bits = 1,
.parity = UART_PARITY_NONE
};
uart_setup(&cfg);
// ...
}There are three ways to enable features and control compilation:
Uncomment the desired definitions in config/config.h:
/* Enable use of UART in I/O streams (stdin/stdout/stderr) */
//#define AVR_UART_IOSTREAM 1
/* Enable runtime UART configuration (call uart_setup with config struct) */
//#define AVR_UART_RUNTIME_CONFIG 1
/* Enable UART input pattern match */
//#define AVR_UART_MATCH 1
/* Use strncmp for pattern matching */
//#define AVR_UART_STRNCMP_MATCH 1
/* Emit a trigger signal that can be used by logic analyser to start capture */
//#define AVR_UART_EMIT_TRIGGER 1
/* SIM denotes that source code will compiled for simulation */
//#define AVR_UART_SIMULATION 1
/* Denotes that source code will be compiled for off-target testing */
//#define AVR_UART_SIMTEST 1
/* Demo mode with serial communication program */
//#define AVR_UART_DEMO 1
/* Enable debug build with debugging information and symbols */
//#define AVR_UART_DEBUG 1
/* Preserve compilation intermediaries */
//#define AVR_UART_SAVE_TEPMS 1
/* Set the compiler optimization level (-O0, -O1, -O2, -O3, -Os, etc.) */
//#define AVR_UART_OPTIM -OsPass flags to make:
IOSTREAM=1 make # Enable STDIO
RUNTIMECONF=1 make # Enable runtime configuration
MATCH=1 make # Enable pattern matching
TRIGGER=1 make # Enable trigger signal
SIM=1 make # Compile for simulation
SIMTEST=1 make # Compile for off-target testing
DEMO=1 make # Demo mode
DEBUG=1 make # Debug build
SAVETEMPS=1 make # Preserve intermediate filesDefine flags directly in your source code before including uart.h:
#define AVR_UART_MATCH
#define AVR_UART_STDIO
#include <uart.h>void uart_setup(); // Compile-time config
void uart_setup(&cfg); // Runtime configvoid uart_send_byte(char c); // Send single byte (blocking)
char uart_try_send_byte(char c); // Send single byte (non-blocking)
void uart_send(const char *str, size_t len); // Send string
void uart_pgm_send(PGM_P str); // Send from flash memory
void uart_send_uint(unsigned int u); // Send unsigned int
void uart_send_int(int n); // Send signed int
void uart_send_float(float f, uint8_t m); // Send float
void uart_newline(void); // Send CRLF
#define uart_sendln(str, len) // Macro: send + newline
void uart_clear(void); // Clear terminal screenchar uart_recv_byte(void); // Receive byte (blocking)
char uart_try_recv_byte(void); // Receive byte (non-blocking)
size_t uart_recv(char *str, size_t n); // Receive string
char uart_peek_byte(void); // Peek at next byte
size_t uart_peek(char *str, size_t n); // Peek at multiple bytesvoid uart_flush_rx(void); // Flush receive buffer
void uart_flush_tx(void); // Flush transmit buffer (waits for completion)
#define uart_flush() // Macro: flush both buffersEnable with AVR_UART_MATCH define:
void on_command(void *data) {
uart_send("Command received!\r\n", 20);
}
int main(void) {
uart_setup();
uart_register_match("cmd", on_command, NULL);
while (1) {
uart_check_match(); // Process triggered patterns
}
}#define ascii(c) // Convert digit to ASCII
#define c_RETURN // Carriage return (0x0D)
#define c_NEWLINE // Newline (0x0A)
#define c_TAB // Tab (0x09)
#define c_BKSPACE // Backspace (0x08)
#define c_ESCAPE // Escape (0x1B)
#define c_CLEARSCREEN_STRING // ANSI clear screen
#define c_NEWLINE_STRING // CRLF stringDefine AVR_UART_STDIO before including uart.h to enable stdio-style I/O:
#define AVR_UART_STDIO
#include <uart.h>
int main(void) {
uart_setup();
printf("Hello via printf!\r\n");
int c = getchar();
putchar(c);
return 0;
}Or enable via build flag:
# Using command line flag
IOSTREAM=1 make
# Or uncomment in config/config.h:
# /* Enable use of UART in I/O streams (stdin/stdout/stderr) */
# //#define AVR_UART_IOSTREAM 1Build can be triggered from any subdirectory in the project. The Makefiles automatically discover the project root using relative paths.
make # Build library + tests
make build-lib # Build library only
make build-tests # Build tests only
make clean # Clean allmake -C src # Build library
cd src && make # Or use cd
make -C tests # Build all tests
cd tests && make # Or use cd
make -C tests/host # Build host tests
cd tests/host && make # Or use cd
make -C tests/target # Build target firmware
cd tests/target && make # Or use cdmake MATCH=1 # At project root
make -C src MATCH=1 # In src directorymake flash # Flash firmware to AVR
make size # Show memory usage
make cpp # Preprocess only
make disasm # Generate disassembly
make disasmall # Generate full disassembly
make sim # Run with simavr
make sim-gdb # Run with simavr in GDB mode
make serial-monitor # Open serial monitormake help # Show available flags| Variable | Description |
|---|---|
IOSTREAM |
Enable UART like stdin/stdout/stderr |
RUNTIMECONF |
Enable runtime UART configuration |
MATCH |
Enable UART input pattern match |
STRNCMP |
Use strncmp for pattern matching |
TRIGGER |
Emit trigger signal for logic analyzer |
SIM |
Compile for simulation |
SIMTEST |
Compile for off-target testing |
DEMO |
Demo mode with serial communication program |
| Variable | Default | Description |
|---|---|---|
DEVICE |
atmega328p | AVR device |
CLOCK |
16000000 | CPU clock frequency |
DEBUG |
- | Enable debug build |
SAVETEMPS |
- | Preserve intermediate files |
OPTIM |
- | Compiler optimization level |
RELEASE |
- | Release build |
INCLUDE_DIRS |
- | Include paths |
The test strategy uses a computer host driver program and microcontroller target firmware that communicate over UART to test the library functionality.
- Host Driver (
tests/host/uart_test.c): Runs on the development machine, sends commands and verifies responses - Target Firmware (
tests/target/main.c): Runs on the AVR microcontroller, responds to test commands
The host sends test data over serial, the target receives and processes it, then sends responses back to the host for verification.
Run host driver on your development machine:
# Build host driver
make -C tests/host
# Or from subdirectory
cd tests/host && make
# Run with serial device
./tests/host/uart_test -d /dev/ttyUSB0
# Or use make target
make -C tests/host test_run DEVICE=/dev/ttyUSB0Build and flash the target firmware to your AVR and run host tests:
# Build target firmware
make -C tests/target
# Or from subdirectory
cd tests/target && make
# Flash to AVR
make -C tests/target flash
# Or from subdirectory
cd tests/target && make flashThe test suite consists of a bash script run_tests provided in the tests directory. It iterates through several values for baud rate, character size, stop bits and parity type; builds the firmware and flashes the target microcontroller, and then runs the host driver for each case.
See summary of the tests.
The project supports simulation using simavr.
# Build with SIM flag
SIM=1 make -C tests/target
# Run simulation
make -C tests/target sim
# Or run with GDB debug
make -C tests/target sim-gdbFor testing without actual hardware, use SIMTEST to compile for off-target testing:
# Build with SIMTEST flag
SIMTEST=1 make -C tests/targetThis allows running the target firmware test program in a simulated environment using simavr's simduino board.
The simduino board emulates an Arduino board using simavr. It loads the target firmware and allows the host driver program to communicate with the emulation over a pseudoterminal device.
Prerequisites:
- Build simavr from source
- Note the path to the compiled simduino ELF file
Running off-target tests:
- Build the target firmware with SIMTEST flag:
SIMTEST=1 make -C tests/target- Run simduino with the target firmware:
/path/to/simavr/examples/board_simduino/<simavr_build_target>/simduino.elf -v -v -v tests/target/main.hexNote: The path to simduino.elf can vary depending on the host platform where simavr was built (uses obj-$(shell $(CC) -dumpmachine) format, e.g., obj-x86_64-linux-gnu on Debian Linux).
- In another terminal, run the host driver:
make -C tests/host test_run DEVICE=/dev/pts/XThe simduino board requires the binary ihex file (not ELF). The -v -v -v flags enable verbose logging.
When compiled with SIM=1, the target firmware generates VCD (Value Change Dump) files that can be viewed in GTKWave. The firmware sends a string of lowercase and uppercase English alphabets. The traces include:
- TxD (transmit signal)
- RxD (receive signal)
- Trigger signal (if enabled)
- UDR0 (UART data register)
Open the generated VCD file in GTKWave:
gtkwave simulation/atmega328p_uart_trace.vcd &The waveform shows UART communication at 9600 baud with 8N1 format (8 data bits, no parity, 1 stop bit).
avr-uart/
├── include/ # Public header files
│ ├── uart.h # Main UART API
│ ├── uart_config.h # Configuration definitions
│ └── match.h # Pattern matching API
├── src/ # Implementation
│ ├── uart.c
│ └── match.c
├── port/ # Hardware abstraction
│ ├── port.h # Port interface
│ └── mega328p/ # ATmega328P specific code
├── tests/ # Test applications
│ ├── host/ # Host driver program
│ │ ├── uart_test.c # Host test driver
│ │ ├── logger.h # Logging utilities
│ │ └── Makefile
│ └── target/ # Target firmware
│ ├── main.c # Target test program
│ └── Makefile
├── config/ # Build configuration
├── docs/ # Generated documentation
└── Makefile.common # Common build definitions
MIT License - see LICENSE file for details.
Documentation generated with OpenCode (minimax-m2.5-free model).
