walker2048 发表于 2024-5-13 15:31

【FireBeetle 2 ESP32 C6开发板】1、移植nanopb到esp32上

<div class='showpostmsg'> 本帖最后由 walker2048 于 2024-5-13 15:36 编辑

### 前言
很早以前就对espnow感兴趣,近段时间才开始玩。在使用espnow的过程中,每一次传输的数据长度只有250字节(最大情况下)。如果使用Json之类的常见序列化信息传输方式,这个数据长度有点尴尬。这时候,我想起去年尝试GRPC的时候,偶然间学习到的一个内容,也就是Protobuf。
![](https://pica.zhimg.com/v2-6fbb22690047c24bb3da00e6bfcca01e_720w.jpg?source=172ae18b)
Google Protocol Buffer( 简称 Protobuf) 是 Google 公司内部的混合语言数据标准,目前已经正在使用的有超过 48,162 种报文格式定义和超过 12,183 个 .proto 文件。他们用于 RPC 系统和持续数据存储系统。

Protocol Buffers 是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化。它很适合做数据存储或 RPC 数据交换格式。可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。

#### nanopb简介
但是Google官方的Protobuf工具并不能生成直接用于小型单片机使用的C语言代码,我找到了github上大佬实现的一个小型化的Protobuf支持库,也就是nanopb。它是一个轻量级的Protobuf库,虽然功能并不是非常强大,用它来做一些扁平化的数据序列化是非常不错的选择。

#### 一、移植代码
在官方github仓库下(https://github.com/nanopb/nanopb),可以直接找到需要的几个文件。


#### 二、添加CMakeList.txt文件
为了让esp-idf顺利编译nanopb,我们需要添加CMake组件配置文件,文件内容如下
这里添加了nanopb的源码文件,以及simple案例的代码文件。宏定义抄了OpenHarmony开源组件的定义和nanopb源码的定义,我也没详细搞清楚到底是什么。
```c
idf_component_register(SRCS "pb_common.c" "pb_decode.c" "pb_encode.c" "simple.c" "simple.pb.c"
                     INCLUDE_DIRS .)

add_definitions("-D_GNU_SOURCE" "-D_HAS_EXCEPTIONS=0" "-DHAVE_CONFIG_H" "-DPB_ENABLE_MALLOC")
```

#### 三、编译运行
编译后可以看到,程序是可以正常运行的,也顺利将数据结构封包和解包了,包长度109,比JSON原始数据长度要小一半多。

并且flash体积也只增加了12K,可以说以非常小的体积实现了对应的功能。

并且封包的数据,使用常规的Protocol Buffer解析程序,也能解析到传输的内容,已经达到了我的目的(最小代码修改情况下,实现小体积和跨平台的数据序列化)。

#### 四、代码分析
以下是这个案例的数据报文定义:
```c
// simple.proto

syntax = "proto3";
import "nanopb.proto";

message SimpleMessage {
    int32 luckyNumber = 1;
    int32 unluckyNumber = 2;
    string ip = 3[(nanopb).max_length = 16];
}

message SimpleStruct {
    repeated SimpleMessage msg =1;
}
```
在这里定义了一个简单的消息数据结构,包含了两个int32长度的数字,以及一个长度16的ip字符串。然后再定义了一个可以包含零到多个简单消息数据的SimpleStruct数据。

测试代码如下:
由于我们定义了一个数量不确定的SimpleStruct数据,这个数据结构就不是扁平化的了,它不能直接用nanopb的解包和封包函数,需要另行编写回调函数来处理这个数据结构的封包和解包。
而SimpleMessage这个数据结构是固定的,它可以直接使用nanopb的原有函数直接处理。

```c
#include <stdio.h>
#include <pb_encode.h>
#include <pb_decode.h>
#include "simple.pb.h"

typedef struct {
    SimpleMessage message;
    int32_t num;
} MsgList;

void MsgListAddMsg(MsgList* list, int32_t lucky, int32_t unlucky, char* str){
    if(list->num < 12){
      list->message.luckyNumber = lucky;
      list->message.unluckyNumber = unlucky;
      strcpy(list->message.ip, str);
      list->num++;
    }
}

bool MsgList_encode(pb_ostream_t *ostream, const pb_field_t *field, void * const *arg)
{
    MsgList* source = (MsgList*)(*arg);

    // encode all numbers
    for (int i = 0; i < source->num; i++)
    {
      if (!pb_encode_tag_for_field(ostream, field))
      {
            const char * error = PB_GET_ERROR(ostream);
            printf("SimpleMessage_encode_numbers error: %s", error);
            return false;
      }

      if (!pb_encode_submessage(ostream, SimpleMessage_fields, &source->message))
      {
            const char * error = PB_GET_ERROR(ostream);
            printf("SimpleMessage_encode_numbers error: %s", error);
            return false;
      }
    }

    return true;
}

bool MsgList_decode_single_message(pb_istream_t *istream, const pb_field_t *field, void **arg)
{
    MsgList * dest = (MsgList*)(*arg);
    SimpleMessage msg = SimpleMessage_init_zero;
    if (istream != NULL && field->tag == SimpleStruct_msg_tag)
    {
      if (!pb_decode(istream, SimpleMessage_fields, &msg))
      {
            const char * error = PB_GET_ERROR(istream);
            printf("MsgList decode single_message error: %s", error);
            return false;
      }
      memcpy(&dest->message, &msg, sizeof(msg));
      printf("Your lucky number was %ld, unlucky is %ld, ip is %s!\n", msg.luckyNumber, msg.unluckyNumber, msg.ip);
    };
    return true;
}

int pbtest()
{
    /* This is the buffer where we will store our message. */
    uint8_t buffer;
    size_t message_length;
    bool status;
    /* Encode our message */
    {
      /* Allocate space on the stack to store the message data.
         *
         * Nanopb generates simple struct definitions for all the messages.
         * - check out the contents of simple.pb.h!
         * It is a good idea to always initialize your structures
         * so that you do not have garbage data from RAM in there.
         */
      SimpleStruct stu = SimpleStruct_init_zero;
      /* Create a stream that will write to our buffer. */
      pb_ostream_t stream = pb_ostream_from_buffer(buffer, sizeof(buffer));
      
      /* Fill in the lucky number */
      MsgList list = {};
      MsgListAddMsg(&list, 14, 3, "192.168.50.40");
      MsgListAddMsg(&list, 18, 31, "192.168.50.144");
      MsgListAddMsg(&list, 31, 40, "192.168.50.131");
      MsgListAddMsg(&list, 42, 12, "192.168.50.142");
      MsgListAddMsg(&list, 25, 51, "192.168.50.141");

      for(int i=0; i< list.num; i++){
            printf("lucky number was %ld, unlucky is %ld, ip is %s!\n", list.message.luckyNumber, list.message.unluckyNumber, list.message.ip);
      }

      stu.msg.arg = &list;
      stu.msg.funcs.encode = MsgList_encode;
      
      /* Now we are ready to encode the message! */
      status = pb_encode(&stream, SimpleStruct_fields, &stu);
      message_length = stream.bytes_written;

      /* Then just check for any errors.. */
      if (!status)
      {
            printf("Encoding failed: %s\n", PB_GET_ERROR(&stream));
            return 1;
      }
    }
   
    /* Now we could transmit the message over network, store it in a file or
   * wrap it to a pigeon's leg.
   */

    /* But because we are lazy, we will just decode it immediately. */
   
    {
      /* Allocate space for the decoded message. */
      SimpleStruct stu = SimpleStruct_init_zero;
      MsgList newlist = {};
      stu.msg.arg = &newlist;
      stu.msg.funcs.decode = MsgList_decode_single_message;
      
      /* Create a stream that reads from the buffer. */
      pb_istream_t stream = pb_istream_from_buffer(buffer, message_length);
      
      printf("Encoded length is %d\n", message_length);
      for(int i = 0; i < message_length; i++){
            printf("%02x", buffer);
      }
      printf("\nEnd of buffer\n");
      
      /* Now we are ready to decode the message. */
      status = pb_decode(&stream, SimpleStruct_fields, &stu);
      
      /* Check for errors... */
      if (!status)
      {
            printf("Decoding failed: %s\n", PB_GET_ERROR(&stream));
            return 1;
      }
      
   }
    return 0;
}


```</div><script>                                        var loginstr = '<div class="locked">查看本帖全部内容,请<a href="javascript:;"   style="color:#e60000" class="loginf">登录</a>或者<a href="https://bbs.eeworld.com.cn/member.php?mod=register_eeworld.php&action=wechat" style="color:#e60000" target="_blank">注册</a></div>';
                                       
                                        if(parseInt(discuz_uid)==0){
                                               
                                        }                </script><script type="text/javascript">(function(d,c){var a=d.createElement("script"),m=d.getElementsByTagName("script"),eewurl="//counter.eeworld.com.cn/pv/count/";a.src=eewurl+c;m.parentNode.insertBefore(a,m)})(document,523)</script>
页: [1]
查看完整版本: 【FireBeetle 2 ESP32 C6开发板】1、移植nanopb到esp32上