一、新建工程
1. 打開 STM32CubeMX 軟件,點擊“新建工程”
2. 選擇 MCU 和封裝
3. 配置時鐘
RCC 設置,選擇 HSE(外部高速時鐘) 為 Crystal/Ceramic Resonator(晶振/陶瓷諧振器)
選擇 Clock Configuration,配置系統時鐘 SYSCLK 為 72MHz
修改 HCLK 的值為 72 后,輸入回車,軟件會自動修改所有配置
4. 配置調試模式
非常重要的一步,否則會造成第一次燒錄程序后續無法識別調試器
SYS 設置,選擇 Debug 為 Serial Wire
二、EXTI外部中斷
2.1 參數配置
在 System Core
中選擇 GPIO
設置。
在右邊圖中找到按鍵對應引腳,選擇
GPIO_EXTIx
。這里的
x
是指掛載在中斷線幾上,如 GPIO_EXTI0 就是掛載在中斷線0上。- 開啟下降沿觸發中斷:即在 按下按鍵時 電平由高變為低時觸發,則在
GPIO mode
中選擇External Interrupt Mode with Falling edge trigger detection
- 開啟上升沿觸發中斷:即在 按下按鍵后松開時 電平由低變為高時觸發,則在
GPIO mode
中選擇External Interrupt Mode with Rising edge trigger detection
- 開啟下降沿上升沿都觸發中斷:即在 按下時觸發,松開時再次觸發,則在
GPIO mode
中選擇External Interrupt Mode with Rising/Falling edge trigger detection
- 如果硬件上已外部上拉或下拉,則在
GPIO Pull-up/Pull-down
中選擇No pull-up and no pull-down
既不上拉也不下拉。 - 如果硬件外部沒有上拉,則在
GPIO Pull-up/Pull-down
中選擇Pull-up
內部上拉電阻。
配置NVIC
中斷優先級分組規則Priority Group
默認為4個比特位,一般情況下不改。
勾選剛剛配置的外部中斷線0和13,并配置搶占優先級Preemption Priority
和響應優先級Sub Priority
。- 搶占優先級,數字越小,優先級越高
-
若搶占優先級相同,判斷子優先級,同樣,數字越小,優先級越高
2.2 生成代碼
輸入項目名和項目路徑
選擇應用的 IDE 開發環境 MDK-ARM V5
每個外設生成獨立的
’.c/.h’
文件不勾:所有初始化代碼都生成在 main.c
勾選:初始化代碼生成在對應的外設文件。 如 GPIO 初始化代碼生成在 gpio.c 中。
點擊 GENERATE CODE 生成代碼
2.3 修改中斷回調函數
打開 stm32f1xx_it.c
中斷服務函數文件,找到 EXTI0 中斷的服務函數 EXTI0_IRQHandler()
中斷服務函數里面就調用了 GPIO 外部中斷處理函數 HAL_GPIO_EXTI_IRQHandler()
打開
stm32f1xx_hal_gpio.c
文件,找到外部中斷處理函數原型 HAL_GPIO_EXTI_IRQHandler()
,其主要作用就是判斷是幾號線中斷,清除中斷標識位,然后調用中斷回調函數 HAL_GPIO_EXTI_Callback()
。/* NOTE: This function Should not be modified, when the callback is needed,
the HAL_GPIO_EXTI_Callback could be implemented in the user file
*/
這個函數不應該被改變,如果需要使用回調函數,請重新在用戶文件中實現該函數。
HAL_GPIO_EXTI_Callback()
按照官方提示我們應該再次定義該函數,__weak
是一個弱化標識,帶有這個的函數就是一個弱化函數,就是你可以在其他地方寫一個名稱和參數都一模一樣的函數,編譯器就會忽略這一個函數,而去執行你寫的那個函數;而 UNUSED(GPIO_Pin)
,這就是一個防報錯的定義,當傳進來的GPIO端口號沒有做任何處理的時候,編譯器也不會報出警告。其實我們在開發的時候已經不需要去理會中斷服務函數了,只需要找到這個中斷回調函數并將其重寫即可而這個回調函數還有一點非常便利的地方這里沒有體現出來,就是當同時有多個中斷使能的時候,STM32CubeMX會自動地將幾個中斷的服務函數規整到一起并調用一個回調函數,也就是無論幾個中斷,我們只需要重寫一個回調函并判斷傳進來的端口號即可。
接下來我們就在 stm32f1xx_it.c
這個文件的最下面添加 HAL_GPIO_EXTI_Callback()
/* USER CODE BEGIN 1 */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if(GPIO_Pin==KEY1_Pin)
{
// 自定義應用程序
HAL_GPIO_TogglePin(LED_G_GPIO_Port,LED_G_Pin);
}
}
/* USER CODE END 1 */
2.3 HAL庫與標準庫代碼比較
STM32CubeMX 使用 HAL 庫生成的代碼:
#define KEY2_Pin GPIO_PIN_13
#define KEY2_GPIO_Port GPIOC
#define KEY2_EXTI_IRQn EXTI15_10_IRQn
#define KEY1_Pin GPIO_PIN_0
#define KEY1_GPIO_Port GPIOA
#define KEY1_EXTI_IRQn EXTI0_IRQn
/**
* @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_GPIOC_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
/*Configure GPIO pin : KEY2_Pin */
GPIO_InitStruct.Pin = KEY2_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(KEY2_GPIO_Port, &GPIO_InitStruct);
/*Configure GPIO pin : KEY1_Pin */
GPIO_InitStruct.Pin = KEY1_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(KEY1_GPIO_Port, &GPIO_InitStruct);
/* EXTI interrupt init*/
HAL_NVIC_SetPriority(EXTI0_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(EXTI0_IRQn);
HAL_NVIC_SetPriority(EXTI15_10_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);
}
/**
* @brief This function handles EXTI line0 interrupt.
*/
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 */
}
/**
* @brief This function handles EXTI line[15:10] interrupts.
*/
void EXTI15_10_IRQHandler(void)
{
/* USER CODE BEGIN EXTI15_10_IRQn 0 */
/* USER CODE END EXTI15_10_IRQn 0 */
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_13);
/* USER CODE BEGIN EXTI15_10_IRQn 1 */
/* USER CODE END EXTI15_10_IRQn 1 */
}
/**
* @brief This function handles EXTI interrupt request.
* @param GPIO_Pin: Specifies the pins connected EXTI line
* @retval None
*/
void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin)
{
/* EXTI line interrupt detected */
if (__HAL_GPIO_EXTI_GET_IT(GPIO_Pin) != 0x00u)
{
__HAL_GPIO_EXTI_CLEAR_IT(GPIO_Pin);
HAL_GPIO_EXTI_Callback(GPIO_Pin);
}
}
使用 STM32 標準庫的代碼:
#define KEY1_INT_GPIO_PORT GPIOA
#define KEY1_INT_GPIO_CLK (RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO)
#define KEY1_INT_GPIO_PIN GPIO_Pin_0
#define KEY1_INT_EXTI_PORTSOURCE GPIO_PortSourceGPIOA
#define KEY1_INT_EXTI_PINSOURCE GPIO_PinSource0
#define KEY1_INT_EXTI_LINE EXTI_Line0
#define KEY1_INT_EXTI_IRQ EXTI0_IRQn
#define KEY1_IRQHandler EXTI0_IRQHandler
#define KEY2_INT_GPIO_PORT GPIOC
#define KEY2_INT_GPIO_CLK (RCC_APB2Periph_GPIOC|RCC_APB2Periph_AFIO)
#define KEY2_INT_GPIO_PIN GPIO_Pin_13
#define KEY2_INT_EXTI_PORTSOURCE GPIO_PortSourceGPIOC
#define KEY2_INT_EXTI_PINSOURCE GPIO_PinSource13
#define KEY2_INT_EXTI_LINE EXTI_Line13
#define KEY2_INT_EXTI_IRQ EXTI15_10_IRQn
#define KEY2_IRQHandler EXTI15_10_IRQHandler
/**
* @brief 配置嵌套向量中斷控制器NVIC
* @param 無
* @retval 無
*/
static void NVIC_Configuration(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
/* 配置NVIC為優先級組1 */
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
/* 配置中斷源:按鍵1 */
NVIC_InitStructure.NVIC_IRQChannel = KEY1_INT_EXTI_IRQ;
/* 配置搶占優先級 */
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
/* 配置子優先級 */
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
/* 使能中斷通道 */
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
/* 配置中斷源:按鍵2,其他使用上面相關配置 */
NVIC_InitStructure.NVIC_IRQChannel = KEY2_INT_EXTI_IRQ;
NVIC_Init(&NVIC_InitStructure);
}
/**
* @brief 配置 IO為EXTI中斷口,并設置中斷優先級
* @param 無
* @retval 無
*/
void EXTI_Key_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
EXTI_InitTypeDef EXTI_InitStructure;
/*開啟按鍵GPIO口的時鐘*/
RCC_APB2PeriphClockCmd(KEY1_INT_GPIO_CLK,ENABLE);
/* 配置 NVIC 中斷*/
NVIC_Configuration();
/*--------------------------KEY1配置-----------------------------*/
/* 選擇按鍵用到的GPIO */
GPIO_InitStructure.GPIO_Pin = KEY1_INT_GPIO_PIN;
/* 配置為浮空輸入 */
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(KEY1_INT_GPIO_PORT, &GPIO_InitStructure);
/* 選擇EXTI的信號源 */
GPIO_EXTILineConfig(KEY1_INT_EXTI_PORTSOURCE, KEY1_INT_EXTI_PINSOURCE);
EXTI_InitStructure.EXTI_Line = KEY1_INT_EXTI_LINE;
/* EXTI為中斷模式 */
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
/* 上升沿中斷 */
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
/* 使能中斷 */
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
/*--------------------------KEY2配置-----------------------------*/
/* 選擇按鍵用到的GPIO */
GPIO_InitStructure.GPIO_Pin = KEY2_INT_GPIO_PIN;
/* 配置為浮空輸入 */
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(KEY2_INT_GPIO_PORT, &GPIO_InitStructure);
/* 選擇EXTI的信號源 */
GPIO_EXTILineConfig(KEY2_INT_EXTI_PORTSOURCE, KEY2_INT_EXTI_PINSOURCE);
EXTI_InitStructure.EXTI_Line = KEY2_INT_EXTI_LINE;
/* EXTI為中斷模式 */
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
/* 下降沿中斷 */
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
/* 使能中斷 */
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
}
void KEY1_IRQHandler(void)
{
//確保是否產生了EXTI Line中斷
if(EXTI_GetITStatus(KEY1_INT_EXTI_LINE) != RESET)
{
// LED1 取反
LED1_TOGGLE;
//清除中斷標志位
EXTI_ClearITPendingBit(KEY1_INT_EXTI_LINE);
}
}
void KEY2_IRQHandler(void)
{
//確保是否產生了EXTI Line中斷
if(EXTI_GetITStatus(KEY2_INT_EXTI_LINE) != RESET)
{
// LED2 取反
LED2_TOGGLE;
//清除中斷標志位
EXTI_ClearITPendingBit(KEY2_INT_EXTI_LINE);
}
}
__HAL_RCC_GPIOC_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE();
對應 RCC_APB2PeriphClockCmd(KEY1_GPIO_CLK|KEY2_GPIO_CLK,ENABLE);
HAL_GPIO_Init(KEY1_GPIO_Port, &GPIO_InitStruct);
對應 GPIO_Init(KEY1_GPIO_PORT, &GPIO_InitStructure);
HAL_NVIC_SetPriority(EXTI0_IRQn, 0, 0);HAL_NVIC_EnableIRQ(EXTI0_IRQn);
對應 NVIC_Init(&NVIC_InitStructure);EXTI_Init(&EXTI_InitStructure);
__HAL_GPIO_EXTI_GET_IT(GPIO_Pin)
對應 EXTI_GetITStatus(KEY1_INT_EXTI_LINE)
__HAL_GPIO_EXTI_CLEAR_IT(GPIO_Pin)
對應 EXTI_ClearITPendingBit(KEY1_INT_EXTI_LINE)
三、注意事項
用戶代碼要加在 USER CODE BEGIN N
和 USER CODE END N
之間,否則下次使用 STM32CubeMX 重新生成代碼后,會被刪除。
? 由 Leung 寫于 2021 年 1 月 12 日
? 參考:STM32CubeMX系列教程2:外部中斷(EXIT)
【STM32Cube_05】使用EXIT中斷檢測按鍵(NVIC配置+HAL庫中斷處理機制)
《嵌入式-STM32開發指南》第二部分 基礎篇 - 第3章 按鍵(HAL庫)
STM32CubeMX實戰教程(三)——外部中斷(中斷及HAL_Delay函數避坑)