在 PY32F002A 上实测 TinyFrame 的 Flash 和 RAM 静态消耗

选一个通信库之前,通常想知道它到底占多少 Flash 和 RAM。文档里写的"轻量级"不解决实际问题——3KB RAM 的芯片上能不能跑,CRC16 查找表有多大,缓冲区参数调到多少合适,这些需要实际编译一次才知道。

这次用 IOsetting/py32f0-template 模板,把 TinyFrame 加到 PY32F002A 工程里,用 ARM GCC 编译,测出静态增量。Demo 实现了三个 UART 查询-响应指令:PING、STATUS 和 ECHO。

测试方法

在空白模板上先编译一次作为基线,再加入 TinyFrame 编译第二次,差值就是库本身的静态开销。

环境:

基线工程只做 LED 闪烁 + UART printf。加入 TinyFrame 后,Demo 在 UART 上接收 TinyFrame 帧,按 TYPE 字段分发到三个监听器并返回响应。两次编译除了 TinyFrame 相关代码外,HAL 和 BSP 部分完全一致。

TinyFrame 配置

这份配置是针对 3KB RAM 裁剪过的,也是本次测量的实际参数:

参数 说明
TF_MAX_PAYLOAD_RX 128 最大接收 payload,也是最大的单块 RAM 消耗
TF_SENDBUF_LEN 64 发送缓冲区
TF_MAX_TYPE_LST 5 Type 监听器槽位数
TF_MAX_ID_LST 3 ID 监听器槽位数
TF_MAX_GEN_LST 1 通用监听器槽位数
TF_ID_BYTES / TF_TYPE_BYTES 1 ID 和类型字段宽度
TF_LEN_BYTES 2 长度字段宽度
TF_CKSUM_TYPE TF_CKSUM_CRC16 校验类型
TF_USE_SOF_BYTE 1 启用帧首字节
TF_USE_MUTEX 0 单线程,不加锁

Demo 注册了三个 Type 监听器和一个 Generic 监听器,初始化时调用 TF_InitStatic() 静态分配。UART 接收字节通过 TF_Accept() 喂入解析器,TF_WriteImpl()HAL_UART_Transmit() 阻塞发送。

测量结果

两次编译的 arm-none-eabi-size 对比:

Flash (text+data) RAM (data+bss)
基线(HAL + UART + printf) 9,988 B 1,496 B
加入 TinyFrame 12,836 B 1,856 B
TinyFrame 净增量 +2,848 B +360 B

Flash 多出约 2.8KB,RAM 多出 360B。对于 20KB/3KB 的 PY32F002A,加上 HAL 基线后总共用掉 63% Flash 和 60% RAM,还有空间给业务逻辑。

增量拆解

从 linker map 里把 TinyFrame 相关的符号挑出来:

项目 大小 存储
tf 实例(含 128B RX buf + 64B TX buf + 监听器槽位) 352 B RAM (.bss)
CRC16 查找表 crc16_table 512 B Flash (.rodata)
TF_HandleReceivedMessage 解析核心 336 B Flash (.text)
其余 TF 函数(Init, Send, Query, Listener 管理, Tick...) ~1,680 B Flash (.text)

最大的单块 Flash 开销是 CRC16 的 512 字节查找表——占 TinyFrame 增量的 18%。RAM 开销的大头是 tf 实例本身,其中 RX 缓冲区和 TX 缓冲区合计 192 字节。

可调开关

如果 Flash 或 RAM 更紧张,可以从这几个方向压:

校验类型。 CRC16 → CRC8 省掉 500B Flash(512B 查找表换成 256B),换 XOR 或 None 再省约 20B。代价是检错能力下降,在 UART 等本身有一定误码率的链路上不建议关掉。

缓冲区大小。 TF_MAX_PAYLOAD_RX 从 128 降到 64,直接省 64B RAM。TF_SENDBUF_LEN 从 64 降到 32,再省 32B。这两个参数要和实际业务帧大小匹配——如果最大指令 payload 只有 20 字节,128 的缓冲区就是浪费。

监听器槽位。 TF_MAX_TYPE_LSTTF_MAX_ID_LSTTF_MAX_GEN_LST 每个槽位在 tf 实例中占十几个字节(指针 + 标志位)。按实际需要的最大并发监听数设置,多余的槽位是纯浪费。

字段宽度。 TF_LEN_BYTES 从 2 降到 1(如果 payload 不超过 255 字节),省少量 Flash 和几个字节 RAM。TF_ID_BYTES 同理。

去掉 SOF。 TF_USE_SOF_BYTE 设为 0,省掉帧首字节检查和相关的几行代码。在本身有帧边界机制的链路上(如 BLE Notify 每次一个完整包),SOF 可以省略。

压缩到极限后的估算(CRC8 + RX 64 + TX 32 + 最简监听器):

配置 Flash 增量 RAM 增量
本文配置(CRC16, RX128, TX64) ~2,848 B 360 B
极限压榨(CRC8, RX64, TX32) ~2,100 B 264 B

结论

TinyFrame 在 Cortex-M0+ 上的静态增量约 2.8KB Flash + 360B RAM(CRC16 配置),压到极限约 2.1KB Flash + 264B RAM(CRC8 配置)。对于 20KB/3KB 的 PY32F002A 这种级别的芯片,加上 HAL 后还剩约 7KB Flash 和 1.1KB RAM 给业务层,完全够用。

这个测量方法的要点是:在真实 MCU 工程里编译,拿基线差分,不看 .o 文件大小或文档里的估算。选了芯片和工具链之后跑一次,数字比任何"轻量级"的描述都可靠。