STM32F40X系列单片机死机有两个主要原因:堆栈溢出或者死循环。
一、堆栈溢出
这种虽然可以通过调整堆栈大小来增加堆栈,堆栈大小并非无限大,当堆栈大小超过一定大小,就会编译出错,这种方法治标不治本。
这种死机常出现在FreeRTOS系统中,Error:..\..\FreeRTOS\portable\RVDS\ARM_CM4F\port.c,766
只有在串口调试助手中可以看到,在编译过程是看不到的。
这种原因主要是优先级分组设置错误,对于这个NVIC,有个重要的知识点就是优先级分组,抢占优先级和子优先级,下面就以STM32为例进行介绍,STM32F1xx和F4xx都是只使用了这个8位寄存器的高四位[7:4]。
优先级分组 | 抢占优先级 | 子优先级 | 高四位使用情况描述 |
---|---|---|---|
NVIC_PriorityGroup_0 | 0级抢占优先级 | 0-15级子优先级 | 0bit用于抢占优先级, 4bit全用于子优先级 |
NVIC_PriorityGroup_1 | 0-1级抢占优先级 | 0-7级子优先级 | 1bit用于抢占优先级, 3bit用于子优先级 |
NVIC_PriorityGroup_2 | 0-3级抢占优先级 | 0-3级子优先级 | 2bit用于抢占优先级, 2bit用于子优先级 |
NVIC_PriorityGroup_3 | 0-7级抢占优先级 | 0-1级子优先级 | 3bit用于抢占优先级, 1bit用于子优先级 |
NVIC_PriorityGroup_4 | 0-15级抢占优先级 | 0级子优先级 | 4bit全用于抢占优先级, 0bit用于子优先级 |
从上面的表格可以看出,STM32支持5种优先级分组,系统上电复位后,默认使用的是优先级分组0,也就是没有抢占式优先级,只有子优先级,关于这个抢占优先级和这个子优先级有几点一定要说清楚。
1. 具有高抢占式优先级的中断可以在具有低抢占式优先级的中断服务程序执行过程中被响应,即中断嵌套,或者说高抢占式优先级的中断可以抢占低抢占式优先级的中断的执行。
2. 在抢占式优先级相同的情况下,有几个子优先级不同的中断同时到来,那么高子优先级的中断优先被响应。
3. 在抢占式优先级相同的情况下,如果有低子优先级中断正在执行,高子优先级的中断要等待已被响应的低子优先级中断执行结束后才能得到响应,即子优先级不支持中断嵌套。
4.Reset、NMI、Hard Fault 优先级为负数,高于普通中断优先级,且优先级不可配置。
5.对于初学者还有一个比较纠结的问题就是系统中断(比如:PendSV,SVC,SysTick)是不是一定比外部中断(比如SPI,USART)要高,答案:不是的,它们是在同一个NVIC下面设置的。
抢占优先级优先级高于子优先级,这里使用NVIC_PriorityGroup_4,全部用于抢占优先级,避免和系统冲突。
当使用xTaskCreate();时,任务函数中没有while(1)循环,也会出现这种问题,任务函数中必须这样写:
void dispose_task(HWND hwnd)
{
uint16_t i=0;
uint32_t timecount=0;
uint8_t ucStatus;
while(1)
{
//任务内容
}
GUI_msleep(10);
}
同时case WM_DESTROY:中也必须有删除任务函数vTaskDelete();,否则打开其它应用还会创建其它任务,原任务还在执行,也会出现多个任务同时执行堆栈溢出死机的情况。
case WM_DESTROY:
{
IsConnect=0;//
vTaskDelete(task_handle);
return PostQuitMessage(hwnd);
}
另:当局部变量中数组定义过大时,也会出现堆栈溢出死机的情况,可以采用定义较小数组或者用全局变量代替局部变量。
二、死循环
当使用串口接收数据时,STM32F40X系列单片机死机就是死循环。主要原因是没有定义接收中断。接收中断函数如下:
void HandDevice_IRQHandler(void)
{
uint8_t ucTemp;
uint8_t Clear = Clear;
if (USART_GetITStatus(_485_USART, USART_IT_RXNE) != RESET)
{
USART_ClearITPendingBit(_485_USART,USART_IT_RXNE); // 清中断标志
//1、从寄存器中读取数据
ucTemp = USART_ReceiveData(_485_USART);
LED2_TOGGLE;
if (ucTemp == 0xFF && buff_count == 0) {
command[buff_count++] = ucTemp;
}
else if (buff_count == 1) {
command[buff_count++] = ucTemp;
command_length = Command_Type_Len[ucTemp] - 1; //通过类型得到指令剩余字节长度
}
else if (buff_count < sizeof(command)) { // 检查数组越界
command[buff_count++] = ucTemp;
if (command_length == 2) { // 指令接收完毕
printf("指令接受完毕!\r\n");
LED2_TOGGLE;
Parse_Command(command);
buff_count = 0;
}
--command_length;
}
else {
// 处理错误:指令长度超出数组长度
buff_count = 0;
command_length = 0;
printf("Command buffer overflow.\n");
}
USART_ClearFlag(_485_USART,USART_FLAG_RXNE);//清除RXNE标志位
USART_ClearITPendingBit(_485_USART,USART_FLAG_RXNE);
//清除RXNE中断标志
}
if(USART_GetFlagStatus(_485_USART, USART_IT_ORE) != RESET)
//需要用USART_GetFlagStatus函数来检查ORE溢出中断
{
USART_ClearFlag(_485_USART,USART_FLAG_ORE);//清除ORE标志位
USART_ReceiveData(_485_USART); //抛弃接收到的数据
}
}
打开RXNEIE,默认会同时打开RXNE和ORE中断。必须第一时间清零RXNE,如没及时清零,下一帧数据过来时就会产生Overrun error!,错误就是ORE导致的,解决办法要清除ORE。
仅仅在这里有中断是不行的,void HandDevice_IRQHandler(void)还需要放到头文件中,头文件中还需要定义这两个常量。
#define HandDevice_USART_IRQ USART2_IRQn
#define HandDevice_USART_IRQHandler USART2_IRQHandler
在stm32f4xx_it.c 中需要声明相应的头文件中,还需要这个函数:
void HandDevice_USART_IRQHandler(void)
{
HandDevice_IRQHandler();
}
---
有的死循环死机虽然不能进行任何操作,但是指针位置会变,这种不是出现在中断中,可能是硬件没有初始化。