TinyFrame 可以给串口和 BLE 透传加一层可靠消息帧
TinyFrame 是一个 C 语言消息帧库,适合放在 UART、BLE 透传、USB CDC、socket 这类字节流链路上。底层链路只负责收发字节,TinyFrame 负责把字节组织成一条条有边界、有类型、有校验的消息。
这类库的价值在于减少重复造轮子。嵌入式项目里经常会遇到很多指令:设置时间、读取时间、读取传感器记录、删除记录、测速、查询版本。直接在透传数据里手写解析逻辑,很容易把包边界、长度检查、校验、指令分发和业务逻辑混在一起。TinyFrame 把这些通用动作收进协议层,业务代码只需要按消息类型处理指令。
适用场景
TinyFrame 适合这些场景:
- 串口或 BLE 透传本身只提供字节收发,没有消息边界。
- 指令类型比较多,希望用
type字段分发业务命令。 - 需要长度字段和校验,避免半包、粘包、乱包直接进入业务逻辑。
- 需要请求和响应关联,可以用帧 ID 等待某次请求的回复。
- MCU 资源有限,希望协议层用静态缓冲区和少量 C 文件完成。
TinyFrame 的定位是消息帧库,文件传输调度仍由应用层负责。TinyFrame 可以发送长 payload,也提供 multipart 发送接口,但接收端仍然需要用 TF_MAX_PAYLOAD_RX 缓冲完整一帧后再校验。超出接收缓冲区的大数据,应在应用层拆成多条消息传输。
帧里有什么
TinyFrame 的一帧通常包含这些部分:
- SOF:帧开始字节,可配置。
- ID:帧 ID,用来关联请求和响应。
- LEN:payload 长度。
- TYPE:消息类型,常用来当作指令号。
- HEAD_CKSUM:头部校验。
- DATA:业务数据。
- DATA_CKSUM:payload 校验。
这些字段的字节数可以在 TF_Config.h 里配置。例如 ID、长度、类型字段可以选择 1、2 或 4 字节,校验可以选择无校验、XOR、CRC8、CRC16、CRC32 或自定义校验。两个通信端必须使用同一套配置。
优点
TinyFrame 的优点主要体现在工程边界清楚:
- 协议层负责帧格式、校验、解析状态机和监听器。
- 业务层只关心某个
type对应哪条指令。 - 发送接口只依赖
TF_WriteImpl(),可以接 UART、BLE notify/write、USB 或 TCP。 - 接收接口只需要把收到的字节交给
TF_Accept()。 - 静态初始化可避免
malloc(),适合 MCU 工程。 - 多实例可用,但同一工程里的实例要使用同样的字段配置。
对指令较多的嵌入式项目来说,type 字段很适合作为命令号。新增一条业务指令时,通常只需要定义新的 type,注册对应监听器,然后在回调里处理 payload。
移植步骤
移植时只需要把通用库和工程适配层分清楚。
第一步,把这些文件放进工程:
TinyFrame.cTinyFrame.hTF_Config.h
TF_Config.example.h 可以复制成 TF_Config.h 后再按项目调整。常见配置包括字段长度、校验类型、最大接收 payload、发送缓冲区长度、监听器数量和是否启用互斥锁。
第二步,实现发送出口:
void TF_WriteImpl(TinyFrame *tf, const uint8_t *buff, uint32_t len)
{
ble_or_uart_write(buff, len);
}TinyFrame 不直接操作硬件。所有 TF_Send()、TF_Respond() 最终都会调用 TF_WriteImpl(),所以这个函数就是协议层到物理链路的出口。
第三步,初始化实例并注册监听器:
static TinyFrame tf;
void app_proto_init(void)
{
TF_InitStatic(&tf, TF_SLAVE);
TF_AddTypeListener(&tf, 0x01, on_set_time);
TF_AddTypeListener(&tf, 0x02, on_get_time);
}如果项目允许动态内存,也可以使用 TF_Init()。在 MCU 工程里更常用 TF_InitStatic(),实例由应用自己分配。
第四步,把收到的数据喂给解析器:
void on_link_rx(const uint8_t *data, uint32_t len)
{
TF_Accept(&tf, data, len);
}UART 中断、BLE 写特征回调、USB CDC 接收回调都可以走这条路径。TinyFrame 会在内部累积字段、校验长度和校验和,解析出完整消息后再调用监听器。
第五步,如果使用请求超时或解析超时,周期性调用:
void systick_handler(void)
{
TF_Tick(&tf);
}TF_Tick() 的时间基准由调用周期决定。它用于解析器超时,也用于 ID 监听器超时。
简单发送和响应
发送一条普通消息可以使用 TF_Send():
uint8_t payload[] = { 0x01, 0x02, 0x03 };
TF_Msg msg;
TF_ClearMsg(&msg);
msg.type = 0x10;
msg.data = payload;
msg.len = sizeof(payload);
TF_Send(&tf, &msg);接收某个类型的消息时,监听器拿到的是 TF_Msg:
static TF_Result on_get_time(TinyFrame *tf, TF_Msg *msg)
{
uint8_t rsp[7];
fill_time_payload(rsp);
msg->data = rsp;
msg->len = sizeof(rsp);
TF_Respond(tf, msg);
return TF_STAY;
}TF_Respond() 会复用收到消息的帧 ID,方便请求方把响应和原请求对应起来。监听器返回 TF_STAY 表示继续保留,后续同类型消息还会进入这个回调。
使用时要注意
配置要先定下来。字段长度、校验类型、SOF 开关、大小端和最大 payload 都会影响帧格式,通信双方必须一致。
接收缓冲区要按最大单帧 payload 预留。TinyFrame 解析 payload 后才会交给业务回调,因此 TF_MAX_PAYLOAD_RX 太小会导致大帧被丢弃。大文件、大波形、大批量记录应拆成多条业务消息,不要依赖单帧硬扛。
发送 multipart 只解决发送端不想一次性准备完整 payload 的问题。它不会降低接收端对完整单帧缓冲区的要求。
如果在多线程或中断加主循环环境中共用同一个 TinyFrame 实例,要开启 TF_USE_MUTEX 并实现 TF_ClaimTx()、TF_ReleaseTx(),或者在应用层保证发送接口不会并发进入。
结论
TinyFrame 适合给“透传链路”补齐协议层。它不替代 UART、BLE 或 TCP,也不替代业务协议设计;它负责把字节流变成可校验、可分发、可响应的消息帧。
在指令数量较多的 MCU 项目里,可以把 TinyFrame 放在链路层和业务层之间:链路层收发字节,TinyFrame 解析和封包,业务层按 type 处理命令。这样新增指令时,主要改业务回调,不需要反复重写包解析状态机。
本文由 AI 辅助生成,可能存在错误或遗漏,请以实际资料和官方文档为准。