Skip to content
Open-source embedded library

One wire, many MCUsno lost messages

Baton-C turns a single shared UART line into a deterministic bus: the master passes the baton, events arrive exactly once and in order, states always reach their latest value — with no heap, no RTOS and code that passes MISRA C:2012.

  • No heap
    No RTOS
  • MISRA C:2012
    0 violations
  • 100 % line &
    branch coverage
  • Apache-2.0
    open source
A circuit board with the Baton-C chip drives one wire to three slave modules (0x11, 0x12, 0x13); a request travels to a slave and its answer comes back while noise falls off the line.
The problem

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.

  • Token schedule
  • CRC-16
  • Stop-and-wait

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.

  • Static memory
  • Bare metal
  • Zephyr

Code you can certify

Zero MISRA C:2012 violations, a documented deviation register, 100 % line and branch coverage and static analysis in CI.

  • MISRA C
  • Cppcheck
  • CodeQL
What you get

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.

8application functions
1696 BRAM per node
280 Bstack on Cortex-M4
0MISRA C:2012 violations

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.