Zephyr 序列化工具

概述 链接到标题

序列化技术在物联网设备、传感器网络中广泛用于设备之间的数据传输和通信。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 5–10×
内存占用 极低
是否需要 Schema 否(可选) 是(必须)
代码复杂度 最低
Zephyr module 否(内置,Intel) 是(zcbor,Nordic) 是(nanopb,Google)
典型应用 Web / API IoT / CoAP MCU / RTOS

CBOR 链接到标题

使用示例可以参考 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 的配置项设置 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 配置选项位于 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.hzephyr/lib/utils/json.c。配置 CONFIG_JSON_LIBRARY=y 后,json.c 会被加入构建。如果需要支持浮点数序列化,还需配置 CONFIG_JSON_LIBRARY_FP_SUPPORT=y

使用方法 链接到标题

原生的 JSON 使用分为三个步骤:

  1. 定义数据结构:从 json.h 中选取相应的宏生成数据结构描述符
  2. 编码:根据描述符类型选择相应的编码函数进行编码
  3. 解码:根据描述符类型选择相应的解码函数进行解码

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 示例,本文主要介绍配置方法。

配置 链接到标题

安装依赖:在 Ubuntu 下需要安装 protocol buffer 编译器:

sudo apt install protobuf-compiler

Zephyr 在处理 proto 文件编译时使用 Python 脚本,会优先调用 grpcio-tools 进行处理。如果找不到该 pip 包,则会使用系统中的 protoc。按照官方步骤建立 Zephyr 环境时,该包已包含在内,因此无需额外安装可执行程序。

需要配置 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 支持 6 个布尔配置项,对应关系如下:

  • 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 配置方法 链接到标题

  1. 导入 CMake 模块:在应用的 CMakeLists.txt 中添加如下内容:
list(APPEND CMAKE_MODULE_PATH ${ZEPHYR_BASE}/modules/nanopb)
include(nanopb)
  1. 添加 proto 文件:使用 zephyr_nanopb_sources() 函数添加 .proto 文件,确保生成的头文件和源文件在构建目标之前创建:
zephyr_nanopb_sources(app src/simple.proto)
  1. 配置生成选项:Nanopb 提供生成选项,允许配置消息或字段,例如设置固定大小或跳过某些字段。可以通过 .options.in 文件和 CMake 变量完成。

示例 proto 文件

syntax = "proto3";

message SimpleMessage {
    int32 lucky_number = 1;
    bytes buffer = 2;
    int32 unlucky_number = 3;
}

要控制 buffer 的最大大小和跳过 unlucky_number,可以创建 simple.options.in 文件。这部分与正常的 Nanopb 配置相同,nanopb 的 cmake 会控制脚本工具来解析该文件:

SimpleMessage.buffer max_size:@CONFIG_SAMPLE_BUFFER_SIZE@ fixed_length:true
SimpleMessage.unlucky_number type:@unlucky_number_type@

这里主要看引用的方式为@Keywork@

  • @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@是用的nanopb的类型,需要在CMakeLists.txt中通过通过变量来设置

if(CONFIG_SAMPLE_UNLUCKY_NUMBER)
    set(unlucky_number_type "FT_DEFAULT")
else()
    set(unlucky_number_type "FT_IGNORE")
endif()

参考 链接到标题

https://docs.zephyrproject.org/latest/services/serialization/index.html