開發(fā)環(huán)境:
MDK:凱爾 5.30
STM32立方體MX:V6.4.0
單片機(jī):STM32F103ZET6
5.1普通方式
5.1.1 普通方式工作原理
按鍵 GPIO 端口有兩個(gè)方案可以選擇,一是采用上拉輸入模式,因?yàn)榘存I在沒按下的時(shí)候,是默認(rèn)為高電平的,采且內(nèi)部上拉模式正好符合這個(gè)要求。 第二個(gè)方案是直接采用浮空輸入模式,因?yàn)榘凑沼布娐穲D,在芯片外部接了上拉電阻,其實(shí)就沒必要再配置成內(nèi)部上拉輸入模式了,因?yàn)樵谕獠可侠c內(nèi)部上拉效果是一樣的。
5.1.2普通方式實(shí)現(xiàn)-標(biāo)準(zhǔn)庫(kù)
完整代碼請(qǐng)參附件,這里只貼出核心代碼。
- GPIO 初始化配置
void Key_GPIO_Config(void)
{
GPIO_InitTypeDefGPIO_InitStructure;
/*開啟按鍵端口(PA)的時(shí)鐘*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitStructure.GPIO_Pin= GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode= GPIO_Mode_IPU;//上拉輸入
GPIO_Init(GPIOA,&GPIO_InitStructure);
}
Key_GPIO_Config() 與 LED 的 GPIO 初始化函數(shù) LED_GPIO_Config() 類似,區(qū)別只是在這個(gè)函數(shù)中,要開啟的 GPIO 的端口時(shí)鐘不一樣,并且把檢測(cè)按鍵用的引腳 Pin 的模式設(shè)置為適合按鍵應(yīng)用的上拉輸入模式(由于接了外部上拉電阻,也可以使用浮空輸入,讀者可自行修改代碼做實(shí)驗(yàn))。 若 GPIO 被設(shè)置為輸入模式,不需要設(shè)置 GPIO 端口的最大輸出速度,當(dāng)然,如果配置了這個(gè)速度也沒關(guān)系,GPIO_Init() 函數(shù)會(huì)自動(dòng)忽略它。 在 RCC_APB2PeriphClockCmd()和 GPIO_InitStructure.GPIO_Pin 的輸入?yún)?shù)設(shè)置之中,我們可以用符號(hào)“|”,同時(shí)配置多個(gè)參數(shù)。 如:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOG,ENABLE);
輸入?yún)?shù)為RCC_APB2Periph_GPIOB| RCC_APB2Periph_GPIOG ,這樣調(diào)用之后,就把 GPIOB 和 GPIOG 的時(shí)鐘都開啟了。
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;
以上代碼則表示將要同時(shí)配置 GPIOG 端口的 Pin5 和 Pin6。
- 按鍵消抖
uint8_t Key_Scan(GPIO_TypeDef*GPIOx,u16 GPIO_Pin,uint8_t Down_state)
{
/*檢測(cè)是否有按鍵按下 */
if(GPIO_ReadInputDataBit(GPIOx,GPIO_Pin)== Down_state )
{
/*延時(shí)消抖*/
Key_Delay(10000);
if(GPIO_ReadInputDataBit(GPIOx,GPIO_Pin)== Down_state )
{
/*等待按鍵釋放 */
while(GPIO_ReadInputDataBit(GPIOx,GPIO_Pin)== Down_state);
return KEY_ON;
}
else
returnKEY_OFF;
}
else
returnKEY_OFF;
}
相信延時(shí)消抖的原理大家在學(xué)習(xí)其他單片機(jī)時(shí)就已經(jīng)了解了,本函數(shù)的功能就是掃描輸入?yún)?shù)中指定的引腳,檢測(cè)其電平變化,并作延時(shí)消抖處理,最終對(duì)按鍵消息進(jìn)行確認(rèn)。
- 利用GPIO_ReadInputDataBit() 讀取輸入數(shù)據(jù),若從相應(yīng)引腳讀取的數(shù)據(jù)等于 0(KEY_ON),低電平,表明可能有按鍵按下,調(diào)用延時(shí)函數(shù)。否則返回 KEY_OFF,表示按鍵沒有被按下。
- 延時(shí)之后再次利用GPIO_ReadInputDataBit() 讀取輸入數(shù)據(jù),若依然為低電平,表明確實(shí)有按鍵被按下了。否則返回 KEY_OFF,表示按鍵沒有被按下。
- 循環(huán)調(diào)用 GPIO_ReadInputDataBit()一直檢測(cè)按鍵的電平,直至按鍵被釋放,被釋放后,返回表示按鍵被按下的標(biāo)志 KEY_ON。以上是按鍵消抖的流程,調(diào)用了一個(gè)庫(kù)函數(shù)GPIO_ReadInputDataBit()。輸入?yún)?shù)為要讀取的端口、引腳,返回引腳的輸入電平狀態(tài),高電平為 1,低電平為 0。
5.1.3普通方式實(shí)現(xiàn)-HAL庫(kù)
5.1.3.1 STM32Cube生成工程
關(guān)于如何使用STM32Cube新建工程在前文已經(jīng)講解過了,這里直說配置GPIO部分內(nèi)容。本文要實(shí)現(xiàn)按鍵功能,通過按鍵實(shí)現(xiàn)LED的亮滅。我門在第一個(gè)程序的基礎(chǔ)上進(jìn)行修改即可,不必每次都新建工程。根據(jù)按鍵電路,KEY1的引腳是PA0,我們將PA0的GPIO設(shè)置為下拉的輸入模式,保留3個(gè)LED的GPIO配置。
初始化基本配置后,我們重新生成工程,接下來按鍵編程。
5.1.3.2具體代碼分析
在看代碼前,我們先看看按鍵掃描編程的流程:
1)使能按鍵引腳時(shí)鐘,本文的引腳是PA0;
2)初始化按鍵,即初始化GPIO機(jī)構(gòu)體,在前文已經(jīng)詳細(xì)講解過了;
3)在無限循環(huán)中不斷讀取PA0的電平值,同時(shí)進(jìn)行按鍵消抖;
4)判斷按鍵被按下時(shí),進(jìn)行相應(yīng)的處理。
- GPIO 初始化配置
static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_GPIOG_CLK_ENABLE();
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET);
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOG, GPIO_PIN_6|GPIO_PIN_7, GPIO_PIN_RESET);
/*Configure GPIO pin : PA0 */
GPIO_InitStruct.Pin = GPIO_PIN_0;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/*Configure GPIO pin : PB0 */
GPIO_InitStruct.Pin = GPIO_PIN_0;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
/*Configure GPIO pins : PG6 PG7 */
GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOG, &GPIO_InitStruct);
}
按鍵與 LED 的 GPIO 初始化函數(shù)類似,區(qū)別只是在這個(gè)函數(shù)中,要開啟的 GPIO 的端口時(shí)鐘不一樣,并且把檢測(cè)按鍵用的引腳 Pin 的模式設(shè)置為適合按鍵應(yīng)用的上拉輸入模式(由于接了外部上拉電阻,也可以使用浮空輸入,讀者可自行修改代碼做實(shí)驗(yàn))。若 GPIO 被設(shè)置為輸入模式,不需要設(shè)置 GPIO 端口的最大輸出速度。
- 按鍵狀態(tài)監(jiān)測(cè)及按鍵消抖
uint8_t Key_Scan(void)
{
if(HAL_GPIO_ReadPin(KEY_GPIO,GPIO_PIN_0)== KEY_DOWN_LEVEL )
{
HAL_Delay(10);
if(HAL_GPIO_ReadPin(KEY_GPIO,GPIO_PIN_0)== KEY_DOWN_LEVEL )
{
while(HAL_GPIO_ReadPin(KEY_GPIO,GPIO_PIN_0)== KEY_DOWN_LEVEL);
return KEY_DOWN;
}
else
{
returnKEY_UP;
}
}
returnKEY_UP;
}
相信延時(shí)消抖的原理大家在學(xué)習(xí)其他單片機(jī)時(shí)就已經(jīng)了解了,本函數(shù)的功能就是掃描輸入?yún)?shù)中指定的引腳,檢測(cè)其電平變化,并作延時(shí)消抖處理,最終對(duì)按鍵消息進(jìn)行確認(rèn)。
- 利用HAL_GPIO_ReadPin()函數(shù)讀取輸入數(shù)據(jù),若從相應(yīng)引腳讀取的數(shù)據(jù)等于 0(KEY_DOWN),低電平,表明可能有按鍵按下,調(diào)用延時(shí)函數(shù)。否則返回 KEY_UP,表示按鍵沒有被按下。
- 延時(shí)之后再次利用 HAL_GPIO_ReadPin()函數(shù)讀取輸入數(shù)據(jù),若依然為低電平,表明確實(shí)有按鍵被按下了。否則返回 KEY_UP,表示按鍵沒有被按下。
- 循環(huán)調(diào)用HAL_GPIO_ReadPin()函數(shù)一直檢測(cè)按鍵的電平,直至按鍵被釋放,被釋放后,返回表示按鍵被按下的標(biāo)志 KEY_DOWN。以上是按鍵消抖的流程,調(diào)用了一個(gè)庫(kù)函數(shù) HAL_GPIO_ReadPin()函數(shù)。輸入?yún)?shù)為要讀取的端口、引腳,返回引腳的輸入電平狀態(tài),高電平為 1,低電平為 0。
Main函數(shù)如下:
/* USER CODE END Header */
/* Includes------------------------------------------------------------------*/
#include "main.h"
/* Private includes----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
/* USER CODE END Includes */
/* Private typedef-----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
typedef enum{
KEY_UP= 0,
KEY_DOWN= 1,
}KEYState_Type;
/* USER CODE END PTD */
/* Private define------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define KEY_GPIO GPIOA
#define KEY_GPIO_PIN GPIO_PIN_0
#define KEY_DOWN_LEVEL 1
/* 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-----------------------------------------------*/
uint8_t Key_Scan(void);
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 entrypoint.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCUConfiguration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and theSystick. */
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 */
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
if(KEY_DOWN_LEVEL== Key_Scan())
{
HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_0);
HAL_GPIO_TogglePin(GPIOG,GPIO_PIN_6);
HAL_GPIO_TogglePin(GPIOG,GPIO_PIN_7);
}
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
/**
* @brief Key Scan
* @retval uint8_t
*/
uint8_t Key_Scan(void)
{
if(HAL_GPIO_ReadPin(KEY_GPIO,GPIO_PIN_0)== KEY_DOWN_LEVEL )
{
HAL_Delay(10);
if(HAL_GPIO_ReadPin(KEY_GPIO,GPIO_PIN_0)== KEY_DOWN_LEVEL )
{
while(HAL_GPIO_ReadPin(KEY_GPIO,GPIO_PIN_0)== KEY_DOWN_LEVEL);
return KEY_DOWN;
}
else
{
returnKEY_UP;
}
}
returnKEY_UP;
}
/**
* @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 specifiedparameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
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_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) !=HAL_OK)
{
Error_Handler();
}
}
/**
* @brief GPIO Initialization Function
* @param None
* @retval None
*/
static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_GPIOG_CLK_ENABLE();
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET);
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOG, GPIO_PIN_6|GPIO_PIN_7, GPIO_PIN_RESET);
/*Configure GPIO pin : PA0 */
GPIO_InitStruct.Pin = GPIO_PIN_0;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/*Configure GPIO pin : PB0 */
GPIO_InitStruct.Pin = GPIO_PIN_0;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
/*Configure GPIO pins : PG6 PG7 */
GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOG, &GPIO_InitStruct);
}
/* USER CODE BEGIN 4 */
/* USER CODE END 4 */
/**
* @brief This function isexecuted 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 returnstate */
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of thesource file and the source line number
* where the assert_paramerror has occurred.
* @param file: pointer to thesource file name
* @param line: assert_param errorline source number
* @retval None
*/
void assert_failed(uint8_t*file, uint32_t line)
{
/* USERCODE BEGIN 6 */
/* User can add his own implementation to report the file name and linenumber,
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****/
5.2 EXTI方式
5.2.1 EXTI的工作原理
EXTI(ExternalInterrupt)就是指外部中斷,通過 GPIO 檢測(cè)輸入脈沖,引起中斷事件,打斷原來的代碼執(zhí)行流程,進(jìn)入到中斷服務(wù)函數(shù)中進(jìn)行處理,處理完后再返回到中斷之前的代碼中執(zhí)行。
- STM32 的中斷和異常
Cortex 內(nèi)核具有強(qiáng)大的異常響應(yīng)系統(tǒng),它把能夠打斷當(dāng)前代碼執(zhí)行流程的事件分為異常(exception)和中斷(interrupt),并把它們用一個(gè)表管理起來,編號(hào)為 0 ~ 15 的稱為內(nèi)核異常,而 16 以上的則稱為外部中斷(外是相對(duì)內(nèi)核而言),這個(gè)表就稱為中斷向量表。
而 STM32 對(duì)這個(gè)表重新進(jìn)行了編排,把編號(hào)從–3 至 6 的中斷向量定義為系統(tǒng)異常,編號(hào)為負(fù)的內(nèi)核異常不能被設(shè)置優(yōu)先級(jí),如復(fù)位(Reset)、不可屏蔽中斷(NMI)、硬錯(cuò)誤(Hardfault)。從編號(hào) 7 開始的為外部中斷,這些中斷的優(yōu)先級(jí)都是可以自行設(shè)置的。詳細(xì)的 STM32 中斷向量表見下表。
- NVIC 中斷控制器
STM32 的中斷如此之多,配置起來并不容易,因此我們需要一個(gè)強(qiáng)大而方便的中斷控制器 NVIC (Nested VectoredInterrupt Controller)。NVIC 是屬于 Cortex 內(nèi)核的器件,不可屏蔽中斷(NMI)和外部中斷都由它來處理,而 SYSTICK 不是由 NVIC 來控制的。
- NVIC 結(jié)構(gòu)體成員
當(dāng)我們要使用 NVIC 來配置中斷時(shí),自然想到 ST 庫(kù)肯定也已經(jīng)把它封裝成庫(kù)函數(shù)了。查找?guī)鞄椭臋n,發(fā)現(xiàn)在 Modules->STM32F10x_StdPeriph_Driver->misc 查找到一個(gè)NVIC_Init() 函數(shù)。對(duì) NVIC 初始化,首先要定義并填充一個(gè) NVIC_InitTypeDef類型的結(jié)構(gòu)體。這個(gè)結(jié)構(gòu)體有 4 個(gè)成員,見下表。
結(jié)構(gòu)體成員名稱 | 描述 |
---|---|
NVIC_IRQChannel | 需要配置的中斷向量 |
NVIC_IRQChannelCmd | 使能或關(guān)閉相應(yīng)中斷向量的中斷響應(yīng) |
NVIC_IRQChannelPreemptionPriority | 配置相應(yīng)中斷向量搶占優(yōu)先級(jí) |
NVIC_IRQChannelSubPriority | 配置相應(yīng)中斷向量的響應(yīng)優(yōu)先級(jí) |
前面兩個(gè)結(jié)構(gòu)體成員都很好理解,首先要用 NVIC_IRQChannel 參數(shù)來選擇將要配置的中斷向量,用NVIC_IRQChannelCmd 參數(shù)來進(jìn)行使能(ENABLE)或關(guān)閉(DISABLE)該中斷。在NVIC_IRQChannelPreemptionPriority 成員要配置中斷向量的搶占優(yōu)先級(jí),在NVIC_IRQChannelSubPriority需要配置中斷向量的響應(yīng)優(yōu)先級(jí)。對(duì)于中斷的配置,最重要的便是配置其優(yōu)先級(jí),但 STM32 的同一個(gè)中斷向量為什么需要設(shè)置兩種優(yōu)先級(jí)?這兩種優(yōu)先級(jí)有什么區(qū)別?
- 搶占優(yōu)先級(jí)和響應(yīng)優(yōu)先級(jí)
STM32 的中斷向量具有兩個(gè)屬性,一個(gè)為搶占屬性,另一個(gè)為響應(yīng)屬性,其屬性編號(hào)越小,表明它的優(yōu)先級(jí)別越高。
搶占,是指打斷其他中斷的屬性,即因?yàn)榫哂羞@個(gè)屬性會(huì)出現(xiàn)嵌套中斷(在執(zhí)行中斷服務(wù)函數(shù) A 的過程中被中斷 B 打斷,執(zhí)行完中斷服務(wù)函數(shù) B 再繼續(xù)執(zhí)行中斷服務(wù)函數(shù)A),搶占屬性由NVIC_IRQChannelPreemptionPriority 的參數(shù)配置。
而響應(yīng)屬性則應(yīng)用在搶占屬性相同的情況下,當(dāng)兩個(gè)中斷向量的搶占優(yōu)先級(jí)相同時(shí),如果兩個(gè)中斷同時(shí)到達(dá),則先處理響應(yīng)優(yōu)先級(jí)高的中斷,響應(yīng)屬性由NVIC_IRQChannelSubPriority參數(shù)配置。例如,現(xiàn)在有三個(gè)中斷向量,見下表。
中斷向量 | 搶占優(yōu)先級(jí) | 響應(yīng)優(yōu)先級(jí) |
---|---|---|
A | 0 | 0 |
B | 1個(gè) | 0 |
C | 1個(gè) | 1個(gè) |
若內(nèi)核正在執(zhí)行 C 的中斷服務(wù)函數(shù),則它能被搶占優(yōu)先級(jí)更高的中斷 A 打斷,由于 B和 C 的搶占優(yōu)先級(jí)相同,所以 C 不能被 B 打斷。但如果 B 和 C 中斷是同時(shí)到達(dá)的,內(nèi)核就會(huì)首先響應(yīng)響應(yīng)優(yōu)先級(jí)別更高的 B 中斷
- NVIC 的優(yōu)先級(jí)組
在配置優(yōu)先級(jí)的時(shí)候,還要注意一個(gè)很重要的問題,即中斷種類的數(shù)量。NVIC 只可以配置 16 種中斷向量的優(yōu)先級(jí),也就是說,搶占優(yōu)先級(jí)和響應(yīng)優(yōu)先級(jí)的數(shù)量由一個(gè) 4 位的數(shù)字來決定,把這個(gè) 4 位數(shù)字的位數(shù)分配成搶占優(yōu)先級(jí)部分和響應(yīng)優(yōu)先級(jí)部分。有 5 組分配方式:
- 第 0 組:所有 4 位用來配置響應(yīng)優(yōu)先級(jí)。即 16 種中斷向量具有都不相同的響應(yīng)優(yōu)先級(jí)。
- 第 1 組:最高 1 位用來配置搶占優(yōu)先級(jí),低 3 位用來配置響應(yīng)優(yōu)先級(jí)。 表示有 2 ^1^ =2 種級(jí)別的搶占優(yōu)先級(jí)(0 級(jí),1 級(jí)),有 2 ^3^ =8 種響應(yīng)優(yōu)先級(jí),即在 16 種中斷向量之中,有8 種中斷,其搶占優(yōu)先級(jí)都為 0 級(jí),而它們的響應(yīng)優(yōu)先級(jí)分別為 0 ~ 7,其余 8 種中斷向量的搶占優(yōu)先級(jí)則都為 1 級(jí),響應(yīng)優(yōu)先級(jí)別分別為 0~7。
- 第 2 組:2 位用來配置搶占優(yōu)先級(jí),2 位用來配置響應(yīng)優(yōu)先級(jí)。即 2 ^2^ =4 種搶占優(yōu)先級(jí),2 ^2^ =4 種響應(yīng)優(yōu)先級(jí)。
- 第 3 組:高 3 位用來配置搶占優(yōu)先級(jí),最低 1 位用來配置響應(yīng)優(yōu)先級(jí)。即有 8 種搶占優(yōu)先級(jí),2 種響應(yīng) 2 優(yōu)先級(jí)。
- 第 4 組:所有 4 位用來配置搶占優(yōu)先級(jí),即 NVIC 配置的 2 ^4^ =16 種中斷向量都是只有搶占屬性,沒有響應(yīng)屬性。
要配置這些優(yōu)先級(jí)組,可以采用庫(kù)函數(shù)NVIC_PriorityGroupConfi g(),可輸入的參數(shù)為NVIC_PriorityGroup_0 ~NVIC_PriorityGroup_4,分別為以上介紹的 5 種分配組。
于是,有讀者覺得疑惑了,如此強(qiáng)的 STM32,所有GPIO都能夠配置成外部中斷,USART、ADC 等外設(shè)也有中斷,而 NVIC 只能配置 16 種中斷向量,那么在某個(gè)工程中使用超過 16 個(gè)中斷怎么辦呢? 注意 NVIC 能配置的是 16 種中斷向量,而不是16 個(gè),當(dāng)工程中有超過 16 個(gè)中斷向量時(shí),必然有兩個(gè)以上的中斷向量是使用相同的中斷種類,而具有相同中斷種類的中斷向量不能互相嵌套。
STM2 單片機(jī)的所有 I/O 端口都可以配置為 EXTI 中斷模式,用來捕捉外部信號(hào),可以配置為下降沿中斷、上升沿中斷和上升下降沿中斷這三種模式。 它們以圖 3- 2 所示方式連接到 16 個(gè)外部中斷 / 事件線上。
- EXTI 外部中斷
STM32 的所有 GPIO 都引入到 EXTI 外部中斷線上,使得所有的 GPIO 都能作為外部中斷的輸入源。 GPIO 與 EXTI 的連接方式見下圖。
觀察下圖可知,PA0 ~ PG0 連接到 EXTI0 、PA1 ~ PG1 連接到 EXTI1、……、PA15 ~ PG15 連接到 EXTI15。 這里大家要注意的是:PAx ~ PGx 端口的中斷事件都連接到了 EXTIx,即同一時(shí)刻 EXTIx 只能響應(yīng)一個(gè)端口的事件觸發(fā),不能夠同一時(shí)間響應(yīng)所有GPIO 端口的事件,但可以分時(shí)復(fù)用。 它可以配置為上升沿觸發(fā)、下降沿觸發(fā)或雙邊沿觸發(fā)。 EXTI 最普通的應(yīng)用就是接上一個(gè)按鍵,設(shè)置為下降沿觸發(fā),用中斷來檢測(cè)按鍵。
5.2.2 EXTI的寄存器描述
EXTI 寄存器的寄存器主要有6個(gè),下面分別描述。
- 中斷屏蔽寄存器(EXTI_IMR)
- 事件屏蔽寄存器(EXTI_EMR)
- 上升沿觸發(fā)選擇寄存器(EXTI_RTSR)
注意: 外部喚醒線是邊沿觸發(fā)的,這些線上不能出現(xiàn)毛刺信號(hào)。 在寫EXTI_RTSR寄存器時(shí),在外部中斷線上的上升沿信號(hào)不能被識(shí)別,掛起位也不會(huì)被置位。 在同一中斷線上,可以同時(shí)設(shè)置上升沿和下降沿觸發(fā)。 即任一邊沿都可觸發(fā)中斷
- 下降沿觸發(fā)選擇寄存器(EXTI_FTSR)
注意: 外部喚醒線是邊沿觸發(fā)的,這些線上不能出現(xiàn)毛刺信號(hào)。 在寫EXTI_FTSR寄存器時(shí),在外部中斷線上的下降沿信號(hào)不能被識(shí)別,掛起位不會(huì)被置位。 在同一中斷線上,可以同時(shí)設(shè)置上升沿和下降沿觸發(fā)。 即任一邊沿都可觸發(fā)中斷。
- 軟件中斷事件寄存器(EXTI_SWIER)
- 掛起寄存器(EXTI_PR)
5.2.3 EXTI方式實(shí)現(xiàn)-標(biāo)準(zhǔn)庫(kù)
- 部署外部掛起
現(xiàn)在我們重點(diǎn)分析 EXTI_PA0_Config() 這個(gè)函數(shù),它完成了配置一個(gè) I/O 為 EXTI 中斷的一般步驟,主要有以下功能:
1)使能 EXTIx 線的時(shí)鐘和第二功能 AFIO 時(shí)鐘。
2)配置 EXTIx 線的中斷優(yōu)先級(jí)。
3)配置 EXTI 中斷線 I/O。
4)選定要配置為 EXTI 的 I/O 口線和 I/O 口的工作模式。
5)EXTI 中斷線工作模式配置。
void EXTI_PA0_Config(void)
{
GPIO_InitTypeDefGPIO_InitStructure;
EXTI_InitTypeDefEXTI_InitStructure;
/*config the extiline clock and AFIO clock */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA| RCC_APB2Periph_AFIO,ENABLE);
/*config the NVIC */
NVIC_Configuration();
/*EXTI line gpio config*/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; // 下拉輸入
GPIO_Init(GPIOA, &GPIO_InitStructure);
/*EXTI line mode config */
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);
EXTI_InitStructure.EXTI_Line = EXTI_Line0;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;//下降沿觸發(fā)中斷
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
}
EXTI_PA0_Config()代碼中,配置好 NVIC 后,還要對(duì) GPIOA 進(jìn)行初始化,這部分和按鍵輪詢的設(shè)置類似。
接下來,調(diào)用GPIO_EXTILineConfi g() 函數(shù)把 GPIOA、Pin0 設(shè)置為 EXTI 輸入線。 選擇好了 GPIO,開始填寫 EXTI 的初始化結(jié)構(gòu)體。 從這些參數(shù)的名字,相信讀者已經(jīng)知道如何把它應(yīng)用到按鍵檢測(cè)中。
1). EXTI_Line =EXTI_Line0 :給 EXTI_Line 成員賦值。 選擇 EXTI_Line0 線進(jìn)行配置,因?yàn)榘存I的 PA0 連接到了 EXTI_Line0。
2). EXTI_Mode =EXTI_Mode_Interrupt :給 EXTI_Mode 成員賦值。 把 EXTI_Line0的模式設(shè)置為中斷模式(EXTI_Mode_Interrupt)。 這個(gè)結(jié)構(gòu)體成員也可以賦值為事件模式EXTI_Mode_Event ,這個(gè)模式不會(huì)立刻觸發(fā)中斷,而只是在寄存器上把相應(yīng)的事件標(biāo)志位置 1,應(yīng)用這個(gè)模式需要不停地查詢相應(yīng)的寄存器。
3). EXTI_Trigger =EXTI_Trigger_Falling :給 EXTI_Trigger 成員賦值。 把觸發(fā)方式(EXTI_Trigger)設(shè)置為下降沿觸發(fā)(EXTI_Trigger_Falling)。
4) . EXTI_LineCmd =ENABLE :給 EXTI_LineCmd 成員賦值。 把 EXTI_LineCmd 設(shè)置為使能。
5)最后調(diào)用 EXTI_Init() 把 EXTI 初始化結(jié)構(gòu)體的參數(shù)寫入寄存器。
- AFIO 時(shí)鐘
代碼中調(diào)用RCC_APB2PeriphClockCmd()時(shí)還輸入了參數(shù)RCC_APB2Periph_AFIO,表示開啟 AFIO的時(shí)鐘。
AFIO (alternate-functionI/O),指 GPIO 端口的復(fù)用功能,GPIO 除了用作普通的輸入輸出(主功能),還可以作為片上外設(shè)的復(fù)用輸入輸出,如串口、ADC,這些就是復(fù)用功能。 大多數(shù) GPIO 都有一個(gè)默認(rèn)復(fù)用功能,有的 GPIO 還有重映射功能。 重映射功能是指把原來屬于 A 引腳的默認(rèn)復(fù)用功能,轉(zhuǎn)移到 B 引腳進(jìn)行使用,前提是 B 引腳具有這個(gè)重映射功能。
當(dāng)把 GPIO 用作 EXTI 外部中斷或使用重映射功能的時(shí)候,必須開啟 AFIO 時(shí)鐘,而在使用默認(rèn)復(fù)用功能的時(shí)候,就不必開啟 AFIO 時(shí)鐘了。
- NVIC 初始化配置
static voidNVIC_Configuration(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
/* Configure one bit for preemption priority */
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
/* 配置中斷源 */
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
本代碼中調(diào)用了NVIC_PriorityGroupConfi g() 庫(kù)函數(shù),把 NVIC 中斷優(yōu)先級(jí)分組設(shè)置為第 1 組。 接下來開始向 NVIC 初始化結(jié)構(gòu)體寫入?yún)?shù) . NVIC_IRQChannel=EXTI0_IRQn,表示要配置的為 EXTI 第 1 線的中斷向量。 因?yàn)榘存I PA0 對(duì)應(yīng)的 EXTI 線為EXTI0。 這些可寫入的參數(shù)可以在 stm32f10x.h 文件的 IRQn 類型定義中查找到。 然后配置搶占優(yōu)先級(jí)和響應(yīng)優(yōu)先級(jí),因?yàn)檫@個(gè)工程簡(jiǎn)單,就直接把它設(shè)置為最高級(jí)中斷。 填充完結(jié)構(gòu)體,別忘記最后要調(diào)用 NVIC_Init() 函數(shù)來向寄存器寫入?yún)?shù)。 這里要注意的是,如果用的 IO 口是 IO0 ~ IO4,那么對(duì)應(yīng)的中斷向量是 EXTI0_IRQn ~ EXTI4_IRQn,如果用的 IO 是I05 ~ IO9 中的一個(gè)的話,對(duì)應(yīng)的中斷向量只能是 EXTI9_5_IRQn, 如果用的 IO 是 I010~IO15中的一個(gè)的話,對(duì)應(yīng)的中斷向量只能是 EXTI15_10_IRQn。 舉例:如果 PE5 或者 PE6 作為EXTI 中斷口,那么對(duì)應(yīng)的中斷向量都是 EXTI9_5_IRQn,在同一時(shí)刻只能相應(yīng)來自一個(gè)IO 的 EXTI 中斷。
- 編寫中斷服務(wù)函數(shù)
在這個(gè) EXTI 設(shè)置中我們把 PA0 連接到內(nèi)部的 EXTI0,GPIO 配置為上拉輸入,工作在下降沿中斷。在外圍電路上我們將 PA0 接到了 key上。當(dāng)按鍵沒有按下時(shí),PA0 始終為高,當(dāng)按鍵按下時(shí) PA0 變?yōu)榈?,從?PA0 上產(chǎn)生一個(gè)下降沿跳變,EXTI0 會(huì)捕捉到這一跳變,并產(chǎn)生相應(yīng)的中斷,中斷服務(wù)程序在 stm32f10x_it.c 中實(shí)現(xiàn)。stm32f10x_it.c 文件是專門用來存放中斷服務(wù)函數(shù)的。文件中默認(rèn)只有幾個(gè)關(guān)于系統(tǒng)異常的中斷服務(wù)函數(shù),而且都是空函數(shù),在需要的時(shí)候自行編寫。那么中斷服務(wù)函數(shù)名是不是可以自己定義呢?不可以。中斷服務(wù)函數(shù)的名字必須要與啟動(dòng)文件startup_stm32f10x_hd.s中的中斷向量表定義一致。
EXTI0_. IRQHandler表示為 EXTI0 中斷向量的服務(wù)函數(shù)名。 于是,我們就可以在 stm32f10x_it.c 文件中加入名為EXTI0_IRQHandler() 的函數(shù)。
void EXTI0_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line0)!= RESET) //確保是否產(chǎn)生了 EXTI Line 中斷
{
//LED取反
LED_TOGGLE;
EXTI_ClearITPendingBit(EXTI_Line0); //清除中斷標(biāo)志位
}
}
其內(nèi)容比較容易理解,進(jìn)入中斷后,調(diào)用庫(kù)函數(shù)EXTI_GetITStatus() 來重新檢查是否產(chǎn)生了 EXTI_Line 中斷,接下來把 LED 取反,操作完畢后,調(diào)用EXTI_ClearITPendingBit()清除中斷標(biāo)志位再退出中斷服務(wù)函數(shù)。
5.2.4 EXTI方式實(shí)現(xiàn)-HAL庫(kù)
5.2.4.1 STM32Cube生成工程
根據(jù)按鍵電路,KEY1的引腳是PA0,我們將PA0的GPIO設(shè)置為上升沿觸發(fā)的外部中斷模式,保留3個(gè)LED的GPIO配置。
如上圖中對(duì)KEY的GPIO進(jìn)行了初始化配置,接下來就要進(jìn)行NVIC配置。 NVIC選項(xiàng)用于設(shè)置中斷的優(yōu)先級(jí),這里先設(shè)置優(yōu)先級(jí)組為4位搶占式優(yōu)先級(jí)為1,響應(yīng)式優(yōu)先級(jí)為0; EXTI[15:10]; EXTI10- EXTI15中短線在中斷向量表中占用同一個(gè)優(yōu)先級(jí),所以EXTI10- EXTI15中斷線優(yōu)先級(jí)都是一樣的,同意配置。 EXTI5 EXTI9情況也是一樣。
最后生成工程文件即可。
5.2.4.2 EXTI代碼分析
在看代碼前,我們先看看按鍵中斷編程的流程:
1)使能AFIO時(shí)鐘,設(shè)置NVIC優(yōu)先級(jí)NVIC_PRIORITYGROUP_4;
2)使能按鍵引腳PA0,將其設(shè)置為上升沿觸發(fā)中斷模式并使能下拉;
3)配置按鍵引腳中斷優(yōu)先級(jí)并使能中斷;
4)編寫中斷回調(diào)函數(shù),同時(shí)進(jìn)行消抖處理。
- 配置GPIO
static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_GPIOG_CLK_ENABLE();
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET);
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOG, GPIO_PIN_6|GPIO_PIN_7, GPIO_PIN_RESET);
/*Configure GPIO pin : PA0 */
GPIO_InitStruct.Pin = GPIO_PIN_0;
GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING;
GPIO_InitStruct.Pull = GPIO_PULLDOWN;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/*Configure GPIO pin : PB0 */
GPIO_InitStruct.Pin = GPIO_PIN_0;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
/*Configure GPIO pins : PG6 PG7 */
GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOG, &GPIO_InitStruct);
/* EXTI interrupt init*/
HAL_NVIC_SetPriority(EXTI0_IRQn, 1, 0);
HAL_NVIC_EnableIRQ(EXTI0_IRQn);
}
這部分和按鍵輪詢的設(shè)置類似,需要對(duì)GPIO進(jìn)行初始化設(shè)置。
- AFIO 時(shí)鐘
代碼中調(diào)用__HAL_RCC_AFIO_CLK_ENABLE()函數(shù),表示開啟 AFIO的時(shí)鐘。 這個(gè)函數(shù)HAL_Init函數(shù)調(diào)用HAL_MspInit()函數(shù)實(shí)現(xiàn)的。
AFIO (alternate-functionI/O),指 GPIO 端口的復(fù)用功能,GPIO 除了用作普通的輸入輸出(主功能),還可以作為片上外設(shè)的復(fù)用輸入輸出,如串口、ADC,這些就是復(fù)用功能。 大多數(shù) GPIO 都有一個(gè)默認(rèn)復(fù)用功能,有的 GPIO 還有重映射功能。 重映射功能是指把原來屬于 A 引腳的默認(rèn)復(fù)用功能,轉(zhuǎn)移到 B 引腳進(jìn)行使用,前提是 B 引腳具有這個(gè)重映射功能。
當(dāng)把 GPIO 用作 EXTI 外部中斷或使用重映射功能的時(shí)候,必須開啟 AFIO 時(shí)鐘,而在使用默認(rèn)復(fù)用功能的時(shí)候,就不必開啟 AFIO 時(shí)鐘了。
- NVIC 初始化配置
HAL_StatusTypeDef HAL_Init(void)
{
…
/* Set Interrupt Group Priority */
HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);
…
}
static void MX_GPIO_Init(void)
{
….
/* EXTI interrupt init*/
HAL_NVIC_SetPriority(EXTI0_IRQn, 1, 0);
HAL_NVIC_EnableIRQ(EXTI0_IRQn);
}
本代碼中調(diào)用了HAL_NVIC_SetPriorityGrouping()庫(kù)函數(shù),把 NVIC 中斷優(yōu)先級(jí)分組設(shè)置為4組。 MX_GPIO_Init()函數(shù)的最后兩個(gè)函數(shù)是關(guān)于中斷優(yōu)先級(jí)分組和使能中斷的。
HAL_NVIC_SetPriority(),共有三個(gè)參數(shù):
1.中斷向量號(hào)
中斷向量號(hào)在stm32f103xe.h中定義的。
2.搶占優(yōu)先級(jí):設(shè)置了兩位搶占優(yōu)先級(jí),那么搶占優(yōu)先級(jí)可以是00-11,即0-3。
3.響應(yīng)優(yōu)先級(jí):同樣是兩位。
HAL_NVIC_EnableIRQ()函數(shù)用于使能外部中斷線,外部中斷線10-15是共用一個(gè)中斷向量的。
這里要注意的是,如果用的 IO 口是 IO0 ~ IO4,那么對(duì)應(yīng)的中斷向量是 EXTI0_IRQn ~EXTI4_IRQn,如果用的 IO 是I05 ~ IO9 中的一個(gè)的話,對(duì)應(yīng)的中斷向量只能是 EXTI9_5_IRQn, 如果用的 IO 是 I010 ~ IO15中的一個(gè)的話,對(duì)應(yīng)的中斷向量只能是 EXTI15_10_IRQn。 舉例:如果 PE5 或者 PE6 作為EXTI 中斷口,那么對(duì)應(yīng)的中斷向量都是 EXTI9_5_IRQn,在同一時(shí)刻只能相應(yīng)來自一個(gè)IO 的 EXTI 中斷。
- 編寫中斷服務(wù)函數(shù)
在這個(gè) EXTI 設(shè)置中我們把 PA0 連接到內(nèi)部的 EXTI0,GPIO 配置為上拉輸入,工作在下降沿中斷。在外圍電路上我們將 PA0 接到了 key上。當(dāng)按鍵沒有按下時(shí),PA0 始終為高,當(dāng)按鍵按下時(shí) PA0 變?yōu)榈停瑥亩?PA0 上產(chǎn)生一個(gè)下降沿跳變,EXTI0 會(huì)捕捉到這一跳變,并產(chǎn)生相應(yīng)的中斷,中斷服務(wù)程序在 stm32f10x_it.c 中實(shí)現(xiàn)。stm32f10x_it.c 文件是專門用來存放中斷服務(wù)函數(shù)的。文件中默認(rèn)只有幾個(gè)關(guān)于系統(tǒng)異常的中斷服務(wù)函數(shù),而且都是空函數(shù),在需要的時(shí)候自行編寫。那么中斷服務(wù)函數(shù)名是不是可以自己定義呢?不可以。中斷服務(wù)函數(shù)的名字必須要與啟動(dòng)文件startup_stm32f10x_hd.s中的中斷向量表定義一致。
EXTI0_IRQHandler 表示為 EXTI0 中斷向量的服務(wù)函數(shù)名。于是,我們就可以在 stm32f10x_it.c 文件中加入名為EXTI0_IRQHandler() 的函數(shù)。
void EXTI0_IRQHandler(void)
{
/* USER CODE BEGIN EXTI0_IRQn 0 */
/* USER CODE END EXTI0_IRQn 0 */
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);
/* USER CODE BEGIN EXTI0_IRQn 1 */
/* USER CODE END EXTI0_IRQn 1 */
}
EXTI0_IRQHandler()函數(shù)調(diào)用HAL_GPIO_EXTI_IRQHandler()函數(shù),我們進(jìn)入HAL_GPIO_EXTI_IRQHandler()函數(shù),發(fā)現(xiàn)又調(diào)用了函數(shù)HAL_GPIO_EXTI_Callback(),再此進(jìn)入HAL_GPIO_EXTI_Callback()函數(shù),HAL_GPIO_EXTI_Callback()就是回調(diào)函數(shù)。
__weak 是一個(gè)弱化標(biāo)識(shí),帶有這個(gè)的函數(shù)就是一個(gè)弱化函數(shù),就是你可以在其他地方寫一個(gè)名稱和參數(shù)都一模一樣的函數(shù),編譯器就會(huì)忽略這一個(gè)函數(shù),而去執(zhí)行你寫的那個(gè)函數(shù); 而UNUSED(GPIO_Pin) ,這就是一個(gè)防報(bào)錯(cuò)的定義,當(dāng)傳進(jìn)來的GPIO端口號(hào)沒有做任何處理的時(shí)候,編譯器也不會(huì)報(bào)出警告。 其實(shí)我們?cè)陂_發(fā)的時(shí)候已經(jīng)不需要去理會(huì)中斷服務(wù)函數(shù)了,只需要找到這個(gè)中斷回調(diào)函數(shù)并將其重寫即可而這個(gè)回調(diào)函數(shù)還有一點(diǎn)非常便利的地方這里沒有體現(xiàn)出來,就是當(dāng)同時(shí)有多個(gè)中斷使能的時(shí)候,STM32CubeMX會(huì)自動(dòng)地將幾個(gè)中斷的服務(wù)函數(shù)規(guī)整到一起并調(diào)用一個(gè)回調(diào)函數(shù),也就是無論幾個(gè)中斷,我們只需要重寫一個(gè)回調(diào)函并判斷傳進(jìn)來的端口號(hào)即可。
那么接下來我們就在stm32f4xx_it.c這個(gè)文件的最下面添加以下代碼:
voidHAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if(GPIO_Pin==KEY_GPIO_PIN)
{
HAL_Delay(100);
if(HAL_GPIO_ReadPin(KEY_GPIO,KEY_GPIO_PIN)== KEY_DOWN_LEVEL)
{
HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_0);
HAL_GPIO_TogglePin(GPIOG,GPIO_PIN_6);
HAL_GPIO_TogglePin(GPIOG,GPIO_PIN_7);
}
}
}
其內(nèi)容比較容易理解,進(jìn)入中斷后,調(diào)用庫(kù)函數(shù) HAL_GPIO_ReadPin來重新檢查是否產(chǎn)生了中斷,接下來把 LED 取反。
5.3實(shí)驗(yàn)現(xiàn)象
編譯好程序后,下載到板子上,不管是普通方式還是中斷方式,當(dāng)按在按鍵S1時(shí),LED1或亮或滅。
-
電路圖
+關(guān)注
關(guān)注
10388文章
10732瀏覽量
539164 -
mcu
+關(guān)注
關(guān)注
146文章
17734瀏覽量
358547 -
ARM
+關(guān)注
關(guān)注
134文章
9273瀏覽量
373750 -
按鍵
+關(guān)注
關(guān)注
4文章
225瀏覽量
57895 -
GPIO
+關(guān)注
關(guān)注
16文章
1243瀏覽量
53344
發(fā)布評(píng)論請(qǐng)先 登錄
ARM Cortex-M的音頻性能解析

ARM Cortex-M學(xué)習(xí)筆記:初識(shí)Systick定時(shí)器

ARM Cortex-M處理器詳解 精選資料分享
ARM Cortex-M堆棧機(jī)制介紹
ARM Cortex-M內(nèi)核的相關(guān)資料推薦
ARM Cortex-M 系列微控制器(ST)
【ARM白皮書】ARM Cortex-M處理器入門
spmt284 Tiva C Series ARM Cortex-M Microcontrollers 新舊型號(hào)對(duì)應(yīng)
傳統(tǒng)的單片機(jī)和ARM較量 助推MCU踏上高端Cortex-M市場(chǎng)
Atmel Studio 6軟件中如何調(diào)試ARM Cortex-M
米爾科技Cortex-M Prototyping System +介紹

從Cortex-M到 Cortex-A認(rèn)識(shí)ARM處理器
MCU學(xué)習(xí)筆記_ARM Cortex M0_簡(jiǎn)介

mcookie與單片機(jī)的關(guān)系_使用ARM Cortex-M MCU拓展單片機(jī)教學(xué)

評(píng)論