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状态
}
}
总结下来代码基本上和上一期内容没有变化,只是在按键扫描到后的功能上发生了改变。运行结果如下: