温馨提醒:
由于我最害怕的就是接触各种新协议,尤其是对各种协议和解析协议数据简直就是职业生涯的噩梦,但工作中不免和不同的协议打交道。本着要啃就啃最难的,大不了放弃的心态。所以我学习了如何自定义制定自己的协议,如果你学会自己自定义协议后,那以后无论什么新协议对你来说都不再困难,也不再恐惧。但本文纯属个人学习经验分享,而且可能随时学不下去写不下去了会弃坑,如介意,请千万不要食用。
芯片选型
Ciga Device --- GD32F470系列
协议定义
什么是协议?不要把协议想的那么困难,协议有简单的,也有复杂的。最易懂的解释就是,我们约定好数据是什么格式,大家都按照这个规则来收发数据,这个规则就是协议。
举个例子,最简单的协议数据我们甚至可以用一个字符串来表示,类似于"led:1",咱们约定好,发送端发送对应的LED数字,用冒号隔开,冒号后面是数字几,就点亮第几盏灯;那接收端收到数据后也用这个格式来解析出数据,再点亮数字对应的第几盏灯。
但在集成电路开发中,我们的寄存器资源是非常珍贵的,它不像手机内存动不动就几个G,有的芯片的内存可能只有几KB。再加上在电路与计算机底层中,0和1能让它们计算得更快。所以我们通常用位来存储数据,我们也用位和字节来制定协议。千万不要小看一个字节,一个字节有8个BIT,可以表示256种数据。试想一下,如果我们按照字符串"led:1"来传输数据,这需要5个字节,但如果我们约定用BIT来传输,1个BIT就够了,甚至不到1字节,是不是传输效率大大滴增加?计算效率大大滴提升?
大多数硬件协议定义通常都是有套路的,下面我们来详细康康。
硬件协议通用套路
帧头+命令+数据长度位+数据位+校验位+帧尾。(通常用字节进行传输)
| | 帧头 | 命令位 | 数据长度 | 数据位 | 校验位 | 帧尾 |
| 字节数 | 1 | 1 | 1 | n | 1 | 1 |
默认值 | 0x7a | 待定 | 待定 | 待定 | 待定 | 0x7b |
---|---|---|---|---|---|---|
[协议定义] |
- **帧头:**可以是任意数据,代表着从这里我要开始传输数据啦;
- **命令位:**表示命令的类型;(比如1是开灯;0是关灯)
- **数据长度:**通常用来说明传输的数据位有多少字节,经常用于校验数据传输中是否有数据丢失或其他异常情况;
- **校验位:**也是用来确定我们的数据传输是否正确而不是其他方伪造或存在丢失情况;
- **帧尾:**可以是任意数据,代表着我的数据传完啦、结束啦;
此时我们有个需求:上位机要传输PID的调试数据,数据位中有4个数据,通道ID(1个字节),P、I、D都是浮点类型数据(每个4字节),我们可以像下面这样定义:
|-----|------|------|------|-----|---|---|---|-----|------|
| | 帧头 | 命令位 | 数据长度 | 数据位 |||| 校验位 | 帧尾 |
| | 帧头 | 命令位 | 数据长度 | idx | P | I | D | 校验位 | 帧尾 |
| 字节数 | 1 | 1 | 1 | 1 | 4 | 4 | 4 | 1 | 1 |
| 默认值 | 0x7a | 0x01 | 待定 | | | | | 待定 | 0x7b |
- idx:1个字节,int类型, 表示配置哪一组PID
- P: 4个字节,float类型。P值
- I: 4个字节,float类型。I值
- D: 4个字节,float类型。D值
协议生成
协议生成其实就是按照我们上面制定的那些规则拼装出规则数据再发出去。是的,你没看错,就是把数据按规则拼装出来,再发出去,就这么简单。
这里不得不插入一下校验位是咋肥事,否则没法发送校验码。(当然我们也可以不发送校验码,或者把校验码位固定发送成0x00也不是不行哈!只要解析的时候也按照这个套路来那就么得问题)
校验位
数据在传输过程中,可能会存在数据出错的情况。为了保证数据传输的正确性,因此会采取一些方法来判断数据是否正确,或者在数据出错的时候及时发现进行改正。常用的几种数据校验方式有奇偶校验、CRC校验、LRC校验、格雷码校验、和校验、异或校验等。
我们只说说异或校验、和校验、奇偶校验,其他的我也不会哈,请自行问AI吧!
- 奇校验(ODD): 校验位被设置为确保数据位中1的总数为奇数。例如,数据位中的"1"总数为奇数,校验位被设置为低电平(拉低为0),否则设置为高电平。故而,如果接收方统计发现"1"总数为偶数,且校验是低电平,则校验失败,否则成功。
- 偶校验(Even): 校验位被设置为确保数据位中1的总数为偶数。例如,数据位中的"1"总数为偶数,校验位被设置为低电平(拉低为0),否则设置为高电平。故而,如果接收方统计发现"1"总数为奇数,且校验是低电平,则校验失败,否则成功。
- 异或校验: 帧头、命令位、数据长度、所有的数据位全部异或后=校验位的值;
- 和校验: 帧头+命令位+数据长度+所有的数据位=校验位的值;