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。

相关推荐
小心亦新14 分钟前
STM32学习13 定时器1中断
stm32·嵌入式硬件·学习
QK_002 小时前
阿波罗学习--day1
stm32·单片机
czhaii3 小时前
单片机光学自动打靶机寻点流程分析
单片机·嵌入式硬件·硬件工程
【云轩】5 小时前
离散量输入输出电路设计:V/地、V/开、地/开信号与单片机接口的电路实现
单片机·嵌入式硬件·mongodb
2401_854151555 小时前
STM32 低功耗模式:SLEEP / STOP / STANDBY 怎么选
stm32·单片机·嵌入式硬件
非鱼䲆鱻䲜5 小时前
CCS 20.5.0的printf重定向串口怎么实现?
linux·单片机·电赛·ccs
謓泽6 小时前
【6.26】芯片测试入门 从零搭自动化测试框架|PyVISA+OOP 保姆级教程
stm32·单片机·fpga开发·雷达·tr
巴巴媛6667 小时前
STM32学习笔记【36.CAN收发实验】
笔记·stm32·学习
凉、介7 小时前
Linux 设备驱动匹配机制
linux·笔记·单片机·学习·操作系统·嵌入式
KaifuZeng8 小时前
模电面试问题汇总二
单片机·嵌入式硬件·面试·模电·通信与接口协议