本文基于正点原子 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 关键寄存器
- BASEPRI 寄存器 :设置中断屏蔽阈值,优先级数值≥阈值的中断被屏蔽;数值更小(优先级更高)不受影响,FreeRTOS 开关中断底层就是操作 BASEPRI,而不是 PRIMASK。
- FreeRTOS 配置项
configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY,本实验设置为5。- 抢占优先级 0~4 :优先级高于阈值,不受 FreeRTOS 开关中断影响;
- 抢占优先级 5~15 :受 FreeRTOS 管理,调用
portDISABLE_INTERRUPTS()会被屏蔽。
- FreeRTOS 强制要求:中断优先级分组设置为
NVIC_PRIORITYGROUP_4,全部 4bit 用于抢占优先级,无子优先级。
本实验定时器配置:
表格
| 定时器 | 抢占优先级 | 是否受 FreeRTOS 管理 | 现象 |
|---|---|---|---|
| TIM3 | 4 | ❌不受管理 | FreeRTOS 关中断依然可以正常进中断 |
| TIM5 | 6 | ✅受管理 | FreeRTOS 关中断,中断被屏蔽,停止输出 |
⚠️重点区分: 中断优先级:数值越小优先级越高 ;FreeRTOS 任务优先级:数值越大优先级越高,二者逻辑相反,很多 bug 来源于此。
1.2 实验逻辑
- 初始化 TIM3、TIM5,1Hz 更新中断,每 1s 串口打印对应字符串;
- 创建 task1 任务,计数到第 5 次循环,调用
portDISABLE_INTERRUPTS()关闭 FreeRTOS 管理的中断,忙等待 5 秒;之后调用portENABLE_INTERRUPTS()打开中断; - 串口观察:关中断期间,TIM3 持续打印,TIM5 停止打印;开中断后 TIM5 恢复打印。
二、工程前置条件
本教程基于正点原子 FreeRTOS 移植好的基础工程,你需要已经完成 FreeRTOS 移植:
-
FreeRTOS 已经添加进工程,
heap_4.c已加入; -
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) ) -
系统优先级分组设置
HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4); -
串口 1 初始化波特率 115200;
-
头文件:
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
-
关中断之前:TIM3 输出、TIM5 输出交替打印,1 秒一次;
TIM3 输出
TIM5 输出
TIM3 输出
TIM5 输出 -
打印
---------- FreeRTOS 关闭中断 portDISABLE_INTERRUPTS() ----------之后 5 秒时间内:
✅ TIM3 仍然持续输出;❌ TIM5 完全停止输出。 原理:
portDISABLE_INTERRUPTS()写 BASEPRI 寄存器,屏蔽优先级≥5 的中断;TIM3 抢占优先级 4 高于阈值不受影响,TIM5 优先级 6 被屏蔽。
- 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 屏蔽。