1. 实验前置知识
RTOS可以控制的中断优先级在5~15范围内的,0-4的中断优先级不属于RTOS的控制范围。后面会通过实验来验证。这个管理范围配置如下:
#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 15 /* 中断最低优先级 */
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5 /* FreeRTOS可管理的最高中断优先级 */
中断优先级配置示例图如下:
FreeRTOS开关中断
开关中断的宏定义如下,我们在开启关闭中断的时候用这里的宏就行的。
#define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI()
#define portENABLE_INTERRUPTS() vPortSetBASEPRI( 0 )
#define taskDISABLE_INTERRUPTS() portDISABLE_INTERRUPTS()
#define taskENABLE_INTERRUPTS() portENABLE_INTERRUPTS()
FreeRTOS进出临界区
这边临界区的概念是:临界区内的代码不允许打断,所以进入的时候我们会关闭中断,出去的时候会开启中断,当然这里提到的中断指的是被RTOS所管理的中断。进出代码如下:
/* 进入临界区 */
#define taskENTER_CRITICAL() portENTER_CRITICAL()
#define portENTER_CRITICAL() vPortEnterCritical()
/* 中断中进入临界区 */
#define taskENTER_CRITICAL_FROM_ISR() portSET_INTERRUPT_MASK_FROM_ISR()
#define portSET_INTERRUPT_MASK_FROM_ISR() ulPortRaiseBASEPRI()
/* 退出临界区 */
#define taskEXIT_CRITICAL() portEXIT_CRITICAL()
#define portEXIT_CRITICAL() vPortExitCritical()
/* 中断中退出临界区 */
#define taskEXIT_CRITICAL_FROM_ISR(x) portCLEAR_INTERRUPT_MASK_FROM_ISR(x)
#define portCLEAR_INTERRUPT_MASK_FROM_ISR(x) vPortSetBASEPRI(x)
普通进入临界区的宏taskENTER_CRITICAL()与中断中进入进阶区的宏taskENTER_CRITICAL_FROM_ISR(),前者是任务级的,进入临界区后屏蔽任务之间相互抢资源。后者是中断中进入的。taskENTER_CRITICAL()宏展开后是函数vPortEnterCritical()。下面通过实验来测试一下。
2. FreeRTOS中断测试实验
目的与功能:主要用于测试FreeRTOS打开和关闭中断的影响。设计了两个任务,start_task用于初始化定时器和创建其他任务。task1用于打开和关闭中断。
这部分代码在freertos_demo函数中初始化了定时器6和7,并创建start_task任务。函数执行先进入临界区,初始化定时器之后创建start_task并退出临界区。接着start_task任务创建task1任务,在task1任务中测试rtos开启中断与关闭中断的影响。
void freertos_demo(void){
taskENTER_CRITICAL();
//初始化tim6 tim7
btim_timx_int_init(10000-1, 7200-1);
btim_tim7_int_init(10000-1, 7200-1);
//创建开始任务
xTaskCreate(
(TaskFunction_t ) start_task,
(const char* ) "start_task", /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
(uint16_t ) START_TASK_SIZE,
(void * ) NULL,
(UBaseType_t ) START_TASK_PRIO,
(TaskHandle_t* ) &STARTTask_Handler);
//开启任务调度
vTaskStartScheduler();
taskEXIT_CRITICAL(); //退出临界区
}
/*开始任务任务函数
* 该任务用于创建其他任务,本实验创建LED0任务和LED1任务。
* 功能:LED0与LED1以不同频率进行闪烁
*/
void start_task(void *pvParameters){
//进入临界区,目的是为了打断中断
taskENTER_CRITICAL();
//创建任务1
xTaskCreate(
(TaskFunction_t ) task1_task,
(const char* ) "task1_task", /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
(uint16_t ) TASK1_TASK_SIZE,
(void * ) NULL,
(UBaseType_t ) TASK1_TASK_PRIO,
(TaskHandle_t* ) &TASK1Task_Handler);
vTaskDelete(NULL); //删除开始任务,为NULL默认删除该任务,或者写该任务句柄
taskEXIT_CRITICAL(); //退出临界区
}
/*
*task1任务
*/
void task1_task(void *pvParameters){
uint32_t task1_num = 0;
while(1)
{
if(++task1_num == 5)
{
printf("FreeRTOS关闭中断\r\n");
portDISABLE_INTERRUPTS();
delay_ms(5000);
printf("FreeRTOS开启中断\r\n");
portENABLE_INTERRUPTS();
}
vTaskDelay(1000);
}
定时器6设置中断优先级为4及定时器7设置中断优先级为6,定时器7实在FreeRTOS管理范围内的,而定时器6不在。我们在中断函数中打印看是那个定时器输出的,以进行对比,达到验证FreeRTOS管理中断优先级在5-15之间的中断。定时器初始化函数代码如下:
TIM_HandleTypeDef g_timx_handle; /* 定时器x句柄 */
TIM_HandleTypeDef g_tim7_handle; /* 定时器7句柄 */
/**
* @brief 基本定时器TIMX定时中断初始化函数
* @note
* 基本定时器的时钟来自APB1,当PPRE1 ≥ 2分频的时候
* 基本定时器的时钟为APB1时钟的2倍, 而APB1为36M, 所以定时器时钟 = 72Mhz
* 定时器溢出时间计算方法: Tout = ((arr + 1) * (psc + 1)) / Ft us.
* Ft=定时器工作频率,单位:Mhz
*
* @param arr: 自动重装值。
* @param psc: 时钟预分频数
* @retval 无
*/
void btim_timx_int_init(uint16_t arr, uint16_t psc)
{
BTIM_TIMX_INT_CLK_ENABLE(); /* 使能TIMx时钟 */
g_timx_handle.Instance = BTIM_TIMX_INT; /* 通用定时器x */
g_timx_handle.Init.Prescaler = psc; /* 分频 */
g_timx_handle.Init.CounterMode = TIM_COUNTERMODE_UP; /* 向上计数器 */
g_timx_handle.Init.Period = arr; /* 自动装载值 */
g_timx_handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; /* 时钟分频因子 */
HAL_TIM_Base_Init(&g_timx_handle);
HAL_NVIC_SetPriority(BTIM_TIMX_INT_IRQn, 4, 0); /* 设置中断优先级,抢占优先级1,子优先级3 */
HAL_NVIC_EnableIRQ(BTIM_TIMX_INT_IRQn); /* 开启ITMx中断 */
HAL_TIM_Base_Start_IT(&g_timx_handle); /* 使能定时器x和定时器x更新中断 */
}
/**
* @brief 基本定时器TIM7定时中断初始化函数
* @note
* 基本定时器的时钟来自APB1,当PPRE1 ≥ 2分频的时候
* 基本定时器的时钟为APB1时钟的2倍, 而APB1为36M, 所以定时器时钟 = 72Mhz
* 定时器溢出时间计算方法: Tout = ((arr + 1) * (psc + 1)) / Ft us.
* Ft=定时器工作频率,单位:Mhz
*
* @param arr: 自动重装值。
* @param psc: 时钟预分频数
* @retval 无
*/
void btim_tim7_int_init(uint16_t arr, uint16_t psc)
{
BTIM_TIM7_INT_CLK_ENABLE(); /* 使能TIM7时钟 */
g_tim7_handle.Instance = BTIM_TIM7_INT; /* 通用定时器7 */
g_tim7_handle.Init.Prescaler = psc; /* 分频 */
g_tim7_handle.Init.CounterMode = TIM_COUNTERMODE_UP; /* 向上计数器 */
g_tim7_handle.Init.Period = arr; /* 自动装载值 */
g_tim7_handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; /* 时钟分频因子 */
HAL_TIM_Base_Init(&g_tim7_handle);
HAL_NVIC_SetPriority(BTIM_TIM7_INT_IRQn, 6, 0); /* 设置中断优先级,抢占优先级1,子优先级3 */
HAL_NVIC_EnableIRQ(BTIM_TIM7_INT_IRQn); /* 开启ITMx中断 */
HAL_TIM_Base_Start_IT(&g_tim7_handle); /* 使能定时器x和定时器x更新中断 */
}
/**
* @brief 定时器中断服务函数
* @param 无
* @retval 无
*/
void BTIM_TIMX_INT_IRQHandler(void)
{
HAL_TIM_IRQHandler(&g_timx_handle);
}
void BTIM_TIM7_INT_IRQHandler(void)
{
HAL_TIM_IRQHandler(&g_tim7_handle);
}
/**
* @brief 定时器更新中断回调函数
* @param htim:定时器句柄指针
* @retval 无
*/
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim == (&g_timx_handle))
{
printf("TIM6输出\r\n");
}
else if (htim == (&g_tim7_handle))
{
printf("TIM7输出\r\n");
}
}