tinyproto
TinyProto Library

Table of Contents

TinyProto is a lightweight, portable HDLC-based Layer 2 protocol library for embedded systems and microcontrollers. It provides reliable, framed communication over UART, SPI, I2C, USB CDC, TCP sockets, or any byte-oriented transport — with automatic retransmission, CRC error detection, and zero dynamic memory allocation.

Based on RFC 1662 and the HDLC specification.

Introduction

TinyProto is a Layer 2 (Data Link) protocol implementation. It is designed for resource-constrained environments — from 8-bit AVR microcontrollers to full Linux/Windows desktop systems. All memory is statically allocated; no malloc is ever called.

The library supports three protocol layers:

Layer API Description
Light Tiny light protocol API functions SLIP-like framing with optional CRC. No acknowledgments. Low overhead.
HDLC LL HDLC low level protocol API Low-level HDLC framing: flag bytes, byte stuffing, CRC. Building block for custom protocols.
Full-Duplex (FD) Tiny Full Duplex API functions Complete HDLC implementation with sliding window (1–7 frames), ACK/NAK, automatic retransmission, and two operating modes: ABM and NRM.

Use Tiny Full Duplex API functions for reliable communication between devices. Use Tiny light protocol API functions when you need minimal overhead. Use HDLC low level protocol API if you need raw HDLC framing as a building block.

Operating Modes (Full-Duplex)

The Full-Duplex protocol (Tiny Full Duplex API functions) supports two HDLC operating modes:

ABM — Asynchronous Balanced Mode (Peer-to-Peer)

Both endpoints are equal peers. Either side can initiate data transfer at any time. Connection is established via SABM/UA exchange. Supports hot plug/unplug with automatic reconnection.

Use ABM for point-to-point links between two MCUs, or MCU-to-PC communication.

tiny_fd_init_t init = {0};
init.on_read_cb = on_frame_received;
// ... set buffer, window_frames, crc_type, etc.
tiny_fd_init(&handle, &init);

NRM — Normal Response Mode (Multi-drop / RS-485)

One primary station controls communication with one or more secondary stations. The primary polls secondaries in round-robin order using P/F (Poll/Final) bits. Secondaries only transmit when polled. Connection is established via SNRM/UA exchange.

Use NRM for RS-485 buses, multi-drop serial networks, or sensor networks.

Primary station setup:

tiny_fd_init_t init = {0};
init.addr = TINY_FD_PRIMARY_ADDR; // address 0 = primary
init.peers_count = 3; // support up to 3 secondaries
init.on_read_cb = on_frame_received;
// ... set buffer, window_frames, crc_type, etc.
tiny_fd_init(&handle, &init);
// Register secondary stations

Secondary station setup:

tiny_fd_init_t init = {0};
init.addr = 1; // unique address 1–62
init.on_read_cb = on_frame_received;
// ... set buffer, window_frames, crc_type, etc.
tiny_fd_init(&handle, &init);

HDLC Frame Types

TinyProto implements all standard HDLC frame types:

Information frames (I-frames): Carry user data with sequence numbers for reliable delivery.

Supervisory frames (S-frames):

Unnumbered frames (U-frames):

Packet Structure

HDLC low-level frame format (used by HDLC low level protocol API and Tiny light protocol API functions):

     8        any len    None/8/16/32     8
 |   7E   |    DATA     |    FCS     |   7E   |

Full-Duplex frame format (used by Tiny Full Duplex API functions):

     8     ADDR  CONTROL     any len    None/8/16/32     8
 |   7E   | ADDR | CTRL  | USER DATA  |    FCS     |   7E   |

User-Defined Callbacks

Full-Duplex API Callbacks

The FD protocol uses the following callback signatures:

// Called when a complete I-frame is received
void on_frame_read(void *udata, uint8_t addr, uint8_t *pdata, int size);
// Called when a frame has been sent
void on_frame_send(void *udata, uint8_t addr, const uint8_t *pdata, int size);
// Called on connect/disconnect events
void on_connect_event(void *udata, uint8_t addr, bool connected);
// Called when a UI (connectionless) frame is received
void on_read_ui(void *udata, uint8_t addr, uint8_t *pdata, int size);

Note: the addr parameter identifies the remote peer address. In ABM mode it is always 0. In NRM mode it identifies the secondary station.

HDLC Low-Level Callbacks

// Called when a complete frame is received
void on_frame_read(void *udata, uint8_t *pdata, int size);
// Called when a frame has been sent
void on_frame_send(void *udata, const uint8_t *pdata, int size);

Light Protocol Callbacks

The Light protocol uses read/write callbacks to interact with the communication channel:

// Write bytes to the communication channel
int write_func(void *udata, const void *data, int len);
// Read bytes from the communication channel
int read_func(void *udata, void *data, int len);

CRC Options

All protocol layers support configurable error detection:

Option Constant Description
Auto HDLC_CRC_DEFAULT Automatically selects the best available CRC (prefers CRC-16)
8-bit checksum HDLC_CRC_8 Simple sum of all payload bytes. Lowest overhead.
16-bit FCS HDLC_CRC_16 CCITT CRC-16 as defined in RFC 1662. Recommended for most use cases.
32-bit FCS HDLC_CRC_32 CCITT CRC-32. Best error detection at higher overhead.
Disabled HDLC_CRC_OFF No CRC. Use only on error-free transports.

Buffer Sizing

The Full-Duplex protocol requires a pre-allocated buffer. Use the helper macros to calculate the required size:

// Simple: specify MTU and sliding window size
int size = tiny_fd_buffer_size_by_mtu(64, 4); // 64-byte payload, window of 4
// Extended: specify peers, MTU, window, CRC type, and extra RX buffer
int size = tiny_fd_buffer_size_by_mtu_ex(3, 64, 4, HDLC_CRC_16, 1);

Getting Started

See Getting Started for step-by-step examples in C, C++ and Python.

Arduino

See Arduino API (C++) for Arduino-specific examples using both the Light and Full-Duplex protocols.

Supported Platforms

TinyProto runs on any platform with a C99/C++11 compiler: