2. GPIO控制与按键中断
任务目标
在嵌入式AI MCU上,通过GPIO读取按键状态,并利用外部中断机制实现按键触发时翻转LED的亮灭状态。本案例以STM32F407系列MCU为例,使用标准外设库实现,核心目标是掌握GPIO输入配置、外部中断线映射、中断服务函数编写及中断优先级的设置方法。
硬件连接与原理
- LED正极通过限流电阻接至PA6(GPIOA Pin6),阴极接地;
- 按键一端接PB12(GPIOB Pin12),另一端接地,按键按下为低电平;
- PA6配置为推挽输出,PB12配置为上拉输入并映射到EXTI12外部中断线。
完整实现代码
#include "stm32f4xx.h"
// 简单延时函数(约1ms @168MHz,实际值略有偏差)
void Delay_MS(uint32_t ms) {
for (uint32_t i = 0; i < ms * 4000; i++) {
__NOP();
}
}
// GPIO初始化:PA6输出,PB12输入
void GPIO_Init_Config(void) {
GPIO_InitTypeDef GPIO_InitStruct;
// 使能GPIOA和GPIOB时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA | RCC_AHB1Periph_GPIOB, ENABLE);
// PA6 推挽输出
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
// PB12 上拉输入
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_12;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN;
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(GPIOB, &GPIO_InitStruct);
}
// EXTI配置:PB12映射到EXTI12
void EXTI_Init_Config(void) {
EXTI_InitTypeDef EXTI_InitStruct;
NVIC_InitTypeDef NVIC_InitStruct;
// 使能SYSCFG时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);
// 将PB12连接到EXTI12
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOB, EXTI_PinSource12);
// 配置EXTI12:下降沿触发(按键按下)
EXTI_InitStruct.EXTI_Line = EXTI_Line12;
EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_InitStruct.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStruct);
// 配置NVIC中断优先级
NVIC_InitStruct.NVIC_IRQChannel = EXTI15_10_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0x02;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0x00;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);
}
// 主函数
int main(void) {
GPIO_Init_Config();
EXTI_Init_Config();
// 初始状态:LED灭
GPIO_ResetBits(GPIOA, GPIO_Pin_6);
while (1) {
// 主循环无任务,等待中断
}
}
// EXTI15_10中断服务函数
void EXTI15_10_IRQHandler(void) {
if (EXTI_GetITStatus(EXTI_Line12) != RESET) {
// 翻转LED状态
if (GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_6) == Bit_SET) {
GPIO_ResetBits(GPIOA, GPIO_Pin_6);
} else {
GPIO_SetBits(GPIOA, GPIO_Pin_6);
}
// 清除中断标志位
EXTI_ClearITPendingBit(EXTI_Line12);
}
}
编译与运行
- 使用Keil MDK或IAR创建STM32F407工程,将上述代码保存为
main.c并加入工程; - 工程配置中需定义
STM32F40_41xxx宏,并添加标准外设库头文件路径; - 编译无误后,通过ST-Link或J-Link下载至开发板。
验证方法
下载程序后,观察以下现象以确认功能正确:
- 上电后LED保持熄灭状态;
- 每次按下按键并释放,LED状态翻转一次(亮→灭或灭→亮);
- 长按按键不会导致LED连续翻转,只有下降沿触发一次中断。
若LED状态与按键动作不一致,可通过调试器在EXTI15_10_IRQHandler处设置断点,检查中断是否进入以及GPIO翻转逻辑是否正确。
关键说明
- EXTI12属于EXTI15_10中断通道,所有10~15号线共享同一个中断服务函数,因此必须在函数内判断具体中断标志位;
- 按键未按下时PB12被内部上拉电阻拉高,按下时接地产生下降沿,故配置为下降沿触发;
- 中断服务函数中必须清除中断标志位,否则中断会持续触发导致程序卡死。