0
收藏
微博
微信
复制链接

【STM32 cubemx】0005 HAL库开发:uart串口中断和fifo环形队列

2024-02-29 14:59
980

上一节我们学习了串口的轮询收发,以及HAL库自带的中断收发。也分析了优缺点,这一节我们来讲讲如何改写HAL库函数,使它能更高效地收发数据,更方便地使用。

早些年在使用51单片机、AVR单片机的时候,最有效的办法是开两个环形缓冲区,一个用作发送、一个用作接收。

接收的实现:开启接收数据中断;当收到数据产生中断时,在中断服务程序中,把收到的数据放入接收环形fifo中;主循环中检测接收fifo中有无数据,有的话取走使用即可。

发送的实现:当要发送数据时,先检查有无数据在发送(一般是查询发送寄存器的状态),如果不在发送,则把数据放入发送寄存器发送出去;如果正在发送,则把数据放入发送环形fifo中;另一方面,开启发送完中断,当发送完一个字节数据时,则进入中断,在中断服务程序中从发送fifo中取一个数据放入发送寄存器发送出去;如果fifo中为空,则表示已发完。

这套方法,可以使得cpu在处理接收数据时,不用担心数据处理不及时而丢失,因为有fifo作为缓冲;另一方面,在发送时,只用消耗往fifo中搬移数据的时间,之后中断会将数据发完,这样cpu可以去处理其他事情,而不用等到数据发完。

STM32同样可以这样使用,本节我们也是套用这个方法,去实现中断发送和接收。同时兼顾提高效率,尽量少写代码、复用HAL库函数。


1)cubemx生成代码

首先还是在cubemx中生成代码,选择器件、设置SYS(调试接口)、设置RCC(外部晶振时钟源)。

然后设置串口引脚,选择uart1,异步串口,选完后,已经使用的串口引脚PA9和PA10会变成绿色;然后选择开启串口全局中断:

5bd47d1be24f94960d6a28a1bba44a.jpg

之后,在时钟选项卡设置主时钟为72M;在project Manager选项卡设置工程名和路径,生成工程代码。


2)程序的实现讲解

(完整代码可以关注我的公众号留言索取,完全免费。这里受限于篇幅无法贴全。)

这里的FIFO实现,我使用的是一套开源的代码KFIFO,这套代码是linux内核的代码,非常简洁而且效率很高,唯一的小缺点是fifo长度必须是2的n次方。Fifo的代码很通用,这里就不细讲,主要讲解如何与串口中断结合使用。

函数都在myUart.c中实现,我们着重分析一下其中最主要的一个函数,也就是中断服务函数,这个函数包含了接收、发送、以及错误处理:

df05f4e754631fbe04dbf6add961ea.jpg

这里有两块最主要的处理部分,接收和发送。

接收的部分简单一些,我们先讲讲接收部分。就是图中注释Receiver下方的几行,这部分实现的是:先判断有接收中断产生,然后将收到的数存放到接收fifo中。

发送部分较为复杂一点,就是图中Transmitter下方的几行,也是先判断是否有发送完中断,如果有,就从发送fifo中取一个数,放到发送寄存器中。

最终调用的函数是这么实现的,如果fifo中有数则取一个放入发送寄存器中,无数则表示已发送完毕,关闭发送完中断。

6fcf483a4f9358bd6a3fece193a544.jpg

发送过程还需要应用函数的配合,如下是发送一个字节的函数:

7d1b7ff3ed57cd906d2c04db956c4f.jpg

这个函数是供用户调用的应用函数。它的作用是发送一个字节,实现过程是:先打开发送完中断,然后判断是否有数据还未发完,如果有数未发完,则将数据写入fifo中,等待后续发送;如果没有数在发送,则直接将这个字节的数据写入发送寄存器,系统会自动发送出去。

整个发送数据的起始,是由应用函数发起的,它会向发送寄存器写入第一个待发送的数据;这个数据发送完之后,会触发发送完中断,在中断服务程序中,从fifo中取数发送,发送完下一个字节又会触发下一次中断,直到fifo中的数都被取完、发完。


3)代码移植和实例

先将KFIFO.c和myUart.c代码和对应的*.h文件拷贝到工程下,添加到工程中;

在usart.c文件的HAL_UART_MspInit函数中,添加如下代码,作用是打开接收、发送、以及错误检测中断:(这部分代码是HAL库里实现的,可以方便地设置中断)

a05795e82e10aba8064ba6cc05c70a.jpg

中断服务程序中,如下编写,用条件编译,将原来的中断服务函数去除掉(这样代码写在user代码区,重新生成代码时也不会被cubemx删除),添加自己的中断服务函数:

a65a56ff2f8db17a311fd2dd2ba631.jpg

在main函数中,串口初始化之前,调用一下fifo初始化的函数:

02efe42ef26a41f06e57e485c90364.jpg

接下来就可以愉快地使用了。

如下图是一个例子,在主循环中判断有无收到数据,如果有收到,则将数据原样返回:

32f8200d1913efee69ad91d4fd0ed4.jpg

这里特地使用了一个5000ms的延时,以便于观察串口在收到一长串数据时的处理。

进入debug模式,设置断点,在串口调试助手中发送0123456789abcdef,断点停止如下,这里模拟的是延时5s期间,收到了一串数据,中断接收后放在了接收缓冲区中:

08ec6024abaf92abec3a19df4307a9.jpg

继续向下运行的话,把断点设置在延时处,使其可以运行到跳出内部的while,可以看到:

186dd09797122448ba6b6a87c633d7.jpg

发送fifo中已经被填入了数据,由于最早需要发送的0和1是可以被填入发送寄存器直接发送的,所以被发送出来了;后面的2~f,由于0和1还未发送完,则被填入了发送fifo中;而我们的断点设置在紧接着填充完数据之后,所以2以及之后的数据,都还没有被发出来就被断点强制停下了。

此时如果解除断点,向后运行,可以看到后面的数据都被中断自动发送出来了:

78e10a521a76669dbaf29814bbd9e4.jpg

这个方法使用时,也要注意及时取走数据,保证fifo不溢出;或者ram够用的话,可以开大fifo。

我们在HAL库的基础上,改造成了中断与fifo结合的形式,主要是为了两个目的:一是方便使用,使用中断加fifo可以跟方便接收和发送数据,不用考虑多次调用发送函数而等待,也不用考虑接收数据不及时;二是提高效率,HAL库的代码写得很完善,但是冗余操作太多了,需要取一个折中。

登录后查看更多
0
评论 0
收藏
侵权举报
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表凡亿课堂立场。文章及其配图仅供工程师学习之用,如有内容图片侵权或者其他问题,请联系本站作侵删。

热门评论0

相关文章

小白白学电子

专注电子电路、嵌入式软硬件、FPGA,学习和分享

开班信息