FreeRTOS(4)——中断管理
本文最后更新于20 天前,其中的信息可能已经过时,如有错误请发送邮件到big_fw@foxmail.com

FreeRTOS 的中断管理,本质上是在 Cortex-M 的硬件中断机制之上,规定一套“哪些中断可以被内核临时屏蔽、哪些中断可以调用 FreeRTOS API、任务切换相关中断应该放在哪个优先级”的规则。

理解这部分内容时,最容易混淆的是两套优先级:

  • 中断优先级:数值越小,优先级越高。
  • FreeRTOS 任务优先级:数值越大,优先级越高。

这两个概念不是一套东西。中断优先级由 Cortex-M/NVIC 硬件管理,任务优先级由 FreeRTOS 调度器管理。

什么是中断

中断就是 CPU 在正常执行程序时,被外设或系统事件打断,转而去处理更紧急的事件。

一个完整的中断执行过程可以简单分成三步:

  1. 外设产生中断请求,比如 GPIO 外部中断、定时器中断。
  2. CPU 响应中断,暂停当前程序,进入对应的中断服务函数,也就是 ISR。
  3. ISR 执行完毕后退出中断,CPU 回到被打断的位置继续执行。

在裸机程序里,中断通常用于快速响应外设事件;在 FreeRTOS 中,中断仍然由硬件触发,但如果中断中要和任务交互,就必须遵守 FreeRTOS 的中断管理规则。

STM32 的中断优先级

ARM Cortex-M 使用 8 位宽的寄存器配置中断优先级,理论范围是 0~255。STM32 只使用其中的高 4 位 [7:4],所以最多提供 16 个中断优先级,范围是 0~15。

注意:中断优先级数值越小,优先级越高。

例如:

  • 优先级 0 最高。
  • 优先级 15 最低。
  • 优先级 4 可以抢占优先级 6。
  • 优先级 6 不能抢占优先级 4。

STM32 的中断优先级又可以分成两部分:

  • 抢占优先级:决定一个中断能不能打断另一个正在执行的中断。
  • 子优先级:当两个中断同时发生,并且抢占优先级相同时,决定谁先执行。

FreeRTOS 官方建议把所有优先级位都分配给抢占优先级,避免子优先级带来额外复杂度。在 STM32 HAL 中通常这样设置:

HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);

NVIC_PRIORITYGROUP_4 表示 4 bit 都用于抢占优先级,0 bit 用于子优先级。这样 STM32 的 0~15 就都可以直接作为抢占优先级使用。

FreeRTOS 对中断优先级的要求

FreeRTOS 中有一个非常关键的配置项:

#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5

它表示:允许调用 FreeRTOS API 的最高中断优先级。

因为 STM32 中断优先级是“数值越小越高”,所以当该值配置为 5 时,可以这样理解:

  • 优先级 0~4:硬件优先级更高,不受 FreeRTOS 管理,不能调用 FreeRTOS 的 API。
  • 优先级 5~15:处于 FreeRTOS 可管理范围内,可以调用带 FromISR 后缀的 API。

因此,如果某个中断服务函数里需要释放信号量、发送队列、通知任务等,就要满足两个条件:

  1. 该中断的优先级必须在 FreeRTOS 可管理范围内,比如 5~15。
  2. 在 ISR 中调用 FreeRTOS API 时,必须使用带 FromISR 后缀的函数。

例如:

xSemaphoreGiveFromISR();
xQueueSendFromISR();
xTaskNotifyFromISR();

不要在中断服务函数里调用普通任务上下文版本的 API,比如 xSemaphoreGive()xQueueSend()

系统异常优先级寄存器 SHPR

Cortex-M 中有 3 个系统异常优先级配置寄存器:

寄存器地址
SHPR10xE000ED18
SHPR20xE000ED1C
SHPR30xE000ED20

FreeRTOS 任务切换相关的两个系统异常是:

  • PendSV
  • SysTick

FreeRTOS 会把 PendSV 和 SysTick 的中断优先级设置为最低优先级。

这样做的目的很明确:任务切换不能阻塞系统中更重要的外设中断响应。也就是说,调度器可以慢一点执行,但不能影响真正紧急的硬件中断。

BASEPRI:FreeRTOS 中断屏蔽的核心

Cortex-M 中常见的中断屏蔽寄存器有 3 个:

  • PRIMASK
  • FAULTMASK
  • BASEPRI

FreeRTOS 的中断管理主要依赖 BASEPRI

BASEPRI 的作用是屏蔽某个阈值及其以下优先级的中断。这里的“以下”要结合 Cortex-M 的优先级规则理解:数值越大,逻辑优先级越低。

例如,当 BASEPRI = 0x50 时,表示屏蔽优先级 5~15 的中断,而优先级 0~4 的中断仍然可以正常响应。

为什么是 0x50?因为 STM32 只使用中断优先级寄存器的高 4 位:

#define configMAX_SYSCALL_INTERRUPT_PRIORITY \
   (configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS))

当:

#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5
#define configPRIO_BITS 4

那么:

configMAX_SYSCALL_INTERRUPT_PRIORITY = 5 << 4 = 0x50

也就是说,FreeRTOS 关中断时并不是把所有中断都关掉,而是只屏蔽自己能管理的那一部分中断。

FreeRTOS 的关中断和开中断

FreeRTOS 中关闭可管理中断的宏如下:

#define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI()

static portFORCE_INLINE void vPortRaiseBASEPRI(void)
{
   uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;

   __asm
  {
       msr basepri, ulNewBASEPRI
       dsb
       isb
  }
}

这段代码把 BASEPRI 设置为 configMAX_SYSCALL_INTERRUPT_PRIORITY,从而屏蔽 FreeRTOS 可管理范围内的中断。

对应的开中断宏如下:

#define portENABLE_INTERRUPTS() vPortSetBASEPRI(0)

static portFORCE_INLINE void vPortSetBASEPRI(uint32_t ulBASEPRI)
{
   __asm
  {
       msr basepri, ulBASEPRI
  }
}

BASEPRI 被设置为 0 时,就不再屏蔽任何中断。

这里要注意:portDISABLE_INTERRUPTS() 不等于屏蔽全部中断。优先级高于 configMAX_SYSCALL_INTERRUPT_PRIORITY 的中断,比如 0~4,仍然可以响应。

ISR 中调用 FreeRTOS API 的规则

在中断服务函数中使用 FreeRTOS API 时,可以记住三句话:

  1. 中断优先级必须在 FreeRTOS 管理范围内。
  2. ISR 中必须使用带 FromISR 后缀的 API。
  3. 建议优先级分组设置为 NVIC_PRIORITYGROUP_4

configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY = 5 为例:

  • 优先级 4 的中断不能调用 FreeRTOS API,因为它不受 FreeRTOS 管理。
  • 优先级 6 的中断可以调用 FromISR API,因为它在 FreeRTOS 管理范围内。

这也是很多 FreeRTOS 初学者容易踩坑的地方:不是“中断优先级越高越能调用 API”,恰恰相反,优先级太高的中断不能被 FreeRTOS 内核屏蔽,所以也不能安全地调用依赖内核临界区保护的 API。

实验设计

本节实验的目标是观察 FreeRTOS 中断管理的效果。

实验中使用两个定时器中断:

  • 一个定时器中断优先级为 4。
  • 一个定时器中断优先级为 6。

假设系统配置为:

#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5

那么:

  • 优先级 4 高于 FreeRTOS 管理范围,不会被 BASEPRI = 0x50 屏蔽。
  • 优先级 6 位于 FreeRTOS 管理范围内,会被 BASEPRI = 0x50 屏蔽。

实验设计两个任务:

任务作用
start_task创建 task1 任务
task1调用关中断和开中断函数,观察两个定时器中断的打印现象

实验现象是:两个定时器每 1 秒打印一段字符串。当 FreeRTOS 关闭可管理中断时,优先级 6 的定时器中断会停止响应,而优先级 4 的定时器中断仍然可以响应;当重新开中断后,被管理的中断继续恢复。

通过这个实验可以直观看到:FreeRTOS 的“关中断”并不是全局关闭所有中断,而是通过 BASEPRI 屏蔽一部分低逻辑优先级的中断。

Github:Hui404/FreeRTOS_4

小结

FreeRTOS 中断管理可以归纳为以下几点:

  1. STM32 中断优先级使用高 4 位,所以通常有 0~15 共 16 级。
  2. 中断优先级数值越小越高,任务优先级数值越大越高。
  3. 建议使用 NVIC_PRIORITYGROUP_4,把所有优先级位都作为抢占优先级。
  4. PendSV 和 SysTick 通常被设置为最低优先级,避免任务切换影响外设中断响应。
  5. FreeRTOS 使用 BASEPRI 实现中断屏蔽。
  6. BASEPRI = 0x50 时,通常表示屏蔽优先级 5~15,优先级 0~4 仍可响应。
  7. ISR 中调用 FreeRTOS API 时,中断优先级必须在可管理范围内,并且必须使用 FromISR 后缀函数。

掌握这几个规则后,再看 FreeRTOS 的临界区、任务切换、队列和信号量的中断用法,就会清晰很多。

参考资料

FreeRTOS 官方 Cortex-M3/M4 说明:https://www.freertos.org/RTOS-Cortex-M3-M4.html

《Cortex-M3 权威指南》

文末附加内容
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇