摘要:串口是通信中最常用的通信方式,可能写串口的驱动,能写几十种方法, 查询方式,中断方式,DMA方式,定时器方式。可能也其中几种方式的组合形式,经典的用法是:发送用查询方式,接收用中断方式,或者DMA+空闲中断。本篇不讲串口是啥,现在还在讲串口是啥,估计会被喷。今天来聊一聊串口常用的几种方式,最简单的方法就不说了。
一、经典方法
查询方式可靠性很高,要考虑下个数据包覆盖上一个数据包的问题,小数据量,在10个字节以内,可以这样考虑, 很简单,很方便,很可靠。但是在数据量大的时候,程序阻塞的时间特别长,影响其他比较重要的外设的处理。
中断方式中断方式 , 不占用系统资源,但是如果数据量大,会频繁中断cpu, 会其他高优先的数据处理造成影响。但是没有DMA不占用资源的好处, 如果没有串口队列的实现,必须通过标志位判断上一个包数据是否发送完成,在把新的数据覆盖到串口的缓冲区。
DMA方式优点: 不占用系统资源,减少CPU对中断的响应。如何不建立数据包的队列,还是会出现,需要等待阻塞的问题。
二、环形队列
队列这个词在数据局结构中出现的比较多,与之对应的就是堆栈,但是两者的读取方式又完全不同。
FIFO 是First-In First-Out的缩写,它是一个具有先入先出特点的缓冲区。串口设计FIFO的目的是为了提高串口的通讯性能。如果没有FIFO或者说缓冲区的长度只有1字节,那么使用接收中断,就意味着每次收到一个字节的数据就要进一次中断,这样频繁进中断会占用CPU资源。另外如果没有及时读走数据,那么下一个字节数据就会覆盖之前的数据,导致数据丢失,这在通讯速率高的场合很有可能出现。
使用FIFO,可以在连续接收若干个数据后才产生一次中断,然后一起进行处理。这样可以提高接收效率,避免频繁进中断,适用于大数据传输。你可能会想到如果FIFO中的数据没有达到指定长度而无法产生中断怎么办,通常MCU会有接收超时中断,即在一定的时间内没有接收到数据会进入中断,可以利用这个中断把不足FIFO长度的数据最后都读取完。
FIFO类似售票排队窗口,先到的人看到能先买到票,然后先走,后来的人只能后买到票。
在计算机中,每个信息都是存储在存储单元中的,当有大量数据的时候,我们不能存储所有的数据,那么计算机处理数据的时候,只能先处理先来的,那么处理完后呢,就会把数据释放掉,再处理下一个。那么,已经处理的数据的内存就会被浪费掉。因为后来的数据只能往后排队,如过要将剩余的数据都往前移动一次,那么效率就会低下了,肯定不现实,所以,环形队列就出现了。
点击下方视频动态演示出队入队
1、环形队列的实现
在计算机中,是没有环形的内存的,只不过是我们将顺序的内存处理过,让某一段内存形成环形,使他们首尾相连,简单来说,这其实就是一个数组,只不过有两个指针,一个指向列队头,一个指向列队尾。指向列队头的指针是缓冲区可读的数据,指向列队尾的指针是缓冲区可写的数据,通过移动这两个指针即可对缓冲区的数据进行读写操作了,直到缓冲区已满(头尾相接),将数据处理完,可以释放掉数据,又可以进行存储新的数据了。
实现的原理:
视频来自正在一名考研的UP主:秃头少女王某人。计算机专业考研这个是必考点,视频讲的很棒,祝她一战成硕,金榜题名!
串口环形缓冲区收发:在初学单片机的时候我们知道的串口收发都是:接收一个数据,触发中断,然后把数据发回来。这种处理方式是没有缓冲的,当数量太大的时候,亦或者当数据接收太快的时候,我们来不及处理已经收到的数据,那么,当再次收到数据的时候,就会将之前还未处理的数据覆盖掉。那么就会出现丢包的现象了,对我们的程序是一个致命的创伤。
那么如何避免这种情况的发生呢,很显然,上面说的一些队列的特性很容易帮我们实现我们需要的情况。将接受的数据缓存一下,让处理的速度有些许缓冲,使得处理的速度赶得上接收的速度,上面又已经分析了普通队列与环形队列的优劣了,那么我们肯定是用环形队列来进行实现了。下面就是代码的实现:
2、定义一个结构体
3、初始化队列
初始化结构体相关信息:使得我们的环形缓冲区是头尾相连的,并且里面没有数据,也就是空的队列,所有元素清0。
4、数据压入队列
5、从队列中读出数据
对于读写操作需要注意的地方有两个:
“
1:判断队列是否为空或者满,如果空的话,是不允许读取数据的,返回0。如果是满的话,也是不允许写入数据的,避免将已有数据覆盖掉。那么如果处理的速度赶不上接收的速度,可以适当增大缓冲区的大小,用空间换取时间。2:防止指针越界非法访问,程序有说明,需要使用者对整个缓冲区的大小进行把握。
”
四、环形缓冲器
环形缓冲器(ringr buffer),也称作圆形队列(circular queue),循环缓冲区(cyclic buffer),圆形缓冲区(circula buffer),是一种用于表示一个固定尺寸、头尾相连的缓冲区的数据结构,适合缓存数据流。
圆形缓冲区的一个有用特性是:当一个数据元素被用掉后,其余数据元素不需要移动其存储位置。相反,一个非圆形缓冲区(例如一个普通的队列)在用掉一个数据元素后,其余数据元素需要向前搬移。换句话说,圆形缓冲区适合实现先进先出缓冲区,而非圆形缓冲区适合后进先出缓冲区。
那么如何将环形缓冲器ringr buffer应用到串口上面呢?这里我们使用的源码。
1、定义一个结构体
2、初始化ringbuffer
3、将数据压入ringbuffer
4、从ringbuffer中读数据
5、移植ringbuffer
1.首先将RT-Thread的ringbuffer.c和ringbuffer.h文件加入到我们的MDK工程中去。
2.编写串口相关的底层硬件bsp代码,也就是初始化GPIO和串口相关的配置,这个就很简单,大家应该都会。在串口初始化代码中记得要手动将串口的非空中断和空闲中断打开。
3.定义一个结构头rt_ringbuffer 类型的变量ring_buf,变量名随便取,阿猫阿狗都可以,只要你自己认得就行。
4.定义一个串口接收缓冲区数组,数组名随便取,阿猫阿狗都可以,只要你自己认得就行。
5.初始化ringbuffer
位置放在哪里都可以,我这里就放在串口串口初始化之前了。
6.编写中断服务函数
这里面的代码我写的应该很简单了,首先我们在初始化中是能了接收中断和空闲中断,那么如果有数据过来,就会触发中断。进入中断服务函数,进来之后首先判断接收中断标志位是否置位为1,如果是1说明收数据来了,通过函数将数据存入中,再通过将数据压入队列之中。如果数据接收完了就会触发空闲中断,这时通过函数将数据读出到我们定义的数组中打印出来。
7.主函数
主函数啥都不要写,完了。
五、队列FIFO
队列 (Queue):是一种先进先出(First In First Out ,简称 FIFO)的线性表,只允许在一端插入,在另一端进行删除。
FIFO一般用于不同时钟域之间的数据传输,比如FIFO的一端是AD数据采集,另一端是计算机的PCI总线,假设其AD采集的速率为16位 100K SPS,那么每秒的数据量为100K×16bit=1.6Mbps,而PCI总线的速度为33MHz,总线宽度32bit,其最大传输速率为1056Mbps,在两个不同的时钟域间就可以采用FIFO来作为数据缓冲。另外对于不同宽度的数据接口也可以用FIFO,例如单片机位8位数据输出,而DSP可能是16位数据输入,在单片机与DSP连接时就可以使用FIFO来达到数据匹配的目的。
1、定义一个结构体
2、初始化FIFO
3、初始化串口
4、将数据压入FIFO
什么时候要将数据压入FIFO?当然是在向串口发送数据的时候将数据压入FIFO去啦,这里面的逻辑也很简单就是将要发送的数据压入FIFO缓冲区,之后打开发送中断就可以了。
如果要发送的数据没有超过发送缓冲区大小,实现起来还比较容易,直接把数据填到 FIFO 里面,并使能发送空中断即可。如果超过了 FIFO 大小,就需要等待有空间可用,针对这种情况有个重要的知识点,就是当缓冲刚刚填满的时候要判断发送空中断是否开启了,如果填满了还没有开启,就会卡死在 while 循环中,所以多了一个刚填满时的判断,填满了还没有开启发送空中断,要开启下。
5、从FIFO中读出数据
什么时候要从FIFO中读出?当然是在从串口获取数据的时候将从FIFO中读数据啦。
6、中断服务函数
7、主函数
8、关于串口扫盲
串口扫盲就是几个状态标志位不好理解,只要理解好这张图就好办了,其他的请参考相关的教程。
当发送数据寄存器里的数据被全部取完时,该寄存器是空的,那么该标志位就会被置1。通过这个标志位的值可以判断发送数据寄存器中的数据有没有完全被取走,当该寄存器是空的时候,可以提醒CPU继续往该寄存器里存入新的数据;
当发送移位寄存器里的每个字节通过TX脚一位一位发送出去之后,该标志位值就会被置1。通过这个标志位的值可以判断发送移位寄存器里的数据有没有被全部发送出去;
和之间的联系结合上面流程图来进行说明,实际上发送移位寄存器通过TX脚发送数据这个过程是比较耗时的,所以在此过程进行时,可通过判断当,即发送数据寄存器里的数据已被全部转入发送移位寄存器时,就让CPU往发送数据寄存器转入新的数据。当发送移位寄存器把数据帧全部发送出去之后,可通过判断,证明数据帧的最后一个字节都已经通过TX脚发送完了。