nlohmann::json 的 to_cbor() 不适合裸机嵌入式,因其依赖 STL、隐式 malloc、不可控栈帧且不支持流式编码;TinyCBOR 零 malloc、纯静态操作、错误可检、栈深度可控,更适合 MCU。

直接用 nlohmann::json 的 to_cbor() 和 from_cbor() 最快上手,但嵌入式端不能这么干——它会悄悄 malloc、依赖 STL 容器、生成不可控栈帧,STM32F4 上一跑就 hardfault。
为什么 nlohmann/json 的 to_cbor 不适合裸机嵌入式
它底层把 std::vector 当作缓冲区,而 vector 构造默认调用 operator new;即使你传入预分配内存(如 std::vector with custom allocator),其内部仍可能触发异常路径的堆分配。更关键的是,它不暴露编码器状态,无法做流式写入或中断恢复,也不支持只编码部分字段(比如跳过某个可选 sensor_id)。
真正适合 MCU 的是 TinyCBOR 这类零 malloc、纯栈/静态缓冲操作的库:
-
CborEncoder和CborParser全部状态存于结构体中,ptr、remaining等字段全由你控制 - 所有函数返回
CborError,比如CborErrorOutOfMemory表示缓冲区溢出,不是靠异常或静默截断 - 编译后 ROM 占用约 10 KB(ARM Cortex-M4, -Os),RAM 零堆占用,栈深度可静态估算
用 TinyCBOR 编码传感器数据:从 struct 到 CBOR 字节流
假设你有一个温度上报结构:
struct SensorReport {
uint32_t timestamp;
int16_t temperature;
uint8_t battery_level;
bool active;
};
正确做法是手动构造映射(map),而非尝试“自动反射”:
- 先声明一个足够大的静态缓冲区,比如
uint8_t cbor_buf[64](实测 4 字段 map 通常 ≤ 32 字节) - 初始化
CborEncoder enc,设置enc.ptr = cbor_buf、enc.end = sizeof(cbor_buf)、enc.base = cbor_buf - 调用
cbor_encode_map(&enc, 4)告诉编码器要写 4 个 key-value 对 - 逐个写 key(必须是 string 或 int)和 value:
cbor_encode_text_stringz(&enc, "ts")→cbor_encode_uint(&enc, rpt.timestamp)
注意:key 用 text_stringz(带结尾
注意:key 用 text_stringz(带结尾 \0)还是 text_string(显式长度)取决于你是否想省那 1 字节;TinyCBOR 不支持 symbol key(如整数键 0x01),得自己约定字符串名。
text_string(显式长度)取决于你是否想省那 1 字节;TinyCBOR 不支持 symbol key(如整数键 0x01),得自己约定字符串名。
使用约定式提交(Conventional Commits)生成标准化的 Git 提交信息。适用于以下场景: (1) 暂存更改时需要提交信息; (2) 审查 diff 时希望获得结构化摘要; (3) 编写 PR 描述或变更日志(changelog)条目; (4) 用户明确要求“为本次更改编写提交信息”。 该功能会分析已暂存/未暂存的 diff,并生成按类型(type)划分范围的提交信息。
解码时如何避免 buffer overrun 和类型错配
解码比编码更容易翻车,常见错误是没检查类型就强转:
- 永远先调用
cbor_value_get_type(&it, &type),再 switchtype分支处理,别直接cbor_value_get_int - 字符串要用
cbor_value_calculate_string_length先算长度,再用cbor_value_copy_text_string拷贝到目标 buffer,否则可能越界读 - 数组/映射需用
cbor_value_enter_container+ 循环cbor_value_advance,且每次 advance 后必须检查返回值是否为CborNoError - 如果字段可选(如 firmware_version 可能不存在),用
cbor_value_map_find_value(&it, "ver", &sub),它返回CborErrorNotFound而非崩溃
没有“自动跳过未知字段”这种便利功能——你得显式 cbor_value_advance 掉它,否则解析器卡在原地,后续字段全乱。
CBOR 标签(tag)在固件升级中的实际用途
标准 CBOR tag 如 24(byte string)、25(UTF-8 string)不用管,但自定义 tag 是嵌入式通信的关键扩展点:
- 用 tag 27 表示 Unix 时间戳:
cbor_encode_tag(&enc, 27),然后跟一个 uint64_t,接收端看到 tag 27 就按时间戳语义解析 - OTA 固件块可用 tag 24 + 自定义 tag 55799(RFC 7049 的“未分配标签”,常被厂商私用)包裹校验和:
cbor_encode_tag(&enc, 55799)→cbor_encode_byte_string(&enc, hash_bytes, 32) - 注意:TinyCBOR 默认不验证 tag 合法性,tag 值只要 ≤ 255 就能编码,但跨平台互通时建议只用已注册 tag(查 iana.org/cbor-tags)
标签本身不压缩体积,但它让二进制载荷具备自描述能力——同一份 CBOR 流,Python 端用 cbor2.loads() 能自动识别 tag 27 为 datetime,而 MCU 端可忽略 tag 直接取内层值,兼容性比硬编码字段名更灵活。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










