第5章 外部中断实验
第五章 外部中断实验
1. 硬件设计
本实验用到的硬件资源和按键输入实验一模一样,不多介绍了
但是这里我们使用的是中断来检测按键,还是KEY_UP 控制 DS0, DS1 互斥点亮; KEY2 控制 DS0, 按一次亮,再按一次灭; KEY1 控制 DS1,效果同 KEY2; KEY0 则同时控制 DS0 和 DS1,按一次,他们的状态就翻转一次。
2. 软件设计
2.1 EXTI初始化及NVIC配置
void EXTI_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOE_CLK_ENABLE();
// KEY_UP高电平有效
GPIO_InitStructure.Pin = KEY_UP_PIN;
GPIO_InitStructure.Mode = GPIO_MODE_IT_RISING; // 上升沿触发
GPIO_InitStructure.Pull = GPIO_PULLDOWN;
HAL_GPIO_Init(GPIOA, &GPIO_InitStructure);
// 普通按键低电平有效
GPIO_InitStructure.Pin = KEY0_PIN | KEY1_PIN | KEY2_PIN;
GPIO_InitStructure.Mode = GPIO_MODE_IT_FALLING; // 下降沿触发
GPIO_InitStructure.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOE, &GPIO_InitStructure);
// NVIC配置
// 中断线0即PA0连接到EXTI0(KEY_up)
HAL_NVIC_SetPriority(EXTI0_IRQn, 2, 0); // 抢占优先级为2,子优先级为0
HAL_NVIC_EnableIRQ(EXTI0_IRQn); // 使能中断
// 中断线2即PE2连接到EXTI2(KEY2)
HAL_NVIC_SetPriority(EXTI2_IRQn, 2, 1); // 抢占优先级为2,子优先级为
HAL_NVIC_EnableIRQ(EXTI2_IRQn);
// 中断线3即PE3连接到EXTI3(KEY1)
HAL_NVIC_SetPriority(EXTI3_IRQn, 2, 2); // 抢占优先级为2,子优先级为
HAL_NVIC_EnableIRQ(EXTI3_IRQn);
// 中断线4即PE4连接到EXTI4(KEY0)
HAL_NVIC_SetPriority(EXTI4_IRQn, 2, 3); // 抢占优先级为2,子优先级为
HAL_NVIC_EnableIRQ(EXTI4_IRQn);
}
2.2 中断函数(HAL库)
/**
* @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) != RESET)
{
__HAL_GPIO_EXTI_CLEAR_IT(GPIO_Pin);
HAL_GPIO_EXTI_Callback(GPIO_Pin);
}
}
这个函数是HAL自带的,作用就是当中断线产生中断时,首先清除中断标志,然后执行我们的中断服务函数
2.3 中断回调及中断服务函数
// 中断回调函数
void EXTI0_IRQHandler(void)
{
HAL_GPIO_EXTI_IRQHandler(KEY_UP_PIN); // 调用HAL库的中断处理函数
}
void EXTI2_IRQHandler(void)
{
HAL_GPIO_EXTI_IRQHandler(KEY2_PIN);
}
void EXTI3_IRQHandler(void)
{
HAL_GPIO_EXTI_IRQHandler(KEY1_PIN);
}
void EXTI4_IRQHandler(void)
{
HAL_GPIO_EXTI_IRQHandler(KEY0_PIN);
}
// 中断服务函数
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
delay_ms(100);
switch(GPIO_Pin)
{
case KEY_UP_PIN:
if(KEY_UP_GET == 1) // KEY_UP按下
{
LED1 = !LED1;
LED0 = !LED1;
}
break;
case KEY0_PIN:
if(KEY0_GET == 0)
{
LED0 = !LED0;
}
break;
case KEY2_PIN:
if(KEY2_GET == 0)
{
LED1 = !LED1;
}
break;
case KEY1_PIN:
if(KEY1_GET == 0)
{
LED0 = !LED0;
LED1 = !LED1;
}
break;
}
}
2.4 主函数
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "key.h"
#include "exti.h"
int main(void)
{
HAL_Init();
Stm32_Clock_Init(336,8,2,7);
delay_init(168);
uart_init(115200);
LED_Init();
KEY_Init();
EXTI_Init();
while(1)
{
printf("串口正常工作中...\r\n");
delay_ms(1000);
}
}
3. 重点部分分析
exti.c 文件总共包含 6 个函数。外部中断初始化函数 void EXTIX_Init 用来配置 IO 口外部中断相关步骤并使能中断,另一个函数 HAL_GPIO_EXTI_Callback 是外部中断共用回调函数,用来处理所有外部中断真正的控制逻辑。其他 4 个都是中断服务函数。
void EXTI0_IRQHandler(void)是外部中断 0 的服务函数,负责 KEY_UP 按键的中断检测;
void EXTI2_IRQHandler(void)是外部中断 2 的服务函数,负责 KEY2 按键的中断检测;
void EXTI3_IRQHandler(void)是外部中断 3 的服务函数,负责 KEY1 按键的中断检测;
void EXTI4_IRQHandler(void)是外部中断 4 的服务函数,负责 KEY0 按键的中断检测;
下面我们分别介绍这几个函数。
首先是外部中断初始化函数 void EXTIX_Init(void), 该函数内部主要做了两件事情。首先是调用 IO 口初始化函数 HAL_GPIO_Init 来初始化 IO 口,其次是设置中断优先级并使能中断线。
接下来我们看看外部中断服务函数,一共 4 个。所有的中断服务函数内部都只调用了同样一个函数 HAL_GPIO_EXTI_IRQHandler,该函数是外部中断共用入口函数,函数内部会进行中断标志位清零, 并且调用中断处理共用回调函数 HAL_GPIO_EXTI_Callback。
最后是外部中断回调函数 HAL_GPIO_EXTI_Callback,该函数用来编写真正的外部中断控制逻辑。该函数有一个入口参数就是 IO 口序号。所以我们在该函数内部,一般通过判断 IO 口序号值来确定中断是来自哪个 IO 口,也就是哪个中断线,然后编写相应的控制逻辑。所以在该函数内部,我们通过 switch 语句判断 IO 口来源,例如是来自 GPIO_PIN_0,那么一定是来自PA0,因为中断线一次只能连接一个 IO 口,而四个 IO 口中序号为 0 的 IO 口只有 PA0,所以中断线 0 一定是连接 PA0,也就是外部中断由 PA0 触发。
4. 小结
总的来说外部中断还算比较简单,核心就是设置中断号和NVIC,当然最重要的是写中断函数,我们来简单回顾一下整个流程,为了简化我们只使用一个按键:
4.1 实验目标
- 使用外部中断检测按键按下事件。
- 点亮 LED 指示灯作为反馈。
4.2 硬件连接
- 按键连接:
- 将一个按键的一端连接到开发板的某个 GPIO 引脚(例如 PA0)。
- 按键的另一端连接到 GND。
- LED 连接:
- 如果使用开发板上的 LED,可以直接使用。否则,将 LED 的正极连接到 GPIO 引脚(例如 PC13),负极接地,并串联一个 220Ω 电阻。
4.3 参考代码
#include "main.h"
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin);
int main(void)
{
// 初始化 HAL 库
HAL_Init();
// 配置系统时钟
SystemClock_Config();
// 初始化 GPIO
MX_GPIO_Init();
while (1)
{
// 主循环可以为空,因为中断会处理 LED 点亮
}
}
// 外部中断回调函数
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if(GPIO_Pin == GPIO_PIN_0) // 检查是否为 PA0
{
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13); // 切换 LED 状态
}
}
// GPIO 初始化函数
static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
// 启用 GPIO 时钟
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE();
// 配置 PA0 为外部中断
GPIO_InitStruct.Pin = GPIO_PIN_0;
GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING; // 下降沿触发中断
GPIO_InitStruct.Pull = GPIO_PULLDOWN; // 下拉
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
// 配置 PC13 为输出,用于 LED
GPIO_InitStruct.Pin = GPIO_PIN_13;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; // 推挽输出
GPIO_InitStruct.Pull = GPIO_NOPULL; // 不上拉
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; // 低频
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
// 配置中断优先级
HAL_NVIC_SetPriority(EXTI0_IRQn, 2, 0);
HAL_NVIC_EnableIRQ(EXTI0_IRQn); // 使能外部中断
}
// 配置系统时钟
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;
RCC_OscInitStruct.PLL.PLLM = 16;
RCC_OscInitStruct.PLL.PLLN = 336;
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
RCC_OscInitStruct.PLL.PLLQ = 7;
HAL_RCC_OscConfig(&RCC_OscInitStruct);
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | 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;
HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5);
}
2024.9.29 第一次修订,后期不再维护
2024.12.23 再次简化内容和代码