Protocol Buffer在MCU上的实现--C语言

一 什么是Protocol Buffer

Protocol Buffer是一种支持多平台、多语言、可扩展的的数据序列化机制,相较于XML来说,protobuf更小更快更简单,支持自定义的数据结构,用protobuf编译器生成特定语言的源代码,目前protoBuf对主流的编程语言都提供了支持,非常方便的进行序列化和反序列化。

官方网址

官方支持的语言有:

  • Java
  • Python
  • Objective-C
  • C++
  • Dart
  • Go
  • Ruby
  • C#

这里对Protocol Buffer的语法不做过多解释,这篇文章主要介绍两部分内容

  • MCU端集成nanopb
  • Android端集成Protocol Buffer

Protocol Buffer语法

二 MCU端集成nanopb

可以看到官方并没有对C语言进行支持,想要在MCU上跑Protocol Buffer需要另外一个开源工具Nanopb

nanopb是protocol buffer协议的纯C实现,没有依赖其他库,只需要几个C文件就可以了。非常适合用来做嵌入式设备的通信协议。

Nanopb基础用法

1 移植nanopb

nanopb下载,解压到本地
在这里插入图片描述
在我们的工程中集成Nanopb只需要添加这几个文件就可以了。

  • pb.h
  • pb_common.c
  • pb_common.h
  • pb_decode.c
  • pb_decode.h
  • pb_encode.c
  • pb_encode.h

2 编译.proto文件

  1. 安装protoc

将protoc.exe放到C:\Windows\System32目录下,确保Path已经包含了C:\Windows\System32路径,如果没有,作为程序员的我们应该知道怎么配置Path了吧。

下载protoc.exe
在这里插入图片描述

在命令行验证是否安装成功
在这里插入图片描述
2. 编写一个简单的.proto

syntax = "proto2";

message SimpleMessage {
    required int32 lucky_number = 1;
	required string name = 2;
}

将文件保存成为simple.proto

  1. 开始编译

将simple.proto放到目录nanopb-0.4.5-windows-x86\generator

通过执行命令(.\nanopb_generator.py .\simple.proto)将simple.proto编译成.c和.h

在这里插入图片描述
在当前目录生成了simple.pb.h和simple.pb.c文件,将这两个文件添加到自己的工程。

3. 简单demo

/**
encode callbak
 **/
bool encode_string(pb_ostream_t *stream, const pb_field_t *field, void * const *arg)
{
    char *str = *arg;
    if (!pb_encode_tag_for_field(stream, field))
        return false;
    
    return pb_encode_string(stream, (uint8_t*)str, strlen(str));
}

/**
decode callbak
 **/
bool decode_string(pb_istream_t *stream, const pb_field_t *field, void **arg)
{
    int i=0;
    char* tmp = *arg;
    while (stream->bytes_left)
    {
        uint64_t value;
        if (!pb_decode_varint(stream, &value))
            return false;
        *(tmp+i)=value;
        i++;
    }
    return true;
}

int simple_main(void)
{
    /* This is the buffer where we will store our message. */
    uint8_t buffer[128];
    size_t message_length;

	char* nameStr = "abcdefg";
    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.
         */
        SimpleMessage message = SimpleMessage_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 */
        message.lucky_number = 1569875;
		message.name.arg = nameStr;
		message.name.funcs.encode = encode_string;

		
        
        /* Now we are ready to encode the message! */
        status = pb_encode(&stream, SimpleMessage_fields, &message);
        message_length = stream.bytes_written;

		NRF_LOG_DEBUG("message_legth: %d",message_length);
        
        /* Then just check for any errors.. */
        if (!status)
        {
            NRF_LOG_DEBUG("Encoding failed: %s", 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. */
        SimpleMessage message = SimpleMessage_init_zero;
		char nameDe[64];
		memset(nameDe, 0, 64);
		message.name.funcs.decode = decode_string;
		message.name.arg = nameDe;
        
        /* Create a stream that reads from the buffer. */
        pb_istream_t stream = pb_istream_from_buffer(buffer, message_length);
        
        /* Now we are ready to decode the message. */
        status = pb_decode(&stream, SimpleMessage_fields, &message);
        
        /* Check for errors... */
        if (!status)
        {
            NRF_LOG_DEBUG("Decoding failed: %s", PB_GET_ERROR(&stream));
            return 1;
        }
        
        /* Print the data contained in the message. */
        NRF_LOG_DEBUG("Your lucky number was %d!", (int)message.lucky_number);

		NRF_LOG_DEBUG("Your name: %s",&nameDe[0]);
    }
    
    return 0;
}

至此我们的MCU端就集成完毕了,可以根据自己的业务需要编写对应的序列化和反序列换的接口函数。

三 Android端集成Protocol Buffer

1 工程配置

  1. 在工程的build.gradle中添加protobuf-gradle-plugin
   dependencies {
       classpath 'com.android.tools.build:gradle:3.2.1'
       classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.6' //for protoc
   }
  1. 在app的build.gradle中添加protobuf
apply plugin: 'com.google.protobuf' //for protoc

//编写编译任务,调用plugin编译生成java文件
protobuf {
    protoc {
        artifact = 'com.google.protobuf:protoc:3.0.0'//编译器版本
    }
    plugins {
        javalite {
            artifact = 'com.google.protobuf:protoc-gen-javalite:3.0.0'//指定当前工程使用的protobuf版本为javalite版,以生成javalite版的java类
        }
    }
    generateProtoTasks.generatedFilesBaseDir = "$projectDir/src/main/java/com/hytto/test/protocbuf/java" //指定编译生成java类的存放位置
    generateProtoTasks {
        all().each { task ->
            task.plugins {
                javalite {
                    outputSubDir = '' //指定存放位置的二级目录,这里未指定
                }
            }
        }
    }
}
//指定原始.proto文件的位置
android {
    sourceSets {
        main {
            java {
                srcDirs 'src/main/java/com/hytto/test/protocbuf/java'
            }
            proto {
                srcDirs 'src/main/java/com/hytto/test/protocbuf/proto'
            }
        }
    }
}

dependencies {
    implementation 'com.google.protobuf:protobuf-lite:3.0.0' //依赖protobuf-lite库
}

将simple.proto放到’src/main/java/com/hytto/test/protocbuf/proto’这个目录,然后点击编译。
编译成功之后可以看到Simple.java

在这里插入图片描述

2 简单的demo

  		/**
         * 序列化
         */
        
        Simple.SimpleMessage.Builder builder = Simple.SimpleMessage.newBuilder();

        String name = "abcdefg";
        builder.setLuckyNumber(1569875);
        builder.setName(name);

        Simple.SimpleMessage message = builder.build();

        msgBytes = message.toByteArray();

        String strMsg = "length " + msgBytes.length;
        for (int i = 0; i < msgBytes.length; i++){
            String str = String.format(" %02x", msgBytes[i]);
            strMsg += str;
        }
        Log.d(TAG, strMsg);
        

        /**
         * 反序列化
         */
        
        try {
            Simple.SimpleMessage messageDe = Simple.SimpleMessage.parseFrom(msgBytes);

            String nameDe = messageDe.getName();
            int luckNumDe = messageDe.getLuckyNumber();

            Log.d(TAG, "decode name: " + nameDe + "; luckNumDe: " + luckNumDe);
        } catch (InvalidProtocolBufferException e) {
            e.printStackTrace();
        }
        

  • 5
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
n-ARQ(n次自动重传请求)是一种数据传输协议,可以在数据传输过程中检测丢失的数据包并进行重传,以确保数据的完整性和可靠性。下面是一个简单的C语言实现n-ARQ协议的示例: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #define MAX_SEQ 1 #define MAX_PKT 1024 #define TIMEOUT 5 typedef enum {frame_arrival, timeout, crc_err} event_type; typedef struct { char data[MAX_PKT]; } packet; typedef struct { int seq; int ack; int crc; packet info; } frame; static int phl_ready = 0; int between(int a, int b, int c) { if(((a <= b) && (b < c)) || ((c < a) && (a <= b)) || ((b < c) && (c < a))) { return 1; } else { return 0; } } void send_data(int frame_nr, int frame_expected, packet buffer[]) { frame s; s.info = buffer[frame_nr % MAX_SEQ]; s.seq = frame_nr; s.ack = (frame_expected + MAX_SEQ - 1) % MAX_SEQ; s.crc = 0; for(int i = 0; i < sizeof(frame); i++) { s.crc += *((char*)&s + i); } printf("Sending frame %d\n", frame_nr); phl_ready = 0; send_frame(s); start_timer(frame_nr % MAX_SEQ); } void protocol5(void) { int ack_expected = 0; int next_frame_to_send = 0; int frame_expected = 0; int too_far = MAX_SEQ; packet buffer[MAX_SEQ]; frame r; int nbuffered = 0; int i; for(i = 0; i < MAX_SEQ; i++) { buffer[i].data[0] = '0' + i; } while(1) { wait_for_event(&event); switch(event.type) { case(frame_arrival): recv_frame(&r); if(r.seq == frame_expected) { printf("Receiving frame %d\n", frame_expected); if(r.crc != 0) { printf("CRC error\n"); send_data((ack_expected + MAX_SEQ - 1) % MAX_SEQ, frame_expected, buffer); } else { nbuffered--; frame_expected = (frame_expected + 1) % MAX_SEQ; ack_expected = (ack_expected + 1) % MAX_SEQ; stop_timer(frame_expected % MAX_SEQ); } } else { printf("Receiving frame %d\n", r.seq); } while(between(ack_expected, r.ack, next_frame_to_send)) { nbuffered--; stop_timer(ack_expected % MAX_SEQ); ack_expected = (ack_expected + 1) % MAX_SEQ; } break; case(timeout): printf("Timeout %d\n", frame_expected); send_data((ack_expected + MAX_SEQ - 1) % MAX_SEQ, frame_expected, buffer); break; case(crc_err): printf("CRC error\n"); send_data((ack_expected + MAX_SEQ - 1) % MAX_SEQ, frame_expected, buffer); break; default: break; } if(nbuffered < MAX_SEQ && phl_ready) { printf("Buffering packet %d\n", next_frame_to_send); send_data(next_frame_to_send, frame_expected, buffer); next_frame_to_send = (next_frame_to_send + 1) % MAX_SEQ; nbuffered++; } if(between(ack_expected, r.ack, next_frame_to_send)) { printf("Acknowledge %d\n", r.ack); } } } int main(int argc, char const *argv[]) { protocol5(); return 0; } ``` 在这个示例中,我们使用了一个简单的环形缓冲区来存储数据包。我们使用一个循环变量`next_frame_to_send`来追踪下一个要发送的数据包,使用`frame_expected`来追踪下一个期望接收的数据包,使用`ack_expected`来追踪下一个期望接收的确认帧。 在主程序中,我们调用了`protocol5`函数来实现n-ARQ协议。在该函数中,我们使用一个无限循环来等待事件的发生。当一个帧到达时,我们首先检查它的序号是否与期望的一致,如果一致,则说明这是我们期望的帧。我们首先检查CRC校验是否通过,如果通过,则将帧的数据包存入缓冲区,然后更新期望的帧号和确认帧号,并停止计时器。如果CRC校验失败,则重新发送上一个确认帧。 如果我们收到的不是期望的帧,则说明它是一个老的或未来的帧,我们只需简单地丢弃即可。 当一个计时器超时时,我们重新发送上一个确认帧。如果我们收到了一个CRC校验错误,则也重新发送上一个确认帧。 如果我们的缓冲区中有空闲空间,并且物理层准备好了发送下一个帧,则我们将下一个帧发送出去,并更新`next_frame_to_send`和`nbuffered`变量。 最后,如果我们收到了一个确认帧,则我们更新`ack_expected`变量并打印一条确认消息。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值