03. µCOS-Ⅲ的中断管理
一、什么是中断
让 CPU 打断正常运行的程序,转而去处理紧急的事件(程序),就叫 中断。中断执行机制,可以简单概括为三步:
- 中断请求,外设产生中断请求,例如 GPIO 外部中断、定时器中断。
- 响应中断,CPU 停止执行当前程序,转而去处理中断处理程序(ISR)。
- 退出中断,执行完毕,返回被打断的程序处,继续往下执行。
ARM Cortex-M 使用 8 位宽的寄存器来配置中断的优先级,这个寄存器就是 中断优先级配置寄存器。但是 STM32 只用了中断优先级配置寄存器的高 4 位 [7:4],所以 STM32 提供最大 16 级的中断优先级。STM32 的优先级可以分为 抢占优先级 和 子优先级。抢占优先级高的中断可以打断正在执行但抢占优先级低的中断。当同时发生具有相同抢占优先级的中断时,子优先级数值小的优先执行,但不能互相打断。
一共有 5 种分配方式,对应着中断优先级分组的 5 个组。
为了方便 µCOS-Ⅲ 管理,建议将所有优先级位指定为抢占优先级位,即将优先级分组设置为组 4。函数 HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4
)即可完成设置,HAL_Init()
中会调用该函数。µCOS-Ⅲ 的中断管理范围,通过宏 CPU_CFG_KA_IPL_BOUNDA
(该宏在 cpu_cfg.h
头文件中定义) 设置。如将该宏定义为 4,即管理中断优先级范围:4 ~ 15。在中断服务函数中,如果调用到 µCOS-Ⅲ 的 API 函数,那么该中断优先级必须在 µCOS-Ⅲ 所管理的范围内。
中断优先级数值越小,越优先。
二、三个中断优先级配置寄存器
除了外部中断,系统中断有独立的中断优先级配置寄存器,分别为 SHPR1、SHPR2、SHPR3。
SHPR1 寄存器的地址为 0xE000ED18,用于配置 MemManage、BusFault、UsageFault 的中断优先级。
SHPR2 寄存器的地址为 0xE000ED1C,用于配置 SVCall 的中断优先级。
SHPR3 寄存器的地址为 0xE000ED20,用于配置 PendSV、SysTick 的中断优先级。
PendSV 中断的优先级设置为最低 15,以保证系统任务切换不会阻塞系统其他中断的响应!
SysTick 中断的优先级设置为 µCOS-Ⅲ 所管理的最高优先级,以保证系统时钟节拍的精度!
三、三个中断屏蔽寄存器
ARM Cortex-M 有三个用于屏蔽中断的寄存器,分别为 PRIMASK、FAULTMASK 和 BASEPRI。
PRIMASK 寄存器有 32bit,但只有 bit0 有效,是可读可写的,将 PRIMASK 寄存器设置为 1 用于屏蔽除 NMI 和 HardFault 外的所有异常和中断,将 PRIMASK 寄存器清 0 用于使能中断。该寄存器的默认值就是 0,表示没有关中断。
FAULTMASK 寄存器有 32bit,但只有 bit0 有效,也是可读可写的,将 FAULTMASK 寄存器设置为 1 用于屏蔽除 NMI 外的所有异常和中断,将 FAULTMASK 寄存器清零用于使能中断。该寄存器的默认值也是 0,表示没有关异常。
BASEPRI 有 32bit,但只有低 8 位 [7:0] 有效,也是可读可写的。BASEPRI 用于设置一个中断屏蔽的阈值,设置好 BASEPRI 后,中断优先级低于 BASEPRI 的中断就都会被屏蔽掉,µCOS-Ⅲ 就是使用 BASEPRI 寄存器来管理受 µCOS-Ⅲ 管理的中断的,而不受 µCOS-Ⅲ 管理的中断,则不受 µCOS-Ⅲ 的影响。该寄存器的默认值也是 0,表示不关闭任何中断。
比如: BASEPRI 设置为 0x40,代表中断优先级在 4 ~ 15 内的均被屏蔽,0 ~ 3 的中断优先级正常执行。
四、临界段代码保护
临界段代码也叫做临界区,是指那些必须完整运行,不能被打断的代码段。因此,µC/OS-Ⅲ 在进入临界段代码的时候需要关闭中断,当处理完临界段代码以后再打开中断。
#define CPU_SR_ALLOC() CPU_SR cpu_sr = (CPU_SR)0 // 临界区保护
#define CPU_CRITICAL_ENTER() do { CPU_INT_DIS(); } while (0) // 进入临界区
#define CPU_CRITICAL_EXIT() do { CPU_INT_EN(); } while (0) // 退出临界区
进入临界区,以及退出临界区,就是操作 BASEPRI 寄存器,临界区是直接屏蔽了中断,系统任务调用和 ISR 都是靠中断实现的。
进入临界区和退出临界区必须成对使用,且不支持嵌套,并且我们在使用临界区时,要尽量保持临界区耗时短。
五、实验例程
5.1、定时器的定时功能
定时器定时功能初始化函数:
TIM_HandleTypeDef g_tim6_handle;
TIM_HandleTypeDef g_tim7_handle;
/**
* @brief 定时器定时功能初始化函数
*
* @param htim 定时器句柄
* @param TIMx 定时器寄存器基地址,可选值: TIMx, x可选范围: 1 ~ 14
* @param prescaler 预分频系数,可选值: 0 ~ 65535
* @param period 自动重装载值,可选值: 0 ~ 65535
*
* @note 默认为向上计数模式
*/
void TIM_Base_Init(TIM_HandleTypeDef *htim, TIM_TypeDef *TIMx, uint16_t prescaler, uint16_t period)
{
htim->Instance = TIMx; // 定时器寄存器基地址
htim->Init.CounterMode = TIM_COUNTERMODE_UP; // 计数模式
htim->Init.Prescaler = prescaler; // 预分频系数
htim->Init.Period = period; // 自动重装载值
HAL_TIM_Base_Init(htim);
}
基本定时器底层初始化函数:
/**
* @brief 基本定时器底层初始化函数
*
* @param htim 定时器句柄
*/
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM6)
{
__HAL_RCC_TIM6_CLK_ENABLE(); // 使能定时器6的时钟
HAL_NVIC_EnableIRQ(TIM6_DAC_IRQn); // 使能定时器6中断
HAL_NVIC_SetPriority(TIM6_DAC_IRQn, 3, 0); // 设置中断优先级
}
else if (htim->Instance == TIM7)
{
__HAL_RCC_TIM7_CLK_ENABLE(); // 使能定时器7的时钟
HAL_NVIC_EnableIRQ(TIM7_IRQn); // 使能定时器7中断
HAL_NVIC_SetPriority(TIM7_IRQn, 6, 0); // 设置中断优先级
}
}
定时器 6 中断服务函数:
/**
* @brief 定时器6中断服务函数
*
*/
void TIM6_DAC_IRQHandler(void)
{
HAL_TIM_IRQHandler(&g_tim6_handle); // 调用HAL库公共处理函数
}
定时器 7 中断服务函数:
/**
* @brief 定时器7中断服务函数
*
*/
void TIM7_IRQHandler(void)
{
HAL_TIM_IRQHandler(&g_tim7_handle);
}
定时器更新中断回调函数:
/**
* @brief 定时器更新中断回调函数
*
* @param htim 定时器句柄
*/
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM6)
{
HAL_GPIO_TogglePin(GPIOF, GPIO_PIN_9);
}
else if (htim->Instance == TIM7)
{
HAL_GPIO_TogglePin(GPIOF, GPIO_PIN_10);
}
}
5.2、µC/OS-Ⅲ的任务函数
main() 函数:
int main(void)
{
HAL_Init();
System_Clock_Init(8, 336, 2, 7);
Delay_Init(168);
LED_Init();
Key_Init();
TIM_Base_Init(&g_tim6_handle, TIM6, 8399, 9999);
TIM_Base_Init(&g_tim7_handle, TIM7, 8399, 9999);
__HAL_TIM_CLEAR_IT(&g_tim6_handle, TIM_IT_UPDATE); // 清除更新中断标志位
HAL_TIM_Base_Start_IT(&g_tim6_handle);
__HAL_TIM_CLEAR_IT(&g_tim7_handle, TIM_IT_UPDATE); // 清除更新中断标志位
HAL_TIM_Base_Start_IT(&g_tim7_handle);
UC_OS3_Demo();
return 0;
}
µC/OS-Ⅲ 例程入口函数:
/**
* @brief µC/OS-Ⅲ例程入口函数
*
*/
void UC_OS3_Demo(void)
{
OS_ERR error = {0};
OSInit(&error); // 初始化µC/OS-Ⅲ
// 创建开始任务
OSTaskCreate((OS_TCB * ) &start_task_tcb, // 任务控制块
(CPU_CHAR * ) "start_task", // 任务名
(OS_TASK_PTR ) Start_Task, // 任务函数
(void * ) 0, // 任务参数
(OS_PRIO ) START_TASK_PRIORITY, // 任务优先级
(CPU_STK * ) start_task_stack, // 任务堆栈
(CPU_STK_SIZE) START_TASK_STACK_SIZE / 10, // 任务栈的使用警戒线
(CPU_STK_SIZE) START_TASK_STACK_SIZE, // 任务栈大小
(OS_MSG_QTY ) 0, // 消息队列长度
(OS_TICK ) 0, // 时间片长度
(void * ) 0, // 扩展内存
(OS_OPT ) (OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR), // 任务选项
(OS_ERR * ) &error); // 错误码
OSStart(&error); // 开始任务调度
}
START_TASK 任务配置:
/**
* START_TASK 任务配置
* 包括:任务优先级 任务栈大小 任务控制块 任务栈 任务函数
*/
#define START_TASK_PRIORITY 5
#define START_TASK_STACK_SIZE 256
OS_TCB start_task_tcb;
CPU_STK start_task_stack[START_TASK_STACK_SIZE];
void Start_Task(void *p_arg);
/**
* @brief 开始任务的任务函数
*
* @param p_arg 任务参数
*/
void Start_Task(void *p_arg)
{
OS_ERR error = {0};
CPU_INT32U cnts = 0;
CPU_Init(); // 初始化CPU库
CPU_SR_ALLOC(); // 临界区保护
cnts = HAL_RCC_GetSysClockFreq() / OS_CFG_TICK_RATE_HZ;
OS_CPU_SysTickInit(cnts); // 根据配置的节拍频率配置SysTick中断及优先级
CPU_CRITICAL_ENTER(); // 进入临界区
// 创建任务1
OSTaskCreate((OS_TCB * ) &task1_tcb, // 任务控制块
(CPU_CHAR * ) "task1", // 任务名
(OS_TASK_PTR ) Task1, // 任务函数
(void * ) 0, // 任务参数
(OS_PRIO ) TASK1_PRIORITY, // 任务优先级
(CPU_STK * ) task1_stack, // 任务堆栈
(CPU_STK_SIZE) TASK1_STACK_SIZE / 10, // 任务栈的使用警戒线
(CPU_STK_SIZE) TASK1_STACK_SIZE, // 任务栈大小
(OS_MSG_QTY ) 0, // 消息队列长度
(OS_TICK ) 0, // 时间片长度
(void * ) 0, // 扩展内存
(OS_OPT ) (OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR), // 任务选项
(OS_ERR * ) &error); // 错误码
CPU_CRITICAL_EXIT(); // 退出临界区
OSTaskDel(NULL, &error); // 删除任务
}
TASK1 任务配置:
/**
* TASK1 任务配置
* 包括:任务优先级 任务栈大小 任务控制块 任务栈 任务函数
*/
#define TASK1_PRIORITY 4
#define TASK1_STACK_SIZE 256
OS_TCB task1_tcb;
CPU_STK task1_stack[TASK1_STACK_SIZE];
void Task1(void *p_arg);
/**
* @brief 任务1的任务函数
*
* @param p_arg 任务参数
*/
void Task1(void *p_arg)
{
OS_ERR error = {0};
CPU_SR_ALLOC(); // 临界区保护
while (1)
{
switch (Key_Scan(0))
{
case KEY1_PRESS:
CPU_CRITICAL_ENTER(); // 进入临界区,关中断
Delay_ms(5000); // 延迟5s,不进行任务切换
CPU_CRITICAL_EXIT(); // 退出临界区,开中断
break;
}
OSTimeDly(1000, OS_OPT_TIME_DLY, &error);
}
}