STM32F1+HAL库+FreeTOTS学习5——内核中断管理及中断控制函数

发布于:2024-07-05 ⋅ 阅读:(39) ⋅ 点赞:(0)

上一期我们学习了FreeRTOS中任务挂起与恢复,在中断服务程序中恢复任务过程中,尤其强调了中断必须设置优先级在5-15之间,且中断优先级分组为4,这是为什么呢,让我们一起来探索一些吧。

中断简介

关于STM32中中断的基本概念,我这里就不做赘述了,可以参考 :夜深人静学32系列10——GPIO中断/NVIC/EXTI/SYSCFG详解,外部中断控制LED ,这里只介绍几个重要的内容。

中断优先级寄存器拓展

  1. 中断优先级

在CreteX-M3/4/7的内核中,一个支持16(内核中断)和最多240和外部中断输入,每个中断都有八个位来控制其优先级,理论上最大优先级可以达到256,但是其具体值都是由芯片厂商根据需求裁剪提供给用户的。
在这里插入图片描述

我们本次使用的STM32F103RCT6的芯片,它的中断控制器(NVIC)一共支持68种外部中断,不包含Cretex-M3自带的16个内核中断,同时支持16个可编程中断优先级(使用4为中断优先级)

在这里插入图片描述

  • 这里需要注意:中断优先级和FreeRTOS中任务优先级概念不同
  • 中断优先级数字越小,优先级越高
  • FreeRTOS任务优先级数字越大,优先级越高。
    在这里插入图片描述
  1. 系统异常优先级寄存器
    在Cretex-M3内核中,除了240个外部中断优先级寄存器(实际上是八个32位寄存器,最后一个没用完),还有三个系统内核中断优先级寄存器:SHPR1、SHPR2、SHPR3
    在这里插入图片描述

FreeRTOS中PendSV和Systick中断优先级配置

前面我们有讲过FreeRTOS的一个重要作用就是实现多任务运行,但其实同一时刻只有一个任务在运行,只不过任务之间切换速度很快,给人一种多任务同时运行的假象。那么这里面有两个很重要的东西,一个时心跳节拍和任务调度。分别由Systick中断和PendSV中断来控制。

  1. SysTick中断:FreeRTOS中有自己独立的时钟,同时我们之前学习过一个时间片的概念,即每个任务执行的时间,当时间片用完,就会产生一个Systick中断,里面会重新开启任务调度,执行优先级最高的任务,像这样,每经过一个时间片,系统就会重新选择需要运行的任务,我们把它叫做心跳节拍,心跳节拍的频率由宏:configTICK_RATE_HZ 决定,单位为HZ,比如设置为1000,则表示一个时间片长度为1ms。
  2. PendSV中断:PendSV则是用来完成任务切换功能,所需要的中断。
  3. 在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中断管理函数

  1. 关中断函数
	portDISABLE_INTERRUPTS();
	printf("屏蔽中断5-15\r\n");
/*
屏蔽优先级为5 - 15的中断
*/
  1. 开中断服务函数
	portENABLE_INTERRUPTS();
  printf("开启中断5-15\r\n");

/*
开启所有中断相应。
*/

在这里插入图片描述

代码验证

下面是任务要求

在这里插入图片描述

定时器配置,其他配置和代码参考上一期内容: STM32F1+HAL库+FreeTOTS学习4——任务挂起与恢复

在这里插入图片描述

下面是代码实现部分,只展示逻辑部分,和定时器中断相关代码

  1. 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 */
}

  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 */

  1. 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__ */

  1. 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状态

    }
}


总结下来代码基本上和上一期内容没有变化,只是在按键扫描到后的功能上发生了改变。运行结果如下:

在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到