有专门做预算的网站没,怎么查询网站的空间商,广州网站开发制作,深圳建设工程交易服务网老网站#x1f680;write in front#x1f680; #x1f50e;大家好#xff0c;我是黄桃罐头#xff0c;希望你看完之后#xff0c;能对你有所帮助#xff0c;不足请指正#xff01;共同学习交流 #x1f381;欢迎各位→点赞#x1f44d; 收藏⭐️ 留言#x1f4dd;… 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博客 术语
英文缩写描述GPIOGeneral Purpose Input Onuput通用输入输出AFIOAlternate Function Input Output复用输入输出AOAnalog Output模拟输出DODigital Output数字输出内部时钟源 CK_INTClock Internal内部时钟源外部时钟源 ETRExternal Trigger 时钟源 External 触发外部时钟源 ETRExternal Trigger mode 1外部时钟源 External 触发 时钟模式1外部时钟源 ETRExternal Trigger mode 2外部时钟源 External 触发 时钟模式2外部时钟源 ITRxInternal Trigger inputs外部时钟源ITRx Internal trigger inputs内部触发输入外部时钟源 TIxexTernal Input pin 外部时钟源 TIx external input pin外部输入引脚CCRCapture/Comapre Register捕获/比较寄存器OCOutput Compare输出比较ICInput Capture输入捕获TI1FP1TI1 Filter Polarity 1Extern Input 1 Filter Polarity 1外部输入1滤波极性1TI1FP2TI1 Filter Polarity 2Extern Input 1 Filter Polarity 2外部输入1滤波极性2DMADirect 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代码都是文本数据包的格式。 缺点就是解析效率低比如发送一个数100hex数据包,就是一个字节100完事儿文本数据包就得是三个字节的字符’1’’0’’0’收到之后还要把字符转换成数据才能得到一百。 所以说我们需要根据实际场景来选择和设计数据包格式。
接下来我们就来学一下数据包的收发流程。
4.数据包的收发流程
首先是数据包的发送这个比较简单。
发送数据包
如果想发送一个数据包就定义一个数组填充数据然后用上节我们写过的SendArray函数一发就完事了文本数据包这里也很简单写一个字符串然后调用SendString一发送也完事了。
所以说发送这个数据包是很简单的因为发送过程是完全自主可控的想发啥就发啥我们写代码的时候也能感受到串口发送比接收简单多了。
接下来接收一个数据包就比较复杂了。
接收数据包
这里演示了固定包长hex数据包的接收方法和可变包长文本数据包的接收方法其他的数据包也都可以套用这个形式。下节写程序就会根据这里面的流程来。
HEX数据包接收
我们先看一下如何来接收这个固定包长的hex数据包。
首先根据之前的代码我们知道每收到一个字节程序都会进一步中断。在中断函数里我们可以拿到这一个字节但拿到之后我们就得退出中断了。所以每拿到一个数据都是一个独立的过程。而对于数据包来说很明显它具有前后关联性。包头之后是数据数据之后是包尾。对于包头、数据和包尾这三种状态我们都需要有不同的处理逻辑。所以在程序中我们需要设计一个能记住不同状态的机制。
在不同状态执行不同的操作同时还要进行状态的合理转移。这种程序设计思维就叫做‘状态机’。在这里我们就使用状态机的方法来接收一个数据包要想设计一个好的状态机程序画一个这样的状态转移图是必要的。 对于上面这样一个固定包长hex数据包来说我们可以定义三个状态第一个状态是等待包头、第二个状态是接收数据、第三个状态是等待包尾每个状态需要用一个变量来标志一下。比如这里用变量s来标志。三个状态依次为s等于0s等于1s等于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’所以需要第三个状态。如果只有一个包尾在出现包尾之后就可以直接回到初始状态了只需要两个状态就行。因为接收数据和等待包尾需要在一个状态里同时进行。
由于串口的包头包尾不会出现在数据中所以基本不会出现数据错位的现象。这就是使用状态机接收文本数据包的方法。
下节我们就写程序验证一下以上所学的内容。