FreeRTOS 中断管理实战:BASEPRI 寄存器实验

本文基于正点原子 FreeRTOS 开发指南 V1.10 第 4 章 4.1~4.4 内容,搭建可直接复制粘贴运行的 STM32 FreeRTOS 中断测试工程,复现教材的 TIM3/TIM5 中断对比实验,直观理解BASEPRI寄存器、FreeRTOS 开关中断对不同优先级中断的屏蔽效果。 硬件:STM32F103(战舰 / 精英),HAL 库,FreeRTOS V10.4.6;MDK‑ARM‑AC5 编译器; 实验现象:TIM3 (抢占优先级 4,不受 FreeRTOS 管理),TIM5 (抢占优先级 6,受 FreeRTOS 管理);FreeRTOS 调用portDISABLE_INTERRUPTS()关中断时,TIM5 停止输出,TIM3 仍然正常输出。

一、原理

1.1 Cortex‑M 关键寄存器

  1. BASEPRI 寄存器 :设置中断屏蔽阈值,优先级数值≥阈值的中断被屏蔽;数值更小(优先级更高)不受影响,FreeRTOS 开关中断底层就是操作 BASEPRI,而不是 PRIMASK。
  2. FreeRTOS 配置项configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY,本实验设置为5。
    • 抢占优先级 0~4 :优先级高于阈值,不受 FreeRTOS 开关中断影响;
    • 抢占优先级 5~15 :受 FreeRTOS 管理,调用portDISABLE_INTERRUPTS()会被屏蔽。
  3. FreeRTOS 强制要求:中断优先级分组设置为 NVIC_PRIORITYGROUP_4,全部 4bit 用于抢占优先级,无子优先级。

本实验定时器配置:

表格

定时器 抢占优先级 是否受 FreeRTOS 管理 现象
TIM3 4 ❌不受管理 FreeRTOS 关中断依然可以正常进中断
TIM5 6 ✅受管理 FreeRTOS 关中断,中断被屏蔽,停止输出

⚠️重点区分: 中断优先级:数值越小优先级越高 ;FreeRTOS 任务优先级:数值越大优先级越高,二者逻辑相反,很多 bug 来源于此。

1.2 实验逻辑

  1. 初始化 TIM3、TIM5,1Hz 更新中断,每 1s 串口打印对应字符串;
  2. 创建 task1 任务,计数到第 5 次循环,调用portDISABLE_INTERRUPTS()关闭 FreeRTOS 管理的中断,忙等待 5 秒;之后调用portENABLE_INTERRUPTS()打开中断;
  3. 串口观察:关中断期间,TIM3 持续打印,TIM5 停止打印;开中断后 TIM5 恢复打印。

二、工程前置条件

本教程基于正点原子 FreeRTOS 移植好的基础工程,你需要已经完成 FreeRTOS 移植:

  1. FreeRTOS 已经添加进工程,heap_4.c已加入;

  2. FreeRTOSConfig.h配置关键项:

    #define configPRIO_BITS 4
    #define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 15
    #define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5
    #define configKERNEL_INTERRUPT_PRIORITY ( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
    #define configMAX_SYSCALL_INTERRUPT_PRIORITY ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )

  3. 系统优先级分组设置HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);

  4. 串口 1 初始化波特率 115200;

  5. 头文件:FreeRTOS.h、task.h已经可以正常包含。

👉 如果还没有移植,参考正点原子 FreeRTOS 指南第二章移植章节。

三、全部代码(freertos_demo.c,完整可复制)

将下面全部代码直接替换你的freertos_demo.c,freertos_demo.h只声明void freertos_demo(void);

复制代码
#include "freertos_demo.h"
#include "./SYSTEM/usart/usart.h"
#include "./BSP/LED/led.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/btim/btim.h"

/* FreeRTOS头文件 */
#include "FreeRTOS.h"
#include "task.h"

/************************ 任务配置 ************************/
#define START_TASK_PRIO        1
#define START_STK_SIZE         128
TaskHandle_t StartTask_Handler;
void start_task(void *pvParameters);

#define TASK1_PRIO             2
#define TASK1_STK_SIZE         128
TaskHandle_t Task1Task_Handler;
void task1(void *pvParameters);

/**********************************************************/

/**
 * @brief  FreeRTOS例程入口函数,main中调用
 */
void freertos_demo(void)
{
    lcd_show_string(10, 10, 220,32,32,"STM32", RED);
    lcd_show_string(10, 47, 220,24,24,"Interrupt Test", RED);
    lcd_show_string(10, 76, 220,16,16,"ATOM@ALIENTEK", RED);

    /* 创建开始任务 */
    xTaskCreate((TaskFunction_t )start_task,
                (const char*    )"start_task",
                (uint16_t       )START_STK_SIZE,
                (void*          )NULL,
                (UBaseType_t    )START_TASK_PRIO,
                (TaskHandle_t*  )&StartTask_Handler);

    vTaskStartScheduler();          /* 开启任务调度器 */
}

/**
 * @brief  start_task:初始化硬件、创建测试任务,然后删除自己
 */
void start_task(void *pvParameters)
{
    taskENTER_CRITICAL();

    /* 初始化TIM3、TIM5基本定时器更新中断,1Hz */
    btim_tim3_int_init(10000-1, 7200-1);  //72M系统时钟  7200分频,10000重装载 1Hz
    btim_tim5_int_init(10000-1, 7200-1);

    /* 创建测试任务task1 */
    xTaskCreate((TaskFunction_t )task1,
                (const char*    )"task1",
                (uint16_t       )TASK1_STK_SIZE,
                (void*          )NULL,
                (UBaseType_t    )TASK1_PRIO,
                (TaskHandle_t*  )&Task1Task_Handler);

    vTaskDelete(StartTask_Handler);
    taskEXIT_CRITICAL();
}

/**
 * @brief task1任务:触发FreeRTOS开关中断,观察TIM3 TIM5现象
 */
void task1(void *pvParameters)
{
    uint32_t task1_num = 0;
    while(1)
    {
        if(++task1_num == 5)
        {
            printf("---------- FreeRTOS 关闭中断 portDISABLE_INTERRUPTS() ----------\r\n");
            portDISABLE_INTERRUPTS();  /* 操作BASEPRI,屏蔽受FreeRTOS管理中断 */

            delay_ms(5000);             /* 忙延时5s!!!注意这里是裸机delay_ms,不是vTaskDelay */

            printf("---------- FreeRTOS 打开中断 portENABLE_INTERRUPTS() ----------\r\n");
            portENABLE_INTERRUPTS();   /* 清除BASEPRI,恢复全部受管理中断 */
        }
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

freertos_demo.h

复制代码
#ifndef __FREERTOS_DEMO_H
#define __FREERTOS_DEMO_H

void freertos_demo(void);

#endif

四、btim.c 定时器驱动代码(基本定时器中断)

如果你的工程没有btim.c,直接复制下面代码;STM32F1 版本。 ⚠️重点!!这里设置 NVIC 抢占优先级:TIM3 =4,TIM5=6,这是实验的灵魂!

复制代码
#include "btim.h"
#include "stm32f1xx_hal.h"
#include "./SYSTEM/usart/usart.h"

static TIM_HandleTypeDef g_tim3_handle;
static TIM_HandleTypeDef g_tim5_handle;

/**
 * @brief 基本定时器TIM3更新中断初始化
 * @param  arr:自动重装载值
 * @param  psc:预分频系数
 * @retval 无
 * @note 抢占优先级4,高于configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY(5),不受FreeRTOS管理
 */
void btim_tim3_int_init(uint16_t arr,uint16_t psc)
{
    g_tim3_handle.Instance = TIM3;
    g_tim3_handle.Init.Prescaler = psc;
    g_tim3_handle.Init.CounterMode = TIM_COUNTERMODE_UP;
    g_tim3_handle.Init.Period = arr;
    g_tim3_handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    HAL_TIM_Base_Init(&g_tim3_handle);

    HAL_TIM_Base_Start_IT(&g_tim3_handle);

    /* 抢占优先级 4!!!! */
    HAL_NVIC_SetPriority(TIM3_IRQn,4,0);
    HAL_NVIC_EnableIRQ(TIM3_IRQn);
}

/**
 * @brief 基本定时器TIM5更新中断初始化
 * @param  arr:自动重装载值
 * @param  psc:预分频系数
 * @retval 无
 * @note 抢占优先级6,在FreeRTOS管理范围内
 */
void btim_tim5_int_init(uint16_t arr,uint16_t psc)
{
    g_tim5_handle.Instance = TIM5;
    g_tim5_handle.Init.Prescaler = psc;
    g_tim5_handle.Init.CounterMode = TIM_COUNTERMODE_UP;
    g_tim5_handle.Init.Period = arr;
    g_tim5_handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    HAL_TIM_Base_Init(&g_tim5_handle);

    HAL_TIM_Base_Start_IT(&g_tim5_handle);

    /* 抢占优先级 6!!!! */
    HAL_NVIC_SetPriority(TIM5_IRQn,6,0);
    HAL_NVIC_EnableIRQ(TIM5_IRQn);
}

/**
 * @brief 定时器底层Msp回调
 */
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim)
{
    if(htim->Instance == TIM3)
    {
        __HAL_RCC_TIM3_CLK_ENABLE();
    }
    else if(htim->Instance == TIM5)
    {
        __HAL_RCC_TIM5_CLK_ENABLE();
    }
}

/**
 * @brief 定时器更新中断回调函数
 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if(htim->Instance == TIM3)
    {
        printf("TIM3 输出\r\n");
    }
    else if(htim->Instance == TIM5)
    {
        printf("TIM5 输出\r\n");
    }
}

btim.h 头文件

复制代码
#ifndef __BTIM_H
#define __BTIM_H
#include "stm32f1xx_hal.h"

void btim_tim3_int_init(uint16_t arr,uint16_t psc);
void btim_tim5_int_init(uint16_t arr,uint16_t psc);

#endif

五、main.c 关键片段

main 函数完成硬件初始化,调用freertos_demo();注意一定要设置中断优先级分组 4!

复制代码
int main(void)
{
    HAL_Init();
    sys_stm32_clock_init(RCC_PLL_MUL9); /*72M时钟*/

    /* ✅FreeRTOS强制:优先级分组4!!!!!*/
    HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);

    delay_init(72);
    usart_init(115200);
    led_init();
    lcd_init();
    key_init();

    freertos_demo();   /*进入FreeRTOS应用,永不返回*/
}

六、实验现象说明

串口波特率:115200

  1. 关中断之前:TIM3 输出、TIM5 输出交替打印,1 秒一次;

    TIM3 输出
    TIM5 输出
    TIM3 输出
    TIM5 输出

  2. 打印---------- FreeRTOS 关闭中断 portDISABLE_INTERRUPTS() ----------之后 5 秒时间内:

✅ TIM3 仍然持续输出;❌ TIM5 完全停止输出。 原理:portDISABLE_INTERRUPTS()写 BASEPRI 寄存器,屏蔽优先级≥5 的中断;TIM3 抢占优先级 4 高于阈值不受影响,TIM5 优先级 6 被屏蔽。

  1. 5 秒之后打印---------- FreeRTOS 打开中断 portENABLE_INTERRUPTS() ----------:

BASEPRI 写 0,全部中断恢复,TIM5 重新开始打印输出。

串口输出样例:

复制代码
TIM3 输出
TIM5 输出
TIM3 输出
TIM5 输出
---------- FreeRTOS 关闭中断 portDISABLE_INTERRUPTS() ----------
TIM3 输出
TIM3 输出
TIM3 输出
TIM3 输出
TIM3 输出
---------- FreeRTOS 打开中断 portENABLE_INTERRUPTS() ----------
TIM5 输出
TIM3 输出
TIM5 输出

七、源码底层拆解(对应 4.3 章节)

portDISABLE_INTERRUPTS () 宏定义

复制代码
#define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI()

static portFORCE_INLINE void vPortRaiseBASEPRI( void )
{
uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;
  __asm
  {
    msr basepri, ulNewBASEPRI
    dsb
    isb
  }
}

本质汇编指令msr basepri, ulNewBASEPRI修改 BASEPRI 寄存器,不是 PRIMASK!不会关掉最高优先级中断。

复制代码
#define portENABLE_INTERRUPTS() vPortSetBASEPRI( 0 )
static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI )
{
  __asm
  {
    msr basepri, ulBASEPRI
  }
}

写 0 清除 BASEPRI 屏蔽。

相关推荐
HRTOS2 小时前
HRTOS:一个名字背后的目标——不断逼近硬实时
经验分享·单片机·系统架构·51单片机
UrSpecial4 小时前
GPIO 是什么?
单片机·嵌入式硬件
剑指offer.6 小时前
ARM裸机开发-SPI
arm开发·嵌入式硬件·嵌入式·arm
修船大队长6 小时前
配置接受报文每个报文对应一个mailbox配置流程记录
网络·单片机·mcu
搁浅小泽8 小时前
二极管选型技术规范
单片机·嵌入式硬件·汽车
搁浅小泽8 小时前
电感选型技术规范
单片机·嵌入式硬件·汽车
..Dauntless..9 小时前
【stm32】串口——在MCU内部的工作过程
stm32·单片机·嵌入式硬件
lzqrzpt9 小时前
临沂LED驱动电源工程选型标准与工艺对比解析
python·单片机·嵌入式硬件·物联网
小黄蚁10 小时前
使用LVGL模拟示波器创建正弦函数波形
单片机·学习