STM32-串口收发HEX数据包&串口收发文本数据包(二十六)

串口收发HEX数据包&串口收发文本数据包

HEX数据包接线图:

文本数据包接线图:

然后这些代码先删掉,

我们到串口的 C 文件这里,在这个文件里我打算加上收发 HEX 数据包的部分哈,其中这个 HEX 数据包的格式我定义的就和 PPT 这里的一样,固定包长含包头包尾,其中包头为 FF,载荷数据固定 4 字节,包围为 F E。

好,我们来写一下,在这里为了收发数据包,我们先定义两个缓冲区的数组哈,第一个是 unit 八杠 t serial TX packet,数据个数为四个,第二个复制一下叫做 RX packet,数据个数也为四个,这四个数据只存储发送或接收的载荷数据哈,包头包尾就不存了,之后这个 RX flag 留着,如果收到一个数据包就置 RX flag,

接着继续往下,这些初始化的代码都不需要更改啊,发送的模块函数也先放着,之后 get x data 这个函数删掉,get x flag 这个留着啊,最后中断这些东西先删掉啊,

好,在这里我们先写一个 send packet 的函数,我们想要的效果是调用一下这个函数,TX packet 数组的四个数据就会自动加上包头包尾发送数据,我们来写一下 void serial send packet void 函数里面其实也非常简单哈,第一步 serial send byte发送包头 0XFF 第二步 serial send array 参数给 serial TX packet,长度为 4 哈,这样就可以依次把四个载荷数据发出去了,第三步再 send bat 发送包尾 0XFE,

这样是不是就行了,那我们现在测试一下发送是不是好使啊,把这个函数放到头文件声明一下,这个 get x data 的函数没有了哈,删掉。

然后这里上面这个数组我们也直接在头文件声明为外部可调用哈,前面加一个 extern,数组声明的时候数量可以不要哈,这个如果模块里有数组需要外部调用,我一般就习惯直接把变量声明出去了哈。如果要分装写个 set get 的话也可以,那就得用指针进行传递哈,不过觉得那样太麻烦了,所以这里就直接声明出去吧。

然后回到 main 点。c 在初始化之后,我们可以先填充发送缓冲区数组 serial TX packet,第 0 个等于 0X01,复制一下啊,一给 02,二给 03,三给 04。数组填充好之后,调用一下 serial send packet,之后 send packet 就会取出数组的内容,加上包头包尾统一发送数据了啊,

我们看一下效果。编译下载这里打开串口,发送模式和接收模式都选择 HEX 模式,然后按下复位,可以看到数据包就发送过来了。数据包格式是 FF 四个载荷数据 FE 实际上就是升点位对吧,

这个没有问题。那接下来我们就来写一下接收这样一个数据包的代码哈,回到串口这里。

上面接收数据包的缓冲区和标志位我们已经定义好了哈。然后在接收中断函数里,我们就需要用状态机来执行接收逻辑了。接收数据包,然后把载荷数据存在 RX packet 的数组里,这是我们的任务哈。

根据 PPT 的这个状态转移图,首先我们要定义一个标志当前状态的变量 s。那在中断这里,我们可以在函数里面定义一个静态变量哈, static unit 8 杠 7 rx state 等于 0。这个静态变量类似于全局变量哈,函数进入只会初始化一次 0,在函数退出后数据仍然有效。与全局变量不同的是,静态变量只能在本函数使用哈。那我们就用这个 x_ state 当做状态变量 s,然后根据 PPT 三个状态 s 分别为 012,所以在这下面根据 x_ state 的不同我们需要进入不同的处理程序。 if x_ state 等于等于 0 就进入等待包头的程序。 else if x_ state 等于等于 1 就进入接收数据的程序。再 else if x_ state 等于等于 2 就进入等待包尾的程序。

注意这里一定要用 else if 哈, else 不要去掉。如果你只是三个并列的 if 可能在状态转移的时候会出现问题啊,比如在状态 0 你想转移到状态一,就置 `x_ state` 等于一,结果就会造成下面状态一的条件就立马满足了,这样会出现连续两个 `if` 都同时成立的情况,这个情况我们不希望出现哈,所以这里使用 `else if`。 保证每次进来之后只能选择执行其中一个状态的代码,或者你用 switch case 语句哈,也可以保证只有一个条件满足。

好,这就是状态选择的部分。然后就依次写每个状态执行的操作和状态转移条件就行了。首先获取一下 x data 哈, U1NT8 杠 t rx data 等于 uart receive data USART1。对于状态 0,我们的操作是 if RXDATA 等于等于 0XFF,就说明收到包头了,那就可以转移状态 RXSTATE 等于 1。如果 RXDATA 不等于 FF 呢,就不转移状态。你可以加一个 else, RXSTATE 等于 0。当然也可以不写 else 哈, RXSTATE 还是 0 也是一样的效果。

之后进入下一个状态,接收数据。这里要依次接收 4 个数据存在 RXPacket 的数组里。所以还需要一个变量来记一下接收几个了可以在上面啊,再定义一个静态变量,名字叫 P, IX packet, 只是接收到哪一个了,最开始默认为 0 哈。然后在这里就可以接收了, serial IX packet D, P, IX packet 个数据等于 X data。 将 X data 存在接收数组里,之后别忘了 P, IX packet 加加,移动到下一个位置。这样就是每进一次接收状态,数据就转存一次缓存数组,同时存的位置加加。那当 if p x packet 大于等于 4 时,也就是 4 个载荷数据已经收完了,这时就可以转移到下一个状态了, x state 等于 2。同时这个 p x packet 也别忘了清 0 哈,为下次接收做准备。我们可以在状态 0 转移到状态 1 时提前清一个 0 哈。这样状态一的逻辑就完成了。

最后写一下状态二的逻辑,等待包尾。这个应该也很简单啊, if RXDATA 等于等于 0XFE 判断是不是包尾。如果是的话就 RXSTATE 等于 0 回到最初的状态。同时代表个数据包接收到了,可以置一个接收标志位, serial RX flag 等于一。如果不等于 FE 的话,就是还没收到包尾,我们同样不做处理,仍然在这个状态等待包尾哈。好到这里我们这个接收数据包的状态机程序就完成了。

大家可以对照 PPT 的这个图来理解哈,都是对应的。那我们来测试一下。首先上面这个接收缓存数组也放在头文件声明一下哈,加个 extern。 另外还有 get x flag 这个函数用来判断是不是接收到了数据包。

然后到 main 点 c 这里,主循环里面我们可以不断读取 x flag, if serial get x flag 等于等于一,即收到了数据包,则在屏幕上显示一下, O I D,Q,HAX,number,一行一列显示, serial RX packet 的第零个,长度为二,复制粘贴一下。一行四列显示数据一,一行七列显示数据二,一行十列显示数据三,这样就完成了。

我们试一下啊,编译,下载,看一下。串口助手这里,在发送区按照数据包格式发送数据,包头 FF, 数据给个一一二二三三四四,包尾 FE, 发送。可以看到 OLED 显示了这个数据包的载荷数据哈,一一二二三三四四。

我们改一下,比如 FE FF 八八 FE,这时载荷数据和包头包尾是有重复的啊。再发送看一下,目前收到的数据仍然没问题,因为我们程序在接收载荷数据的时候,并不会判断包头包尾所以这时载荷数据即使和包头包尾重复也干扰不到我们。

这就是数据包接收的程序。之后这个程序还隐藏有一个问题,大家需要注意一下哈。

就是这个 rx packet 的数组,它是一个同时被写入又同时被读出的数组。在中断函数里我们会依次写入它,在主函数里我们又会依次读出它。这会造成什么问题呢?就是数据包之间可能会混在一起。比如你读出的过程太慢了,前面两个数据刚读出来,等了一会才继续往后读去,那这时后面的数据就有可能会刷新为下一个数据包的数据。也就是你读出的数据可能一部分属于上一个数据包,另一部分属于下一个数据包。这个问题需要注意一下哈。

那解决方法呢,可以是在接收部分加入判断,就是在每个数据包读取处理完毕后,再接收下一个数据包。当然很多情况下其实还可以不进行处理哈。像这种 HEX数据包,多是用于传输各种传感器的每个独立数据,比如陀螺仪的 XYZ 轴数据,温湿度数据等等。它们相邻数据包之间的数据具有连续性哈。这样即使相邻数据包混在一起了也没关系,所以这种情况下就不需要关心这个问题。具体到底怎么处理还需要大家结合实际情况来操作啊,这里就提一下这个可能存在的问题,大家了解一下就行了。好那我们这个收发 head 数据包的程序大概就讲完了。

这是数据包的程序大概就讲完了,接下来我们就来完善一下最终的程序现象,那在这最上面先 include 一下 k,点 h 之后定义变量 unit 8 杠 t k number,然后 k eyInit ,OLED 显示字符串写一下哈 oid show string 一行一列显示 TX packet,复制一下三行一列显示 RX packet,

先写一下发送的逻辑啊,按一下按键变换一下数据发送到串口助手上,首先这里先把发送缓存赋一个初始值啊,send packet 先剪切在主循环里 k number 等于 k,get number if k number 等于等于一,按键按下就执行发送,先变换一下测试数据啊,这里复制一下给每个数据都加加之后 send packet 发送数据包,最后我们用 oled 显示一下,复制一下下面这个显示的部分哈,行号改成二在第二行显示,显示的内容改成 tx packet 这样就行了,

最后下面这个位置也换一下哈,接收到的数据放在第四行显示,这样我们这整个程序就完成了,

我们来最终测试一下下载看一下。首先是发送数据包,按一下按键变换一次数据,发送一个数据包,oled 显示发送数据,串口助手收到数据没问题啊,

然后是接收数据包,我们发送指定格式的数据包,oled 显示接收到的数据包,

换个数据包试试也没问题啊,好,这就是第一个程序串口收发 hex 数据包的内容,那接下来我们就继续写一下下一个程序串口收发文本数据包回到工程文件夹哈,我们。

串口收发文本数据包:

进来之后这个按键部分删掉,发送部分删掉,while 循环里面的东西也全都删掉,

然后是串口的 c 文件,我们修改一下我们这个程序的数据包格式定义啊,就是 PPT 的这里是可变包长含包头,包尾,以艾特符号为包头,换行的两个符号为包尾,中间的载荷字符数量不固定啊,

那程序这里呢,我们这里就只写接收的部分啊,因为发送的话不方便向 hex,数组不一样一个个更改的,所以发送就直接在主函数里生个 string 或者 printf 就行了,非常简单。那接收部分我们来实现一下,首先缓存区发送的删掉哈,

接收的数据类型定义为 char,用于接收字符,同时数量我们给多点哈,防止溢出,给个 100,这要求单条指令最长不能超过 100 个字符啊,那接收缓存区就定好了,

之后是中断的状态机部分,参考一下 PPT 这里的状态转移图哈,

那这里状态 0 等待包头就是 x data 是不是等于艾特字符,如果等于就转移到状态一,计数器清 0 啊,如果不等于就维持状态 0,

之后状态一这里因为载荷字符数量并不确定,所以每次接收之前我们必须先判断是不是包围,所以这里的逻辑应该是 if X data 是不是等于等于杠 r,如果是的话就 X state 等于 2,跳转到状态 2,如果不是的话,l s 才需要接收数据,接收数据是这两行啊,下面不要了,这是状态一的逻辑,

最后是状态二,在这个状态我们需要检测 s data 是不是等于等于杠 n,等待第二个包围啊,如果是的话,状态置 0,接收标志位置一同时接收到之后啊,我们还需要给我这个字符数组的最后加一个字符串结束标志位杠 0 哈,方便我们后续对字符串进行处理,要不然你要修 string 它没没有结束标注位就不知道这个字符串到底有多长了,加结束标注位也很简单哈,我们复制下这一条写入字符数组的下一位置数据给反斜杠 0,这样它才是一个完整的字符串啊,

那我们试一下现象改一下头文件,这个删掉啊,这个类型是恰下面这个函数SendPacket删掉,这样就完成了,

我们在主函数测试一下看看主循环这里 if serial get x flag 等于等于一,接收到数据包之后,OLED 显示一下,这里收到的是字符串,所以可以使 oid show string 四行一列显示 serial rx packet 对的,当然在显示之前我们还需要清除一下第四行哈,因为这个字符串的长度不确定,如果先显示一个长的,再显示一个短的,那长的那个字符串的屁股就会露出来,所以复制一下先给 16 个空格,这就相当于擦除第四行了,再显示就行了,

那我们试一下编译这个错误哈,这个函数忘了删了,删掉再编译,

下载看一下串口助手,这里发送和接收都选择文本模式,然后按照规定的格式发送数据包,比如艾特 A B C 换行,这个换行一定要打哈,发送可以看到这里屏幕显示 A B C 这个字符串,

随便修改一下哈,发送可以看到也是没问题的,

字符串都有了,那之后的内容其实就比较好办了是吧,我们完善一下最终的程序,先把 LED 加进来,include LED 点进去,然后 LED 一例的初始化,

之后在这里就是判断字符串是不是等于我们规定的指令在执行相应的操作了是吧?判断字符串呢,我们可以调用 C 语言字符串的官方库,在上面 include string 点 h,这个库里面有很多字符串的处理函数啊,学 C 语言的时候都见过,如果不清楚的话可以在网上搜搜复习一下。

那在这里我们判断两个字符串是否相等需要用到一个函数啊,string compare 第一个字符串给 serial rx packet 第二个手写一个哈,比如 LED 下划线 on,如果相等的话,函数返回 0,所以套个 if,如果 string compare 比较字符串等于等于 0 相等,那就是指令匹配,我们可以执行 LD1 二点亮 LD,同时我们可以反馈一些信息哈,向串口助手回传一个字符串,Serial send string LED 二 OK 杠 r 杠 n。然后 OID 也显示一下发送的数据哈,这两行复制一下位置是第二行内容是 LED 上 OK,这样 LED 上指令的动作就完成了,

之后我们复制一下,来个 LED off 的,这里 else if 接收到的数据与 LED off 相同,则 LED 一 off 回传 off OK,显示 off OK

之后再来一个 else,也就是上面的指令都不匹配,则不执行操作,复制一下,回传 error command,显示 error command 错误指令啊,好了,这样程序的逻辑就完成了,

我们来测试一下,下载看一下,我们先发送一个 LED ON,LED 点亮,

回传 LED ON,OK,再来个 LED OFF,LED 熄灭,

回传 LED OFF,OK,再来个其他的指令,LED 无操作哈,回传 error command

这就是我们第二个程序的现象。

最后还有个问题需要说明哈,同样还是之前的那个问题,如果连续发送数据包,程序处理不及时,可能导致数据包错位哈,在这里文本数据包每个数据包是独立的,不存在连续,这如果错位了问题就比较大,所以在程序这里我们可以修改一下哈,等每次处理完成之后再开始接收下一个数据包,怎么改呢,

我们可以利用这个 x flag,在这里就不使用读取 flag 之后立刻清除的设呀,我们可以这样,在中断函数这里等待包头的时候再加一个条件,如果数据等于包头,并且 serial rx flag 等于等于 0 才执行接收,

否则就是你发太快了,我还没处理完呢,就跳过这个数据包,然后上面这个读取标志位之后立刻清 0 的函数先删掉哈,

我们可以把这个 rx flag 也声明为外部可调用,暂时不分装了哈,之前的函数删掉,

然后主函数这里,if serial x flag 等于等于一,代表接收到数据包了,执行操作,等操作完成之后再 x flag 等于零,把 flag 清零,

在中断这里只有 flag 为零了才会继续接收下一个数据包,这样写数据和读数据就是严格分开的,不会同时进行,就可以避免数据包错位的现象了。

不过这样的话,你发送数据包的频率就不能太快了哈,否则会丢弃部分包。这是这个逻辑。

或者你还可以再定一个指令缓冲区,把接收好的字符串放在这个指令缓冲区进行排队啊,这样处理起来会更加有条理。有关这个数据包的手法哈,其实还是有非常多的细节问题需要考虑的。是吧,实际应用的话还是得多想想。

那我们试下这个代码编译下载看一下。发送 LED ON,开灯。

发送 LED OFF,关灯。

没问题啊,现象和刚才的是一样的。如果你数据包发送频率比较低啊,有足够长的时间处理,那其实都没问题啊,就是发太快的时候可能会有些问题