Protocol Buffers在MCU上的nanopb介绍及使用详解

打印 上一主题 下一主题

主题 894|帖子 894|积分 2682

在嵌入式系统和资源受限的环境中,传统的Protocol Buffers 大概显得过于巨大。因此,nanopb 应运而生,它是一个轻量级的 Protocol Buffers 天生器,专为嵌入式系统设计c语言设计。本文将介绍如何安装和使用 nanopb,以及通过一个简单的例子来展示它的基本用法。
  网上的大多数文章都是只讲如何使用。实在新手刚拿到后,很重要的一点是如何用起来?如何安装环境?protoc工具在哪里搞到?这里从环境介绍到详细使用做个总结,留作备忘。
什么是Protocol Buffers

Protocol Buffer是谷歌推出的,和开辟语言无关、平台无关、可扩展的机制,用于序列化结构化数据——像XML,但它更小、更快、更简单。
关键特点:
1、跨平台。
2、可扩展。
3、序列化结构化数据。
4、使用简单。
官方网址:https://developers.google.com/protocol-buffers
支持最常使用的语言
1、C++
2、C#
3、Java
4、Python
什么是 nanopb

nanopb 是一个非常轻量级的 C 库,用于 Protocol Buffers 的序列化和反序列化。它专为嵌入式系统设计,可以运行在内存和存储空间有限的环境中。nanopb 支持 Protocol Buffers 2.3 和 3.0 版本的标准,因此可以用于大多数现有的 Protocol Buffers 定义文件。
堆栈地址:https://gitcode.com/gh_mirrors/na/nanopb
github仓:https://github.com/nanopb/nanopb
安装 nanopb

这个很重要。很多人拿到堆栈后,苦于找不到protoc工具,不知道如何天生pb.c文件。实在原因就出在这里,没有安装环境。而官方在这里的介绍简单了些。nanopb 的安装可以通过 pip3 和从源码编译两种方式举行。本文保举使用 pip3 安装,因为它更加简便快捷。
首先,确保你的开辟环境中已经安装了 Python3 和 pip3。假如还没有安装,可以通过以下下令安装:
  1. sudo apt-get update
  2. sudo apt-get install python3 python3-pip
复制代码
接下来,使用 pip3 安装 nanopb 的相关工具。这里我们使用阿里云的镜像源来加快安装:
  1. pip3 install protobuf grpcio-tools -i https://mirrors.aliyun.com/pypi/simple/
复制代码
安装完成后,你必要确保 nanopb 的天生器工具 protoc 和 nanopb_generator 可用。上面安装完依赖后,这两个工具自然可用。可以将/root/test/c/nanopb/generator/加入搭排环境变量里,linux下方便使用protoc。 你可以通过以下下令来天生 .pb.c 和 .pb.h 文件:
  1. ../../generator/protoc  --nanopb_out=. simple.proto
  2. # 或者
  3. nanopb_generator  simple.proto
复制代码
这里 simple.proto 是你的 Protocol Buffers 定义文件。天生器会根据这个文件天生对应的 C 代码文件。
使用 nanopb

要使用 Nanopb 库,您必要执行以下两个步骤:

  • 使用 protoc 编译您的 .proto 文件以天生适用于 Nanopb 的文件。
  • 在项目中包罗 pb_encode.c、pb_decode.c 和 pb_common.c。
开始学习的最佳方式是研究 “examples/simple” 目录中的示例项目。它包罗了一个 Makefile,在大多数 Linux 系统上可以直接工作。然而,对于其他范例的构建系统,可以参考该目录下 README.txt 中的手动步骤。
下面,我们将通过一个简单的例子来展示如何使用 nanopb。
假设我们有一个简单的 Protocol Buffers 定义文件 simple.proto,
内容如下:
  1. syntax = "proto2";
  2. message SimpleMessage {
  3.     required int32 lucky_number = 1;
  4. }
复制代码
这个定义文件中只包罗一个消息定义 SimpleMessage,它有一个 int32 范例的字段 lucky_number。
接下来,我们将编写 C 代码来处置惩罚这个消息。示例代码如下:
  1. #include <stdio.h>
  2. #include <pb_encode.h>
  3. #include <pb_decode.h>
  4. #include "simple.pb.h"
  5. int main()
  6. {
  7.     /* This is the buffer where we will store our message. */
  8.     uint8_t buffer[128];
  9.     size_t message_length;
  10.     bool status;
  11.    
  12.     /* Encode our message */
  13.     {
  14.         /* Allocate space on the stack to store the message data.
  15.          *
  16.          * Nanopb generates simple struct definitions for all the messages.
  17.          * - check out the contents of simple.pb.h!
  18.          * It is a good idea to always initialize your structures
  19.          * so that you do not have garbage data from RAM in there.
  20.          */
  21.         SimpleMessage message = SimpleMessage_init_zero;
  22.         
  23.         /* Create a stream that will write to our buffer. */
  24.         pb_ostream_t stream = pb_ostream_from_buffer(buffer, sizeof(buffer));
  25.         
  26.         /* Fill in the lucky number */
  27.         message.lucky_number = 13;
  28.         
  29.         /* Now we are ready to encode the message! */
  30.         status = pb_encode(&stream, SimpleMessage_fields, &message);
  31.         message_length = stream.bytes_written;
  32.         
  33.         /* Then just check for any errors.. */
  34.         if (!status)
  35.         {
  36.             printf("Encoding failed: %s\n", PB_GET_ERROR(&stream));
  37.             return 1;
  38.         }
  39.     }
  40.    
  41.     /* Now we could transmit the message over network, store it in a file or
  42.      * wrap it to a pigeon's leg.
  43.      */
  44.     /* But because we are lazy, we will just decode it immediately. */
  45.    
  46.     {
  47.         /* Allocate space for the decoded message. */
  48.         SimpleMessage message = SimpleMessage_init_zero;
  49.         
  50.         /* Create a stream that reads from the buffer. */
  51.         pb_istream_t stream = pb_istream_from_buffer(buffer, message_length);
  52.         
  53.         /* Now we are ready to decode the message. */
  54.         status = pb_decode(&stream, SimpleMessage_fields, &message);
  55.         
  56.         /* Check for errors... */
  57.         if (!status)
  58.         {
  59.             printf("Decoding failed: %s\n", PB_GET_ERROR(&stream));
  60.             return 1;
  61.         }
  62.         
  63.         /* Print the data contained in the message. */
  64.         printf("Your lucky number was %d!\n", (int)message.lucky_number);
  65.     }
  66.    
  67.     return 0;
  68. }
复制代码
这段代码首先初始化了一个 SimpleMessage 结构体,然后使用 pb_encode 函数将这个结构体编码到一个字节缓冲区中。接着,它又将这个缓冲区中的数据解码回一个 SimpleMessage 结构体,并输出其中的 lucky_number 字段。
Makefile 文件

为了简化编译过程,我们可以使用 Makefile 文件来管理编译规则。以下是示例 Makefile 文件的内容:
  1. # Include the nanopb provided Makefile rules
  2. include ../../extra/nanopb.mk
  3. # Compiler flags to enable all warnings & debug info
  4. CFLAGS = -Wall -Werror -g -O0
  5. CFLAGS += -I$(NANOPB_DIR)
  6. # C source code files that are required
  7. CSRC  = simple.c                   # The main program
  8. CSRC += simple.pb.c                # The compiled protocol definition
  9. CSRC += $(NANOPB_DIR)/pb_encode.c  # The nanopb encoder
  10. CSRC += $(NANOPB_DIR)/pb_decode.c  # The nanopb decoder
  11. CSRC += $(NANOPB_DIR)/pb_common.c  # The nanopb common parts
  12. # Build rule for the main program
  13. simple: $(CSRC)
  14.         $(CC) $(CFLAGS) -osimple $(CSRC)
  15. # Build rule for the protocol
  16. simple.pb.c: simple.proto
  17.         $(PROTOC) $(PROTOC_OPTS) --nanopb_out=. $<
复制代码
这个 Makefile 文件首先包罗了 nanopb 提供的 Makefile 规则 nanopb.mk。然后,它定义了一些编译器标志 CFLAGS,用于在编译过程中启用所有警告并包罗调试信息。CSRC 变量列出了所有必要编译的 C 源代码文件,包括主步伐文件 simple.c、编译后的 Protocol Buffers 定义文件 simple.pb.c 以及 nanopb 库的核心文件。
最后,Makefile 文件定义了天生终极可执行文件 simple 的规则,以及从 Protocol Buffers 定义文件 simple.proto 天生 C 源代码文件 simple.pb.c 的规则。
nanopb.mk 文件

nanopb.mk 文件包罗了 nanopb 提供的 Makefile 规则,用于天生 .pb.c 和 .pb.h 文件以及定义 nanopb 库的核心文件路径。以下是 nanopb.mk 文件的内容:
  1. # This is an include file for Makefiles. It provides rules for building
  2. # .pb.c and .pb.h files out of .proto, as well the path to nanopb core.
  3. # Path to the nanopb root directory
  4. NANOPB_DIR := $(patsubst %/,%,$(dir $(patsubst %/,%,$(dir $(lastword $(MAKEFILE_LIST)))))))
  5. # Files for the nanopb core
  6. NANOPB_CORE = $(NANOPB_DIR)/pb_encode.c $(NANOPB_DIR)/pb_decode.c $(NANOPB_DIR)/pb_common.c
  7. # Check if we are running on Windows
  8. ifdef windir
  9. WINDOWS = 1
  10. endif
  11. ifdef WINDIR
  12. WINDOWS = 1
  13. endif
  14. # Check whether to use binary version of nanopb_generator or the
  15. # system-supplied python interpreter.
  16. ifneq "$(wildcard $(NANOPB_DIR)/generator-bin)" ""
  17.         # Binary package
  18.         PROTOC = $(NANOPB_DIR)/generator-bin/protoc
  19.         PROTOC_OPTS =
  20. else
  21.         # Source only or git checkout
  22.         PROTOC_OPTS =
  23.         ifdef WINDOWS
  24.             PROTOC = python $(NANOPB_DIR)/generator/protoc
  25.         else
  26.             PROTOC = $(NANOPB_DIR)/generator/protoc
  27.         endif
  28. endif
  29. # Rule for building .pb.c and .pb.h
  30. %.pb.c %.pb.h: %.proto %.options
  31.         $(PROTOC) $(PROTOC_OPTS) --nanopb_out=. $<
  32. %.pb.c %.pb.h: %.proto
  33.         $(PROTOC) $(PROTOC_OPTS) --nanopb_out=. $<
复制代码
这个文件首先定义了 nanopb 的根目录路径 NANOPB_DIR,然后列出了 nanopb 库的核心文件路径 NANOPB_CORE。接着,它查抄当前操作系统是否为 Windows,并根据操作系统的差别来设置 PROTOC 变量,以指向正确的 protoc 天生器工具。最后,它定义了天生 .pb.c 和 .pb.h 文件的规则。
稍复杂的使用示例

接下来做一个稍复杂的使用,proto文件定义了两个message:KeyValue和DeviceConfig。其中,DeviceConfig包罗一些基本范例的字段和一个重复的KeyValue字段。天生的nanopb头文件中对于字符串和重复字段,使用了pb_callback_t范例,这意味着这些字段必要通过回调函数来处置惩罚,大概在天生时大概没有设置相应的选项来优化为静态数组。
假如有以下proto文件定义:
  1. syntax = "proto2";
  2. package example;
  3. message KeyValue {
  4.     required string key = 1;
  5.     required int32 value = 2;
  6. }
  7. message DeviceConfig {
  8.     required int32 BrdNum = 1;
  9.     required int32 address = 2;
  10.     required string type = 3;
  11.     required int32 priority = 4;
  12.     required int32 accessTime = 5;
  13.     required string brdLocation = 6;
  14.     repeated KeyValue bitMean = 7;
  15. }
复制代码
编码(序列化)的过程大致是:初始化消息结构体,填充数据,然后调用pb_encode函数。对于回调处置惩罚的字段(如字符串和重复字段),必要设置回调函数大概在结构体中正确填充数据。比方,pb_callback_t范例的字段必要用户提供函数来处置惩罚数据的编码,大概在结构体中直接设置对应的数据指针和大小。
在提供的DeviceConfig结构体中,type、brdLocation和bitMean都是回调范例。对于字符串字段,通常可以使用pb_callback_t的简单方式,比方直接指定字符串的指针和长度,大概设置一个encode函数。而bitMean是repeated的KeyValue,必要处置惩罚为重复字段,大概使用多次回调大概预分配数组。但根据天生的代码,这里大概还是必要使用回调来处置惩罚每个条目。
因此在以下代码中,处置惩罚这些回调字段是关键。对于编码,必要为每个pb_callback_t字段设置对应的函数,大概直接填充数据。比方,对于字符串字段,大概可以使用pb_ostream_from_buffer来创建一个输出流,然后使用pb_encode函数来编码数据。
以下为编码息争码的使用:
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <pb_encode.h>
  5. #include <pb_decode.h>
  6. #include "simple.pb.h"
  7. // 编码回调 ---------------------------------------------------
  8. bool encode_string(pb_ostream_t *stream, const pb_field_t *field, void *const *arg) {
  9.     const char *str = *(const char **)*arg;
  10.     if (!pb_encode_tag_for_field(stream, field))
  11.         return false;
  12.     return pb_encode_string(stream, (uint8_t*)str, strlen(str));
  13. }
  14. bool encode_bitMean(pb_ostream_t *stream, const pb_field_t *field, void *const *arg) {
  15.     const example_KeyValue *items = (const example_KeyValue *)*arg;
  16.     for (size_t i = 0; i < 2; i++) {
  17.         if (!pb_encode_tag_for_field(stream, field))
  18.             return false;
  19.         if (!pb_encode_submessage(stream, example_KeyValue_fields, &items[i]))
  20.             return false;
  21.     }
  22.     return true;
  23. }
  24. // 解码回调 ---------------------------------------------------
  25. typedef struct {
  26.     char *key;
  27.     int32_t value;
  28. } KeyValueData;
  29. bool decode_string(pb_istream_t *stream, const pb_field_t *field, void **arg) {
  30.     char **strptr = (char **)*arg;
  31.     size_t len = stream->bytes_left;
  32.     *strptr = malloc(len + 1);
  33.     if (!pb_read(stream, (uint8_t*)*strptr, len)) {
  34.         free(*strptr);
  35.         return false;
  36.     }
  37.     (*strptr)[len] = '\0';
  38.     return true;
  39. }
  40. bool decode_bitMean(pb_istream_t *stream, const pb_field_t *field, void **arg) {
  41.     KeyValueData **items = (KeyValueData **)*arg;
  42.     size_t count = (*items == NULL) ? 0 : (*items)[0].value;
  43.    
  44.     // 扩展数组空间(使用[0]存储计数)
  45.     KeyValueData *new_items = realloc(*items, (count + 1 + 1) * sizeof(KeyValueData));
  46.     if (!new_items) return false;
  47.    
  48.     *items = new_items;
  49.     (*items)[0].value = count + 1; // 更新计数
  50.    
  51.     KeyValueData *item = &(*items)[count + 1]; // 数据从[1]开始
  52.     item->key = NULL;
  53.    
  54.     example_KeyValue kv = example_KeyValue_init_zero;
  55.     kv.key.arg = &item->key;
  56.     kv.key.funcs.decode = decode_string;
  57.    
  58.     if (!pb_decode(stream, example_KeyValue_fields, &kv)) {
  59.         return false;
  60.     }
  61.     item->value = kv.value;
  62.     return true;
  63. }
  64. int main() {
  65.     /**************** 编码阶段 ****************/
  66.     example_DeviceConfig encode_config = example_DeviceConfig_init_default;
  67.     uint8_t buffer[256];
  68.    
  69.     // 配置编码参数
  70.     encode_config.BrdNum = 123;
  71.     encode_config.address = 456;
  72.     encode_config.priority = 1;
  73.     encode_config.accessTime = 999;
  74.     const char *type_str = "temperature";
  75.     encode_config.type.arg = &type_str;
  76.     encode_config.type.funcs.encode = encode_string;
  77.     const char *loc_str = "Room 101";
  78.     encode_config.brdLocation.arg = &loc_str;
  79.     encode_config.brdLocation.funcs.encode = encode_string;
  80.     example_KeyValue bit_means[2] = {
  81.         {.key.arg = (void*)&(const char*[]){"status"},   .key.funcs.encode = encode_string, .value = 100},
  82.         {.key.arg = (void*)&(const char*[]){"error"},   .key.funcs.encode = encode_string, .value = 0}
  83.     };
  84.     encode_config.bitMean.arg = bit_means;
  85.     encode_config.bitMean.funcs.encode = encode_bitMean;
  86.     pb_ostream_t ostream = pb_ostream_from_buffer(buffer, sizeof(buffer));
  87.     if (!pb_encode(&ostream, example_DeviceConfig_fields, &encode_config)) {
  88.         fprintf(stderr, "Encode error: %s\n", PB_GET_ERROR(&ostream));
  89.         return 1;
  90.     }
  91.     /**************** 解码阶段 ****************/
  92.     example_DeviceConfig decode_config = example_DeviceConfig_init_zero;
  93.     KeyValueData *bit_means_decoded = NULL;
  94.     char *decoded_type = NULL;
  95.     char *decoded_loc = NULL;
  96.     // 配置解码回调
  97.     decode_config.type.arg = &decoded_type;
  98.     decode_config.type.funcs.decode = decode_string;
  99.    
  100.     decode_config.brdLocation.arg = &decoded_loc;
  101.     decode_config.brdLocation.funcs.decode = decode_string;
  102.    
  103.     decode_config.bitMean.arg = &bit_means_decoded;
  104.     decode_config.bitMean.funcs.decode = decode_bitMean;
  105.     pb_istream_t istream = pb_istream_from_buffer(buffer, ostream.bytes_written); // 关键修正点
  106.     if (!pb_decode(&istream, example_DeviceConfig_fields, &decode_config)) {
  107.         fprintf(stderr, "Decode error: %s\n", PB_GET_ERROR(&istream));
  108.         
  109.         free(decoded_type);
  110.         free(decoded_loc);
  111.         if (bit_means_decoded) {
  112.             for (size_t i = 1; i <= bit_means_decoded[0].value; i++)
  113.                 free(bit_means_decoded[i].key);
  114.             free(bit_means_decoded);
  115.         }
  116.         return 1;
  117.     }
  118.     /**************** 输出结果 ****************/
  119.     printf("Decoded config:\n"
  120.            "Type: %s\n"
  121.            "Location: %s\n"
  122.            "BitMeans count: %d\n",
  123.            decoded_type, decoded_loc,
  124.            bit_means_decoded ? bit_means_decoded[0].value : 0);
  125.     if (bit_means_decoded) {
  126.         for (int i = 1; i <= bit_means_decoded[0].value; i++) {
  127.             printf("  [%d] %s = %d\n",
  128.                    i, bit_means_decoded[i].key, bit_means_decoded[i].value);
  129.         }
  130.     }
  131.     /**************** 清理资源 ****************/
  132.     free(decoded_type);
  133.     free(decoded_loc);
  134.     if (bit_means_decoded) {
  135.         for (int i = 1; i <= bit_means_decoded[0].value; i++) {
  136.             free(bit_means_decoded[i].key);
  137.         }
  138.         free(bit_means_decoded);
  139.     }
  140.     return 0;
  141. }
复制代码
优化使用示例

上述示例,使用了回调和动态内存分配,稍显复杂,改为以下方式则代码更简洁、内存管理更安全。
以下是通过添加nanopb选项优化proto定义后的使用示例:
1. 修改后的proto文件

  1. syntax = "proto2";
  2. import "nanopb.proto";  // 需要nanopb库中的选项定义
  3. package example;
  4. message KeyValue {
  5.     required string key = 1 [(nanopb).max_size = 64];  // 限制key最大64字节
  6.     required int32 value = 2;
  7. }
  8. message DeviceConfig {
  9.     required int32 BrdNum = 1;
  10.     required int32 address = 2;
  11.     required string type = 3 [(nanopb).max_size = 64];    // 限制type长度
  12.     required int32 priority = 4;
  13.     required int32 accessTime = 5;
  14.     required string brdLocation = 6 [(nanopb).max_size = 128]; // 限制位置字符串
  15.     repeated KeyValue bitMean = 7 [(nanopb).max_count = 10];   // 最多10个元素
  16. }
复制代码
2. 天生新的头文件

使用nanopb天生器下令:
  1. protoc --nanopb_out=. simple.proto
复制代码
3. 优化后的使用示例

  1. #include "simple.pb.h"
  2. #include <pb_encode.h>
  3. #include <pb_decode.h>
  4. #include <stdio.h>
  5. #include <string.h>
  6. int main() {
  7.     /**************** 编码示例 ****************/
  8.     example_DeviceConfig config = example_DeviceConfig_init_default;
  9.    
  10.     // 直接赋值字符串(不再需要回调)
  11.     strncpy(config.type, "temperature", sizeof(config.type));
  12.     strncpy(config.brdLocation, "Room 101", sizeof(config.brdLocation));
  13.    
  14.     // 设置基本数值字段
  15.     config.BrdNum = 123;
  16.     config.address = 456;
  17.     config.priority = 1;
  18.     config.accessTime = 999;
  19.     // 填充重复字段
  20.     config.bitMean_count = 2;  // 自动生成的数组长度字段
  21.     strncpy(config.bitMean[0].key, "status", sizeof(config.bitMean[0].key));
  22.     config.bitMean[0].value = 100;
  23.     strncpy(config.bitMean[1].key, "error", sizeof(config.bitMean[1].key));
  24.     config.bitMean[1].value = 0;
  25.     // 编码缓冲区
  26.     uint8_t buffer[256];
  27.     pb_ostream_t ostream = pb_ostream_from_buffer(buffer, sizeof(buffer));
  28.    
  29.     if (!pb_encode(&ostream, example_DeviceConfig_fields, &config)) {
  30.         fprintf(stderr, "Encode failed: %s\n", PB_GET_ERROR(&ostream));
  31.         return 1;
  32.     }
  33.     /**************** 解码示例 ****************/
  34.     example_DeviceConfig decoded = example_DeviceConfig_init_default;
  35.     pb_istream_t istream = pb_istream_from_buffer(buffer, ostream.bytes_written);
  36.    
  37.     if (!pb_decode(&istream, example_DeviceConfig_fields, &decoded)) {
  38.         fprintf(stderr, "Decode failed: %s\n", PB_GET_ERROR(&istream));
  39.         return 1;
  40.     }
  41.     /**************** 输出结果 ****************/
  42.     printf("Decoded config:\n"
  43.            "Type: %s\n"
  44.            "Location: %s\n"
  45.            "BitMeans count: %d\n",
  46.            decoded.type,
  47.            decoded.brdLocation,
  48.            decoded.bitMean_count);
  49.     for (int i = 0; i < decoded.bitMean_count; i++) {
  50.         printf("  [%d] %s = %d\n",
  51.                i, decoded.bitMean[i].key, decoded.bitMean[i].value);
  52.     }
  53.     return 0;
  54. }
复制代码
优化子女码的主要变化


  • 数据结构变化
    1. // 原始回调方式
    2. pb_callback_t type;
    3. // 优化后静态分配
    4. char type[64];
    5. size_t type_size;
    复制代码
  • 重复字段处置惩罚
    1. // 原始方式
    2. pb_callback_t bitMean;
    3. // 优化后静态数组
    4. example_KeyValue bitMean[10];
    5. size_t bitMean_count;
    复制代码
  • 字符串处置惩罚
    1. // 之前需要回调
    2. config.type.funcs.encode = encode_string;
    3. // 现在直接操作
    4. strncpy(config.type, "text", sizeof(config.type));
    复制代码
关键优势阐明


  • 内存管理简化

    • 所有字符串字段变为固定长度char数组
    • 重复字段变为固定大小的数组+count计数器
    • 不再必要手动malloc/free

  • 性能提升

    • 消除回调函数开销
    • 数据内存连续,提高缓存命中率

  • 代码可读性加强

    • 字段访问方式与通例结构体一致
    • 减少约60%的样板代码

注意事项


  • 字段长度限定

    • 超出max_size的字符串会被截断
    • 高出max_count的数组元素会被丢弃

  • 默认值初始化
    1. example_DeviceConfig_init_default  // 会清零所有字段
    2. example_DeviceConfig_init_zero     // 同上,两者等效
    复制代码
  • 字符串处置惩罚发起
    1. // 使用strncpy防止溢出
    2. strncpy(config.type, src, sizeof(config.type));
    3. config.type[sizeof(config.type)-1] = '\0'; // 确保终止符
    复制代码
  • 协议兼容性

    • 修改后的proto仍然与标准protobuf兼容
    • nanopb选项仅影响天生代码的实现方式

保举使用场景



  • 嵌入式系统(内存受限环境)
  • 必要确定性内存分配的场景
  • 对性能要求较高的实时系统
  • 希望简化代码逻辑的项目
通过这种优化方式,代码复杂度显著降低,同时保持了协议的高效性。发起根据实际字段的预期最大长度来设置公道的max_size和max_count值,在内存使用和机动性之间取得平衡。

总结

通过本文,我们相识了什么是 nanopb,及其在嵌入式系统中的应用场景。我们还学习了如何安装和使用 nanopb,包括编写 Protocol Buffers 定义文件、C 代码文件以及 Makefile 文件。通过这些步骤,你可以轻松地在你的项目中集成 nanopb,以便更高效地举行消息的序列化和反序列化。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

民工心事

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表