STM32 CubeMX學習:6. 按鍵的外部中斷
系列文章目錄
- 前期的準備
- 點亮 LED
- 閃爍 LED
- 定時器閃爍LED
- PWM控制LED亮度
- 常見的PWM裝置
- 按鍵的外部中斷
- ADC模數轉換
- 序列槽收發
- 序列槽列印遙控器資料
- 未完待續…
文章目錄
- STM32 CubeMX學習:6. 按鍵的外部中斷
- 0 前言
- 1 基礎知識
-
- 1.1 按鍵原理圖
- 1.2 按鍵軟體消抖
- 1.3 外部中斷
- 2 程式的學習
-
- 2.1 按鍵的外部中斷在CubeMX裡的配置
- 2.2 HAL_GPIO_ReadPin函數介紹
- 2.3 中斷回調函數介紹
- 2.4 程式中的前背景
- 2.5 程式流程
- 3 運作效果
- 總結
0 前言
這次的部落格。我們将了解按鍵的硬體原理,學習如何使用STM32的外部中斷功能讀取按鍵輸入,如何使用軟體消抖來消除按鍵輸入産生的電壓的抖動。此外,我們還将再一次借助按鍵中斷的例子了解通過中斷實作程式前背景的思想。So,我們開始吧
1 基礎知識
1.1 按鍵原理圖
在探索者 STM32F4開發闆上的按鍵 KEY0 連接配接在 PE4 上、KEY1 連接配接在 PE3 上、KEY2 連接配接在 PE2 上、KEY_UP連接配接在 PA0 上。如圖所示:

在這塊開發闆上,KEY0、KEY1 和 KEY2 是低電平有效的,而 KEY_UP 是高電平有效的,并且外部都沒有上下拉電阻,是以,我們需要在 STM32F4 内部設定上下拉。
1.2 按鍵軟體消抖
考慮到真實的情況,由于按鍵的機械結構具有彈性,按下時開關不會立刻接通,斷開時也不會立刻斷開,這就導緻按鍵的輸入信号在按下和斷開時都會存在抖動,如果不先将抖動問題進行處理,則讀取的按鍵信号可能會出現錯誤。
為了消除這一問題,我們可以通過軟體消抖或者硬體消抖兩種方式來實作,我們在這裡主要采用軟體濾波的實作方法。軟體濾波的思想其實非常簡單,大家很容易就明白了。抖動的産生在按鍵按下和松開的兩個邊沿時刻,也叫下降沿(電平從高到低)和上升沿(電平從低到高)時刻,是以我們隻需要在邊沿時進行延時,等到按鍵輸入已經穩定再進行信号讀取即可,是不是很簡單呢?
一般采用軟體消抖時,會進行20ms的延時,示波器采集按鍵波形如圖所示。
1.3 外部中斷
單片機的外部中斷通常是由GPIO的電平跳變引起的中斷。在STM32中,每一個GPIO都可以作為外部中斷的觸發源,外部中斷一共有16條線,對應着GPIO的0-15引腳,每一條外部中斷都可以與任意一組的對應引腳相連,但不能重複使用。
例如,外部中斷Line0可以和PA0,PB0,PC0等任意一條0号引腳相連,但如果已經和PA0相連,就不能同時和PB0,PC0其他引腳相連。大家在規劃中斷的時候要格外小心呦。
外部中斷支援GPIO的三種電平跳變的模式,如下所示:
- 上升沿中斷:當GPIO的電平從低電平跳變成高電平時,引發外部中斷。
- 下降沿中斷:當GPIO的電平從高電平跳變成低電平時,引發外部中斷。
- 上升沿和下降沿中斷:當GPIO的電平從低電平跳變成高電平和從高電平跳變成低電平時,都能引發外部中斷。
2 程式的學習
2.1 按鍵的外部中斷在CubeMX裡的配置
STM32的GPIO提供外部中斷功能,當GPIO檢測到電壓跳變時,就會發出中斷觸發信号給STM32,使程式進入外部中斷服務函數。
(今天的程式,我們可以在LED燈的基礎之上完成)
- 将PA0号引腳設定為按鍵的輸入引腳,将其設定為外部中斷模式。
STM32 CubeMX學習:6. 按鍵的外部中斷STM32 CubeMX學習:6. 按鍵的外部中斷0 前言1 基礎知識2 程式的學習3 運作效果總結 - 接着點開GPIO标簽頁,對引腳進行如下設定,将GPIO模式設定為升降沿觸發的外部中斷,上下拉電阻設定為下拉電阻,最後設定使用者标簽為WK_UP。
STM32 CubeMX學習:6. 按鍵的外部中斷STM32 CubeMX學習:6. 按鍵的外部中斷0 前言1 基礎知識2 程式的學習3 運作效果總結
在NVIC标簽頁下,可以看到外部中斷已經開啟。
- 點選“GENERATE CODE”生成代碼
2.2 HAL_GPIO_ReadPin函數介紹
HAL庫提供了讀取引腳上的電平的函數HAL_GPIO_ReadPin。該函數說明如下:
此函數的傳回參數為GPIO_PinState,如果是高電平則傳回GPIO_PIN_SET(對應為1),如果是低電平則傳回GPIO_PIN_RESET(對應為0);
該函數的作用在于傳回引腳電平;
該函數具有兩個參數:
(1)GPIOx——對應GPIO總線,其中x可以是A…I。例如,PH10,則輸入GPIOH
(2)GPIO_Pin——對應引腳數。可以是0-15。例如,PH10,則輸入GPIO_PIN_10
2.3 中斷回調函數介紹
每當産生外部中斷時,程式首先會進入外部中斷服務函數。在stm32f4xx_it.c中,可以找到函數EXTI0_IRQHandler,它通過調用函數HAL_GPIO_EXTI_IRQHandler對中斷類型進行判斷,并對涉及中斷的寄存器進行處理,在處理完成後,它将調用中斷回調函數HAL_GPIO_EXTI_Callback,在中斷回調函數中編寫在此次中斷中需要執行的功能。
2.4 程式中的前背景
在本次實驗中,發現主循環和中斷回調函數中都有代碼。這是一個非常典型的以前背景模式組織的工程。但是,什麼是前背景模式呢?我們可以想象一下一個餐廳的運作模式,餐廳往往分為前台的叫餐員和背景的大廚,前台隻有在來了客人,或者背景做好了一道菜時才會工作,而後廚則一直在忙着做菜,隻有前台來了新的單子或者已經有菜做好了才會停下一會手中的活。
在單片機中,中斷就是前台,而循環就是背景,中斷隻在中斷源産生時才會進行相應的處理,而循環則一直保持工作,隻有被中斷打斷時才會暫停。前背景程式的異同可以參見下表:
前台程式 | 背景程式 | |
---|---|---|
運作方式 | 中斷 | 循環 |
處理的任務類型 | 突發型任務 | 重複型任務 |
任務的特點 | 任務輕,要求響應及時 | 任務重,穩定執行 |
編寫前背景程式時,需要注意盡量避免在前台程式中執行過長或者過于耗時的代碼,讓前台程式能夠盡快執行完畢,以保證其能夠實時響應突發的事件,比較繁雜和耗時的任務一般放在背景程式中處理。
前背景模式可以幫助我們提高單片機的時間使用率,進而組織起比較複雜的工程。
2.5 程式流程
我們今天的程式中前背景任務各自承擔的任務為:
- 前台程式——記錄按鍵翻轉的狀态rising_falling_flag
- 背景程式——執行處理工作,根據記錄的翻轉狀态進行按鍵狀态的判斷
在主循環中,首先通過邊沿檢測标志 rising_falling_flag 來判斷按鍵是處于按下還是松開的邊沿,如果是下降的邊沿(rising_falling_flag == GPIO_PIN_RESET)則将LED燈熄滅,如果是如果是上升的邊沿(rising_falling_flag == GPIO_PIN_SET)則将LED燈點亮。為了防止誤觸發,通過邊沿檢測的判斷之後,程式還會再對電平進行一次讀取,确認下降沿後跟随的是低電平或者上升沿後跟随的是高電平,如果不是則不切換LED狀态。
在中斷回調函數中,利用HAL_GPIO_ReadPin對rising_falling_flag進行指派,進而判斷觸發中斷的是上升沿還是下降沿。
使用exit_flag來實作主循環和中斷回調函數之間的互斥,保證中斷處理函數中的功能(判斷上升/下降沿)隻在主循環完成判斷之後進行,或者主循環的判斷隻在中斷處理函數運作(即檢測到了一次上升沿或者下降沿)之後再進行。
最終main.c檔案如下所示
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* <h2><center>© Copyright (c) 2021 STMicroelectronics.
* All rights reserved.</center></h2>
*
* This software component is licensed by ST under BSD 3-Clause license,
* the "License"; You may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
* opensource.org/licenses/BSD-3-Clause
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "gpio.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);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
//backgroud program
//背景程式
uint8_t exit_flag = 0;
uint8_t rising_falling_flag;
/**
* @brief exit callback function
* @param[in] GPIO_Pin:gpio pin
* @retval none
*/
/**
* @brief 外部中斷回調
* @param[in] GPIO_Pin:引腳号
* @retval none
*/
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if(GPIO_Pin == WK_UP_Pin)
{
if(exit_flag == 0)
{
exit_flag = 1;
rising_falling_flag = HAL_GPIO_ReadPin(WK_UP_GPIO_Port, WK_UP_Pin);
}
}
}
/* 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 */
HAL_GPIO_WritePin( LED0_GPIO_Port, LED0_Pin, GPIO_PIN_RESET );
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
//foreground program
//前台程式
if(exit_flag == 1)
{
exit_flag = 2;
if(rising_falling_flag == GPIO_PIN_RESET)
{
//debouce
//消抖
HAL_Delay(20);
if(HAL_GPIO_ReadPin(WK_UP_GPIO_Port, WK_UP_Pin) == GPIO_PIN_RESET)
{
HAL_GPIO_WritePin(LED0_GPIO_Port, LED0_Pin, GPIO_PIN_RESET);
exit_flag = 0;
}
else
{
exit_flag = 0;
}
}
else if(rising_falling_flag == GPIO_PIN_SET)
{
//debouce
//消抖
HAL_Delay(20);
if(HAL_GPIO_ReadPin(WK_UP_GPIO_Port, WK_UP_Pin) == GPIO_PIN_SET)
{
HAL_GPIO_WritePin(LED0_GPIO_Port, LED0_Pin, GPIO_PIN_SET);
exit_flag = 0;
}
else
{
exit_flag = 0;
}
}
}
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Configure the main internal regulator output voltage
*/
__HAL_RCC_PWR_CLK_ENABLE();
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
/** Initializes the CPU, AHB and APB busses clocks
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLM = 6;
RCC_OscInitStruct.PLL.PLLN = 168;
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
RCC_OscInitStruct.PLL.PLLQ = 4;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB busses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK)
{
Error_Handler();
}
}
/* 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 */
/* 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,
tex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
3 運作效果
一開始LED0為點亮狀态,當按鍵按下時,紅燈熄滅;當按鍵松開時,紅燈點亮
代碼我已經放到了我的GitHub倉庫,如有需要可以下載下傳使用:
CubeMX學習
總結
按鍵也是一種人機互動的操作,同時也可以設定成STM32的輸入模式,可以用于設定某項功能的開啟等作用。外部中斷也是STM32中重要的中斷類型,常常用于傳感器的資料處理,例如陀螺儀,加速度計,磁力計的資料準備中斷,通知stm32已經有新的傳感器資料産生。