江协科技STM32学习- P28 USART串口数据包

news2024/11/24 11:34:56

       🚀write in front🚀  
🔎大家好,我是黄桃罐头,希望你看完之后,能对你有所帮助,不足请指正!共同学习交流
🎁欢迎各位→点赞👍 + 收藏⭐️ + 留言📝​ 

💬本系列哔哩哔哩江科大STM32的视频为主以及自己的总结梳理📚 

🚀Projeet source code🚀   

💾工程代码放在了本人的Gitee仓库:iPickCan (iPickCan) - Gitee.com

引用:

STM32入门教程-2023版 细致讲解 中文字幕_哔哩哔哩_bilibili

Keil5 MDK版 下载与安装教程(STM32单片机编程软件)_mdk528-CSDN博客

STM32之Keil5 MDK的安装与下载_keil5下载程序到单片机stm32-CSDN博客

0. 江协科技/江科大-STM32入门教程-各章节详细笔记-查阅传送门-STM32标准库开发_江协科技stm32笔记-CSDN博客

【STM32】江科大STM32学习笔记汇总(已完结)_stm32江科大笔记-CSDN博客

江科大STM32学习笔记(上)_stm32博客-CSDN博客

STM32学习笔记一(基于标准库学习)_电平输出推免-CSDN博客

STM32 MCU学习资源-CSDN博客

stm32学习笔记-作者: Vera工程师养成记

stem32江科大自学笔记-CSDN博客

术语:

英文缩写描述
GPIO:General Purpose Input Onuput通用输入输出
AFIO:Alternate Function Input Output复用输入输出
AO:Analog Output模拟输出
DO:Digital Output数字输出
内部时钟源 CK_INT:Clock Internal内部时钟源
外部时钟源 ETR:External Trigger 时钟源 External 触发
外部时钟源 ETR:External Trigger mode 1外部时钟源 External 触发 时钟模式1
外部时钟源 ETR:External Trigger mode 2外部时钟源 External 触发 时钟模式2
外部时钟源 ITRx:Internal Trigger inputs外部时钟源,ITRx (Internal trigger inputs)内部触发输入
外部时钟源 TIx:exTernal Input pin 外部时钟源 TIx (external input pin)外部输入引脚
CCR:Capture/Comapre Register捕获/比较寄存器
OC:Output Compare输出比较
IC:Input Capture输入捕获
TI1FP1:TI1 Filter Polarity 1Extern Input 1 Filter Polarity 1,外部输入1滤波极性1
TI1FP2:TI1 Filter Polarity 2Extern Input 1 Filter Polarity 2,外部输入1滤波极性2
DMA:Direct Memory Access直接存储器存取

正文:

0. 概述

从 2024/06/12 定下计划开始学习下江协科技STM32课程,接下来将会按照哔站上江协科技STM32的教学视频来学习入门STM32 开发,本文是视频教程 P2 STM32简介一讲的笔记。

本节学习一下串口数据包收发的思路和流程

接下来就来学习一下如何去规定一个合理的数据包格式,以及如何收发数据包。

数据包格式一般有两种:一种是Hex数据包,一种是文本数据包

先看一下Hex数据包格式。

1.🚢Hex数据包

首先数据包的作用是把一个个单独的数据给打包起来,方便我们进行多字节的数据通信。

我们之前学习了串口的代码,发送一个字节,接收一个字节都没问题。但在实际应用中,我们可能需要把多个字节打包为一个整体进行发送。比如说我们有个陀螺仪传感器,需要用串口发送数据STM32。陀螺仪的数据,比如x轴一个字节,y轴一个字节,z轴一个字节,总共三个数据需要连续不断的发送,当你像这样xyzxyzxyz连续发送的时候,就会出现一个问题,就是接收方不知道这数据哪个对应x,哪个对应y,哪个对应z,因为接收方可能会从任意位置开始接收,所以会出现数据错位的现象。

这时候我们就需要研究一种方式,把这个数据进行分割,把xyzxyzxyzxyz这一批数据分割开,分成xyz,分成一个个数据包。这样再接收的时候就知道了数据包的第一个数据就是x,第二个是y,第三个是z。这就是数据包的任务,就是把属于同一批的数据进行打包和分割,方便接收方进行识别。

有关分割打包的方法可以是自己发挥想象力来设计,只要逻辑行得通就行。比如可以设计在这个xyzxyzxyzxyz数据流中,数据包的第一个数据,也就是x的数据包,它的最高位置1,其余数据包最高位都置0。这样当接收到数据之后,判断一下最高位,如果是1,就是x数据,然后紧跟着的两个数据就分别是y和z,这就是一种可行的分割方法。

这种方法就是把每个数据的最高位当做标志位来进行分割的。实际也有应用的例子,比如UTF8的编码方法和这就是类似的。

但是本节我们主要讲的数据包分割方法并不是在数据的高位添加标志位这种方式。因为这种方式破坏了原有数据使用起来比较复杂。

我们串口数据包通常使用的是额外添加包头包尾这种方式。比如这里就列举了两种数据包格式

第一种是固定包长,含包头包尾

第二种是可变包长,含包头包尾

也就是每个数据包的长度可以是不一样的。前面是包头,后面是包尾。

数据包格式可以是用户根据需求自己规定的,也可以是你买个模块别的开发者规定的。

我们这里规定是,比如固定包长,一批数据规定有四个字节,在这四个字节之前加个包头,比如定义0xFF为包头。在四个字节之后加一个包尾,比如定义0xFE为包尾。当接收到0xFF之后就知道一个数据包来了,接着再接收到的四个字节就当做数据包的第一,二、三、四个数据存在一个数组里,最后跟一个包尾。当收到0xFE之后就可以置一个标志位,告诉程序收到了一个数据包。然后新的数据包过来再重复之前的过程。

这样就可以在一个连续不断的数据流中分割出我们想要的数据包来。这就是通过添加包头包尾,实现数据分割打包的思路。

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

收发过程中的问题

🐟️🐟️第一个问题就是包头包

这里定义FF为包头FE为包尾,那如果传输的数据本身就是FF和FE怎么办?

这个问题确实存在,如果数据和包头包尾重复可能会引起误判。对应这个问题,我们有如下几种解决方法:

尾和数据载荷重复的问题

第一种方法限制载荷数据的范围

如果可以的话,我们可以在发送的时候,对数据进行限幅,比如xyz三个数据变化范围都可以是0~100,我们可以在载荷中只发送0~100的数据,这样就不会和包头报尾重复了。

第二种方法如果无法避免载荷数据和包头包尾重复就尽量使用固定长度的数据包

这样由于载荷数据是固定的,只要我们通过包头包尾对齐了数据,我们就可以严格知道哪个数据应该是包头包尾,哪个数据应该是载荷数据。在接收载荷数据的时候,我们并不会判断它是否是包头包尾。而在接收包头包尾的时候,我们会判断它是不是确实是包头包尾,用于数据对齐。这样在经过几个数据包的对齐之后,剩下的数据包应该就不会出现问题了。

第三种方法就是增加包头包尾的数量并且让它尽量呈现出载荷数据出现不了的状态

比如我们使用FF、FE作为包头,FD、FC作为包尾,这样也可以避免再和数据和包头包尾重复的情况发生。

🐟️🐟️第二个问题是这个包头包尾并不是全部都需要的

比如我们可以只要一个包头,把包尾删掉。这样数据包的格式就是一个包头FF加四个数据,这样也是可以的。当检测到FF开始接收,收够四个字节后置标志位,一个数据包接收完成,这样也可以。不过这样的话,载荷和包头重复的问题会更严重一些。

比如最严重的情况下,载荷全是FF,包头也是FF,那肯定不知道哪个是包头了。而加上FE作为包尾,无论数据怎么变化,都是可以分辨出包头包尾的。

🐟️🐟️第三个问题就是固定包长和可变包长的选择问题

对应hex数据包来说,如果载荷会出现和包头包尾重复的情况,就最好选择固定包长,这样可以避免接收错误。如果你又会重复又选择可变包长,数据很容易就乱套了。

如果载荷不会和包头包尾重复,可以选择可变包长数据长度,像这样四位、三位、一位、十位、来回任一变,肯定都没问题。因为包头包尾是唯一的,只要出现包头就开始数据包,只要出现包尾就结束数据包,这样就非常灵活。

这就是固定包长和可变包长选择的问题。

🐟️🐟️第四个问题就是各种数据转换为字节流的问题

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

接下来看一下文本数据包。
 

2.🚢文本数据包

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

同样文本数据包也可以有两种模式:

第一种是固定包长,含包头包尾

第二种是可变包长,含包头包尾

由于数据译码成了字符形式,这就会存在大量的字符可以作为包头包尾,可以有效避免载荷和包头包尾重复的问题。比如我这里规定的就是以@这个字符作为包头,以’\r’’\n’,也就是换行这两个字符作为包尾。

在载荷数据中间,可以出现除了包头包尾的任意字符,这很容易做到。所以文本数据包基本不用担心载荷和包头包尾重复的问题使用非常灵活。可变包长、各种字母、符号、数字都可以随意使用。

当我们接收到载荷数据之后,得到的就是一个字符串,在软件中再对字符串进行操作和判断。就可以实现各种指令控制的功能了,而且字符串数据包表达的意义很明显,可以把字符串数据包直接打印到串口助手上,什么指令、什么数据一眼就能看明白。所以这个文本数据包通常会以换行作为包尾。这样在打印的时候就可以一行一行的显示了非常方便。

3.🚢Hex数据包和文本数据包的优缺点

Hex数据包和文本数据包这两种对比下来其实也各有优缺点。

hex数据包

优点是传输最直接解析数据非常简单,比较适合一些模块发送原始的数据。比如一些使用串口通信的陀螺仪,温湿度传感器,

缺点就是灵活性不足,载荷容易和包头包尾重复。

文本数据包

优点是数据直观易理解,非常灵活,比较适合一些输入指令,进行人机交互的场合。比如蓝牙模块常用的AT指令,CNC和3D打印机常用的G代码都是文本数据包的格式。

缺点就是解析效率低,比如发送一个数100,hex数据包,就是一个字节100完事儿,文本数据包就得是三个字节的字符,’1’’0’’0’,收到之后,还要把字符转换成数据才能得到一百。

所以说我们需要根据实际场景来选择和设计数据包格式

接下来我们就来学一下数据包的收发流程。

4.🚢数据包的收发流程

首先是数据包的发送,这个比较简单。

发送数据包

如果想发送一个数据包,就定义一个数组填充数据,然后用上节我们写过的SendArray函数一发就完事了,文本数据包这里也很简单,写一个字符串,然后调用SendString一发送也完事了。

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

接下来接收一个数据包就比较复杂了。

接收数据包

这里演示了固定包长hex数据包的接收方法可变包长文本数据包的接收方法,其他的数据包也都可以套用这个形式。下节写程序就会根据这里面的流程来。

HEX数据包接收

我们先看一下如何来接收这个固定包长的hex数据包。

首先根据之前的代码,我们知道每收到一个字节程序都会进一步中断。在中断函数里,我们可以拿到这一个字节,但拿到之后我们就得退出中断了。所以每拿到一个数据都是一个独立的过程。而对于数据包来说很明显,它具有前后关联性。包头之后是数据,数据之后是包尾。对于包头、数据和包尾这三种状态,我们都需要有不同的处理逻辑。所以在程序中,我们需要设计一个能记住不同状态的机制

在不同状态执行不同的操作同时还要进行状态的合理转移。这种程序设计思维就叫做状态机在这里我们就使用状态机的方法来接收一个数据包,要想设计一个好的状态机程序画一个这样的状态,转移图是必要的。

对于上面这样一个固定包长hex数据包来说,我们可以定义三个状态,第一个状态是等待包头、第二个状态是接收数据、第三个状态是等待包尾,每个状态需要用一个变量来标志一下。比如这里用变量s来标志。三个状态依次为s等于0,s等于1,s等于2。这一点类似于置标志位,只不过标志位只有零和一,而状态机是多标志位状态的一种方式。

然后执行流程是最开始s等于0收到一个数据进中断,根据s等于0进入第一个状态的程序,判断数据是不是包头FF,如果是FF则代表收到包头,之后置s等于1,退出中断结束。这样下次再进中断,根据s等于1就可以进行接收数据的程序了。

在第一个状态,如果收到的不是FF就证明数据包没有对齐,我们应该等待数据包包头的出现。这时状态就仍然是0,下次进中断,就还是判断包头的逻辑,直到出现FF才能转到下一个状态。

之后出现了FF,我们就可以转移到接收数据的状态了。这时再收到数据,我们就直接把它存在数组中。另外再用一个变量记录收纳多少个数据,如果没收够四个数据,就一直是接收状态。如果收够了,就置s等于2,下次进入中断时就可以进入下一个状态了。

最后一个状态就是等待包尾了。判断数据是不是FE,正常情况应该是FE,这样就可以置s等于0,回到最初的状态,开始下一个轮回。当然也有可能这个数据不是FE,比如数据和包头重复,导致包头位置判断错了,这个包尾位置就有可能不是FE,这时就可以进入重复等待包尾的状态,直到接收到真正的包尾。这样加入包尾的判断,更能预防因数据和包头重复造成的错误。这就是使用状态机接收数据包的思路。

这个状态机其实是一种很广泛的编程思路,在很多地方都可以用到。使用的基本步骤是先根据项目要求定义状态画几个圈,然后考虑好各个状态,在什么情况下会进行转移,如何转移,画好线和转移条件,最后根据这个图来进行编程,这样思维就会非常清晰了。比如你要做个菜单,就可以用到状态机的思维,按什么键切换,什么菜单,执行什么样的程序。还有一些芯片内部逻辑也会用到状态机,比如芯片什么情况下进入待机状态,什么情况下进入工作状态,这也是状态机的应用。希望大家可以研究一下,对你的编程肯定会有帮助。

接下来继续我们来看一下这个可变包长文本数据包的接收流程。

文本数据包接收

同样也是利用状态机定义三个状态。第一个状态,等待包头,判断收到的是不是我们规定的@符号,如果是就进入接收状态,在这个状态下依次接收数据。同时,这个状态还应该要兼具等待包尾的功能,因为这是可变包长,我们接收数据的时候,也要时刻监视,是不是收到包尾了,一旦收到包尾了就结束。这个状态的逻辑就应该是收到一个数据判断是不是’\r’,如果不是则正常接收,如果是则不接受,同时跳到下一个状态,等待包尾’\n’,因为这里数据包有两个包尾’\r’’\n’,所以需要第三个状态。如果只有一个包尾,在出现包尾之后,就可以直接回到初始状态了,只需要两个状态就行。因为接收数据和等待包尾需要在一个状态里同时进行。

由于串口的包头包尾不会出现在数据中,所以基本不会出现数据错位的现象。这就是使用状态机接收文本数据包的方法。

下节我们就写程序验证一下以上所学的内容。

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

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

相关文章

C# Retry库

比如网络访问或硬件参数设置需要重试&#xff0c;可引用gunet上的Polly库。 同步方式&#xff08;每次重试有不同的时间间隔&#xff09; var polly Policy.Handle<Exception>().WaitAndRetry(new[] { new TimeSpan(0, 0, 1), new TimeSpan(0, 0, 2), new TimeSpan(0, …

Java避坑案例 - 线程池使用中的风险识别与应对

文章目录 线程池的基本概念创建线程池的注意事项实例1&#xff1a; newFixedThreadPool 使用无界队列&#xff0c;可能因任务积压导致 OOM实例2&#xff1a; newCachedThreadPool 会创建大量线程&#xff0c;可能因线程数量过多导致无法创建新线程。 线程池参数设置的最佳实践线…

基于SSM+微信小程序的社区垃圾回收管理系统(垃圾1)

&#x1f449;文末查看项目功能视频演示获取源码sql脚本视频导入教程视频 1、项目介绍 基于ssm微信小程序的社区垃圾回收管理系统&#xff0c;有管理员&#xff0c;回收员&#xff0c;用户三个角色。 1、管理员功能有个人中心&#xff0c;用户管理&#xff0c;回收员管理&am…

确保组织决策权清晰的有效策略

在组织中&#xff0c;明确的决策权、有效的沟通机制、权责明确的结构是确定和维护清晰决策权的关键要素。明确的决策权确保了每个成员知道自己的职责和权限&#xff0c;有效的沟通机制促进了信息的流通和理解&#xff0c;权责明确的结构则为组织的运作提供了清晰的框架。明确的…

SpringBoot3+SpringSecurity6基于若依系统整合自定义登录流程

SpringBoot3SpringSecurity6基于若依系统整合自定义登录流程 问题背景 在做项目时遇到了要对接统一认证的需求&#xff0c;但是由于框架的不兼容性&#xff08;我们项目是springboot3&#xff0c;jdk17&#xff0c;springsecurity6.1.5&#xff09;等因素&#xff0c;不得不使…

WPF+MVVM案例实战(十四)- 封装一个自定义消息弹窗控件(下)

文章目录 1、案例效果2、弹窗空间使用1.引入用户控件2、按钮命令实现 3、总结4、源代码获取 1、案例效果 2、弹窗空间使用 1.引入用户控件 打开 Wpf_Examples 项目&#xff0c;在引用中添加用户控件库&#xff0c;在 MainWindow.xaml 界面引用控件库&#xff0c;代码如下&…

【论文精读】ID-like Prompt Learning for Few-Shot Out-of-Distribution Detection

&#x1f308; 个人主页&#xff1a;十二月的猫-CSDN博客 &#x1f525; 系列专栏&#xff1a; &#x1f3c0;论文精读_十二月的猫的博客-CSDN博客 &#x1f4aa;&#x1f3fb; 十二月的寒冬阻挡不了春天的脚步&#xff0c;十二点的黑夜遮蔽不住黎明的曙光 注&#xff1a;下文…

PIDNet(语义分割)排坑

PIDNet训练自己的数据集 1. 前言2. 准备工作3. 配置环境4. 排坑过程4.1.1 configs增加了VOC文件夹 并在里面写了yaml参数文件4.1.2 加载VOC格式数据集的类4.1.3 train.py调试 1. 前言 paper小修时reviewer说baseline太老&#xff0c;所以对CVPR2023的PIDNet进行复现&#xff0…

Google Recaptcha V2 简单使用

最新的版本是v3&#xff0c;但是一直习惯用v2&#xff0c;就记录一下v2 的简单用法&#xff0c;以免将来忘记了 首先在这里注册你域名&#xff0c;如果是本机可以直接直接填 localhost 或127.0.0.1 https://www.google.com/recaptcha/about/ 这是列子 网站密钥&#xff1a;是…

autMan奥特曼机器人-内置Redis

autMan内置了redis服务&#xff0c;有的脚本运行需要redis支持 几个注意事项&#xff1a; 启用redis服务后要重启autMan生效&#xff0c;关闭一样的道理。启用redis服务后会增加约200M的内存占用多个autMan的redis服务可以组成集群redis服务

五、快速入门K8s之Pod容器的生命周期

一、容器的初始化init ⭐️ init c &#xff1a; init contariner 初始化容器&#xff0c;只是用来初始化&#xff0c;初始化完成就会死亡可以大于的等于一也可以没有&#xff0c;每个init只有在前一个init c执行完成后才可以执行下一个、init容器总是运行到成功完成为止&#…

sqoop问题汇总记录

此篇博客仅记录在使用sqoop时遇到的各种问题。持续更新&#xff0c;有问题评论区一起探讨&#xff0c;写得有不足之处见谅。 Oracle_to_hive 1. main ERROR Could not register mbeans java.security.AccessControlException: access denied ("javax.management.MBeanTr…

C++对象模型:Function 语意学

Member 的各种调用方式 Nonstatic Member Function 使用C时&#xff0c;成员函数和非成员函数在性能上应该是等价的。当设计类时&#xff0c;我们不应该因为担心效率问题而避免使用成员函数。 实现&#xff1a;编译器会将成员函数转换为一个带有额外this指针参数的非成员函数…

二叉树中的深搜 算法专题

二叉树中的深搜 一. 计算布尔二叉树的值 计算布尔二叉树的值 class Solution {public boolean evaluateTree(TreeNode root) {if(root.left null) return root.val 0? false: true;boolean left evaluateTree(root.left);boolean right evaluateTree(root.right);return…

【Linux】环境ChatGLM-4-9B 模型部署

一、模型介绍 GLM-4-9B 是智谱 AI 推出的最新一代预训练模型 GLM-4 系列中的开源版本。 在语义、数学、推理、代码和知识等多方面的数据集测评中&#xff0c; GLM-4-9B 及其人类偏好对齐的版本 GLM-4-9B-Chat 均表现出超越 Llama-3-8B 的卓越性能。除了能进行多轮对话&#xf…

深入理解Java 线程并发编排工具: 概述和应用场景

目录 前言概述1. CountDownLatch2. CyclicBarrier3. Semaphore&#xff08;信号量)4. Condition 案例CountDownLatch-马拉松场景CyclicBarrier-马拉松场景Semaphore-公交车占座场景Condition-线程等待唤醒场景 前言 在 Java 的 java.util.concurrent (JUC) 包中&#xff0c;提…

C++初阶(八)--内存管理

目录 引入&#xff1a; 一、C中的内存布局 1.内存区域 2.示例变量存储位置说明 二、C语言中动态内存管理 三、C内存管理方式 1.new/delete操作内置类型 2.new和delete操作自定义类型 四、operator new与operator delete函数&#xff08;重要点进行讲解&#xff09; …

架构的本质之 MVC 架构

前言 程序员习惯的编程方式就是三步曲。 所以&#xff0c;为了不至于让一个类撑到爆&#x1f4a5;&#xff0c;需要把黄色的对象、绿色的方法、红色的接口&#xff0c;都分配到不同的包结构下。这就是你编码人生中所接触到的第一个解耦操作。 分层框架 MVC 是一种非常常见且常…

Node学习记录-child_process 子进程

来自&#xff1a;https://juejin.cn/post/7277045020422930488 child_process用于处理CPU密集型应用&#xff0c;Nodejs创建子进程有7个API&#xff0c;其中带Async的是同步API,不带的是异步API child_process.exec(command[, options][, callback]) command:要运行的命令&am…

NVR批量管理软件/平台EasyNVR多个NVR同时管理支持对接阿里云、腾讯云、天翼云、亚马逊S3云存储

随着云计算技术的日益成熟&#xff0c;越来越多的企业开始将其业务迁移到云端&#xff0c;以享受更为灵活、高效且经济的服务模式。在视频监控领域&#xff0c;云存储因其强大的数据处理能力和弹性扩展性&#xff0c;成为视频数据存储的理想选择。NVR批量管理软件/平台EasyNVR&…