第5章 外部中断实验
第五章 外部中断实验
1. 硬件设计
本实验用到的硬件资源和按键输入实验一模一样,不多介绍了
但是这里我们使用的是中断来检测按键,还是KEY_UP 控制 DS0, DS1 互斥点亮; KEY2 控制 DS0, 按一次亮,再按一次灭; KEY1 控制 DS1,效果同 KEY2; KEY0 则同时控制 DS0 和 DS1,按一次,他们的状态就翻转一次。
2. 软件设计
2.1 GPIO初始化
// 外部中断初始化
void EXTI_Init(void)
{
GPIO_InitTypeDef GPIO_Initure;
__HAL_RCC_GPIOA_CLK_ENABLE(); // 开启GPIOA时钟
__HAL_RCC_GPIOE_CLK_ENABLE(); // 开启GPIOE时钟
// 下面是KEY_UP的配置(高电平有效)
GPIO_Initure.Pin = GPIO_PIN_0;// PA0
GPIO_Initure.Mode = GPIO_MODE_IT_RISING;// 上升沿触发
GPIO_Initure.Pull = GPIO_PULLDOWN;
HAL_GPIO_Init(GPIOA, &GPIO_Initure);
// 下面是普通按键配置(低电平有效)
GPIO_Initure.Pin = GPIO_PIN_2|GPIO_PIN_3|GPIO_PIN_4; // PE2,3,4
GPIO_Initure.Mode = GPIO_MODE_IT_FALLING; // 下降沿触发
GPIO_Initure.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOE, &GPIO_Initure);
2.2 NVIC配置
// 中断线0-PA0
HAL_NVIC_SetPriority(EXTI0_IRQn, 2, 0); // 抢占优先级为2,子优先级为0
HAL_NVIC_EnableIRQ(EXTI0_IRQn); // 使能中断线0
// 中断线2-PE2
HAL_NVIC_SetPriority(EXTI2_IRQn, 2, 1); // 抢占优先级为2,子优先级为1
HAL_NVIC_EnableIRQ(EXTI2_IRQn); // 使能中断线2
// 中断线3-PE3
HAL_NVIC_SetPriority(EXTI3_IRQn, 2, 2); // 抢占优先级为2,子优先级为2
HAL_NVIC_EnableIRQ(EXTI3_IRQn); // 使能中断线3
// 中断线4-PE4
HAL_NVIC_SetPriority(EXTI4_IRQn, 2, 3); // 抢占优先级为2,子优先级为3
HAL_NVIC_EnableIRQ(EXTI4_IRQn); // 使能中断线4
}
2.3 中断服务函数
// 中断服务函数
void EXTI0_IRQHandler(void)
{
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0); // 调用中断处理公用函数
}
void EXTI2_IRQHandler(void)
{
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_2); // 调用中断处理公用函数
}
void EXTI3_IRQHandler(void)
{
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_3); // 调用中断处理公用函数
}
void EXTI4_IRQHandler(void)
{
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_4); // 调用中断处理公用函数
}
2.4 中断服务函数内容
// 中断服务程序中需要做的事情
// 在HAL库中所有的外部中断服务函数都会调用此函数
// GPIO_Pin:中断引脚号
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
delay_ms(100); // 消抖
switch(GPIO_Pin)
{
case GPIO_PIN_0:
if(WK_UP == 1) // 高电平有效
{
LED1 = !LED1; // 控制LED0,LED1互斥点亮
LED0 = !LED1;
}
break;
case GPIO_PIN_2:
if(KEY2 == 0) // LED1翻转
{
LED1 = !LED1;
}
break;
case GPIO_PIN_3:
if(KEY1 == 0) // 同时控制LED0,LED1翻转
{
LED0 = !LED0;
LED1 = !LED1;
}
break;
case GPIO_PIN_4:
if(KEY0 == 0)
{
LED0 = !LED0; // 控制LED0翻转
}
break;
}
}
- 主函数
int main(void)
{
HAL_Init(); //初始化 HAL 库
Stm32_Clock_Init(336,8,2,7); //设置时钟,168Mhz
delay_init(168); //初始化延时函数
uart_init(115200); //初始化 USART
LED_Init(); //初始化 LED
KEY_Init(); //初始化按键
EXTI_Init(); //外部中断初始化
while(1)
{
printf("OK\r\n"); //打印 OK 提示程序运行
delay_ms(1000); //每隔 1s 打印一次
}
}
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,当然最重要的是写中断函数,我们来简单回顾一下整个流程,为了简化我们只使用一个按键:
实验目标
- 使用外部中断检测按键按下事件。
- 点亮 LED 指示灯作为反馈。
硬件连接
- 按键连接:
- 将一个按键的一端连接到开发板的某个 GPIO 引脚(例如 PA0)。
- 按键的另一端连接到 GND。
- LED 连接:
- 如果使用开发板上的 LED,可以直接使用。否则,将 LED 的正极连接到 GPIO 引脚(例如 PC13),负极接地,并串联一个 220Ω 电阻。
参考代码
#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 第一次修订,后期不再维护