| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> 嵌入式 -> FreeRTOS串口接收一批数据后卡死bug的解决办法(HAL库发送函数和接收函数不能同时使用) -> 正文阅读 |
|
[嵌入式]FreeRTOS串口接收一批数据后卡死bug的解决办法(HAL库发送函数和接收函数不能同时使用) |
项目背景: keil5+HAL库+FreeRTOS系统+STM32H743,存在两个同等优先级的任务,一个任务通过串口读取数据,一个任务通过串口发送数据,从而与其他设备进行信息的交互。 问题描述:网络调试助手以20HZ频率往单片机串口发数据,每次数据包共11字节。大概成功接收四百次之后,串口直接进不了接收中断。而串口对外发送任务正常进行,说明系统并未卡死。 不想看猜想过程的可以直接滑到最后一个猜想,就是问题的解决方案。 猜想1:和波特率有关系 任务发送数据包一共11字节,即88bit。以20HZ的频率发送到单片机,1760bit/s。而串口通信波特率为115200bit/s。串口的速度还是足够的。 结论:与波特率无关 猜想2:和任务栈空间大小有关系 将任务栈空间增加2倍,问题依然存在。 将任务栈空间增加3倍,问题依然存在。 结论:与任务栈空间无关 猜想3:没有清除读取中断标志位 读取标志位RXNE/RXFNE的定义如下: 从数据手册可以看到,有两种方式可以消除中断标志位。通过对USART_RDR寄存器执行读取操作将该位清零或者向USART-RQR寄存区中的RXFRQ位写入“1”将RXNE标志清零。之前也成功了几百次的传输,说明中断基本流程是无误的。通过《中断分析》可知,USART从接收中断到处理中断,最后会进入到UART_RxISR_8BIT函数内部。
在函数最后有这样一句代码,这是HAL库函数的内容,不需要用户编写。查看其原型,此函数的功能就是将UART特定的请求标志位置1。而 函数第二个参数UART_RXDATA_FLUSH_REQUEST这个宏定义就是上面提到的RXFRQ位。通过向RXFRQ位写入“1”将RXNE标志清零。探索到这里就发现了清除标志位是HAL函数帮忙完成的。 结论:与中断标志位无关。 猜想4:与任务优先级有关 这个猜想是看了《bug解决》博客想到的。先设置三种优先级方案验证: 接收任务优先级为6,发送任务任务为6,就是项目一开始的设置,问题存在; 接收任务优先级为7,发送任务优先级为6,问题依旧存在。 接收任务优先级为5,发送任务优先级为6,问题解决。 FreeRTOS的任务之间具备独立的任务栈空间,而串口通信是全双工通信,理论上是互不影响的。怎么会因为优先级的高低而出现bug?接下来探索一下这个HAL的源码。 在HAL_UART_Receive_IT函数和HAL_UART_Transmit函数的HAL源码都存在这两句代码:
从函数名来看,这是一个锁住/解锁的函数。正是这个罪魁祸首导致了发送和接收两个任务之间的冲突,即发送和接收不能同时使用,硬生生把一个全双工通信变成了半双工通信。源码很好理解,如果没上锁那就上锁,如果上锁了那就返回HAL_BUSY。而Lock是UART串口结构体的成员之一。 ?这个HAL锁资源结构目的就是让一个外设不要被两个地方同时引用。当一个函数在使用huart,此时Lock的状态是HAL_LOCKED,其他函数就不能使用huart,直到使用_HAL_UNLOCK解锁。 但是这个机制很是多余且容易产生bug。下面介绍bug是如何产生的。 正常读取中断的流程是这样的。中断发生会进入HAL_UART_RxCpltCallback回调函数,不管是在回调函数还是在任务,都必须再次调用HAL_UART_Receive_IT函数,目的是重新开启中断。因此在中断处理函数UART_RxISR_8BIT把读取中断关闭了,具体可见《中断分析》。 为什么串口可以正常读取发送几百次后才进入bug?因为bug的发生是需要一定条件的。 我的重新开启中断函数是放在任务的最开始,先开启中断,然后堵塞等待中断回调函数发来的通知。 ?读取任务的优先级高于发送任务优先级的情况 当发送任务中的发送函数运行到上锁和解锁之间的时候,读取中断发生。在读取中断回调函数,会发送通知给读取任务,此时读取任务从堵塞态转为就绪态。 通过portYIELD_FROM_ISR函数,此时读取任务的优先级较高,那就会优先执行读取任务。执行HAL_UART_Receive_IT函数,并通过_HAL_LOCK(huart)上锁。但是因为HAL_UART_Transmit并未完成执行完,此时huart还是上锁状态。所以HAL_UART_Receive_IT函数会返回HAL_BUSY,后续的读取中断自然没有成功开启。而函数也并没有再次开启的机制。接收任务就会一直处于堵塞态等待通知,但是一直等不到。而发送任务重新获得CPU使用权,就可以继续完成解锁,发送数据的功能并没有受到影响。 读取中断发生在上锁前,解锁后都没关系,就是不能发生在中间。 ? 输出HAL_Receive_IT的返回值验证一下,根据定义HAL_OK是0,HAL_BUSY是2. ? ?发送一次5,代表发送任务成功运行一次。发表一次0,代表接收任务成功运行一次。从串口助手输出数据能看到,返回2之后接收任务就卡死了。符合上述我们的猜想。 ?? 读取任务的优先级等于发送任务优先级的情况 前面分析了高于的情况,读取任务会抢占CPU,导致读取中断不能再次开启。那为什么等于的情况下还是会出现bug。portYIELD_FROM_ISR只会在被唤醒的任务高于现有执行的任务优先级,才会完成任务切换。 按照上面的分析,如果不强制切换,那发送任务就可以正常上锁解锁,就不会影响到读取任务的重新开启中断位操作。 这里涉及到FreeRTOS的时间片调度机制。根据FreeRTOS的说明,不同优先级之间,高优先级任务可以抢占低优先级任务的CPU使用权。而同样优先级之间,是使用时间片调度的。 FreeRTOS 与隔壁的 RT-Thread 和 μC/OS 一样,都支持时间片的功能。所谓时间片就是同一个优先级下可以有多个任务,每个任务轮流地享有相同的 CPU 时间,享有 CPU 的 时间我们叫时间片。在 RTOS 中,最小的时间单位为一个 tick,即 SysTick 的中断周期, RT-Thread 和 μC/OS 可以指定时间片的大小为多个 tick,但是 FreeRTOS 不一样,时间片只能是一个 tick。一个tick在本次工程设置就是1ms。 那假设一个tick的时间到了,某个任务没有完成呢?没有完成的话就保存环境变量,切换成下一个同等优先级任务。因为一个tick刚好完成n次任务的可能性不大。所以上述的问题又出现了,当读取回调函数发送通知到读取任务,此时读取任务不会立刻抢占CPU,但是已经是就绪状态,就等着下一个tick开始执行。 假如此时发送函数切换时刚好处于上锁和解锁的中间部分代码,那就回发生读取中断没办法再次开启的bug。 ??发送一次5,代表发送任务成功运行一次。发表一次0,代表接收任务成功运行一次。从串口助手输出数据能看到,返回2之后接收任务就卡死了。符合上述我们的猜想。 解决方案将读取任务的优先级调整至5,低于发送任务。所以读取任务只能乖乖等待发送任务完成一次,进入堵塞状态,才能开始。这样就不会造成上锁返回HAL_BUSY的情况。 |
|
嵌入式 最新文章 |
基于高精度单片机开发红外测温仪方案 |
89C51单片机与DAC0832 |
基于51单片机宠物自动投料喂食器控制系统仿 |
《痞子衡嵌入式半月刊》 第 68 期 |
多思计组实验实验七 简单模型机实验 |
CSC7720 |
启明智显分享| ESP32学习笔记参考--PWM(脉冲 |
STM32初探 |
STM32 总结 |
【STM32】CubeMX例程四---定时器中断(附工 |
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 | -2024/12/30 1:25:41- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |