FreeRTOS——中断实验

一次搞懂 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 → 会被关闭

我要验证:

  1. TIM3(优先级 4):关中断后依然能进中断
  2. TIM4(优先级 5):关中断后直接停掉
  3. 串口能清晰看到差别

二、硬件与环境

  • 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_IRQHandler
  • TIM4_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):用于普通任务,如按键、串口、定时器,可以被安全屏蔽

六、我踩过的坑(帮你们避坑)

  1. 中断服务函数名字必须写对 TIM3_IRQHandler 少一个字母都进不去
  2. 优先级必须写对 TIM3=4,TIM4=5,差 1 结果完全不同
  3. FreeRTOS 必须用分组 4 只有分组 4 才是纯抢占优先级
  4. 不要在中断里加延时 一加就死机

七、总结

这次实验真的让我彻底理解了 FreeRTOS 中断规则:

  • 优先级 0~4:系统关不掉 → 硬实时
  • 优先级 5~15:系统可以关 → 弱实时

以后写项目就知道了: 特别重要的中断,优先级设成 0~4;普通的外设中断,设成 5~15。

相关推荐
嵌入式×边缘AI:打怪升级日志8 小时前
# 超声波测距 — HC-SR04 + 定时器输入捕获
单片机·定时器·超声波
yugi9878388 小时前
STM32 串口计算器实现
stm32·单片机·嵌入式硬件
科芯创展9 小时前
XZ4115B工作电压6-40V 输出电流1.2A 降压恒流LED驱动芯片
stm32·单片机·嵌入式硬件
涂山苏苏⁠10 小时前
stm32之SPI
stm32
求知喻11 小时前
KEIL5构建软件最小系统
单片机·嵌入式
MC_J11 小时前
STM32H7 串口 UART/USART从原理到实战
stm32·单片机·嵌入式硬件
学不懂飞行器12 小时前
电赛保姆级教程】从炸管到国一:电赛电源类(DC-DC/单相逆变)硬核避坑与拓扑全指南
stm32·单片机·嵌入式硬件·电赛·fft
大阳12313 小时前
ARM5.(beep,key,中断)
单片机·嵌入式硬件
崇山峻岭之间13 小时前
单片机RNG实验
单片机·嵌入式硬件