7.4 KiB
7.4 KiB
KeyBorad Protobuf 对齐步骤
这份步骤是按当前工程目录写的,目标是:
- 保留当前 CDC 传输帧:
AA 55 + len + type + data + checksum - 把
data改成 protobuf 二进制载荷 - 上位机使用 protobuf C++
- 下位机使用 nanopb
1. 先确定边界
当前工程里三层职责不要打乱:
KeyBorad\KeyBorad\DRI\Dri_Cdc.*作用: 串口枚举、握手、收发字节流KeyBorad\KeyBorad\COM\Com_CdcEncode.*作用: 把一帧 CDC 包组出来KeyBorad\KeyBorad\COM\Com_CdcDecode.*作用: 从 CDC 字节流缓存里拆出一帧
引入 protobuf 以后,建议职责变成:
DRI只管串口COM只管外层帧proto只管data的结构定义
也就是说:
type继续保留在外层帧里data改为 protobuf 序列化结果len改为 protobuf 载荷实际长度
2. 当前已经准备好的文件
本次已经加了两份协议源文件:
KeyBorad\proto\keyboard.protoKeyBorad\proto\keyboard.options
其中:
keyboard.proto定义了HelloReqPayload / HelloRspPayload / BitmapPayload / FunctionKeyEventPayload / TimeSyncPayload等消息keyboard.options给nanopb的bytes usage_bitmap指定了max_size:29
3. 下载工具
上位机 protobuf
按 protobuf 官方文档,protoc 用 --cpp_out 生成 C++ 代码。
你需要准备:
protoc.exe- protobuf C++ runtime 头文件和库
官方参考:
- protobuf C++ generated code guide
- protobuf releases / protoc binary
下位机 nanopb
你需要准备:
nanopbruntimepb.h,pb_common.c/h,pb_encode.c/h,pb_decode.c/hnanopb_generator.py
官方参考:
- nanopb overview
- nanopb generator reference
4. 生成代码
4.1 生成上位机 C++ 代码
在工程根目录 C:\Users\lst\Desktop\动态链接库版本\20260320_new_keyboard 下执行:
New-Item -ItemType Directory -Force -Path KeyBorad\generated\cpp | Out-Null
protoc --proto_path=KeyBorad\proto --cpp_out=KeyBorad\generated\cpp KeyBorad\proto\keyboard.proto
预期生成:
KeyBorad\generated\cpp\keyboard.pb.hKeyBorad\generated\cpp\keyboard.pb.cc
4.2 生成下位机 nanopb 代码
如果你是 nanopb 源码目录:
New-Item -ItemType Directory -Force -Path KeyBorad\generated\nanopb | Out-Null
python third_party\nanopb\generator\nanopb_generator.py --output-dir=KeyBorad\generated\nanopb --strip-path KeyBorad\proto\keyboard.proto
如果你用的是 nanopb 二进制包,自带 generator,也可以直接换成对应可执行文件。
预期生成:
KeyBorad\generated\nanopb\keyboard.pb.hKeyBorad\generated\nanopb\keyboard.pb.c
5. 先做一处关键改造
必改: 不再把 payload 长度当成固定值
一旦 data 改成 protobuf,HelloRsp / TimeSync / Ack 的 data 长度就不一定再等于今天的固定字节数。
所以迁移时要先把 COM 层规则改成:
len表示 protobuf 载荷实际字节数Com_CdcDecode不再用type -> 固定 len做强校验- 改成:
- 校验帧头
- 校验
len - 校验 checksum
- 可选校验某个
type的最大长度,不再校验固定长度
建议做法:
- 先保留
Packet_Type - 删除或弱化
Packet_len在解码期的“硬匹配” Packet_len只保留给旧协议兼容或做上限参考
6. 上位机接入方式
6.1 在 VCXPROJ 里加入生成文件
把下面文件加入 KeyBorad\KeyBorad\KeyBorad.vcxproj:
..\generated\cpp\keyboard.pb.cc..\generated\cpp\keyboard.pb.h
并加 include 目录:
KeyBorad\generated\cpp- protobuf runtime include 目录
再链接 protobuf C++ runtime 库。
6.2 改造原则
不要再在 Com_Cdc.h 里维护两份字段和 data。
建议改成两层:
- 原始帧:
Packet - protobuf payload:
keyboard::cdc::HelloReqPayload等生成类
6.3 发送流程
以 HelloReq 为例:
- 创建
keyboard::cdc::HelloReqPayload - 填字段
SerializeToString()/SerializeToArray()- 把序列化结果塞进
Packet.data Packet.type = Com_Type_HelloReq- 用
Com_Cdc_Func_BuildFrame()打外层包
伪代码:
keyboard::cdc::HelloReqPayload payload;
payload.set_protocol_version(1);
std::string bytes;
payload.SerializeToString(&bytes);
Packet frame;
frame.type = Com_Type_HelloReq;
frame.data = QByteArray(bytes.data(), static_cast<int>(bytes.size()));
QByteArray tx = Com_Cdc_Func_BuildFrame(frame);
6.4 接收流程
Dri_Cdc_Read()读到完整 CDC 帧Com_Cdc_Func_ParseFrame()拿到Packet- 根据
Packet.type选择 protobuf 消息类型 - 对
Packet.data做ParseFromArray()
伪代码:
Packet frame;
if (!Com_Cdc_Func_ParseFrame(frameBytes, &frame))
{
return false;
}
if (frame.type == Com_Type_HelloRsp)
{
keyboard::cdc::HelloRspPayload payload;
if (!payload.ParseFromArray(frame.data.constData(), frame.data.size()))
{
return false;
}
}
7. 下位机接入方式
下位机不要碰外层 CDC 帧格式,直接沿用:
- 帧头
- 长度
- type
- checksum
只把 type 对应的 data 改成 nanopb 编解码。
建议流程:
- 串口 ISR / DMA 收满一帧
- 校验
AA 55 / len / checksum - 根据
type选keyboard.pb.h里的消息描述符 pb_decode()解 protobuf 载荷- 业务处理
- 回复时
pb_encode()出 payload,再挂回外层 CDC 帧
8. 推荐迁移顺序
建议按下面顺序,不要一次全换:
- 只提交
.proto和.options - 上位机先接
HelloReq / HelloRsp - 下位机把
HelloReq / HelloRsp改成 nanopb - 确认握手通了,再迁移
Ack / Error - 然后迁移
TimeSync / ThemeRgb - 最后迁移
Bitmap / FunctionKeyEvent
原因:
HelloReq / HelloRsp字段简单,最适合先打通生成链路Bitmap有bytes[29],更适合在nanopb选项验证完后再切
9. 你当前 COM 文件还能起什么作用
即使引入 protobuf,当前 COM 层仍然非常有价值:
Com_CdcEncode继续负责外层帧打包Com_CdcDecode继续负责流式拆包Packet_Type继续作为外层快速分发 ID
真正被 protobuf 替代的是:
- 旧的
data手写字段布局 - 手工小端拼 payload 的代码
一句话:
COM继续保留- 但
COM不再定义业务 payload 字段 - payload 交给
.proto
10. 一套最小可复现命令
假设你已经装好 protoc 和 nanopb,最小步骤就是:
Set-Location C:\Users\lst\Desktop\动态链接库版本\20260320_new_keyboard
New-Item -ItemType Directory -Force -Path KeyBorad\generated\cpp | Out-Null
protoc --proto_path=KeyBorad\proto --cpp_out=KeyBorad\generated\cpp KeyBorad\proto\keyboard.proto
New-Item -ItemType Directory -Force -Path KeyBorad\generated\nanopb | Out-Null
python third_party\nanopb\generator\nanopb_generator.py --output-dir=KeyBorad\generated\nanopb --strip-path KeyBorad\proto\keyboard.proto
然后做两边接入:
- 上位机
vcxproj加入keyboard.pb.cc - 下位机工程加入
keyboard.pb.c - 把
HelloReq / HelloRsp的data改为 protobuf payload - 把
Com_CdcDecode的固定长度校验去掉
11. 最后一个关键建议
如果你只是想“共享结构定义”,那就:
- 保留外层
type - 每个
type对应一个 protobuf message
不要一开始就把外层 type 也塞进 protobuf oneof 里。
原因很简单:
- 外层
type对调试串口抓包很直观 - 下位机分发也更省
- 迁移成本最小