03. µCOS-Ⅲ的中断管理

一、什么是中断

  让 CPU 打断正常运行的程序,转而去处理紧急的事件(程序),就叫 中断。中断执行机制,可以简单概括为三步:

  1. 中断请求,外设产生中断请求,例如 GPIO 外部中断、定时器中断。
  2. 响应中断,CPU 停止执行当前程序,转而去处理中断处理程序(ISR)。
  3. 退出中断,执行完毕,返回被打断的程序处,继续往下执行。

  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-Ⅲ 所管理的范围内。

CPU_CFG_KA_IPL_BOUNDARY宏

中断优先级数值越小,越优先。

二、三个中断优先级配置寄存器

  除了外部中断,系统中断有独立的中断优先级配置寄存器,分别为 SHPR1、SHPR2、SHPR3。

  SHPR1 寄存器的地址为 0xE000ED18,用于配置 MemManage、BusFault、UsageFault 的中断优先级。

SHPR1寄存器

  SHPR2 寄存器的地址为 0xE000ED1C,用于配置 SVCall 的中断优先级。

SHPR2寄存器

  SHPR3 寄存器的地址为 0xE000ED20,用于配置 PendSV、SysTick 的中断优先级。

SHPR3寄存器

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 的中断优先级正常执行。

当BASEPRI设置为0x40时中断管理示意图

四、临界段代码保护

  临界段代码也叫做临界区,是指那些必须完整运行,不能被打断的代码段。因此,µC/OS-Ⅲ 在进入临界段代码的时候需要关闭中断,当处理完临界段代码以后再打开中断。

#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);
    HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);

    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);
    }
}
posted @ 2024-02-10 20:40  星光樱梦  阅读(17)  评论(0编辑  收藏  举报