一、实验名称
EXTI 外部中断控制流水灯启停实验(标准外设库,按键中断控制 PA0/PA1/PA2、PC13 流水灯暂停 / 继续)
二、实验目的
- 掌握 STM32 外部中断 EXTI 的配置原理,理解 AFIO、EXTI、NVIC 中断控制器的工作流程。
- 掌握 GPIO 输入模式配置,理解 GPIO 输入相关底层寄存器。
- 学会使用外部中断触发标志位,控制主循环流水灯的暂停与继续。
- 理解中断优先级、中断服务函数、中断标志位清除操作。
- 对照底层寄存器,看懂标准外设库 EXTI、GPIO、NVIC 库函数对应的硬件寄存器操作。
三、实验硬件
STM32F103C8T6 最小系统板
- LED:PA0、PA1、PA2 外接 LED;PC13 板载 LED(低电平点亮)
- 按键:PA0 引脚作为外部中断输入按键,下降沿触发(按键按下产生下降沿,触发外部中断)
四、实验原理与程序设计思路
整体设计思路
LED 部分:复用实验 2 的 GPIO 推挽输出配置,PA0、PA1、PA2、PC13 配置为推挽输出,默认全部熄灭,主循环轮流点亮,实现流水灯。
按键外部中断部分
- 开启 GPIOA、AFIO 复用功能时钟;
- 将 PA0 配置为上拉输入模式,作为按键输入引脚;
- AFIO 配置,将 PA0 映射到 EXTI 外部中断线 0;
- EXTI 配置:EXTI 线 0,下降沿触发,开启中断;
- NVIC 配置:配置 EXTI0 中断通道,设置抢占优先级与子优先级,使能中断;
中断服务函数
:当按键按下,触发 EXTI0 中断,翻转全局标志变量
run_flagrun_flag=1`:流水灯正常运行;
run_flag=0`:流水灯暂停;
主循环逻辑:while (1) 循环内判断
run_flag,标志为 1 时执行流水灯点亮 + 延时;标志为 0 时跳过流水灯代码,保持灯状态不变,实现暂停。
底层寄存器说明(GPIO、EXTI、AFIO、NVIC)
STM32F103,APB2 总线
1. GPIOA 寄存器(PA0,既可以做 LED 输出,本实验 PA0 用作按键输入)
GPIOA 基地址:0x40010800
表格
| 寄存器 | 地址偏移 | 物理地址 | 功能与参数说明 |
|---|---|---|---|
| GPIOA_CRL | 0x00 | 0x40010800 | 端口低配置寄存器,控制 PA0~PA7。PA0 为 bit [3:0]上拉输入模式:CNF=10,MODE=00 |
| GPIOA_IDR | 0x08 | 0x40010808 | 端口输入数据寄存器,只读,读取引脚当前电平,按键输入时读取引脚状态 |
| GPIOA_ODR | 0x0C | 0x4001080C | 端口输出寄存器,LED 输出时控制高低电平 |
| GPIOA_BSRR | 0x10 | 0x40010810 | 置位复位寄存器,快速设置输出引脚电平 |
GPIOC 基地址:0x40011000PC13 属于引脚 13,由GPIOC_CRH(0x40011004)寄存器配置。
2. AFIO 复用寄存器(外部中断引脚映射)
AFIO 基地址:0x40010000AFIO_EXTICR1偏移 0x08,地址0x40010008作用:选择 EXTI 线 0 对应的 GPIO 端口,写入 0000 代表选择 GPIOA 的 PA0。
3. EXTI 外部中断寄存器(EXTI 基地址:0x40010400)
表格
| 寄存器 | 偏移 | 地址 | 功能 |
|---|---|---|---|
| EXTI_IMR | 0x00 | 0x40010400 | 中断屏蔽寄存器,bit0=1:开启 EXTI 线 0 中断 |
| EXTI_EMR | 0x04 | 0x40010404 | 事件屏蔽寄存器,本实验不用 |
| EXTI_FTSR | 0x0C | 0x4001040C | 下降沿触发选择寄存器,bit0=1:EXTI0 下降沿触发中断 |
| EXTI_RTSR | 0x08 | 0x40010408 | 上升沿触发寄存器,本实验关闭 |
| EXTI_PR | 0x14 | 0x40010414 | 中断挂起寄存器,中断触发后置 1,必须软件写 1 清零,否则重复触发中断 |
4. NVIC 嵌套向量中断控制器(内核寄存器,Cortex-M3)
NVIC 用于管理中断优先级、使能中断通道。NVIC_ISER:中断使能寄存器,使能 EXTI0 中断通道;NVIC_IPR:中断优先级寄存器,设置抢占优先级、子优先级。
5. RCC APB2 时钟使能寄存器
RCC 基地址0x40021000,RCC_APB2ENR偏移 0x18,地址0x40021018bit2:GPIOA 时钟;bit0:AFIO 时钟;bit4:GPIOC 时钟。必须先开启时钟,外设才可以工作。
五、完整源代码
#include "stm32f10x.h" #include "Delay.h" uint8_t run_flag = 1; void LED_Init(void) { GPIO_InitTypeDef GPIO_InitStruct; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOC,ENABLE); GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStruct); GPIO_InitStruct.GPIO_Pin = GPIO_Pin_13; GPIO_Init(GPIOC, &GPIO_InitStruct); GPIO_SetBits(GPIOA, GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2); GPIO_SetBits(GPIOC, GPIO_Pin_13); } void KEY_EXTI_Init(void) { GPIO_InitTypeDef GPIO_InitStruct; EXTI_InitTypeDef EXTI_InitStruct; NVIC_InitTypeDef NVIC_InitStruct; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO,ENABLE); GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU; GPIO_Init(GPIOA, &GPIO_InitStruct); GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource0); EXTI_InitStruct.EXTI_Line = EXTI_Line0; EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt; EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Falling; EXTI_InitStruct.EXTI_LineCmd = ENABLE; EXTI_Init(&EXTI_InitStruct); NVIC_InitStruct.NVIC_IRQChannel = EXTI0_IRQn; NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0; NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStruct); } void EXTI0_IRQHandler(void) { if(EXTI_GetITStatus(EXTI_Line0)!=RESET) { run_flag = !run_flag; EXTI_ClearITPendingBit(EXTI_Line0); } } int main(void) { LED_Init(); KEY_EXTI_Init(); while(1) { if(run_flag == 1) { GPIO_ResetBits(GPIOA,GPIO_Pin_0); GPIO_SetBits(GPIOA,GPIO_Pin_1|GPIO_Pin_2); GPIO_SetBits(GPIOC,GPIO_Pin_13); Delay_ms(1000); GPIO_SetBits(GPIOA,GPIO_Pin_0); GPIO_ResetBits(GPIOA,GPIO_Pin_1); GPIO_SetBits(GPIOA,GPIO_Pin_2); GPIO_SetBits(GPIOC,GPIO_Pin_13); Delay_ms(1000); GPIO_SetBits(GPIOA,GPIO_Pin_0|GPIO_Pin_1); GPIO_ResetBits(GPIOA,GPIO_Pin_2); GPIO_SetBits(GPIOC,GPIO_Pin_13); Delay_ms(1000); GPIO_SetBits(GPIOA,GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2); GPIO_ResetBits(GPIOC,GPIO_Pin_13); Delay_ms(1000); } } }六、实验步骤
- 在 Keil MDK 中打开工程,添加
stm32f10x_gpio.c、stm32f10x_exti.c、stm32f10x_nvic.c、stm32f10x_rcc.c库文件。 - 配置头文件路径,宏定义
STM32F10X_MD,USE_STDPERIPH_DRIVER。 - 写入 main.c 代码,编译工程,无报错后进入 Debug 仿真。
- 软件仿真:打开逻辑分析仪,添加
PORTA.0、PORTA.1、PORTA.2、PORTC.13,观察流水波形。 - 仿真触发 PA0 按键下降沿,触发 EXTI0 中断,观察
run_flag翻转,流水灯暂停 / 继续。 - 硬件下载:将程序下载至 STM32 开发板,按下 PA0 按键,切换流水灯启停状态。
七、实验现象与结果分析
实验现象
- 上电后,流水灯默认开启,PA0→PA1→PA2→PC13 依次点亮,每个灯保持 1s,循环往复。
- 按下 PA0 按键,触发外部中断,
run_flag翻转,流水灯立刻暂停,LED 保持当前状态。 - 再次按下按键,
run_flag再次翻转,流水灯恢复继续流动。
9月22日
时序与中断分析
- 中断触发:按键按下,PA0 电平由高变低,产生下降沿,EXTI0 检测到下降沿,置起中断挂起标志,进入中断服务函数。
- 中断服务函数:只翻转全局标志
run_flag,必须清除 EXTI 中断挂起位,否则会持续反复进入中断。 - 主循环特点:中断不会打断延时函数,需要等待当前
Delay_ms(1000)执行完毕,才会判断 run_flag。
八、实验总结
- 外部中断配置流程:开启时钟 (GPIO+AFIO) → GPIO 输入配置 → AFIO 中断线映射 → EXTI 触发方式配置 → NVIC 中断优先级配置,顺序不能颠倒。
- 中断服务函数中,必须手动清除 EXTI 的中断挂起标志位,否则会重复触发中断,程序异常。
- 中断只做标志位翻转,业务逻辑放在主循环,是嵌入式标准开发规范。
- GPIO 输入模式使用 CRL 寄存器配置,IDR 寄存器读取引脚电平;EXTI、AFIO 配合实现引脚外部中断功能,NVIC 负责内核中断管理。
- 本实验使用全局变量
run_flag实现中断与主循环之间的信息交互,实现按键控制流水灯暂停、继续。