完整的 Ubuntu 上安装 nanopb 并实现简单 protobuf 通信的教程。nanopb 是专为嵌入式系统优化的轻量级 C 语言 Protobuf 实现,零动态内存分配,非常适合资源受限环境。
一、安装 nanopb
方法 A:下载二进制包(推荐,最简单)
nanopb 官网提供包含 protoc 编译器的完整二进制包:
# 下载最新版(以 0.4.8 为例,可到 https://jpa.kapsi.fi/nanopb/download/ 查看最新版本)
wget https://jpa.kapsi.fi/nanopb/download/nanopb-0.4.8-linux-x86.tar.gz
# 解压到 /opt
tar -zxvf nanopb-0.4.8-linux-x86.tar.gz
sudo mv nanopb-0.4.8-linux-x86 /opt/nanopb
# 添加到环境变量(可选,方便使用)
echo 'export PATH=$PATH:/opt/nanopb/generator-bin' >> ~/.bashrc
source ~/.bashrc
方法 B:使用系统包管理器
# 安装 protobuf 编译器和 Python 支持
sudo apt-get update
sudo apt-get install -y build-essential protobuf-compiler python3-protobuf
# 克隆 nanopb 源码
git clone https://github.com/nanopb/nanopb.git
cd nanopb
二、创建 Protobuf 定义文件
创建 simple.proto,定义一个简单的传感器消息:
syntax = "proto2";
import "nanopb.proto";
message SensorData {
required int32 id = 1;
required float temperature = 2;
optional string name = 3 [(nanopb).max_size = 40];
repeated int32 readings = 4 [(nanopb).max_count = 10];
}
关键注意事项:
-
使用
proto2语法(nanopb 对 proto2 支持更完善) -
字符串和重复字段必须指定最大长度/数量(使用
(nanopb).max_size和(nanopb).max_count) -
import "nanopb.proto"是为了使用 nanopb 特定的选项
三、生成 C 代码
方法 A(二进制包用户):
# 使用 nanopb 自带的 protoc
/opt/nanopb/generator-bin/protoc --nanopb_out=. simple.proto
方法 B(系统 protoc 用户):
# 使用系统 protoc,需指定 nanopb 插件路径
protoc --plugin=protoc-gen-nanopb=nanopb/generator/protoc-gen-nanopb --nanopb_out=. simple.proto
执行后会生成两个文件:
-
simple.pb.h:结构体定义和元数据 -
simple.pb.c:字段定义
四、编写测试代码
创建 main.c,实现编码和解码:
#include <stdio.h>
#include <string.h>
#include "simple.pb.h"
#include "pb_encode.h"
#include "pb_decode.h"
int main(void)
{
uint8_t buffer[128];
size_t message_length;
bool status;
/* ========== 编码阶段 ========== */
{
SensorData message = SensorData_init_zero;
pb_ostream_t stream = pb_ostream_from_buffer(buffer, sizeof(buffer));
// 填充数据
message.id = 42;
message.temperature = 23.5;
strcpy(message.name, "TemperatureSensor");
// 填充数组
message.readings_count = 3;
message.readings[0] = 200;
message.readings[1] = 210;
message.readings[2] = 205;
// 编码
status = pb_encode(&stream, SensorData_fields, &message);
message_length = stream.bytes_written;
if (!status) {
printf("编码失败: %s\n", PB_GET_ERROR(&stream));
return 1;
}
printf("编码成功,长度: %zu 字节\n", message_length);
printf("原始字节: ");
for (size_t i = 0; i < message_length; i++) {
printf("%02X ", buffer[i]);
}
printf("\n\n");
}
/* ========== 解码阶段 ========== */
{
SensorData message = SensorData_init_zero;
pb_istream_t stream = pb_istream_from_buffer(buffer, message_length);
// 解码
status = pb_decode(&stream, SensorData_fields, &message);
if (!status) {
printf("解码失败: %s\n", PB_GET_ERROR(&stream));
return 1;
}
// 打印结果
printf("解码成功:\n");
printf(" ID: %d\n", message.id);
printf(" 温度: %.1f\n", message.temperature);
printf(" 名称: %s\n", message.name);
printf(" 读数(%d个): ", message.readings_count);
for (uint8_t i = 0; i < message.readings_count; i++) {
printf("%d ", message.readings[i]);
}
printf("\n");
}
return 0;
}
五、编译运行
1. 准备核心库文件
将 nanopb 的核心文件复制到你的项目目录(或用路径引用):
-
pb.h -
pb_common.c/pb_common.h -
pb_encode.c/pb_encode.h -
pb_decode.c/pb_decode.h -
生成的
simple.pb.c/simple.pb.h
2. 编译
gcc -o test_protobuf main.c simple.pb.c pb_encode.c pb_decode.c pb_common.c -I. -Wall
3. 运行
./test_protobuf
预期输出:
编码成功,长度: 21 字节
原始字节: 08 2A 15 00 00 BC 41 1A 11 54 65 6D 70 65 72 61 74 75 72 65 53 65 6E 73 6F 72 22 06 C8 01 D2 01 CD 01
解码成功:
ID: 42
温度: 23.5
名称: TemperatureSensor
读数(3个): 200 210 205
六、项目文件结构参考
my_nanopb_project/
├── simple.proto # Protobuf 定义
├── simple.pb.h # 生成的头文件
├── simple.pb.c # 生成的源文件
├── main.c # 你的应用程序
├── pb.h # nanopb 核心库
├── pb_common.c/h
├── pb_encode.c/h
└── pb_decode.c/h
关键特性说明
-
零动态内存:所有内存分配都是静态的,基于你在
.proto中指定的max_size和max_count -
小体积:典型情况下编解码器仅需 2-10KB Flash,适合 MCU
-
字段选项:更多高级选项(如固定长度数组、回调函数处理大数据)可参考
如果你需要处理更复杂的消息(如嵌套结构、字符串回调、oneof 等),可以参考 nanopb 自带的 examples/simple 和 examples/network_server
1. protobuf 在嵌入式ARM平台的应用(c语言版) - 云的边界 - 博客园
2. protobuf在嵌入式linux下的移植及c语言调用-阿里云开发者社区
3. 嵌入式Linux下Protobuf库的编译与应用 - hczyydqq - 博客园
Protobuf-c 的安装和使用
浙公网安备 33010602011771号