一次搞懂 FreeRTOS 中断屏蔽规则!我用 TIM3/TIM4 做了个硬核实验
最近在学 FreeRTOS 的中断管理,一直听说 **"优先级 0~4 不被屏蔽,5~15 会被屏蔽"**,光看理论记不住,干脆自己写代码实测一把。
我用了两个定时器:TIM3(优先级 4)、TIM4(优先级 5),让任务运行 5 秒后关闭全部可屏蔽中断,看看两个定时器到底谁还能工作。
实测完真的通透了,这篇就把我的实验代码、原理、现象全部讲清楚,一看就懂。
一、实验目的
FreeRTOS 有个关键规则:
- configMAX_SYSCALL_INTERRUPT_PRIORITY = 5(默认)
- 优先级 0、1、2、3、4 → 不会被 portDISABLE_INTERRUPTS () 关闭
- 优先级 5、6、7...15 → 会被关闭
我要验证:
- TIM3(优先级 4):关中断后依然能进中断
- TIM4(优先级 5):关中断后直接停掉
- 串口能清晰看到差别
二、硬件与环境
- STM32F103
- TIM3 中断优先级:4
- TIM4 中断优先级:5
- FreeRTOS
- 串口波特率 115200
三、代码完整讲解
timer.c
#include "timer.h"
#include "stm32f10x.h"
#include "usart.h"
// TIM3初始化:中断优先级4,不会被FreeRTOS屏蔽
void TIMER3_Init(void)
{
TIM_TimeBaseInitTypeDef TIM3InitStruct; // 定时器基本配置结构体
NVIC_InitTypeDef NVIC_InitStruct; // 中断优先级配置结构体
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); // 使能TIM3时钟
// 定时器配置:不分频、向上计数
TIM3InitStruct.TIM_ClockDivision = TIM_CKD_DIV1;
TIM3InitStruct.TIM_CounterMode = TIM_CounterMode_Up;
// 自动重装载值 + 预分频值 → 定时1秒
TIM3InitStruct.TIM_Period = 10000 - 1;
TIM3InitStruct.TIM_Prescaler = 3600 - 1;
TIM_TimeBaseInit(TIM3, &TIM3InitStruct); // 写入配置
TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE); // 使能更新中断
TIM_Cmd(TIM3, ENABLE); // 启动定时器
// 中断优先级配置
NVIC_InitStruct.NVIC_IRQChannel = TIM3_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 4; // 优先级4
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
NVIC_Init(&NVIC_InitStruct);
}
// TIM4初始化:中断优先级5,会被FreeRTOS屏蔽
void TIMER4_Init(void)
{
TIM_TimeBaseInitTypeDef TIM4InitStruct;
NVIC_InitTypeDef NVIC_InitStruct;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
TIM4InitStruct.TIM_ClockDivision = TIM_CKD_DIV1;
TIM4InitStruct.TIM_CounterMode = TIM_CounterMode_Up;
TIM4InitStruct.TIM_Period = 10000 - 1;
TIM4InitStruct.TIM_Prescaler = 3600 - 1;
TIM_TimeBaseInit(TIM4, &TIM4InitStruct);
TIM_ITConfig(TIM4, TIM_IT_Update, ENABLE);
TIM_Cmd(TIM4, ENABLE);
NVIC_InitStruct.NVIC_IRQChannel = TIM4_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 5; // 优先级5
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
NVIC_Init(&NVIC_InitStruct);
}
// TIM3中断服务函数(名字必须正确)
void TIM3_IRQHandler(void)
{
// 判断是否是更新中断触发
if(TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET)
{
printf("Tim3 is interrupt! \r\n");
TIM_ClearITPendingBit(TIM3, TIM_IT_Update); // 清除标志位
}
}
// TIM4中断服务函数
void TIM4_IRQHandler(void)
{
if(TIM_GetITStatus(TIM4, TIM_IT_Update) != RESET)
{
printf("Tim4 is interrupt! \r\n");
TIM_ClearITPendingBit(TIM4, TIM_IT_Update);
}
}
main.c
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "FreeRTOS.h"
#include "task.h"
#include "timer.h"
// 开始任务配置
#define START_TASK_SIZE 128
#define START_TASK_PRIO 1
TaskHandle_t StartTask_Hander;
void start_task(void *pvParameters);
// 测试任务配置
#define INTERRUPT_TASK_SIZE 128
#define INTERRUPT_TASK_PRIO 4
TaskHandle_t INTERRUPTTask_Hander;
void interrupt_task(void *pvParameters);
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4); // FreeRTOS必须用分组4
delay_init();
uart_init(115200);
LED_Init();
TIMER3_Init(); // 优先级4
TIMER4_Init(); // 优先级5
// 创建开始任务
xTaskCreate(start_task, "start_task", START_TASK_SIZE, NULL, START_TASK_PRIO, &StartTask_Hander);
vTaskStartScheduler(); // 开启调度器
}
// 开始任务:只负责创建其他任务
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); // 进入临界区,防止创建任务时被打断
// 创建测试任务
xTaskCreate(interrupt_task, "interrupt_task", INTERRUPT_TASK_SIZE, NULL, INTERRUPT_TASK_PRIO, &INTERRUPTTask_Hander);
vTaskDelete(StartTask_Hander); // 自杀,释放资源
taskEXIT_CRITICAL();
}
// 测试任务:5秒后关闭中断5秒,再打开
void interrupt_task(void *pvParameters)
{
char task_num = 0;
while(1)
{
task_num++;
// 第5次运行时关闭中断
if(task_num == 5)
{
printf("===== 关闭中断 ===== \r\n");
portDISABLE_INTERRUPTS(); // 关闭优先级>=5的中断
delay_xms(5000); // 保持关闭5秒
printf("===== 开启中断 ===== \r\n");
portENABLE_INTERRUPTS();
}
LED0 = ~LED0; // LED闪烁
vTaskDelay(1000); // 延时1秒
}
}
1. 为什么必须设置 NVIC_PriorityGroup_4?
因为 FreeRTOS 只支持分组 4,所有优先级都是抢占优先级,没有子优先级。 如果不设置,中断优先级会乱,实验结果完全错误。
2. 为什么 TIM3(优先级 4)关不断?
FreeRTOS 的 portDISABLE_INTERRUPTS() 只会屏蔽优先级 ≥5 的中断 。 优先级 0~4 属于不受屏蔽的高优先级中断。
3. 为什么 TIM4(优先级 5)能被关掉?
因为 configMAX_SYSCALL_INTERRUPT_PRIORITY = 5 等于或大于 5 的中断都会被屏蔽。
4. 中断服务函数名字不能错
必须是:
TIM3_IRQHandlerTIM4_IRQHandler
名字错了,中断来了也进不去函数。
5. 必须清除中断标志位
TIM_ClearITPendingBit() 不清除会一直触发中断,系统直接卡死。
6. 为什么要在临界区创建任务?
taskENTER_CRITICAL() 创建任务过程不希望被中断打断,否则可能导致任务创建异常。
四、实验现象
1. 开机前 5 秒
两个中断同时正常运行。

2. 第 5 秒 → 执行 portDISABLE_INTERRUPTS ()

3. 关闭中断的 5 秒内
TIM4 完全消失!TIM3 依然疯狂输出!

4. 恢复中断后
两个都恢复。

五、原理一句话讲透
FreeRTOS 的 portDISABLE_INTERRUPTS()
只会屏蔽 优先级 ≥ configMAX_SYSCALL_INTERRUPT_PRIORITY(一般是5)的中断
优先级 0 1 2 3 4 → 关不掉
优先级 5 6 7...15 → 能关掉
为什么要这么设计?
- 高优先级中断(0~4):用于紧急事件,如编码器、高速采样,不能被系统卡住
- 低优先级中断(5~15):用于普通任务,如按键、串口、定时器,可以被安全屏蔽
六、我踩过的坑(帮你们避坑)
- 中断服务函数名字必须写对
TIM3_IRQHandler少一个字母都进不去 - 优先级必须写对 TIM3=4,TIM4=5,差 1 结果完全不同
- FreeRTOS 必须用分组 4 只有分组 4 才是纯抢占优先级
- 不要在中断里加延时 一加就死机
七、总结
这次实验真的让我彻底理解了 FreeRTOS 中断规则:
- 优先级 0~4:系统关不掉 → 硬实时
- 优先级 5~15:系统可以关 → 弱实时
以后写项目就知道了: 特别重要的中断,优先级设成 0~4;普通的外设中断,设成 5~15。