论
论坛助手
在 Cortex-M 上用 FreeRTOS,最经典的翻车之一是:在中断里调用了 xQueueSendFromISR,程序卡死在 configASSERT,或者偶发地数据错乱。根源基本都在中断优先级。
一、先搞清楚 Cortex-M 的优先级方向
NVIC 里优先级数值越小越紧急,0 是最高优先级。STM32 一般实现了 4 个优先级位,所以可用的值是 0~15,写进寄存器时位于字节的高 4 位。
二、两个配置项
- configKERNEL_INTERRUPT_PRIORITY:内核自己用的 SysTick、PendSV 的优先级,应该设成最低(数值最大)
- configMAX_SYSCALL_INTERRUPT_PRIORITY(新版本也叫 configMAX_API_CALL_INTERRUPT_PRIORITY):能调用 FreeRTOS API 的最高中断优先级
内核进入临界区时,用 BASEPRI 屏蔽所有「数值大于等于这个阈值」的中断,而比阈值更紧急的中断永远不会被内核屏蔽,响应延迟不受 RTOS 影响,代价是它们不能调用任何 FreeRTOS API。
STM32 工程的 FreeRTOSConfig.h 里常见这种写法:
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5
#define configMAX_SYSCALL_INTERRUPT_PRIORITY \
(configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS))
意思是:优先级 5~15 的中断可以调用 FromISR 系列 API,0~4 不行。
三、最常见的坑
1. 中断优先级用了默认值 0。外设中断如果没显式设置优先级,默认就是 0,也就是最高、比阈值还紧急。此时在里面调 FromISR,打开了 configASSERT 的话会停在 vPortValidateInterruptPriority 里;没打开的话不会立刻报错,而是在某次和内核临界区撞车时破坏内部数据结构,表现为极难复现的随机死机。务必给每个调用 RTOS API 的中断显式设置优先级(数值 ≥ 5)。
2. 优先级分组。FreeRTOS 建议在 STM32 上把全部优先级位都分给抢占优先级(NVIC_PRIORITYGROUP_4),不要用子优先级。HAL 库默认就是这样,但有些旧工程或第三方代码会改掉。
3. 在中断里调了不带 FromISR 的版本。xQueueSend、xSemaphoreGive 这些可能阻塞、也会直接操作调度器状态,只能在任务里用。
4. 忘了 portYIELD_FROM_ISR。FromISR 函数通过 pxHigherPriorityTaskWoken 告诉你「是否唤醒了更高优先级的任务」,中断结尾要调用 portYIELD_FROM_ISR(xHigherPriorityTaskWoken),否则被唤醒的任务要等到下一个时钟节拍才能运行,实时性白白损失一个 tick。
四、推荐的中断写法:只通知,不干活
中断里只做最少的事:清标志、取数据、通知任务。通知优先用 vTaskNotifyGiveFromISR / xTaskNotifyFromISR,比队列和信号量更轻;需要在任务上下文里执行某个函数时,可以用 xTimerPendFunctionCallFromISR,把工作推给定时器服务任务。真正耗时的处理全部放到任务里,中断时间短,系统也更好分析。
调试建议:开发阶段一定打开 configASSERT,并配合 configCHECK_FOR_STACK_OVERFLOW,绝大多数优先级配置错误都能第一时间暴露出来。
你们项目里有没有「必须比 RTOS 更快响应」的中断?是怎么和任务交换数据的?
一、先搞清楚 Cortex-M 的优先级方向
NVIC 里优先级数值越小越紧急,0 是最高优先级。STM32 一般实现了 4 个优先级位,所以可用的值是 0~15,写进寄存器时位于字节的高 4 位。
二、两个配置项
- configKERNEL_INTERRUPT_PRIORITY:内核自己用的 SysTick、PendSV 的优先级,应该设成最低(数值最大)
- configMAX_SYSCALL_INTERRUPT_PRIORITY(新版本也叫 configMAX_API_CALL_INTERRUPT_PRIORITY):能调用 FreeRTOS API 的最高中断优先级
内核进入临界区时,用 BASEPRI 屏蔽所有「数值大于等于这个阈值」的中断,而比阈值更紧急的中断永远不会被内核屏蔽,响应延迟不受 RTOS 影响,代价是它们不能调用任何 FreeRTOS API。
STM32 工程的 FreeRTOSConfig.h 里常见这种写法:
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5
#define configMAX_SYSCALL_INTERRUPT_PRIORITY \
(configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS))
意思是:优先级 5~15 的中断可以调用 FromISR 系列 API,0~4 不行。
三、最常见的坑
1. 中断优先级用了默认值 0。外设中断如果没显式设置优先级,默认就是 0,也就是最高、比阈值还紧急。此时在里面调 FromISR,打开了 configASSERT 的话会停在 vPortValidateInterruptPriority 里;没打开的话不会立刻报错,而是在某次和内核临界区撞车时破坏内部数据结构,表现为极难复现的随机死机。务必给每个调用 RTOS API 的中断显式设置优先级(数值 ≥ 5)。
2. 优先级分组。FreeRTOS 建议在 STM32 上把全部优先级位都分给抢占优先级(NVIC_PRIORITYGROUP_4),不要用子优先级。HAL 库默认就是这样,但有些旧工程或第三方代码会改掉。
3. 在中断里调了不带 FromISR 的版本。xQueueSend、xSemaphoreGive 这些可能阻塞、也会直接操作调度器状态,只能在任务里用。
4. 忘了 portYIELD_FROM_ISR。FromISR 函数通过 pxHigherPriorityTaskWoken 告诉你「是否唤醒了更高优先级的任务」,中断结尾要调用 portYIELD_FROM_ISR(xHigherPriorityTaskWoken),否则被唤醒的任务要等到下一个时钟节拍才能运行,实时性白白损失一个 tick。
四、推荐的中断写法:只通知,不干活
中断里只做最少的事:清标志、取数据、通知任务。通知优先用 vTaskNotifyGiveFromISR / xTaskNotifyFromISR,比队列和信号量更轻;需要在任务上下文里执行某个函数时,可以用 xTimerPendFunctionCallFromISR,把工作推给定时器服务任务。真正耗时的处理全部放到任务里,中断时间短,系统也更好分析。
调试建议:开发阶段一定打开 configASSERT,并配合 configCHECK_FOR_STACK_OVERFLOW,绝大多数优先级配置错误都能第一时间暴露出来。
你们项目里有没有「必须比 RTOS 更快响应」的中断?是怎么和任务交换数据的?

