在 PY32F002A 上实测 TinyFrame 的 Flash 和 RAM 静态消耗
选一个通信库之前,通常想知道它到底占多少 Flash 和 RAM。文档里写的"轻量级"不解决实际问题——3KB RAM 的芯片上能不能跑,CRC16 查找表有多大,缓冲区参数调到多少合适,这些需要实际编译一次才知道。
这次用 IOsetting/py32f0-template 模板,把 TinyFrame 加到 PY32F002A 工程里,用 ARM GCC 编译,测出静态增量。Demo 实现了三个 UART 查询-响应指令:PING、STATUS 和 ECHO。
测试方法
在空白模板上先编译一次作为基线,再加入 TinyFrame 编译第二次,差值就是库本身的静态开销。
环境:
- 模板:IOsetting/py32f0-template
- 芯片:PY32F002A(Cortex-M0+, 20KB Flash, 3KB RAM)
- 编译器:
arm-none-eabi-gcc 13.2.1,-Os -ffunction-sections -fdata-sections -Wl,--gc-sections - HAL 库:PY32F0xx HAL Driver(UART + GPIO + printf)
- TinyFrame:MightyPork/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_LST、TF_MAX_ID_LST、TF_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 文件大小或文档里的估算。选了芯片和工具链之后跑一次,数字比任何"轻量级"的描述都可靠。