问题产生
我在学习中断的过程中,使用EXTI15外部中断,在其中加入HAL_Delay();就会发生报错
错误地方
其它地方配置
问题原因
在中断服务例程(ISR)中使用 HAL_Delay()
会导致问题的原因是:
-
阻塞性:
-
HAL_Delay()
是一个阻塞函数,它通过不断地轮询系统时钟来实现延迟。在 ISR 中调用此函数会阻塞中断处理,使得其他中断无法被及时响应,导致系统的实时性降低。 -
中断禁止:
-
在某些微控制器中,进入中断处理程序时会自动禁止其他中断。这意味着在
HAL_Delay()
执行期间,其他中断将无法被触发,从而可能导致系统出现未定义行为或错失重要事件。
在嵌入式系统中,尤其是使用 STM32 等微控制器时,并不是说同一时间只能进行一个中断,而是中断是通过优先级和嵌套来管理的。
中断优先级
- 优先级:每个中断可以被设置优先级。当多个中断同时发生时,只有优先级更高的中断会被处理。
- 中断嵌套:如果启用了中断嵌套(通常在 NVIC 配置中设置),高优先级的中断可以打断低优先级的中断。
HAL_Delay()
不应该放在任何中断服务例程(ISR)中使用。原因如下:
阻塞性:
HAL_Delay()
是一个阻塞函数,会使当前执行的任务停止,直到延迟时间结束。这会导致该中断无法及时响应其他中断。中断嵌套:在许多微控制器中,进入 ISR 时会禁用其他中断。如果在 ISR 中调用
HAL_Delay()
,那么在此期间所有其他中断都会被屏蔽,可能导致系统的实时性下降和错失重要事件。系统稳定性:长时间阻塞可能导致系统不稳定,甚至造成死锁或未定义行为。
解决方法
1.按键状态机模式
状态机是一个抽象概念,表示把一个过程抽象为若干个状态之间的切换,这些状态之间
存在一定的联系。状态机的设计主要包括4个要素:
1. 现态:是指当前所处的状态。
2. 条件:当一个条件满足,将会触发一个动作,或者执行一次状态的迁移。
3. 动作:表示条件满足后执行动作。动作执行完毕后,可以迁移到新的状态,也可以
仍旧保持原状态。动作要素不是必需的,当条件满足后,也可以不执行任何
动作,直接迁移到新状态。
4. 次态:表示条件满足后要迁往的新状态。
(1)定义枚举变量
typedef enum{
IDLE,//按键按下
PRESSED,//按键确定状态
RELEASED//按键释放
}ButtonState;
(2)定义结构体
typedef struct {
ButtonState State;
}KeyState;
(3) 初始化按键状态定义Key变量判断是否开启按键判断
uint8_t Key=0;
void KeyInit(KeyState*KeyKey){
Key=1;
KeyKey->State=IDLE;
}
(4)按键三种状态判断
void Key_State_Judge(uint8_t *Key ,KeyState*KeyKey){
if(*Key==1){
switch(KeyKey->State){
case IDLE:
if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_1)==SET){
KeyKey->State=IDLE;
}else{
KeyKey->State=PRESSED;
}
break;
case PRESSED:
if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_1)==SET){
KeyKey->State=IDLE;
}else{
KeyKey->State=RELEASED;
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_1);
}
break;
case RELEASED:
if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_1)==SET){
KeyKey->State=IDLE;
}else{
KeyKey->State=RELEASED;
}
break;
}
}
}
(5)总代码演示
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* Copyright (c) 2024 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
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();
/* USER CODE BEGIN 2 */
typedef enum{
IDLE,//按键按下
PRESSED,//按键确定状态
RELEASED//按键释放
}ButtonState;
typedef struct {
ButtonState State;
}KeyState;
uint8_t Key=0;
void KeyInit(KeyState*KeyKey){
Key=1;
KeyKey->State=IDLE;
}
void Key_State_Judge(uint8_t *Key ,KeyState*KeyKey){
if(*Key==1){
switch(KeyKey->State){
case IDLE:
if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_1)==SET){
KeyKey->State=IDLE;
}else{
KeyKey->State=PRESSED;
}
break;
case PRESSED:
if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_1)==SET){
KeyKey->State=IDLE;
}else{
KeyKey->State=RELEASED;
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_1);
}
break;
case RELEASED:
if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_1)==SET){
KeyKey->State=IDLE;
}else{
KeyKey->State=RELEASED;
}
break;
}
}
}
KeyState KeyKey;
KeyInit(&KeyKey);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
Key_State_Judge(&Key,&KeyKey);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK)
{
Error_Handler();
}
}
/**
* @brief GPIO Initialization Function
* @param None
* @retval None
*/
static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* USER CODE BEGIN MX_GPIO_Init_1 */
/* USER CODE END MX_GPIO_Init_1 */
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET);
/*Configure GPIO pin : PA1 */
GPIO_InitStruct.Pin = GPIO_PIN_1;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/*Configure GPIO pin : PB1 */
GPIO_InitStruct.Pin = GPIO_PIN_1;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
/* USER CODE BEGIN MX_GPIO_Init_2 */
/* USER CODE END MX_GPIO_Init_2 */
}
/* USER CODE BEGIN 4 */
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
配置图片
2.Delay替换成TIM定时器中断
main.c
#include "stm32f10x.h" // Device header
#include "Key.h"
#include "OLED.h"
#include "Timer.h"
#include "LED.h"
uint8_t KeyNum;
int main(void)
{
OLED_Init();
Timer_Init();
Key_Init();
LED_Init();
while (1)
{
KeyNum=Key_GetNum();
if(KeyNum==1)
{
LED1_Turn();
}
}
}
Key.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "Timer.h"
uint16_t Num;
void Delay_TIM()
{
TIM_Cmd(TIM2, ENABLE);
if(Num==20)
{
Num=0;
TIM_Cmd(TIM2, DISABLE);
}
}
/**
* 函 数:按键初始化
* 参 数:无
* 返 回 值:无
*/
void Key_Init(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //开启GPIOB的时钟
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_11;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure); //将PB1和PB11引脚初始化为上拉输入
}
/**
* 函 数:按键获取键码
* 参 数:无
* 返 回 值:按下按键的键码值,范围:0~2,返回0代表没有按键按下
* 注意事项:此函数是阻塞式操作,当按键按住不放时,函数会卡住,直到按键松手
*/
uint8_t Key_GetNum(void)
{
uint8_t KeyNum = 0; //定义变量,默认键码值为0
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0) //读PB1输入寄存器的状态,如果为0,则代表按键1按下
{
Delay_TIM();
while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0); //等待按键松手
Delay_TIM(); //置键码为1
KeyNum=1;
}
return KeyNum; //返回键码值,如果没有按键按下,所有if都不成立,则键码为默认值0
}
void TIM2_IRQHandler(){
if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET){
Num++;
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}
Key.h
#ifndef __KEY_H
#define __KEY_H
void Key_Init(void);
uint8_t Key_GetNum(void);
void Delay_TIM();
#endif
Timer.h
#include "stm32f10x.h" // Device header
void Timer_Init(void){
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
TIM_InternalClockConfig(TIM2);
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
TIM_TimeBaseInitStruct.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseInitStruct.TIM_CounterMode=TIM_CounterMode_Up;
TIM_TimeBaseInitStruct.TIM_Period=100-1;
TIM_TimeBaseInitStruct.TIM_Prescaler=720-1;
TIM_TimeBaseInitStruct.TIM_RepetitionCounter=0;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStruct);
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel=TIM2_IRQn ;
NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=2;
NVIC_InitStruct.NVIC_IRQChannelSubPriority=1;
NVIC_Init(&NVIC_InitStruct);
TIM_Cmd(TIM2, DISABLE);
}
Timer.c
#ifndef __Timer_H
#define __Timer_H
void Timer_Init(void);
#endif