前言

在使用 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

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

开始任务中

c
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:在中断里设置事件组,通过事件组标志位唤醒任务