Skip to content

lexus2k/tinyproto

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

633 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Tiny Protocol
Github actions Coverage Status Documentation License License


TinyProto is a lightweight, portable HDLC-based data link layer protocol for embedded systems and microcontrollers. Communicate reliably over UART, SPI, I2C or any byte stream — with automatic retransmission, CRC error detection, and zero dynamic memory allocation.


Table of Contents

Introduction

TinyProto is a Layer 2 (Data Link) protocol implementation based on RFC 1662 and the HDLC specification. It provides reliable, framed communication between two or more devices over any byte-oriented transport (UART, SPI, I2C, USB CDC, TCP sockets, etc.).

The library is designed for resource-constrained environments — from 8-bit AVR microcontrollers with just 60 bytes of SRAM to full Linux/Windows desktop systems. All memory is statically allocated; no malloc is ever called.

TinyProto is not an application-layer protocol. It can be used as a transport for higher-level protocols such as Protocol Buffers, MessagePack, JSON, or any custom framing you need.

Protocol Modes

TinyProto supports three protocol layers, from simplest to most capable:

Layer API Prefix Description Use Case
Light tiny_light_* SLIP-like framing with optional CRC. No acknowledgments. Simple, low-overhead streaming
HDLC hdlc_* Low-level HDLC framing (flag bytes, byte stuffing, CRC). Custom protocol building blocks
Full-Duplex (FD) tiny_fd_* Complete HDLC implementation with sliding window, ACK/NAK, retransmission Reliable bidirectional communication

Full-Duplex Modes (FD)

The FD protocol supports two HDLC operating modes:

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

┌────────────┐                        ┌────────────┐
│  Device A  │ ◄──── UART/SPI ──────► │  Device B  │
│   (peer)   │      bidirectional     │   (peer)   │
└────────────┘                        └────────────┘
  • Both devices are equal peers — either side can initiate communication
  • Connection established via SABM/UA exchange
  • Supports hot plug/unplug with automatic reconnection
  • Ideal for: point-to-point links between two MCUs, MCU ↔ PC communication

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

┌────────────┐      ┌────────────┐      ┌────────────┐
│  Primary   │─────►│ Secondary1 │      │ Secondary2 │
│  (master)  │◄─────│  addr = 1  │      │  addr = 2  │
│            │─────────────────────────►│            │
│            │◄─────────────────────────│            │
└────────────┘      └────────────┘      └────────────┘
  • One primary station controls communication with multiple secondaries
  • Primary polls each secondary in round-robin order using P/F (Poll/Final) bit
  • Secondaries only transmit when polled
  • Connection established via SNRM/UA exchange
  • Supports full sliding window for each secondary (up to 7 outstanding frames)
  • Ideal for: RS-485 buses, multi-drop serial networks, sensor networks

Frame Types

TinyProto implements all standard HDLC frame types:

Frame Type Description
I Information Carries user data with sequence numbers
RR Supervisory Receiver Ready — acknowledges received frames
RNR Supervisory Receiver Not Ready — flow control
REJ Supervisory Reject — requests retransmission from sequence N
SREJ Supervisory Selective Reject — requests retransmission of specific frame
SABM Unnumbered Set ABM — initiates peer-to-peer connection
SNRM Unnumbered Set NRM — initiates primary/secondary connection
DISC Unnumbered Disconnect — terminates connection
UA Unnumbered Unnumbered Acknowledge — confirms mode setting
DM Unnumbered Disconnected Mode — rejects commands when disconnected
FRMR Unnumbered Frame Reject — reports protocol errors
UI Unnumbered Unnumbered Information — connectionless data
RSET Unnumbered Reset — resets sequence counters

Architecture

┌─────────────────────────────────────────────────┐
│                   Application                   │
├──────────────┬──────────────┬───────────────────┤
│  C++ API     │   C API      │   Python API      │
│  IFd / Hdlc  │  tiny_fd_*   │   tinyproto.*     │
│  Light       │  hdlc_*      │                   │
├──────────────┴──────────────┴───────────────────┤
│            Full-Duplex Protocol (FD)            │
│    ┌─────────────┬────────────┬───────────┐     │
│    │ I-frame     │  S-frame   │  U-frame  │     │
│    │ queue       │  handler   │  handler  │     │
│    └─────────────┴────────────┴───────────┘     │
├─────────────────────────────────────────────────┤
│             HDLC Low-Level Framing              │
│          (byte stuffing, CRC, flags)            │
├─────────────────────────────────────────────────┤
│           Hardware Abstraction Layer            │
│         (timers, mutexes, serial I/O)           │
├─────────────────────────────────────────────────┤
│     UART  │  SPI  │  I2C  │  USB  │  TCP  │ ...│
└─────────────────────────────────────────────────┘

Key Features

  • Reliable Delivery — Sliding window (1–7 frames), automatic retransmission, REJ and SREJ recovery
  • Error Detection — 8-bit checksum, FCS-16 (CCITT), or FCS-32 (CCITT)
  • Two HDLC Modes — ABM (peer-to-peer) and NRM (primary/secondary multi-drop)
  • Hot Plug/Unplug — Automatic connection recovery in ABM mode
  • Zero Dynamic Allocation — All buffers are statically sized; fully deterministic memory usage
  • Tiny Footprint — From 60 bytes SRAM, 1.3 KiB flash (features are compile-time configurable)
  • Large Frames — Payload up to 32 KiB (configurable)
  • Full Logging — Built-in protocol debug logging with CSV export
  • Multi-Language — C, C++, and Python APIs
  • Portable — Works on any platform with a C99 compiler

Supported Platforms

TinyProto runs anywhere a C99/C++11 compiler is available:

Platform MCU Examples Transport
Arduino Uno, Mega, Zero, Due, Nano UART, SPI
ESP32 ESP32, ESP32-S2, ESP32-C3 UART, SPI
ARM Cortex STM32, nRF52, Teensy, SAMD UART, SPI, I2C
AVR ATmega328, ATmega2560 UART
Linux Any UART, TCP, pipes
Windows Any COM ports, TCP
RISC-V ESP32-C3, GD32V UART, SPI

Custom Platform Support

If your platform isn't listed, implement the HAL abstraction (timing + mutex functions):

  1. Add TINY_CUSTOM_PLATFORM to your compiler defines
  2. Implement HAL functions and call tiny_hal_init()
  3. Optionally add CONFIG_TINYHAL_THREAD_SUPPORT for multi-threaded environments

See hal/linux/linux_hal.inl and hal/esp32/esp32_hal.inl for reference implementations, or use the template at tools/hal_template_functions/platform_hal.c.

Quick Start

C API

ABM mode (peer-to-peer):

#include "proto/fd/tiny_fd.h"

// Callback when a complete message is received
void on_frame_received(void *udata, uint8_t addr, uint8_t *buf, int len)
{
    printf("Received %d bytes\n", len);
}

// Allocate protocol buffer (use tiny_fd_buffer_size_by_mtu() to calculate)
uint8_t buffer[tiny_fd_buffer_size_by_mtu(64, 4)];

tiny_fd_handle_t handle;
tiny_fd_init_t init = {
    .pdata        = NULL,
    .on_read_cb   = on_frame_received,
    .buffer       = buffer,
    .buffer_size  = sizeof(buffer),
    .window_frames = 4,
    .send_timeout  = 1000,
    .retry_timeout = 200,
    .retries       = 3,
    .crc_type      = HDLC_CRC_16,
    .mode          = TINY_FD_MODE_ABM,
};

tiny_fd_init(&handle, &init);

// In your main loop:
// 1. Feed received bytes to the protocol
tiny_fd_on_rx_data(handle, rx_bytes, rx_len);

// 2. Get bytes to transmit
uint8_t tx_buf[64];
int tx_len = tiny_fd_get_tx_data(handle, tx_buf, sizeof(tx_buf), 0);
if (tx_len > 0) {
    serial_write(tx_buf, tx_len);
}

// 3. Send application data
tiny_fd_send_packet(handle, "Hello", 6, 1000);

NRM mode (multi-drop primary):

tiny_fd_init_t init = {
    // ... same as above, plus:
    .mode         = TINY_FD_MODE_NRM,
    .peers_count  = 3,           // support up to 3 secondaries
    .addr         = TINY_FD_PRIMARY_ADDR,  // this is the primary station
};

NRM mode (secondary station):

tiny_fd_init_t init = {
    // ... same as above, plus:
    .mode         = TINY_FD_MODE_NRM,
    .addr         = 1,           // unique secondary address (1-63)
};

C++ API

Full-duplex with static buffer (Arduino-friendly):

#include "TinyProtocolFd.h"

// Static buffer — no heap allocation
tinyproto::Fd<1024> proto;

void onReceive(void *udata, uint8_t addr, tinyproto::IPacket &pkt) {
    // Process received message
    Serial.write(pkt.data(), pkt.size());
}

void setup() {
    Serial.begin(115200);
    proto.setReceiveCallback(onReceive);
    proto.setWindowSize(4);
    proto.enableCrc16();
    proto.begin();
}

void loop() {
    // Feed serial data to protocol
    if (Serial.available()) {
        uint8_t byte = Serial.read();
        proto.run_rx(&byte, 1);
    }
    // Transmit protocol data
    uint8_t tx;
    if (proto.run_tx(&tx, 1) == 1) {
        Serial.write(tx);
    }
}

Dynamic buffer (desktop / powerful MCUs):

#include "TinyProtocolFd.h"

// Dynamic allocation — buffer size determined at runtime
tinyproto::FdD proto(4096);
proto.setWindowSize(7);
proto.enableCrc32();
proto.begin();

// Send data
proto.write("Hello World", 12);

Python

import tinyproto

proto = tinyproto.Hdlc()

def on_read(data):
    print("Received:", data.hex())

proto.on_read = on_read
proto.begin()

# Feed raw bytes from your transport
proto.rx(bytearray([0x7E, 0xFF, 0x3F, 0xF3, 0x39, 0x7E]))

# Queue data and get encoded bytes to transmit
proto.put(bytearray(b"Hello"))
tx_data = proto.tx()

Building

Linux

# Using Make
make

# Using CMake
mkdir build && cd build
cmake -DEXAMPLES=ON ..
make

# Run unit tests
make ARCH=linux unittest
./bld/unit_test

Windows

mkdir build && cd build
cmake -G "Visual Studio 16 2019" -DEXAMPLES=ON ..
# Open the generated .sln file in Visual Studio

ESP32 (IDF)

Place the library in your project's components/ directory, then build normally with idf.py build or make.

AVR

make ARCH=avr

Testing

TinyProto has a comprehensive test suite with 101 tests covering all protocol layers:

# Build and run all tests
make ARCH=linux DEBUG_MODE=y unittest && ./bld/unit_test

# Run specific test groups
./bld/unit_test -g TINY_FD_ABM    # ABM mode tests (28 tests)
./bld/unit_test -g TINY_FD_NRM    # NRM mode tests (15 tests)
./bld/unit_test -g CPP_FD         # C++ FD wrapper tests (10 tests)
./bld/unit_test -g CPP_HDLC       # C++ HDLC wrapper tests (3 tests)
./bld/unit_test -g CPP_LIGHT      # C++ Light wrapper tests (3 tests)
./bld/unit_test -g HDLC           # Low-level HDLC tests
./bld/unit_test -g LIGHT          # Light protocol tests

Installation

Arduino

Option 1 — From source:

  1. Download from https://github.com/lexus2k/tinyproto
  2. Copy to Arduino/libraries/tinyproto
  3. Restart Arduino IDE → Examples → tinyproto

Option 2 — Library Manager:

  1. Arduino IDE → Sketch → Include Library → Manage Libraries
  2. Search for "tinyproto" and install

ESP32 IDF

cd your_project/components
git clone https://github.com/lexus2k/tinyproto

PlatformIO

Add to platformio.ini:

lib_deps = lexus2k/tinyproto

Python

cd tinyproto
python setup.py install

Tools

tiny_loopback

A serial loopback testing tool for benchmarking and debugging:

# Build
make ARCH=linux

# Test Light protocol
./bld/tiny_loopback -p /dev/ttyUSB0 -t light -g -c 8 -a -r

# Test Full-Duplex protocol
./bld/tiny_loopback -p /dev/ttyUSB0 -t fd -c 8 -w 3 -g -a -r

API Reference

Full API documentation is available at codedocs.xyz/lexus2k/tinyproto.

Key C Functions

Function Description
tiny_fd_init() Initialize FD protocol instance
tiny_fd_close() Close and free FD instance
tiny_fd_on_rx_data() Feed received bytes to protocol
tiny_fd_get_tx_data() Get bytes to transmit
tiny_fd_send_packet() Queue data for transmission
tiny_fd_send_to() Send to specific peer address (NRM)
tiny_fd_get_status() Check connection status
tiny_fd_disconnect() Initiate disconnect

Key C++ Classes

Class Description
tinyproto::Fd<N> FD protocol with static N-byte buffer
tinyproto::FdD FD protocol with dynamic buffer allocation
tinyproto::Hdlc HDLC low-level framing
tinyproto::Light Lightweight SLIP-like protocol
tinyproto::IPacket Packet buffer for data exchange

License

Dual licensed under GPLv3 and Commercial licenses.

Copyright 2016-2025 (C) Alexey Dynda

GNU General Public License: Protocol Library is free software under the GNU Lesser General Public License v3.0 or later. See COPYING for details.

Commercial License: Available for proprietary use. Contact via email on the GitHub account for details.


If you find this library useful, consider supporting its development.

Bitcoin Ethereum
BTC ETH
3CtUY6Ag2zsvm1JyqeeKeK8kjdG7Tnjr5W 0x20608A71470Bc84a3232621819f578Fb9C02A460

Releases

Sponsor this project

Used by

Contributors

Languages