STM32F1+HAL库+FreeTOTS学习5——中断管理和临界段代码保护
- 中断简介
- 中断优先级寄存器拓展
- FreeRTOS中PendSV和Systick中断优先级配置
- 三个中断屏蔽寄存器
- FreeRTOS中断管理函数
- 代码验证
上一期我们学习了FreeRTOS中任务挂起与恢复,在中断服务程序中恢复任务过程中,尤其强调了中断必须设置优先级在5-15之间,且中断优先级分组为4,这是为什么呢,让我们一起来探索一些吧。
中断简介
关于STM32中中断的基本概念,我这里就不做赘述了,可以参考 :夜深人静学32系列10——GPIO中断/NVIC/EXTI/SYSCFG详解,外部中断控制LED ,这里只介绍几个重要的内容。
中断优先级寄存器拓展
- 中断优先级
在CreteX-M3/4/7的内核中,一个支持16(内核中断)和最多240和外部中断输入,每个中断都有八个位来控制其优先级,理论上最大优先级可以达到256,但是其具体值都是由芯片厂商根据需求裁剪提供给用户的。
我们本次使用的STM32F103RCT6的芯片,它的中断控制器(NVIC)一共支持68种外部中断,不包含Cretex-M3自带的16个内核中断,同时支持16个可编程中断优先级(使用4为中断优先级)
- 这里需要注意:中断优先级和FreeRTOS中任务优先级概念不同
- 中断优先级数字越小,优先级越高
- FreeRTOS任务优先级数字越大,优先级越高。
- 系统异常优先级寄存器
在Cretex-M3内核中,除了240个外部中断优先级寄存器(实际上是八个32位寄存器,最后一个没用完),还有三个系统内核中断优先级寄存器:SHPR1、SHPR2、SHPR3
FreeRTOS中PendSV和Systick中断优先级配置
前面我们有讲过FreeRTOS的一个重要作用就是实现多任务运行,但其实同一时刻只有一个任务在运行,只不过任务之间切换速度很快,给人一种多任务同时运行的假象。那么这里面有两个很重要的东西,一个时心跳节拍和任务调度。分别由Systick中断和PendSV中断来控制。
- SysTick中断:FreeRTOS中有自己独立的时钟,同时我们之前学习过一个时间片的概念,即每个任务执行的时间,当时间片用完,就会产生一个Systick中断,里面会重新开启任务调度,执行优先级最高的任务,像这样,每经过一个时间片,系统就会重新选择需要运行的任务,我们把它叫做心跳节拍,心跳节拍的频率由宏:configTICK_RATE_HZ 决定,单位为HZ,比如设置为1000,则表示一个时间片长度为1ms。
- PendSV中断:PendSV则是用来完成任务切换功能,所需要的中断。
- 在FreeRTOS中,PendSV和Systick的中断优先级都被设置位15,即最低优先级,避免任务切换阻塞系统其他的中断相应。其原理就是操作了SHPR3寄存器。
三个中断屏蔽寄存器
三个中断屏蔽寄存器,分别为 PRIMASK、 FAULTMASK 和BASEPRI
- FreeRTOS所使用的中断管理就是利用的BASEPRI这个寄存器
- BASEPRI:屏蔽优先级低于某一个阈值的中断
- BASEPRI设置为0x50,代表中断优先级在5 - 15内的均被屏蔽,0 - 4的中断优先级正常执行
- 这里设置0x50,屏蔽优先级在5以上的任务是因为BASEPRI寄存器只使用了位【7:4】,其中【3:0】是没有使用的。
FreeRTOS中断管理函数
- 关中断函数
portDISABLE_INTERRUPTS();printf("屏蔽中断5-15\r\n");
/*
屏蔽优先级为5 - 15的中断
*/
- 开中断服务函数
portENABLE_INTERRUPTS();printf("开启中断5-15\r\n");/*
开启所有中断相应。
*/
代码验证
下面是任务要求
定时器配置,其他配置和代码参考上一期内容: STM32F1+HAL库+FreeTOTS学习4——任务挂起与恢复
下面是代码实现部分,只展示逻辑部分,和定时器中断相关代码
- main.c
/*
这里开启了定时器中断和进入FreeRTOS系统
*/int main(void)
{/* USER CODE BEGIN 1 *//* USER CODE END 1 *//* MCU Configuration--------------------------------------------------------*//* Reset of all peripherals, Initializes the Flash interface and the Systick. */HAL_Init();/* USER CODE BEGIN Init *//* USER CODE END Init *//* Configure the system clock */SystemClock_Config();/* USER CODE BEGIN SysInit *//* USER CODE END SysInit *//* Initialize all configured peripherals */MX_GPIO_Init();MX_USART1_UART_Init();MX_TIM5_Init();MX_TIM6_Init();/* USER CODE BEGIN 2 */HAL_TIM_Base_Start_IT(&htim5); //开启定时10中断HAL_TIM_Base_Start_IT(&htim6); //开启定时器11中断freertos_demo();/* USER CODE END 2 *//* Infinite loop *//* USER CODE BEGIN WHILE */while (1){/* USER CODE END WHILE *//* USER CODE BEGIN 3 */}/* USER CODE END 3 */
}/*
这里负责定时器打印
*//*** @brief Period elapsed callback in non blocking mode* @note This function is called when TIM4 interrupt took place, inside* HAL_TIM_IRQHandler(). It makes a direct call to HAL_IncTick() to increment* a global variable "uwTick" used as application time base.* @param htim : TIM handle* @retval None*/
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{/* USER CODE BEGIN Callback 0 *//* USER CODE END Callback 0 */if (htim->Instance == TIM4) {HAL_IncTick();}/* USER CODE BEGIN Callback 1 */if(htim->Instance == TIM5){printf("TIM5优先级4打印\r\n");}if(htim->Instance == TIM6){printf("TIM6优先级6打印\r\n");}/* USER CODE END Callback 1 */
}
- key.c
/* USER CODE BEGIN 2 */#include "freertos_demo.h"
#include "key.h"
#include "usart.h"
void Key0_Down_Task(void)
{portDISABLE_INTERRUPTS();printf("屏蔽中断5-15\r\n");
}
void Key0_Up_Task(void)
{}
void Key1_Down_Task(void)
{portENABLE_INTERRUPTS();printf("开启中断5-15\r\n");
}
void Key1_Up_Task(void)
{vTaskResume(Task1Task_Handler);
}
void Key2_Down_Task(void)
{}
void Key2_Up_Task(void)
{}
void WKUP_Down_Task(void)
{}
void WWKUP_Up_Task(void)
{}void Key_One_Scan(uint8_t KeyName ,void(*OnKeyOneUp)(void), void(*OnKeyOneDown)(void))
{static uint8_t Key_Val[Key_Name_Max]; //按键值的存放位置static uint8_t Key_Flag[Key_Name_Max]; //KEY0~2为0时表示按下,为1表示松开,WKUP反之Key_Val[KeyName] = Key_Val[KeyName] <<1; //每次扫描完,将上一次扫描的结果左移保存switch(KeyName){case Key_Name_Key0: Key_Val[KeyName] = Key_Val[KeyName] | (HAL_GPIO_ReadPin(KEY0_GPIO_Port, KEY0_Pin)); //读取Key0按键值break;case Key_Name_Key1: Key_Val[KeyName] = Key_Val[KeyName] | (HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin)); //读取Key1按键值break;case Key_Name_Key2: Key_Val[KeyName] = Key_Val[KeyName] | (HAL_GPIO_ReadPin(KEY2_GPIO_Port, KEY2_Pin)); //读取Key2按键值break;
// case Key_Name_WKUP: Key_Val[KeyName] = Key_Val[KeyName] | (HAL_GPIO_ReadPin(WKUP_GPIO_Port, WKUP_Pin)); //读取WKUP按键值
// break; default:break;}
// if(KeyName == Key_Name_WKUP) //WKUP的电路图与其他按键不同,所以需要特殊处理
// {
// //WKUP特殊情况
// //当按键标志为1(松开)是,判断是否按下,WKUP按下时为0xff
// if(Key_Val[KeyName] == 0xff && Key_Flag[KeyName] == 1)
// {
// (*OnKeyOneDown)();
// Key_Flag[KeyName] = 0;
// }
// //当按键标志位为0(按下),判断按键是否松开,WKUP松开时为0x00
// if(Key_Val[KeyName] == 0x00 && Key_Flag[KeyName] == 0)
// {
// (*OnKeyOneUp)();
// Key_Flag[KeyName] = 1;
// }
// }
// else //Key0~2按键逻辑判断
// {//Key0~2常规判断//当按键标志为1(松开)是,判断是否按下if(Key_Val[KeyName] == 0x00 && Key_Flag[KeyName] == 1){(*OnKeyOneDown)();Key_Flag[KeyName] = 0;}//当按键标志位为0(按下),判断按键是否松开if(Key_Val[KeyName] == 0xff && Key_Flag[KeyName] == 0){(*OnKeyOneUp)();Key_Flag[KeyName] = 1;} }//}
/* USER CODE END 2 */
- key.h
/* Includes ------------------------------------------------------------------*/
#include "main.h"/* USER CODE BEGIN Includes *//* USER CODE END Includes *//* USER CODE BEGIN Private defines */
typedef enum{Key_Name_Key0 = 0,Key_Name_Key1,Key_Name_Key2,Key_Name_WKUP,Key_Name_Max}EnumKeyOneName;/* USER CODE END Private defines */void MX_GPIO_Init(void);/* USER CODE BEGIN Prototypes */
void Key0_Down_Task(void);void Key0_Up_Task(void);void Key1_Down_Task(void);void Key1_Up_Task(void);void Key2_Down_Task(void);void Key2_Up_Task(void);void WKUP_Down_Task(void);void WWKUP_Up_Task(void);void Key_One_Scan(uint8_t KeyName ,void(*OnKeyOneUp)(void), void(*OnKeyOneDown)(void));
/* USER CODE END Prototypes */#ifdef __cplusplus
}
#endif /*__ GPIO_H__ */
- freertso_demo.c
#include "freertos_demo.h"
#include "main.h"
/*FreeRTOS*********************************************************************************************/#include "key.h"
/******************************************************************************************************/
/*FreeRTOS配置*//* START_TASK 任务 配置* 包括: 任务句柄 任务优先级 堆栈大小 创建任务*/
#define START_TASK_PRIO 1 /* 任务优先级 */
#define START_STK_SIZE 128 /* 任务堆栈大小 */
TaskHandle_t StartTask_Handler; /* 任务句柄 */
void start_task(void *pvParameters); /* 任务函数 *//* TASK1 任务 配置* 包括: 任务句柄 任务优先级 堆栈大小 创建任务*/
#define TASK1_PRIO 1 /* 任务优先级 */
#define TASK1_STK_SIZE 128 /* 任务堆栈大小 */
TaskHandle_t Task1Task_Handler; /* 任务句柄 */
void task1(void *pvParameters); /* 任务函数 *//* TASK2 任务 配置* 包括: 任务句柄 任务优先级 堆栈大小 创建任务*/
#define TASK2_PRIO 1 /* 任务优先级 */
#define TASK2_STK_SIZE 128 /* 任务堆栈大小 */
TaskHandle_t Task2Task_Handler; /* 任务句柄 */
void task2(void *pvParameters); /* 任务函数 *//******************************************************************************************************//* LCD刷屏时使用的颜色 *//*** @brief FreeRTOS例程入口函数* @param 无* @retval 无*/
void freertos_demo(void)
{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* @param pvParameters : 传入参数(未用到)* @retval 无*/
void start_task(void *pvParameters)
{taskENTER_CRITICAL(); /* 进入临界区,关闭中断,此时停止任务调度*//* 创建任务1 */xTaskCreate((TaskFunction_t )task1,(const char* )"task1",(uint16_t )TASK1_STK_SIZE,(void* )NULL,(UBaseType_t )TASK1_PRIO,(TaskHandle_t* )&Task1Task_Handler);/* 创建任务2 */xTaskCreate((TaskFunction_t )task2,(const char* )"task2",(uint16_t )TASK2_STK_SIZE,(void* )NULL,(UBaseType_t )TASK2_PRIO,(TaskHandle_t* )&Task2Task_Handler);vTaskDelete(StartTask_Handler); /* 删除开始任务 */taskEXIT_CRITICAL(); /* 退出临界区,重新开启中断,开启任务调度 */
}/*** @brief task1* @param pvParameters : 传入参数(未用到)* @retval 无*/
void task1(void *pvParameters)
{while(1){HAL_GPIO_TogglePin(LED0_GPIO_Port,LED0_Pin); /* LED0闪烁 */vTaskDelay(1000); /* 延时1000ticks */}
}/*** @brief task2* @param pvParameters : 传入参数(未用到)* @retval 无*/
void task2(void *pvParameters)
{while(1){ Key_One_Scan(Key_Name_Key0,Key0_Up_Task,Key0_Down_Task); //扫描Key0状态Key_One_Scan(Key_Name_Key1,Key1_Up_Task,Key1_Down_Task); //扫描Key1状态Key_One_Scan(Key_Name_Key2,Key2_Up_Task,Key2_Down_Task); //扫描Key2状态}
}
总结下来代码基本上和上一期内容没有变化,只是在按键扫描到后的功能上发生了改变。运行结果如下: