问题描述
控制系统使用的是STM32F4+UCOSII 抢占型内核,最近一段时间出现了程序跑一段时间之后操作系统直接死掉的问题,表现为:操作系统中设有优先级很低的呼吸灯任务,只要操作系统在正常工作,呼吸灯就会不停的跳动,但是当出现问题时,呼吸灯停止跳动,控制底盘运动的任务也死掉,底盘处于失控状态,LCD所在的任务也死掉,不再进行刷新,推测为所有的操作系统的任务均死掉,不能正常工作,但是中断仍然可以响应,写在定时器中断中的急停操作是可以执行的。
问题分析
怀疑是进入了一些错误中断
怀疑是指针或者堆栈出了问题
怀疑最高优先级的任务中存在死循环
怀疑操作系统中存在一些bug
怀疑是中断引起的程序一直在中断中,不能进入正常任务
解决方案
对错误中断进行排查分析,程序中开启的错误中断响应只有HardFault,在进入HardFault的中断服务函数后会进行相应的处理
/* *
* @brief This function handles Hard Fault exception.
* @param None
* @retval None
*/
void HardFault_Handler(void)
{
/* Go to infinite loop when Hard Fault exception occurs */
while (1)
{
BEEP_TOGGLE;
Delay_ms(500);
}
}
因此,从表现上看基本可以排除进入Hard Fault的可能,除此之外,Hard Fault的中断优先级为 -1,如果程序是死在HardFault中,那么其他的中断响应应该也是无法响应的,这与问题的表现,中断仍然可以响应矛盾,因此可以断定,不是进入了HardFault和其他的一些问题中断。
关于指针和堆栈的问题,一般有问题会直接进Hard Fault,而此时基本已经排除了Hard Fault的问题,除此之外也尝试扩大了硬件和操作系统的堆栈,问题没有解决,确定不是这里有问题。
关于操作系统的Bug在网上查了不少,早期的操作系统确实存在问题但是系统中使用的2.92版本应该是不存在此类Bug的。
总结
上述对问题的解释其实有点牵强,因为大多数时候发送和接收都是这样进行处理的,但是都没有出现问题,而且DMA的使用也使用了很长时间也没有出现过问题,并且就算出问题也是随机的,并不是每次都出问题,所以怀疑出现问题应该是跟各个中断之间的优先级、响应时间、以及具体的时序有一些复杂的关系,具体是什么关系就很难寻找了,推荐的做法就是防范于未然,将不用的中断统统关闭,这样总是没有坏处的。
后续:问题的解决
原因
造成该问题的原因是程序进入USART中断,但触发中断的标志位没有被清除,一直循环触发中断,程序卡在中断中出不来
当使用
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE)
使能串口接收中断时,实际上 USART_CR1 寄存器中 RXNEIE 被置 1
但是当RXNEIE = 1时 同时还启动了串口的 ORE 中断
ORE:上溢错误 (Overrun error)
在 RXNE = 1 的情况下,当移位寄存器中当前正在接收的字准备好传输到 RDR 寄存器时,该
位由硬件置 1。如果 USART_CR1 寄存器中 RXNEIE = 1,则会生成中断。该位由软件序列清
零(读入 USART_SR 寄存器,然后读入 USART_DR 寄存器)。
0:无上溢错误
1:检测到上溢错误
注意: 当该位置 1 时, RDR 寄存器的内容不会丢失,但移位寄存器会被覆盖。如果 EIE 位置 1,
则在进行多缓冲区通信时会对 ORE 标志生成一个中断。
当发生上溢错误时程序会响应串口中断进入到如下的中断函数中
void USART1_IRQHandler(void)
{
u8 temp;
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
{
USART_ClearFlag(USART1, USART_FLAG_RXNE); //USART_FLAG_RXNE
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
temp = USART_ReceiveData(USART1);
}
}
但是由于此时触发的是ORE中断,中断函数中的内容并不能满足ORE状态标志位被清除的条件:
读入 USART_SR 寄存器,然后读入 USART_DR 寄存器
因此该标志位始终存在,无法被清除,串口中断一直被触发
程序卡死
解决方案
中断函数中加入如下内容
void USART1_IRQHandler(void)
{
u8 temp;
if(USART_GetITStatus(USART1, USART_IT_ORE_RX) != RESET) //读入USART_SR
{
USART_ReceiveData(USART1); //读入USART_DR
}
else if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
{
USART_ClearFlag(USART1, USART_FLAG_RXNE);
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
temp = USART_ReceiveData(USART1);
}
}
其中
if(USART_GetITStatus(USART1, USART_IT_ORE_RX) != RESET) //读入USART_SR
{
USART_ReceiveData(USART1); //读入USART_DR
}
满足了ORE被清除的条件
读入 USART_SR 寄存器,然后读入 USART_DR 寄存器
特别注意
根据参考手册中显示
ORE:上溢错误 (Overrun error)
在 RXNE = 1 的情况下,当移位寄存器中当前正在接收的字准备好传输到 RDR 寄存器时,该
位由硬件置 1。如果 USART_CR1 寄存器中 RXNEIE = 1,则会生成中断。该位由软件序列清
零(读入 USART_SR 寄存器,然后读入 USART_DR 寄存器)。
0:无上溢错误
1:检测到上溢错误
注意: 当该位置 1 时, RDR 寄存器的内容不会丢失,但移位寄存器会被覆盖。如果 EIE 位置 1,
则在进行多缓冲区通信时会对 ORE 标志生成一个中断。
ORE上溢错误产生中断有两种方式,其中之一是由于普通模式下一个字节尚未处理完,又接收到一个字节。另外一种方式是在开启了错误中断标志位EIE时,在使用DMA进行数据传输时若发生上溢则会产生中断
EIE:错误中断使能 (Error interrupt enable)
对于多缓冲区通信( USART_CR3 寄存器中 DMAR = 1),如果发生帧错误、上溢错误或出
现噪声标志( USART_SR 寄存器中 FE = 1 或 ORE = 1 或 NF = 1),则需要使用错误中断
使能位来使能中断生成。
0:禁止中断
1:当 USART_CR3 寄存器中的 DMAR = 1 并且 USART_SR 寄存器中的 FE = 1 或 ORE = 1
或 NF = 1 时,将生成中断。
与此相对应的,库函数中的中断定义也有两种
/** @defgroup USART_Interrupt_definition
* @{
*/
#define USART_IT_PE ((uint16_t)0x0028)
#define USART_IT_TXE ((uint16_t)0x0727)
#define USART_IT_TC ((uint16_t)0x0626)
#define USART_IT_RXNE ((uint16_t)0x0525)
#define USART_IT_ORE_RX ((uint16_t)0x0325) /* In case interrupt is generated if the RXNEIE bit is set */
#define USART_IT_IDLE ((uint16_t)0x0424)
#define USART_IT_LBD ((uint16_t)0x0846)
#define USART_IT_CTS ((uint16_t)0x096A)
#define USART_IT_ERR ((uint16_t)0x0060)
#define USART_IT_ORE_ER ((uint16_t)0x0360) /* In case interrupt is generated if the EIE bit is set */
#define USART_IT_NE ((uint16_t)0x0260)
#define USART_IT_FE ((uint16_t)0x0160)
/** @defgroup USART_Legacy
* @{
*/
#define USART_IT_ORE USART_IT_ORE_ER
如果在进行中断标志位的查询时使用
if(USART_GetITStatus(USART1, USART_IT_ORE) != RESET)
其实只查询了打开EIE情况下的溢出中断标志位
#define USART_IT_ORE_ER ((uint16_t)0x0360) /* In case interrupt is generated if the EIE bit is set */
而如果没有打开EIE的话,当然是查不到RXNEIE
#define USART_IT_ORE_RX ((uint16_t)0x0325) /* In case interrupt is generated if the RXNEIE bit is set */
所对应的中断标志的
这也是有不少博客中说存在Bug,查询不到中断标志的原因。
因此如果是打开了EIE产生了溢出中断需要在中断服务函数中添加的是
if(USART_GetITStatus(USART1, USART_IT_ORE) != RESET) //读入USART_SR
{
USART_ReceiveData(USART1); //读入USART_DR
}
或者
if(USART_GetITStatus(USART1, USART_IT_ORE_ER ) != RESET) //读入USART_SR
{
USART_ReceiveData(USART1); //读入USART_DR
}
而如果没有打开EIE,则需要添加的是
if(USART_GetITStatus(USART1, USART_IT_ORE_RX) != RESET) //读入USART_SR
{
USART_ReceiveData(USART1); //读入USART_DR
}