Introduction
Baton-C is a C11 library for deterministic master/slave messaging between a few
microcontrollers on one shared UART line (single-wire half duplex, open-drain or RS-485,
9600 bit/s by default). The master passes a token — the baton — to each slave in turn;
only the node that holds it transmits. On top of this collision-free schedule the library
delivers application messages of two kinds: events, delivered exactly once and in
order, and states, of which the latest value always arrives. It is written for
embedded systems that must be predictable and reviewable: no heap, no RTOS, no clock
reads, MISRA C:2012 without violations and 100 % line and branch test coverage.
What the library does
- Schedules the bus. The master polls the slaves in a fixed order. A polled slave may
send DATA frames to other slaves and must end its slot with a RESP within
slot_time_ms. The worst-case cycle depends only on the configuration, not on the
traffic.
- Delivers events reliably. Events are queued (HIGH and NORMAL priority), packed
several per frame, acknowledged, retransmitted with the same sequence number,
filtered for duplicates, and dropped after
event_ttl_ms with a report of the nodes
that did not acknowledge them.
- Delivers states. A state is a mailbox: publishing overwrites it and marks it for
every other node. It cannot overflow; intermediate values may be skipped.
- Handles an unreliable bus. CRC-16 per frame, inter-byte timeout, echo check of the
own transmission, retries, and a schedule that keeps unreachable nodes from eating
the time of the healthy ones.
- Encodes and parses frames. The codec can be used on its own, for example in a bus
monitor.
What the library does not do
| Concern |
Why it is not in the library |
| UART driver, pin setup, transceiver direction |
Hardware-specific; you pass received bytes in and provide a non-blocking write function. |
| Threads, interrupt priorities, locking |
Every system has its own; the library offers two optional critical-section hooks. See the integration guide. |
| A clock |
Every call that needs the time takes now_ms; the library never reads a timer. |
| Message meaning, data layouts, units |
Application data; the library transports byte strings up to their MAX_LEN. |
| Arbitration between several masters |
One master per bus is the design. |
| Broadcast frames |
A broadcast event is sent to each node in its own frame and acknowledged by each one (see design decisions). |
Who it is for
- Small multi-MCU products — a control board and a few sensor, actuator or display
boards connected by one wire, where CAN is too expensive and a plain UART protocol has
no delivery guarantee.
- Safety- or certification-minded firmware that needs bounded execution time, static
memory and a documented MISRA C:2012 status.
- Host tools — a PC or a Linux board can be the master or a bus monitor through a
USB-UART adapter (see the examples).
Key properties
| Property |
Value |
| Language |
C11, no warnings with -Wall -Wextra -Wpedantic -Wconversion |
| Dependencies |
<stddef.h>, <stdint.h>, <stdbool.h>, <string.h> only |
| Memory |
No heap, no global variables; all state in a caller-owned struct baton_node |
| RAM per node |
1696 B with the configuration template (8 nodes, 64-byte payload, queues 256 + 512 B), details |
| Code size |
6620 B on Cortex-M4 with -Os (GCC 15.2), without memcpy/memset/memmove |
| Stack |
280 B worst case in baton_node_process() on Cortex-M4 -Os, details |
| Execution time |
Bounded by the configured sizes; no function blocks or waits |
| Coding standard |
MISRA C:2012, 0 Cppcheck addon violations, 4 deviations |
| Tests |
100 % line and branch coverage; bus simulation with noise and dead nodes; C/Python cross-check |
| Thread safety |
One processing context plus interrupts; see Threading |
| License |
Apache License 2.0 |
Terminology
- Master
- The node that polls the slaves; node index 0. Exactly one per bus.
- Slave
- A node that transmits only while it holds the baton; node index 1..n in the order of
slave_addresses, which must be identical on every node.
- Slot
- The time between a POLL to a slave and its RESP, at most
slot_time_ms.
- Cycle
- One round of POLLs over all slaves.
- Frame
- One unit on the wire:
SYNC DST SRC CTRL LEN PAYLOAD CRC. Types: POLL, RESP, DATA, ACK.
- Message
- One application record (id, length, data) inside a frame payload (TLV). Several
messages share one frame and one acknowledgement.
- Event
- A message class: queued, acknowledged, delivered exactly once, in order.
- State
- A message class: a mailbox whose latest value is delivered to every other node.
- Channel
- The stop-and-wait state between two nodes in one direction: a 4-bit sequence number,
the FIRST flag and the list of messages in the unacknowledged frame.
- Node mask
- A 32-bit set of node indexes; bit 0 is the master, bit i is
slave_addresses[i - 1].
Conventions in this documentation
- Hexadecimal bytes without prefix inside frames (
A5 11 01), with 0x in text and code.
- The CRC is big endian on the wire; node masks inside a RESP are little endian.
- Sizes are shown with the values of the configuration template
(
config/baton_user_config_template.h), timing with the defaults of
baton_node_default_config() at 9600 bit/s.