UART 通信协议开源库调研总结
调研日期:2026-08-19
目标场景:两轴/多轴无刷云台内部 Pitch、Yaw、Roll 控制板之间的 UART 通信
核心诉求:轻量、嵌入式友好、商业许可友好、支持可靠分帧/校验,且消息解析后优先采用 handler/listener 注册制 ,避免业务层大量
switch-case。
1. 需求背景
云台内部存在两类典型通信需求:
1.1 高频实时数据
例如:
- IMU / Gyro
- Encoder
- 角度
- 角速度
- 电机电流
- 控制误差
- 时间戳
- 状态位
特点:
- 频率高,典型 500 Hz~1 kHz
- 对低延迟敏感
- 某一帧丢失后通常无需重传,下一帧即可覆盖
- 数据结构稳定
- 应尽量减少编码开销
1.2 控制、配置、参数类数据
例如:
- PID 参数
- 滤波参数
- 电机参数
- IMU / Encoder 校准
- 模式切换
- 保存参数
- 重启
- 设备信息
- 版本信息
特点:
- 频率低
- 更重视可靠性
- 适合请求/响应
- 关键命令需要 ACK / timeout / retry
- 参数格式需要一定扩展能力
因此不建议所有业务都使用同一种数据表示方式。
2. 理想的软件结构
希望底层协议库能够将:
text
UART 字节流
↓
帧解析
↓
CRC / Checksum
↓
Message Type / ID
↓
Handler Registry
↓
对应业务函数
最终业务代码类似:
c
protocol_register(MSG_AXIS_STATE, axis_state_handler);
protocol_register(MSG_PARAM_SET, param_set_handler);
protocol_register(MSG_CALIBRATE, calibrate_handler);
而不是:
c
switch (msg->type)
{
case MSG_AXIS_STATE:
...
break;
case MSG_PARAM_SET:
...
break;
case MSG_CALIBRATE:
...
break;
}
注册式设计的主要优势:
- 协议层与业务层解耦
- 新增消息无需修改中央
switch - 不同功能模块可以自行注册 handler
- 后期更适合模块化和多人开发
- 更容易做单元测试
- 更适合未来扩展 Pitch / Yaw / Roll / Camera / Sensor 等模块
3. 候选开源库比较
| 项目 | 主要定位 | C/C++ | CRC/Checksum | Handler 注册 | 请求/响应 | 适合高频实时数据 | License | 结论 |
|---|---|---|---|---|---|---|---|---|
| TinyFrame | 串口消息框架 | C | ✅ | ✅ 原生支持 | ✅ | ✅ | MIT | 首选 |
| MIN Protocol | MCU 点对点可靠协议 | C + Python | ✅ | ⚠️ 可自行加 | ✅ | ✅ | MIT | 第二选择 |
| LwPKT | 轻量 Packet Manager | C11 | ✅ | ⚠️ 事件机制 | ❌/自行实现 | ✅ | MIT | 底层很轻,但业务层还需自己做 |
| eRPC | Embedded RPC | C/C++ | transport 负责 | 自动生成服务 | ✅ 很强 | ⚠️ | BSD-3-Clause | 参数/命令很舒服,但实时链路偏重 |
| TinyProto | 可靠链路层 | C/C++ | ✅ | 有上层机制 | ✅ | ✅ | GPL 系列,需谨慎 | 商业闭源固件不优先 |
| microTLV | TLV Payload 编解码 | C | ❌ | ❌ | ❌ | 不推荐用于高频状态 | MIT | 只适合作为 Payload 层辅助 |
4. TinyFrame
项目:
https://github.com/MightyPork/TinyFrame
4.1 定位
TinyFrame 是一个专门用于:
- UART
- RS232
- Socket
- MCU ↔ MCU
- MCU ↔ PC
的轻量消息框架。
它不仅解决 framing,还直接加入了:
- Frame ID
- Message Type
- Header checksum
- Payload checksum
- Listener
- Request / response
- Timeout
- 多消息 session
这正好处于"裸 framing"和"大型 RPC 框架"之间。
4.2 Frame 格式
TinyFrame 的基本格式:
text
SOF
ID
LEN
TYPE
HEAD_CKSUM
DATA
DATA_CKSUM
各字段长度可以配置。
其中最重要的是:
text
TYPE
可以直接作为业务消息类型。
例如:
text
0x01 AXIS_STATE
0x02 SENSOR_SYNC
0x10 PARAM_GET
0x11 PARAM_SET
0x20 CALIBRATE
0x21 SAVE_CONFIG
0x22 REBOOT
4.3 Listener 注册机制
这是 TinyFrame 最符合本项目要求的地方。
TinyFrame 原生支持三类 Listener:
ID Listener
用于等待某一个具体 Message ID 的响应。
适合:
text
PARAM_GET → PARAM_VALUE
SAVE_CONFIG → ACK
CALIBRATE → RESULT
Type Listener
按照 Message Type 注册业务处理函数。
例如概念上:
c
TF_AddTypeListener(tf, MSG_AXIS_STATE, axis_state_handler);
TF_AddTypeListener(tf, MSG_PARAM_SET, param_set_handler);
TF_AddTypeListener(tf, MSG_CALIBRATE, calibrate_handler);
这是本项目最需要的机制。
Generic Listener
当没有更具体的 listener 匹配时,用作 fallback。
4.4 Request / Response
TinyFrame 的 Frame ID 可以在 response 中复用,因此:
text
Pitch
↓
PARAM_GET
ID = 100
↓
Yaw
↓
PARAM_VALUE
ID = 100
发送端天然知道:
这是对哪一条 request 的回复。
而且 listener 可以带 timeout。
因此大量基础设施无需重新实现。
4.5 优点
- API 很小
- 纯 C
- 适合 STM32
- 原生 listener 注册机制
- 自带 request/response
- 自带 timeout
- 自带 checksum
- 无需代码生成器
- MIT License
- 容易 fork 后长期维护
- 非常适合固定点对点 UART 链路
4.6 缺点
最大问题不是架构,而是:
项目较老。
官方 release 很早,后续维护活跃度不高。
因此如果真正用于商业量产产品,不建议完全依赖 upstream。
比较合理的方式:
text
Fork TinyFrame
↓
冻结一个内部版本
↓
精简不需要的功能
↓
补齐 Unit Test
↓
补 Fuzz Test
↓
适配 UART DMA / RingBuffer
↓
公司内部长期维护
这反而很适合作为基础设施。
5. MIN Protocol
项目:
https://github.com/min-protocol/min
全称:
Microcontroller Interconnect Network
5.1 定位
MIN 专门用于:
text
MCU ↔ MCU
MCU ↔ PC
设计目标就是:
- 小 MCU
- 串口
- 较低资源
- 高可靠性
官方 reference implementation 包含:
- Embedded C
- Python Host
协议本身比 TinyFrame 更像一个"已经定义好的微型标准串口协议"。
5.2 优点
- 很轻
- 设计目标就是 MCU
- C reference implementation
- PC 端 Python implementation
- 支持可靠传输机制
- 协议结构明确
- MIT License
- 很适合作为长期冻结的设备内部协议
5.3 不足
相比 TinyFrame:
业务 handler/listener registry 不是它最突出的能力。
如果使用 MIN,建议自己增加一个非常薄的 dispatch table:
c
typedef void (*msg_handler_t)(
const uint8_t *data,
uint16_t len
);
typedef struct
{
uint8_t msg_id;
msg_handler_t handler;
} msg_handler_entry_t;
例如:
c
static const msg_handler_entry_t handlers[] =
{
{ MSG_AXIS_STATE, axis_state_handler },
{ MSG_PARAM_SET, param_set_handler },
{ MSG_CALIBRATE, calibrate_handler },
};
然后:
text
MIN frame parser
↓
message ID
↓
dispatch table
↓
handler
仍然可以完全避免 switch-case。
6. LwPKT
项目:
https://github.com/MaJerle/lwpkt
6.1 特点
LwPKT 是一个非常轻的 Packet Protocol Manager。
特点包括:
- C11
- Platform independent
- CRC
- variable payload
- from / to address
- command field
- Packet ready event
- 适合 UART / USB / RS485 / Ethernet
- MIT License
相比 TinyFrame,它更偏:
"我帮你可靠地得到一个 packet。"
而不是:
"我帮你构造完整业务消息层。"
6.2 适合场景
如果以后内部总线变成:
text
Yaw
Pitch
Roll
Camera
Sensor Board
且需要:
text
FROM
TO
CMD
DATA
CRC
那么 LwPKT 很合适。
但是本项目当前只是:
text
Pitch ↔ Yaw
且非常重视 handler 注册机制,因此 TinyFrame 更直接。
7. eRPC
项目:
https://github.com/EmbeddedRPC/erpc
License:
BSD-3-Clause
7.1 思路完全不同
eRPC 不再让程序员关注:
text
Message ID
Payload
Handler
而是直接定义远程函数。
例如:
text
interface Gimbal
{
setPid(...);
calibrateEncoder();
saveConfig();
reboot();
}
erpcgen 自动生成:
- Client Stub
- Server Stub
- 序列化
- 反序列化
- Function Dispatch
于是调用端可以写:
c
setPid(...);
看起来就像调用本地函数。
7.2 非常适合
text
PARAM_SET
PARAM_GET
CALIBRATE
SAVE
REBOOT
DEVICE_INFO
FW_VERSION
这类低频 RPC。
7.3 不推荐直接用于
text
1 kHz Gyro
1 kHz Encoder
1 kHz Motor Current
虽然技术上可以实现,但抽象层和代码生成体系对于本项目的高频轴状态同步略显过度。
因此:
eRPC 很漂亮,但对 Pitch/Yaw 内部高频实时链路不是首选。
8. microTLV 是否还有必要
项目:
https://github.com/marcinbor85/microtlv
它主要解决:
text
Tag
Length
Value
的 Payload 编解码。
从当前架构看,不建议所有消息都使用 TLV。
原因:
实时数据字段稳定:
text
timestamp
encoder
gyro
angle
current
status
如果每一个字段都加:
text
TAG + LENGTH
会带来不必要的:
- 带宽开销
- CPU 解析开销
- 数据结构复杂度
9. 推荐的数据分层
9.1 高频实时消息:定长 Binary
例如:
c
AxisState
{
timestamp_us
seq
encoder
gyro_x
gyro_y
gyro_z
angle
motor_current
status
}
概念结构:
text
TinyFrame
↓
MSG_AXIS_STATE
↓
Fixed Binary Payload
适合:
text
500 Hz
1 kHz
不需要 ACK。
丢了一帧直接等待下一帧。
9.2 参数消息:轻量 TLV / Parameter Value
没必要引入完整 TLV Framework,也可以自己定义一个极薄的统一格式:
text
PARAM_ID
TYPE
LENGTH
VALUE
例如:
text
PARAM_ID = 0x1010
TYPE = FLOAT32
LENGTH = 4
VALUE = 1.25
这样就已经具备 TLV 的主要扩展能力。
9.3 Command 类:Request / Response
例如:
text
CALIBRATE
SAVE_CONFIG
REBOOT
FACTORY_RESET
使用 TinyFrame 的 Message ID + Response Listener:
text
Request
↓
ID = N
↓
执行
↓
Response
ID = N
同时支持 timeout。
10. 推荐最终架构
目前最推荐:
text
UART DMA
│
↓
Ring Buffer
│
↓
TinyFrame
│
┌───────────┴───────────┐
│ │
↓ ↓
Type Listener ID Listener
│ │
│ Request/Response
│
┌──────┼────────┐
↓ ↓ ↓
State Param Command
│ │ │
Fixed mini ACK/Result
Binary TLV
业务模块:
text
protocol/
├── protocol_port.c
├── protocol_registry.c
├── protocol_message.h
├── protocol_param.c
├── protocol_command.c
├── protocol_state.c
└── third_party/
└── TinyFrame/
11. 建议的消息类型
初期可以控制在很小范围。
text
0x01 HEARTBEAT
0x02 AXIS_STATE
0x03 SENSOR_SYNC
0x10 PARAM_GET
0x11 PARAM_SET
0x12 PARAM_VALUE
0x13 PARAM_SAVE
0x20 MOTOR_ENABLE
0x21 CALIBRATE_IMU
0x22 CALIBRATE_ENCODER
0x23 REBOOT
0x30 DEVICE_INFO
0x31 FW_VERSION
0x32 ERROR_STATUS
后续按模块扩展。
12. 高频通信是否需要 ACK
不建议所有消息 ACK。
不 ACK
text
AXIS_STATE
GYRO
ENCODER
ANGLE
CURRENT
SENSOR_SYNC
原因:
旧实时数据重传价值很低。
需要确认
text
PARAM_SET
PARAM_SAVE
CALIBRATE
REBOOT
MODE_CHANGE
FACTORY_RESET
这些消息使用:
text
Request
↓
Response / ACK
↓
Timeout
↓
Retry
13. 内部协议与外部协议分工
建议明确区分:
text
云台
│
┌─────────────┴─────────────┐
│ │
↓ ↓
内部总线 对外接口
│ │
TinyFrame/UART MAVLink 2
│ │
Pitch ↔ Yaw Gimbal Protocol v2
Roll / Sensor Parameter Protocol
Firmware / Debug
即:
内部
追求:
- 快
- 小
- 简单
- 低延迟
- 易维护
推荐:
TinyFrame + 固定 Binary + 极简参数 TLV
外部
追求:
- 标准化
- 第三方兼容
- 上位机兼容
- 产品生态
推荐:
MAVLink 2
14. 最终结论
第一选择:TinyFrame
当前最符合本项目需求。
核心原因不是它"功能最多",而是它恰好具备需要的几个关键能力:
text
轻量
+
纯 C
+
Frame
+
Checksum
+
Message Type
+
Message ID
+
Listener Registry
+
Request / Response
+
Timeout
+
MIT
尤其:
Type Listener + ID Listener
非常符合"注册式执行函数"的软件架构目标。
第二选择:MIN Protocol
如果未来更希望采用一个:
"协议规格本身已经定义得比较完整、MCU 和 PC 均有标准 reference implementation"
的方案,可以选择 MIN。
但业务 handler registry 建议自己增加薄薄的一层。
不建议为了 TLV 再引入过多依赖
当前项目建议:
text
高频消息 = Fixed Binary
参数 = Param ID + Type + Length + Value
控制 = Request / Response
足够长期扩展。
15. 产品化建议
TinyFrame 虽适合,但项目历史较老。
如果用于商业量产产品,建议:
- Fork TinyFrame。
- 固定内部版本,不跟随 upstream 自动升级。
- 删除项目中不需要的功能。
- 做成内部
gimbal_protocol基础库。 - 加完整 Unit Test。
- 增加 fuzz / malformed frame 测试。
- 增加 UART DMA + RingBuffer adapter。
- 增加协议统计:
- RX packet
- CRC error
- timeout
- lost frame
- unknown type
- 明确定义大小端。
- 不直接 memcpy 未对齐 struct。
- 明确定义协议版本号。
- 为未来 Roll / Camera / Sensor 扩展预留 Message Type 空间。
最终得到的不是"使用 TinyFrame 的代码",而是一套:
公司内部长期维护的 Gimbal Internal Protocol Infrastructure
后续两轴、三轴、体育跟踪云台、摄像机云台都可以复用。
16. 调研项目链接
-
TinyFrame
-
MIN Protocol
-
LwPKT
-
eRPC
-
microTLV
-
TinyProto
当前推荐一句话总结
云台内部 UART:优先 TinyFrame,实时数据采用固定二进制 Payload,参数采用轻量 Param-ID/Type/Length/Value,关键命令走 Request/Response + timeout;外部接口继续使用 MAVLink 2。