控制器与App通讯协议怎么制定

给嵌入式工程师的设备与 App 通讯协议设计清单:帧格式怎么定、字节序和校验怎么选、命令 / 应答 / 主动上报怎么区分、序号匹配与超时重试、错误码、版本兼容和参数表,附帧示例和 CRC16 代码。

多爱普团队12 分钟阅读
控制器与App通讯协议怎么制定
本文目录
  1. 先分层,再定细节
  2. 帧格式:一个够用的模板
  3. 为什么一定要有长度和校验
  4. 三类报文:请求、应答、主动上报
  5. 序号:应答对得上,重发不重复执行
  6. 超时和重试要双方一起定
  7. 写入之后要回读
  8. 错误码要能变成用户看得懂的话
  9. 版本号:固件和 App 不会同时升级
  10. 参数表驱动
  11. 协议文档写什么
  12. 常见问题
  13. 原来的上位机协议能直接给 App 用吗?
  14. 用 JSON 还是二进制?
  15. Modbus 设备还需要另外定协议吗?
  16. 你们能帮我们审一下现有协议吗?

很多工厂的控制板做了很多年,串口协议也有一套,但那套协议通常是给上位机或者自家调试工具用的。现在要接手机 App,就会发现原来的协议不太够用:没有序号,应答对不上请求;没有版本号,固件一升级 App 就解析错;错误只回一个"失败",App 不知道该提示用户什么。

这篇文章是写给嵌入式工程师的。我们做过电动工具、灯具控制器、冷库控制器、工业控制器、BMS 等项目的 App 端,下面把和固件工程师定协议时反复确认的事项整理成一份清单。如果你还在规划整体分工,可以先看 传统设备智能化升级。

先分层,再定细节

协议吵不清楚,多半是因为几层东西混在一起讨论。建议先分成四层:

层管什么例子
链路层数据走什么通道BLE 特征值、串口、4G 模组透传、MQTT 主题
帧层一包数据怎么切、怎么校验帧头、长度、校验、帧尾
命令层这包数据要干什么命令字、序号、应答、错误码
业务层数据代表什么温度、转速、运行模式、故障位

链路层的细节按通道单独确认,蓝牙的特征值规划和 MTU 分包可以看 蓝牙设备GATT服务和通讯协议怎么设计。帧层和命令层最好做成和链路无关的。这样同一套协议,蓝牙能用,4G 模组透传也能用,App 和云端可以共用一份解析代码。我们在电动工具项目里就是这样:手机 App 和电脑端走的都是同一种帧格式。

帧格式:一个够用的模板

下面是我们常推荐的结构,大多数控制器项目直接套用就行:

| 帧头 2B | 长度 2B | 版本 1B | 命令字 1B | 序号 1B | 数据 N B | CRC16 2B |
  AA 55     从版本到数据   协议版本   功能码      0-255      业务数据    覆盖版本到数据

举个例子,App 读取设备运行状态:

请求:AA 55 00 04 01 10 2A 00 [CRC16]
      帧头  长度=4 版本1 读状态 序号42 无参数
应答:AA 55 00 0A 01 90 2A 00 01 0F A0 00 32 00 [CRC16]
      命令字 0x90 = 0x10 | 0x80,表示读状态的应答;序号同样是 42

几个细节要在文档里写死:

  • 长度字段覆盖哪些字节:从版本到数据,还是包含校验。写错一个,两边就切不出完整帧。
  • 字节序:多字节数值用大端还是小端,全协议统一一种。Modbus 是大端,很多 MCU 内存是小端,最容易混。
  • 校验算法和覆盖范围:CRC16 用哪个多项式、初始值是多少,从哪个字节算到哪个字节。

为什么一定要有长度和校验

蓝牙通知、串口、4G 透传都会遇到粘包和半包:一次收到一帧半,或者半帧。只靠帧头找边界不可靠,因为数据里也可能出现 AA 55。正确的切帧方式是:找到帧头,读长度,等收够长度再校验,校验不通过就丢弃这一帧并往后找下一个帧头。

校验用异或最省事,但检错能力弱;条件允许的话用 CRC16。下面是常用的 CRC16/MODBUS 实现,固件和 App 用同一个算法:

uint16_t crc16_modbus(const uint8_t *buf, uint16_t len) {
    uint16_t crc = 0xFFFF;
    for (uint16_t i = 0; i < len; i++) {
        crc ^= buf[i];
        for (uint8_t j = 0; j < 8; j++) {
            if (crc & 0x0001) crc = (crc >> 1) ^ 0xA001;
            else crc >>= 1;
        }
    }
    return crc;  // 低字节在前发送还是高字节在前,也要写进文档
}

三类报文:请求、应答、主动上报

App 和设备之间的报文,基本就三类:

  1. 请求:App 发给设备,读参数、写参数、执行动作
  2. 应答:设备对请求的回复,带结果和数据
  3. 主动上报:设备自己发的,状态变化、告警、周期数据

区分方式常见两种:应答命令字 = 请求命令字 | 0x80;或者单独用一个字段标明报文类型。主动上报要有自己的命令字,App 收到后不需要回应答(除非是告警这类必须确认的消息)。

序号:应答对得上,重发不重复执行

没有序号的协议,App 连续发两条指令,收到一个应答,不知道是哪条的。有了序号,应答带回请求的序号,App 就能准确匹配。

序号还有一个作用是去重。App 超时没收到应答会重发,如果设备其实已经执行过了,再执行一次可能出问题,比如"电机正转 10 圈"执行两遍。设备记住最近处理过的序号,收到重复序号直接回上次的结果,不再执行。

超时和重试要双方一起定

每条命令要约定:

  • 设备最长多久必须应答(比如 500ms;写 Flash 的命令可以长一些)
  • App 超时后重试几次、间隔多久
  • 重试都失败后,App 怎么提示用户

4G 链路的时延比蓝牙大得多,远程指令的超时要单独设置。具体可以看 4G控制器通讯协议怎么设计。

写入之后要回读

App 写了一个参数,设备回"成功",界面就显示新值,这样做不够稳妥。设备可能因为超出范围、当前运行状态不允许等原因没有真正生效。我们的做法是写入应答里带回设备当前的实际值,或者写完立刻再读一次,界面以设备回读的值为准。控制器 App 的参数表和回读设计可以参考 控制器App开发怎么规划。

错误码要能变成用户看得懂的话

"失败"对用户没有意义。错误码至少要区分:

错误码含义App 提示
0x00成功—
0x01命令不支持当前固件不支持此功能,请升级
0x02参数超出范围设定值超出允许范围
0x03当前状态不允许请先停机再修改此参数
0x04校验失败通讯异常,请重试
0x05设备忙设备正在执行其他操作

"当前状态不允许"这类错误对控制器特别重要,比如运行中不允许改电机极对数。

版本号:固件和 App 不会同时升级

设备卖出去以后,市场上会同时存在好几个固件版本,而用户手机上的 App 会自动更新到最新版。所以:

  • 帧里带协议版本号,App 按版本解析
  • 连接后先读一次固件版本和设备能力,App 按能力显示功能
  • 新增字段加在数据末尾,旧版 App 忽略多出来的字节就能兼容
  • 不要改已有字段的含义和位置

参数表驱动

控制器动辄几十上百个参数。如果每个参数都在 App 里写死,固件加一个参数 App 就要发版。更好的做法是用一张参数表描述:地址、名称、类型、单位、缩放系数、取值范围、读写权限。App 按表生成界面,新增参数只改表。

协议文档写什么

最后,所有这些要落到一份协议文档里,作为开发和验收的唯一依据。文档模板可以参考 智能硬件接入APP的完整流程 里的"协议文档模板"一节。我们和客户合作时,通常先给出草案,固件工程师逐条确认能不能实现,再定稿。

常见问题

原来的上位机协议能直接给 App 用吗?

可以先评估。如果已经有长度、校验和明确的应答,往往加个序号和版本号就能用。实在改不动,也可以由 App 适配旧协议,只是 App 侧的处理会复杂一些。

用 JSON 还是二进制?

MCU 资源紧张、走蓝牙或串口透传,用二进制;设备跑 MQTT、资源充足,用 JSON 调试更方便。也可以设备侧用二进制,云端转成 JSON 给 App。

Modbus 设备还需要另外定协议吗?

不一定。已有 Modbus 寄存器表的设备,可以直接按点表读写,App 和云端按寄存器解析。参考 Modbus App开发方案。

你们能帮我们审一下现有协议吗?

可以。把协议文档 发给我们,我们按上面这份清单逐项看一遍,指出 App 对接时可能出问题的地方。

项目咨询

把项目情况发过来,先帮你理清下一步

发来设备类型、协议资料、样机状态和计划上线时间,我们会结合对应的服务和案例给出推进建议。

如果你暂时不方便填写完整信息,也可以直接前往 联系页面 获取电话、邮箱与微信方式。

提交项目需求

适用于 控制器与App通讯协议怎么制定 场景,提交后默认进入业务咨询通道。

微信、电话、邮箱至少填写一项

点击发送即表示您同意我们的隐私政策和服务条款