TinyFrame 可以给串口和 BLE 透传加一层可靠消息帧

TinyFrame 是一个 C 语言消息帧库,适合放在 UART、BLE 透传、USB CDC、socket 这类字节流链路上。底层链路只负责收发字节,TinyFrame 负责把字节组织成一条条有边界、有类型、有校验的消息。

这类库的价值在于减少重复造轮子。嵌入式项目里经常会遇到很多指令:设置时间、读取时间、读取传感器记录、删除记录、测速、查询版本。直接在透传数据里手写解析逻辑,很容易把包边界、长度检查、校验、指令分发和业务逻辑混在一起。TinyFrame 把这些通用动作收进协议层,业务代码只需要按消息类型处理指令。

适用场景

TinyFrame 适合这些场景:

TinyFrame 的定位是消息帧库,文件传输调度仍由应用层负责。TinyFrame 可以发送长 payload,也提供 multipart 发送接口,但接收端仍然需要用 TF_MAX_PAYLOAD_RX 缓冲完整一帧后再校验。超出接收缓冲区的大数据,应在应用层拆成多条消息传输。

帧里有什么

TinyFrame 的一帧通常包含这些部分:

这些字段的字节数可以在 TF_Config.h 里配置。例如 ID、长度、类型字段可以选择 1、2 或 4 字节,校验可以选择无校验、XOR、CRC8、CRC16、CRC32 或自定义校验。两个通信端必须使用同一套配置。

优点

TinyFrame 的优点主要体现在工程边界清楚:

对指令较多的嵌入式项目来说,type 字段很适合作为命令号。新增一条业务指令时,通常只需要定义新的 type,注册对应监听器,然后在回调里处理 payload。

移植步骤

移植时只需要把通用库和工程适配层分清楚。

第一步,把这些文件放进工程:

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 辅助生成,可能存在错误或遗漏,请以实际资料和官方文档为准。