前言

在使用STM32Cubemx创建工程,使用正点原子的外部中断历程使出现程序卡死的问题,这里对这一问题进行总结。

STM32Cubemx配置

首先对STM32进行一系类配置,包括时钟源、时钟树、debug、HAL时基(不要选择滴答定时器,滴答定时器留给FreeRTOS作为系统时钟)等,配置完成后根据原理图对GPIO以及外部中断触发进行配置。

1

根据原理图,将PB5、PE5设置成推挽输出,不使用上下拉电阻,将PE3、PE4配置成GPIO中断,下降沿触发,上拉电阻,将PA0配置成上升沿触发、下拉电阻

1

接着在NVIC进行中断分组、优先级设置

1

移植驱动

这里我们添加正点原子的EXTI外部中断驱动,LED,KEY。

1

由于我们没有使用官方的SYSTEM文件,所以需要对程序以及头文件进行修改。
删除SYSTEM的头文件包含
1

引入FreeRTOS的相关头文件,将驱动文件里的所有延时换成FreeRTOS的延时vTaskdelay

1

写代码测试移植是否成功。

开始任务中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
void StartDefaultTask(void *argument)
{
/* USER CODE BEGIN StartDefaultTask */
/* Infinite loop */
for(;;)
{
LED1(1); /* LED1 灭 */
vTaskDelay(500);
LED1(0); /* LED1 亮 */
vTaskDelay(500);
}
/* USER CODE END StartDefaultTask */
}
```

中断回调函数
```C
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
vTaskDelay(20); /* 消抖 */
switch(GPIO_Pin)
{
case KEY0_INT_GPIO_PIN:
if (KEY0 == 0)
{
LED0_TOGGLE(); /* LED0 状态取反 */
}
break;
case KEY1_INT_GPIO_PIN:
if (KEY1 == 0)
{
LED0_TOGGLE(); /* LED0 状态取反 */
}
break;
case WKUP_INT_GPIO_PIN:
if (WK_UP == 1)
{
LED0(0); /* LEDO亮 */
}
break;
}
portYIELD_FROM_ISR(pdTRUE);
}

烧录后出现问题,开始任务的LED灯运行,当中断触发时,程序发生卡死。

问题的原因

使用debug进行调试,在中断回调函数打断点

1

接着单步运行,最终发现程序卡在这

1
这个函数是关中断,但是没有返回值,也就是不管当前中断的状态,因此这个函数是不能在中断中调用的。

问题的解决

这个问题其实在韦东山老师的课程中说到过:
ISR是在内核中被调用的,ISR执行过程中,用户的任务无法执行。ISR要尽量快,否则:

  • 其他低优先级的中断无法被处理:实时性无法保证
  • 用户任务无法被执行:系统显得很卡顿

对于这类非常耗时的中断处理就要分为2部分:

  • ISR:尽快做些清理、记录工作,然后触发某个任务
  • 任务:更复杂的事情放在任务中处理

这种处理方式叫”中断的延迟处理”(Deferring interrupt processing)

  • t1:任务1运行,任务2阻塞
  • t2:发生中断
    • 该中断的ISR函数被执行,任务1被打断
    • ISR函数要尽快能快速地运行,它做一些必要的操作(比如清除中断),然后唤醒任务2
  • t3:在创建任务时设置任务2的优先级比任务1高(这取决于设计者),所以ISR返回后,运行的是任务2,它要完成中断的处理。任务2就被称为”deferred processing task”,中断的延迟处理任务。
  • t4:任务2处理完中断后,进入阻塞态以等待下一个中断,任务1重新运行

1

  • 解决方法1:使用死延时
  • 解决方法2:不用延时
  • 解决方法3:在中断里设置事件组,通过事件组标志位唤醒任务