在物联网设备与传感器网络中,序列化技术是设备间数据传输与通信的基石。Zephyr RTOS 为开发者提供了多种数据序列化子系统,方便在通信过程中对结构化数据进行编码与解码。你可以根据项目需求选择最合适的格式,从而有效提升开发效率和系统性能。
Zephyr 主要支持以下三种序列化工具:
- CBOR (Concise Binary Object Representation):一种高效的二进制数据序列化格式,特别适合资源受限的物联网设备,以其高效率和简洁性著称。
- JSON (JavaScript Object Notation):广为人知的轻量级数据交换格式,易于阅读和编写,在网络通信中应用广泛。
- Nanopb:专为嵌入式系统设计的轻量级 Protocol Buffers 实现,支持高效的二进制数据编码和解码。
CBOR 和 Nanopb 在 Zephyr 中作为第三方模块集成,而自带的 JSON API 使用也相对简单。因此,本文将重点解析这些模块在 Zephyr 中的整合配置原理与方法,具体的 API 使用细节,你可以直接参考 Zephyr 源码中的示例代码。
序列化工具对比
在选择工具前,一个清晰的对比能帮助你快速决策。下表详细列出了 Zephyr 支持的三种序列化工具的核心特性:
| 维度 |
JSON |
CBOR |
Nanopb (Protobuf for MCU) |
| 序列化类型 |
文本 (Text) |
二进制 (Binary) |
二进制 (Binary) |
| 是否自描述 |
是 (字段名) |
是 (字段类型 + 键) |
否 (依赖 .proto) |
| 可读性 |
极好 |
较差 |
极差 |
| 数据体积 |
100% |
~50–70% |
~20–40% |
| 编解码性能 |
1x |
3x |
5–10x |
| 内存占用 |
高 |
中 |
极低 |
| 是否需要 Schema |
否 |
否 (可选) |
是 (必须) |
| 代码复杂度 |
最低 |
中 |
高 |
| Zephyr module |
否 (内置,Intel) |
是 (zcbor,Nordic) |
是 (nanopb,Google) |
| 典型应用 |
Web / API |
IoT / CoAP |
MCU / RTOS |
CBOR
CBOR 的使用示例可以参考 Zephyr 源码中的 zephyr/tests/subsys/mgmt/mcumgr/os_mgmt_info/src/main.c 文件。
配置与编译控制
CBOR 的代码由 west 工具管理,位于 modules/lib/zcbor 目录下。要启用 zcbor 功能,你需要在项目配置中开启:
CONFIG_ZCBOR=y
Zephyr 通过 zephyr/modules/zcbor/CMakeLists.txt 文件控制编译过程。该文件会根据上述配置添加相应的源文件,并将 Zephyr 的 Kconfig 配置项映射为 ZCBOR 的宏定义:
if(CONFIG_ZCBOR)
zephyr_include_directories(
${ZEPHYR_ZCBOR_MODULE_DIR}/include
)
zephyr_library()
zephyr_library_sources(
${ZEPHYR_ZCBOR_MODULE_DIR}/src/zcbor_common.c
${ZEPHYR_ZCBOR_MODULE_DIR}/src/zcbor_decode.c
${ZEPHYR_ZCBOR_MODULE_DIR}/src/zcbor_encode.c
${ZEPHYR_ZCBOR_MODULE_DIR}/src/zcbor_print.c
)
zephyr_library_compile_definitions(_POSIX_C_SOURCE=200809L)
zephyr_compile_definitions_ifdef(CONFIG_ZCBOR_CANONICAL ZCBOR_CANONICAL)
zephyr_compile_definitions_ifdef(CONFIG_ZCBOR_STOP_ON_ERROR ZCBOR_STOP_ON_ERROR)
zephyr_compile_definitions_ifdef(CONFIG_ZCBOR_VERBOSE ZCBOR_VERBOSE)
zephyr_compile_definitions_ifdef(CONFIG_ZCBOR_ASSERT ZCBOR_ASSERTS)
zephyr_compile_definitions_ifdef(CONFIG_ZCBOR_BIG_ENDIAN ZCBOR_BIG_ENDIAN)
endif()
Kconfig 配置选项
zcbor 的配置选项定义在 zephyr/modules/zcbor/Kconfig 中,主要包括:
- ZCBOR_CANONICAL: 启用规范化 CBOR 编码,禁止不定长的 list/map,并在解码时强制校验 canonical 规则。
- ZCBOR_STOP_ON_ERROR: 允许在发生错误时立即中止执行。
- ZCBOR_VERBOSE: 允许 zcbor 通过
printf 输出调试或日志信息。
- ZCBOR_ASSERT: 启用 zcbor 内部的断言检查。
- ZCBOR_BIG_ENDIAN: 指示目标平台为大端字节序。
- ZCBOR_MAX_STR_LEN: 为
zcbor_tstr_put_term() 函数提供默认的最大字符串长度(默认为 256,但不推荐直接使用此默认值)。
JSON
配置与实现
JSON 功能由 Zephyr 原生提供,代码由 Intel 贡献,相关文件位于 zephyr/include/zephyr/data/json.h 和 zephyr/lib/utils/json.c。配置 CONFIG_JSON_LIBRARY=y 后,json.c 会被加入构建。如果项目需要序列化浮点数,则还需额外配置 CONFIG_JSON_LIBRARY_FP_SUPPORT=y。
使用方法
Zephyr 原生 JSON 库的使用遵循三个基本步骤:
- 定义数据结构:从
json.h 头文件中选取合适的宏来生成数据结构的描述符。
- 编码:根据描述符的类型,调用相应的编码函数进行序列化。
- 解码:根据描述符的类型,调用相应的解码函数进行反序列化。
zephyr/include/zephyr/data/json.h 中每个 API 都有详细的描述和简短示例。更全面的使用范例可以参考 zephyr/tests/lib/json/src/main.c,它几乎涵盖了所有可能的使用场景。
Nanopb
Nanopb 是 Google Protocol Buffers 的 C语言 实现,专为高效的数据序列化与反序列化设计,尤其适合资源受限的嵌入式系统环境。
其 API 的使用方法可以参考 zephyr/samples/modules/nanopb 示例,下文将重点介绍在 Zephyr 项目中的配置方法。
配置
安装依赖:在 Ubuntu 系统下,需要安装 protocol buffer 编译器:
sudo apt install protobuf-compiler
Zephyr 在编译 .proto 文件时使用 Python 脚本,会优先调用 grpcio-tools 包进行处理。如果找不到该包,则会回退使用系统中安装的 protoc 程序。通常,按照官方步骤搭建 Zephyr 开发环境时,grpcio-tools 已被包含,因此一般无需额外安装。
要启用 Nanopb,必须在配置中设置 CONFIG_NANOPB=y。配置生效后,zephyr/modules/nanopb/CMakeLists.txt 中的编译逻辑才会执行。
编译配置
配置生效后,上述 CMakeLists.txt 会将 Nanopb 的源代码加入编译,并根据 Zephyr 的 Kconfig 选项生成对应的 Nanopb 宏定义:
if(CONFIG_NANOPB)
set(NANOPB_DIR ${ZEPHYR_CURRENT_MODULE_DIR})
zephyr_library()
zephyr_library_sources(
${NANOPB_DIR}/pb_common.c
${NANOPB_DIR}/pb_encode.c
${NANOPB_DIR}/pb_decode.c
)
zephyr_include_directories(${NANOPB_DIR})
zephyr_compile_definitions(
PB_MAX_REQUIRED_FIELDS=${CONFIG_NANOPB_MAX_REQUIRED_FIELDS})
zephyr_compile_definitions_ifdef(
CONFIG_NANOPB_ENABLE_MALLOC
PB_ENABLE_MALLOC
)
zephyr_compile_definitions_ifdef(
CONFIG_NANOPB_NO_ERRMSG
PB_NO_ERRMSG
)
zephyr_compile_definitions_ifdef(
CONFIG_NANOPB_BUFFER_ONLY
PB_BUFFER_ONLY
)
zephyr_compile_definitions_ifdef(
CONFIG_NANOPB_WITHOUT_64BIT
PB_WITHOUT_64BIT
)
zephyr_compile_definitions_ifdef(
CONFIG_NANOPB_ENCODE_ARRAYS_UNPACKED
PB_ENCODE_ARRAYS_UNPACKED
)
zephyr_compile_definitions_ifdef(
CONFIG_NANOPB_VALIDATE_UTF8
PB_VALIDATE_UTF8
)
endif()
Kconfig 配置选项
Zephyr 为 Nanopb 提供了 6 个布尔配置项,它们与 Nanopb 原生宏的对应关系如下:
CONFIG_NANOPB_ENABLE_MALLOC : PB_ENABLE_MALLOC - 启用解码时的动态内存分配(malloc)支持。适用于字段长度不固定的场景,但会增加内存碎片风险和代码体积。
CONFIG_NANOPB_NO_ERRMSG : PB_NO_ERRMSG - 关闭详细的错误字符串输出,仅返回成功/失败的布尔值。可以显著减少代码体积,适合资源极度受限且不需要详细错误信息的场合。
CONFIG_NANOPB_BUFFER_ONLY : PB_BUFFER_ONLY - 只支持内存 buffer 的编码/解码,禁用自定义流(如UART、文件流)。以此换取更快的速度和更小的代码体积。
CONFIG_NANOPB_WITHOUT_64BIT : PB_WITHOUT_64BIT - 禁用 int64 / uint64 / fixed64 等 64 位字段的支持,以节省代码空间。
CONFIG_NANOPB_ENCODE_ARRAYS_UNPACKED : PB_ENCODE_ARRAYS_UNPACKED - 将标量数组编码为非 packed 格式(会占用更多字节)。仅在对端解码器不支持 packed arrays(例如某些旧版本的 protobuf.js)时才需要使用。
CONFIG_NANOPB_VALIDATE_UTF8 : PB_VALIDATE_UTF8 - 对接收到的 string 类型字段进行 UTF-8 合法性校验,提高数据安全性,但会引入少量的 CPU 和代码体积开销。
值配置项:
CONFIG_NANOPB_MAX_REQUIRED_FIELDS : PB_MAX_REQUIRED_FIELDS - 设置 proto2 语法中 required 字段的最大检查数量。用于解码后校验是否所有必填字段都已出现。数值越大,占用的 RAM 越多(默认/最小值为 64)。
Nanopb 配置方法
在应用层使用 Nanopb 需要以下步骤:
-
导入 CMake 模块:在你的应用 CMakeLists.txt 文件中添加以下内容:
list(APPEND CMAKE_MODULE_PATH ${ZEPHYR_BASE}/modules/nanopb)
include(nanopb)
-
添加 proto 文件:使用 zephyr_nanopb_sources() 函数来添加你的 .proto 文件。这能确保生成的头文件和源文件在构建目标之前被正确创建:
zephyr_nanopb_sources(app src/simple.proto)
-
配置生成选项:Nanopb 允许通过生成选项来配置消息或字段,例如设置固定大小或跳过某些字段。这可以通过 .options.in 文件和 CMake 变量来完成。
示例 proto 文件 (simple.proto):
syntax = "proto3";
message SimpleMessage {
int32 lucky_number = 1;
bytes buffer = 2;
int32 unlucky_number = 3;
}
假设你想控制 buffer 字段的最大大小,并选择性地跳过 unlucky_number 字段,可以创建一个 simple.options.in 文件。其配置方式与标准 Nanopb 一致,Zephyr 的 CMake 脚本会负责解析它:
SimpleMessage.buffer max_size:@CONFIG_SAMPLE_BUFFER_SIZE@ fixed_length:true
SimpleMessage.unlucky_number type:@unlucky_number_type@
注意其中使用了 @Keyword@ 的引用格式:
@CONFIG_SAMPLE_BUFFER_SIZE@
@unlucky_number_type@
这里的 CONFIG_SAMPLE_BUFFER_SIZE 是直接引用 Kconfig 中的整数值,例如:
config SAMPLE_BUFFER_SIZE
int "Simple message buffer size"
default 8
help
Configure the simple message buffer field's size.
而 @unlucky_number_type@ 则需要通过 CMake 变量来设置,它在 CMakeLists.txt 中定义:
if(CONFIG_SAMPLE_UNLUCKY_NUMBER)
set(unlucky_number_type "FT_DEFAULT")
else()
set(unlucky_number_type "FT_IGNORE")
endif()
总结
在 Zephyr 技术栈中,JSON、CBOR 和 Nanopb 这三种序列化工具各有千秋。JSON 最适合需要高可读性或与 Web 服务交互的场景;CBOR 在二进制效率与一定自描述性之间取得了平衡,是许多 IoT 协议的选择;而 Nanopb 则提供了最高的编码效率和最小的数据体积,非常适合 MCU 间或对带宽、功耗有极致要求的通信。
选择哪种工具,取决于你的具体需求:是追求开发便利,还是极致的性能与资源利用。希望本文的对比与配置指南能帮助你在 Zephyr 项目中做出更合适的技术选型。如果你想深入探讨更多嵌入式系统设计或 RTOS 相关话题,欢迎访问云栈社区进行交流。
参考