ML-MCU
Code for IoT Journal paper 'ML-MCU: A Framework to Train ML Classifiers on MCU-based IoT Edge Devices'
Tiny Software Protocol for communication over UART, SPI, etc
git clone https://github.com/lexus2k/tinyproto.gitlexus2k/tinyprotoTinyProto 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.
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.
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 |
The FD protocol supports two HDLC operating modes:
┌────────────┐ ┌────────────┐
│ Device A │ ◄──── UART/SPI ──────► │ Device B │
│ (peer) │ bidirectional │ (peer) │
└────────────┘ └────────────┘
┌────────────┐ ┌────────────┐ ┌────────────┐
│ Primary │─────►│ Secondary1 │ │ Secondary2 │
│ (master) │◄─────│ addr = 1 │ │ addr = 2 │
│ │─────────────────────────►│ │
│ │◄─────────────────────────│ │
└────────────┘ └────────────┘ └────────────┘
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 |
┌─────────────────────────────────────────────────┐
│ 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 │ ...│
└─────────────────────────────────────────────────┘
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 |
If your platform isn't listed, implement the HAL abstraction (timing + mutex functions):
TINY_CUSTOM_PLATFORM to your compiler definestiny_hal_init()CONFIG_TINYHAL_THREAD_SUPPORT for multi-threaded environmentsSee 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.
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)
};
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);
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()
# Using Make make # Using CMake mkdir build && cd build cmake -DEXAMPLES=ON .. make # Run unit tests make ARCH=linux unittest ./bld/unit_test
mkdir build && cd build cmake -G "Visual Studio 16 2019" -DEXAMPLES=ON .. # Open the generated .sln file in Visual Studio
Place the library in your project's components/ directory, then build normally with idf.py build or make.
make ARCH=avr
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
Option 1 — From source:
Arduino/libraries/tinyprotoOption 2 — Library Manager:
cd your_project/components git clone https://github.com/lexus2k/tinyproto
Add to platformio.ini:
lib_deps = lexus2k/tinyproto
cd tinyproto python setup.py install
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
Full API documentation is available at codedocs.xyz/lexus2k/tinyproto.
| 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 |
| 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 |
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 |
|---|---|
![]() |
![]() |
3CtUY6Ag2zsvm1JyqeeKeK8kjdG7Tnjr5W |
0x20608A71470Bc84a3232621819f578Fb9C02A460 |
more like this
Code for IoT Journal paper 'ML-MCU: A Framework to Train ML Classifiers on MCU-based IoT Edge Devices'
Arduino library and MATLAB/Simulink API for the AutomationShield Arduino expansion boards for control engineering educa…
search projects, people, and tags