串口数据包收发的思路和流程-stm32入门

本节主要内容:

  1. 如何去规定一个合理的数据包格式
  2. 如何收发数据包

1. 数据包格式规定/定义

1.1 HEX 数据包定义

  • 固定包长,含包头包尾
    在这里插入图片描述
  • 可变包长,含包头包尾
    在这里插入图片描述

首先数据包的作用是把一个个单独的数据给打包起来,方便我们进行多字节的数据通信。我们之前学习了串口的代码,发送一个字节,接收一个字节都没问题,但在实际应用中,我们可能需要把多个字节打包为一个整体进行发送,比如说,我们有个陀螺仪传感器,需要用串口发送数据到 STM32,陀螺仪的数据,比如 X 轴一个字节,Y 轴一个字节,Z 轴一个字节,总共 3 个数据,需要连续不断地发送,当你像这样,XYZXYZXYZ 连续发送的时候,就会出现一个问题,就是接收方,它不知道这数据哪个对应 X,哪个对应 Y,哪个对应 Z,因为接收方可能会从任意位置开始接收,所以会出现数据错位的现象,这时候,我们就需要研究一种方式,把这个数据进行分割,把 XYZ 这一批数据分隔开,分成一个个数据包,这样再接收的时候,就知道了数据包的第一个数据就是 X、第二个数据就是 Y,第三个数据就是 Z,这就是数据包的任务,就是把属于同一批的数据进行打包和分割,方便接收方进行识别,那有关分割打包的方法,可以自己发挥想象力来设计,只要逻辑行得通就行。

比如我可以设计,在这个 XYZXYZ 数据流中,数据包的第一个数据,也就是 X 的数据包,它的最高位置 1,其余数据包,最高位都置 0,当我接收到了数据之后,判断一下最高位,如果是 1,那就是 X 数据,然后紧跟着的两个数据就分别是 Y 和 Z,这就是一种可行的分割方法。这种方法就是把每个数据的最高位当作标志位来进行分割的,实际也有应用的例子,比如 UTF8 的编码方式,和这就是类似的,不过它那个编码更高级一些,感兴趣的话可以了解一下。

那本节我们主要讲的数据包分割方法,并不是在数据的高位添加标志位这种方式,因为这种方式破坏了原有数据,使用起来比较复杂,我们串口数据包,通常使用的是额外添加包头包尾的这种方式,比如我这里就列举了两种数据包格式,第一种是固定包长,含包头包尾,也就是每个数据包的长度都固定不变,数据包前面是包头,后面是包尾;第二种是可变包长,含包头包尾,也就是每个数据包的长度可以是不一样的,数据包前面是包头,后面是包尾。它们的数据包格式,可以是用户根据需求,自己规定的,也可以是你买一个模块,别的开发者规定的。

那本节规定是,比如固定包长这里,一批数据规定有 4 个字节,在这 4 个字节之前,加一个包头,比如我定义 0xFF 为包头,在 4 个字节之后,加一个包尾,比如我定义 0xFE 为包尾。那当我接收到 0xFF 之后,我就知道一个数据包来了,接着我再接收到的 4 个字节,就当作数据包的第 1、2、3、4 个数据,存在一个数组里,最后跟一个包尾,当我收到 0xFE 之后,就可以置一个标志位,告诉程序,我收到了一个数据包,然后新的数据包过来,再重复之前的过程,这样就可以在一个连续不断的数据流中,分割出我们想要的数据包了,这就是添加包头包尾实现数据分割打包的思路。

接着我们来研究几个问题:

  1. 包头包尾和数据载荷重复问题

这里定义 FF 为包头,FE 为包尾,如果我传输的数据本身就是 FF 和 FE 怎么办呢?那这个问题确实存在,如果数据和包头包尾重复,可能会引起误判。

对应这个问题我们有如下几种解决方法:

  1. 限制载荷数据的范围,如果可以的话,我们可以在发送的时候,对数据进行限幅,比如 XYZ,3 个数据,变化范围都可以是 0~100,那就好办了,我们可以在载荷中只发送 0~100 的数据,这样就不会和包头包尾重复了。
  2. 如果无法避免载荷数据和包头包尾重复,那我们就尽量使用固定长度的数据包,这样由于载荷数据是固定的,只要我们通过包头包尾对齐了数据,我们就可以严格知道,哪个数据应该是包头包尾,哪个数据应该是载荷数据,在接收载荷数据的时候,我们并不会判断它是否是包头包尾,而在接收包头包尾的时候,我们会判断它是不是确实是包头包尾,用于数据对齐。这样,在经过几个数据包的对齐之后,剩下的数据包应该就不会出现问题了。
  3. 增加包头包尾的数量,并且让它尽量呈现出载荷数据出现不了的状态,比如我们使用 FF、FE 做为包头,FD、FC 作为包尾,这样也可以避免载荷数据和包头包尾重复的情况发生。
  1. 包头包尾并不是全部都需要的。

比如我们可以只要一个包头,把包尾删掉,这样数据包的格式就是,一个包头 FF,加 4 个数据,这样也是可以的,当检测到 FF,开始接收,收够 4 个字节后,置标志位,一个数据包接收完成,这样也可以,不过这样的话,载荷和包头重复的问题会更严重一些,比如最严重的情况下,我载荷全是 FF,包头也是 FF,那你肯定不知道哪个是包头了,而加上 FE 作为包尾,无论数据怎么变化都是可以分辨出包头包尾的。

  1. 固定包长和可变包长的选择问题

对于 HEX 数据包来说,如果你的载荷会出现和包头包尾重复的情况,那就最好选择固定包长,这样可以避免接收错误,如果你又会重复,又选择可变包长,那数据很容易就乱套了;如果载荷不会和包头包尾重复,那可以选择可变包长,数据长度,像这样,4 位、3 位、等等,1 位、10 位,来回任意变肯定都没问题。因为包头包尾是唯一的,只要出现包头,就开始数据包,只要出现包尾,就结束数据包,这样就非常灵活了。

  1. 各种数据转换为字节流的问题

这里数据包都是一个字节一个字节组成的,如果你想发送 16 位的整型数据、32 位的整型数据、float、double、甚至是结构体,其实都没问题,因为它们内部其实都是由一个字节一个字节组成的,只需要用一个 uint8_t 的指针指向它,把它们当作一个字节数组发送就行了。

1.2 文本数据包定义

  • 固定包长,含包头包尾
    在这里插入图片描述
  • 可变包长,含包头包尾
    在这里插入图片描述

文本数据包和 HEX 数据包,就分别对应了文本模式和 HEX 这两种模式。在 HEX 数据包里面,数据都是以原始的字节数据本身呈现的,而在文本数据包里面,每个字节就经过了一层编码和译码,最终表现出来的,就是文本格式,但实际上,每个文本字符背后,其实都还是一个字节的 HEX 数据。

那我们看一下,同样给出了固定包长和可变包长这两种模式,由于数据译码成了字符形式,这就会存在大量的字符可以作为包头包尾,可以有效避免载荷和包头包尾重复的问题。比如本节规定以 @ 这个字符作为包头,以 \r\n 也就是换行,这两个字符作为包尾,在载荷数据中间可以出现除了包头包尾的任意字符,这很容易做到,所以文本数据包基本不用担心载荷和包头包尾重复的问题,使用非常灵活。可变包长、各种字母、符号、数字,都可以随意使用,当我们接收到载荷数据之后,得到的就是一个字符串,在软件中再对字符串进行操作和判断,就可以实现各种指令控制的功能了。而且字符串数据包表达的意义很明显,可以把字符串数据包直接打印到串口助手上,什么指令,什么数据,一眼就能看明白,所以这个文本数据包,通常会以换行作为包尾,这样在打印的时候,就可以一行一行地显示了,非常方便。

那 HEX 数据包与文本数据包这两种对比下来,其实也是各有优缺点的。
HEX 数据包:

  • 优点:传输最直接,解析数据非常简单,比较适合一些模块发送原始的数据。比如一些使用串口通信的陀螺仪、温湿度传感器。
  • 缺点:灵活性不足、载荷容易和包头包尾重复。

文本数据包:

  • 优点:数据直观易理解,非常灵活,比较适合一些输入指令进行人机交互的场合,比如蓝牙模块常用的 AT 指令,CNC 和 3D 打印机常用的 G 代码,都是文本数据包的格式。
  • 缺点:解析效率低。比如发送一个数 100,HEX 数据包就是一个字节 100,完事,文本数据包就得是 3 个字节的字符,‘1’,‘0’,‘0’,收到之后还要把字符转换成数据,才能得到 100。所以说,我们需要根据实际场景来选择和设计数据包格式。

2. 数据包的收发流程

2.1 数据包的发送

数据包的发送非常简单。

在 HEX 数据包这里,我如果想发送一个数据包,就定义一个数组,填充数据,然后用上节我们写过的 SendArray 一发就完事了。

在 文本 数据包这里,我如果想发送数据包,就写一个字符串,然后调用 SendString 一发送也完事了。

数据包的发送非常简单。因为发送过程是完全自主可控的,想发啥就发啥。我们写到的时候也能感受到,串口发送,比接收简单多了。

2.2 数据包的接收

接受一个数据包就比较复杂了。我们来学习一下。
在这里插入图片描述

这里演示了固定包长 HEX 数据包的接收方法。首先,根据前一节的代码,我们知道,每收到一个字节,程序都会进一遍中断,在中断函数里,我们可以拿到这一个字节,但拿到之后,我们就得退出中断了,所以,每拿到一个数据,都是一个独立的过程,而对于数据包来说,很明显它具有前后关联性,包头之后是数据,数据之后是包尾,对于包头、数据和包尾这三种状态,我们都需要有不同的处理逻辑,所以在程序中,我们需要设计一个能记住不同状态的机制,在不同状态执行不同的操作,同时还要进行状态的合理转移,这种程序设计思维,就叫做“状态机”。

在这里我们就使用状态机的方法来接收一个数据包,要想设计一个好的状态机程序,画一个下面这样的状态转移图是必要的。对于上面这样一个固定包长 HEX 数据包来说,我们可以定义 3 个状态,第一个状态是等待包头,第二个状态是接收数据,第三个状态是等待包尾。每个状态需要用一个变量来标志一下,比如我这里用变量 S 来标志,三个状态依次为 S = 0,S = 1,S = 2,这一点类似于置标志位,只不过标志位只有 0 和 1,而状态机是多标志位状态的一种方式,然后执行流程是:

  1. 最开始,S = 0,收到一个数据,进中断,根据 S = 0,进入第一个状态的程序,判断数据是不是包头 FF,如果是 FF,则代表收到包头,之后置 S = 1,退出中断,结束,这样下次再进中断,根据 S = 1,就可以接收数据的程序了,那在第一个状态,如果收到的不是 FF,就证明数据包没有对齐,我们应该等待数据包包头的出现,这时状态就仍然是 0,下次进中断,就还是判断包头的逻辑,直到出现 FF,才能转到下一个状态。
  2. 那之后,出现了 FF,我们就可以转移到接收数据的状态了。这时再收到数据,我们就直接把它存在数组里,另外再用一个变量,记录收了多少个数据,如果没收够四个数据,就一直是接收状态,如果收够了,就置 S = 2,下次中断时,就可以进入下一个状态了。
  3. 那最后一个状态就是等待包尾了,判断数据是不是 FE,正常情况,应该是 FE,这样就可以置 S = 0,回到最初的状态,开始下一个轮回,当然也有可能这个数据不是 FE,比如数据和包头重复了,导致包头位置判断错了,那这个包尾位置就有可能不是 FE,这时就可以进入重复等待包尾的状态,直到接收到真正的包尾。这样加入包尾的判断,更能预防因数据和包头重复造成的错误。

这就是使用状态机接收数据包的思路。这个状态机其实是一种很广泛的编程思路,在很多地方都可以用到。使用的基本步骤是:

  1. 先根据项目要求定义状态,画几个圈
  2. 然后考虑好各个状态在什么情况下会进行转移,如何转移,画好线和转移条件。
  3. 最后根据这个图来进行编程。

这样思维就会非常清晰了。比如你要做个菜单,就可以用到状态机的思维,按什么键,切换什么菜单,执行什么样的程序,还有一些芯片内部逻辑,也会用到状态机。比如芯片什么情况下进入待机状态,什么情况下进入工作状态,这也是状态机的应用,希望大家可以研究一下,对你的编程肯定会有帮助。

在这里插入图片描述

这里演示了可变包长 文本 数据包的接收方法。我们看一下可变包长 文本 数据包的接收流程,同样也是利用状态机,定义了三个状态。

  1. 第一个状态,等待包头,判断收到的是不是我们规定的 @ 符号,如果收到 @,就进入接收状态。
  2. 在接收状态下,依次接收数据,同时,这个状态还应该要兼具等待包尾的功能,因为这是可变包长,我们接收数据的时候,也要时刻监视,是不是收到包尾了,一旦收到包尾了,就结束,那这里,这个状态的逻辑就应该是,收到一个数据,判断是不是 \r,如果不是,就正常接收,如果是,则不接收,同时跳到下一个状态,等待包尾 \n,因为这里数据包有两个包尾 \r,\n,所以需要第三个状态。
  3. 如果只有一个包尾,那在出现包尾之后,就可以直接回到初始状态了,只需要两个状态就行。因为接收数据和等待包尾需要在一个状态里同时进行。由于串口的包头包尾不会出现在数据中,所以基本不会出现数据错位的现象。

这就是使用状态机接收文本数据包的方法。

其他的数据包也都可以套用这两个形式,等会我们写程序就会根据这里面的流程来。

到这里,关于数据包的定义、分类、优缺点和注意事项就介绍完了。

包头 FF 和 包尾 FE 用于控制接收,没有显示。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/174617.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java LeetCode篇-深入了解关于数组的经典解法

🔥博客主页: 【小扳_-CSDN博客】 ❤感谢大家点赞👍收藏⭐评论✍ 文章目录 1.0 轮转数组 1.1 使用移位的方式 1.2 使用三次数组逆转法 2.0 消失的数字 2.1 使用相减法 2.2 使用异或的方式 3.0 合并两个有序数组 3.1 使用三指针方式 3.2 使用合…

Spring Cache(缓存框架)

学习的最大理由是想摆脱平庸,早一天就多一份人生的精彩;迟一天就多一天平庸的困扰。各位小伙伴,如果您: 想系统/深入学习某技术知识点… 一个人摸索学习很难坚持,想组团高效学习… 想写博客但无从下手,急需…

vue 中 js 金额数字转中文

参考:js工具函数之数字转为中文数字和大写金额_js封装工具类函数金额大写-CSDN博客 我使用的框架vol.core。 客户需求要将录入框的金额数字转换成中文在旁边显示,换了几种函数,最终确定如下函数 function changeToChineseMoney(Num) {//判断…

Android Termux SFTP如何实现远程文件传输

文章目录 1. 安装openSSH2. 安装cpolar3. 远程SFTP连接配置4. 远程SFTP访问4. 配置固定远程连接地址 SFTP(SSH File Transfer Protocol)是一种基于SSH(Secure Shell)安全协议的文件传输协议。与FTP协议相比,SFTP使用了…

【24届校招】c++选手还有机会吗?如何选择更好的出路?

一、今年为什么c选手就业形势如此艰难? 去年c岗位的火热,不少c选手拿到高薪offer,今年转c的人群变多,内卷加剧,高学历大佬多如牛毛,很多比较好的c岗位多人投递,僧多肉少。 从行情来说&#xf…

Selenium-Unittest单元测试框架

1、Unittest介绍 为什么要学习单元测试框架 测试用例的组织与运行需要单元测试框架的参与,从而满足不同测试场景的需要,单元测试框架提供了丰富的比较方法:实际结果与预期结果的对比测试结果 单元测试框架提供了丰富的日志:给出测…

livox 半固体激光雷达 gazebo 仿真 | 安装与验证

livox 半固体激光雷达 gazebo 仿真 | 安装与验证 livox 半固体激光雷达 gazebo 仿真 | 安装与验证livox 介绍安装验证 livox 半固体激光雷达 gazebo 仿真 | 安装与验证 livox 介绍 览沃科技有限公司(Livox)成立于2016年。为了革新激光雷达行业&#xf…

web:[NPUCTF2020]ReadlezPHP

题目 打开页面显示如下 没发现其他的线索,查看源代码 发现一个网址,访问这个页面查看 进行代码审计 这段代码是一个简单的 PHP 类,名为 HelloPhp。它有两个公共属性 $a 和 $b,并在构造函数中将它们分别初始化为字符串 "Y-m-…

Java 设计模式之命令模式

命令模式 介绍 命令模式是一种行为类设计模式,核心是将每种请求或操作封装为一个独立的对象,从而可以集中管理这些请求或操作,比如将请求队列化依次执行、或者对操作进行记录和撤销。 命令模式通过将请求的发送者(客户端&#x…

Failed to load resource: the server responded with a status of 404 ()

路径问题: 路径省略前面的http://localhost:8080/ 就行了。

中兴交换机:DHCP的配置

一、配置说明 拓扑图 S1是中兴三层核心交换机,作为DHCP Server使用,同时作为网关,PC通过自动获取IP地址接入网络 注意事项: S1全局下需要配置:IP Pool,DHCP Policy,打开DHCP功能 S1接口下需…

Springboot实现增删改差

一、包结构 二、各层代码 (1)数据User public class User {private Integer id;private String userName;private String note;public User() {super();}public User(Integer i, String userName, String note) {super();this.id i;this.userName userName;this.note note;…

2.前端--HTML标签基本概念【2023.11.25】

1.基本语法规范 HTML 标签是由尖括号包围的关键词&#xff0c;例如 <html>。HTML 标签通常是成对出现的&#xff0c;例如 和 &#xff0c;我们称为双标签。有些特殊的标签必须是单个标签&#xff08;极少情况&#xff09;&#xff0c;例如 <br />我们称为单标签。 …

深度学习之基于YoloV3杂草识别系统

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 深度学习在图像识别领域已经取得了显著的成果&#xff0c;其中基于YOLO&#xff08;You Only Look Once&#xff09…

C语言:求二维数组鞍点 。鞍点就是指二维数组中在该位置上的元素在该行上最大,在该列上最小,也可能没有鞍点。

分析&#xff1a; 在主函数 main 中&#xff0c;程序首先定义一个二维数组 a[5][5] 和五个整型变量 i、j、max、maxj 和 k&#xff0c;并用于寻找鞍点。然后使用 printf 函数输出提示信息。 接下来&#xff0c;程序使用两个 for 循环结构&#xff0c;从键盘输入一个 5x5 的二…

【华为OD题库-038】支持优先级的对列-java

题目 实现一个支持优先级的队列&#xff0c;高优先级先出队列&#xff0c;同优先级时先进先出。 如果两个输入数据和优先级都相同&#xff0c;则后一个数据不入队列被丢弃。 队列存储的数据内容是一个 整数。 输入描述 一组待存入队列的数据(包含内容和优先级)。 输出描述 队列…

力扣6:N字形变化

代码&#xff1a; class Solution { public:string convert(string s, int numRows){int lens.size();if(numRows1){return s;}int d2*numRows-2;int count0;string ret;//第一行&#xff01;for(int i0;i<len;id){rets[i];}//第k行&#xff01;for(int i1;i<numRows-1;…

【云备份】配置加载文件模块

文章目录 配置信息设计配置文件加载cloud.conf配置文件单例模式的使用ReadConfigFile —— 读取配置文件GetInstance —— 创建对象其他函数的实现 具体实现cloud.confconfig.hpp 配置信息设计 使用文件配置加载一些程序运行的关键信息 可以让程序的运行更加灵活 配置信息&am…

爬取极简壁纸

js反编译的代码需要解密之类的&#xff0c;直接给我干蒙圈了&#xff0c;借助selenium可以直接获取到调式工具中的源码&#xff0c;可以获取渲染后的链接&#xff0c;然后将链接交给下载函数&#xff08;使用异步提高效率&#xff09;即可。 后续学习完js反编译的话&#xff0…

羊大师:不同时段喝羊奶,效果会有何不同?

羊大师&#xff1a;不同时段喝羊奶&#xff0c;效果会有何不同&#xff1f; 羊奶是一种营养丰富、口感香浓的健康饮品&#xff0c;被广大消费者所喜爱。然而&#xff0c;你是否知道&#xff0c;不同的喝奶时间对身体的影响也是不同的呢&#xff1f;在不同时段喝羊奶&#xff0…