One shared wire, no second chance
A few microcontrollers share one UART line at 9600 bit/s. Two talking at once destroy each other's bytes, noise flips bits, a board is unplugged in the field — and a lost "stop" command is a bug report. The firmware has a few kilobytes of RAM, no operating system to lean on, and code that has to pass a review.
Collisions, noise and dead nodes
The master passes the baton: only the polled node transmits, for a bounded slot. CRC-16, acknowledgements, retries and duplicate filtering deliver every event exactly once; an unreachable node cannot eat the time of the others.
Tight embedded budgets
1696 B of RAM per node with the default sizes and 280 B of worst-case stack on Cortex-M4; no allocation, no globals, no OS, no clock reads.
Code you can certify
Zero MISRA C:2012 violations, a documented deviation register, 100 % line and branch coverage and static analysis in CI.
A small library with a clear job
A codec, a half-duplex link, an event queue and a node layer — eight functions for the application, the rest public for tools.
Node
Master and slave state machines, reliable events and states on top of a byte-level link and a frame codec.
Any UART wiring
Single wire with echo check, open drain or RS-485: one non-blocking write function and one interrupt call.
Working examples
A bare-metal slave that runs as a test on a simulated bus, a PC serial master and a Zephyr slave.
Baton Inspector
A GUI and Python API: master, monitor or simulation, live sequence diagram with timing, timing rules, field decoding and session logs for automated tests.
Complete documentation
Protocol specification, guides, measured footprint and stack, API reference and a printable manual.
Quality gates in CI
100 % line and branch coverage, a C/Python cross-check, MISRA C:2012 and format checks on every pull request.
Quick start
A minimal slave in one file¶
The application owns one struct baton_node, gives it a write function for the UART and
a callback for received messages, passes every received byte from the UART interrupt and
calls baton_node_process() every millisecond. This slave publishes its uptime as a
state and acknowledges every CMD_SET_OUTPUT event with an OUTPUT_REPORT event.
#include "baton.h"
static const uint8_t slaves[] = {0x11U, 0x12U};
static struct baton_node node;
static int uart_write(void* context, const uint8_t* data, size_t length)
{
(void) context;
return board_uart_write(data, length); /* start a non-blocking transmission */
}
static void on_message(void* context, uint8_t src, uint8_t msg_id, const uint8_t* data,
uint8_t length)
{
(void) context;
if ((msg_id == BATON_MSG_CMD_SET_OUTPUT) && (length == 2U))
{
(void) baton_node_send(&node, src, BATON_MSG_OUTPUT_REPORT, data, 2U, BATON_PRIO_HIGH);
}
}
void uart_rx_irq_handler(uint8_t byte) /* called by the UART receive interrupt */
{
baton_node_rx_isr(&node, byte);
}
int main(void)
{
struct baton_node_config config;
uint32_t next_status = 0U;
baton_node_default_config(&config); /* 9600 bit/s, 250 ms slot, single-wire echo */
config.own_address = 0x11U;
config.master_address = 0x01U;
config.slave_addresses = slaves;
config.slave_count = 2U;
config.write = uart_write;
config.on_message = on_message;
(void) baton_node_init(&node, &config, board_millis());
for (;;)
{
const uint32_t now = board_millis();
baton_node_process(&node, now);
if ((int32_t) (now - next_status) >= 0)
{
uint8_t uptime[4];
baton_put_u32le(uptime, now / 1000U);
(void) baton_node_publish(&node, BATON_MSG_NODE_STATUS, uptime, 4U);
next_status += 1000U;
}
}
}
board_uart_write() and board_millis() stand for your board's UART driver and tick. The
message lists (NODE_STATUS, CMD_SET_OUTPUT, OUTPUT_REPORT) come from
baton_user_config.h, shared by every node of the bus. The examples are
complete programs for bare metal, Zephyr and a PC serial port.
Find your way¶
| If you want to… | Read |
|---|---|
| Understand what the library does and does not do | Introduction |
| Build it, run the tests, add it to your project | Getting Started |
| Understand the layers, the slot schedule and the parser | How It Works |
| Write a master or a slave with it | Using the Library |
| Add messages, change sizes or contribute | Extending the Library |
| Look up the bytes on the wire | Protocol |
| Connect it to a UART, an RTOS or a build system | Integration Guide |
| Watch, measure or test a bus | Baton Inspector |
| Look up a function | API Reference |
| Read everything offline or print it | PDF user manual |
SystemFromScratch®
Part of an open-source embedded portfolio
Baton-C is developed by Anton Vynohradov under SystemFromScratch® — embedded software and hardware built from the first schematic to certification.
SystemFromScratch® is a registered trademark. The Apache License 2.0 grants no rights to use the SystemFromScratch® name or logo.