Overivew
外部中断
功能
外部中断是由芯片外部引脚的电平变化(上升沿、下降沿或双边沿)触发的中断,主要用于响应外部硬件事件(如按键触发、传感器信号变化、外部设备状态改变等)。
特点
触发源:
由 GPIO 引脚的电平变化触发,每个 GPIO 引脚可通过配置映射到相应的外部中断线(EXTI)。STM32 中外部中断线数量有限(通常为 16 条),多个 GPIO 引脚可复用同一条中断线(需通过 AFIO 配置)。响应场景:
适用于处理异步外部事件,如按键按下、传感器触发、外部设备中断请求等,实时响应外部信号变化的场景。触发方式:
可配置为上升沿触发、下降沿触发或双边沿触发。优先级:
支持中断优先级配置,可通过 NVIC(嵌套向量中断控制器)设置抢占优先级和子优先级,确保高优先级事件优先响应。
定时器中断
功能
定时器中断由芯片内部定时器模块触发,通过配置定时器的计数溢出、比较匹配等事件产生中断,主要用于实现定时任务、周期性操作或精确计时。
特点
触发源:
由内部定时器(如 TIM1-TIM17 等)的计数事件触发,例如计数器溢出(更新事件)、比较通道匹配、输入捕获等。定时器的时钟源可来自内部时钟(APB 总线时钟)、外部时钟或其他定时器触发信号。响应场景:
适用于需要周期性执行的任务,如定时采样数据、刷新显示、产生 PWM 信号、延时控制等,依赖精确时间间隔的场景。定时精度:
定时精度高,可通过配置定时器的预分频系数和自动重装载值,实现从微秒级到秒级的精确定时(例如:定时时间 = (预分频值 + 1) × (自动重装载值 + 1) / 定时器时钟频率
)。灵活性:
支持多种工作模式(向上计数、向下计数、中心对齐计数),可配合比较输出、输入捕获等功能实现复杂时序控制,如电机调速、频率测量等。资源特性:
每个定时器独立工作,可同时运行多个定时器中断,互不干扰;中断优先级同样通过 NVIC 配置,便于管理多个定时任务的执行顺序。
核心区别与应用场景
特性 | 外部中断 | 定时器中断 |
---|---|---|
触发源 | 外部 GPIO 引脚电平变化 | 内部定时器计数事件(溢出、比较等) |
主要用途 | 响应外部异步事件(按键、传感器) | 实现精确定时、周期性任务 |
精度 | 依赖外部信号,无固定时间间隔 | 高精度,可精确控制时间间隔 |
典型应用 | 按键检测、外部设备唤醒、异常报警 | 定时采样、PWM 生成、系统滴答定时器 |
程序设计
外部中断
我用的是STM32 F103的芯片,它的外部中断引脚映射(将 GPIO 引脚连接到 EXTI 线)由 AFIO(辅助功能 IO)外设控制,具体通过GPIO_EXTILineConfig()函数配置 SYSCFG_EXTICR 寄存器实现。所以需要使能AFIO时钟。
GPIO_EXTILineConfig()是 F1 系列的函数,而 STM32F4 系列已将引脚映射功能整合到 SYSCFG 外设中,对应函数为SYSCFG_EXTILineConfig(),且需要使能 SYSCFG 时钟(RCC_APB2Periph_SYSCFG)。
外部输入引脚的配置
- 开启GPIOD和AFIO时钟(我用的是PD0引脚)
- 设置PD0为下拉输入(因为会配置为上升沿触发,所以默认应该为低电平)
- 关联PD0到外部中断线0
- 配置中断线0为上升沿触发
- 配置并使能EXTI0中断的NVIC优先级
void EXTI_PD0_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
EXTI_InitTypeDef EXTI_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
// 使能 GPIOD 和 AFIO 时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD | RCC_APB2Periph_AFIO, ENABLE);
// 配置 PD0 为下拉输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; // 修改为下拉输入
GPIO_Init(GPIOD, &GPIO_InitStructure);
// 将 PD0 映射到外部中断线 0(F103使用GPIO_EXTILineConfig)
GPIO_EXTILineConfig(GPIO_PortSourceGPIOD, GPIO_PinSource0);
// 配置外部中断线 0
EXTI_InitStructure.EXTI_Line = EXTI_Line0;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
// 配置 NVIC
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x02;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
中断服务函数
简单打印一个信息即可,注意函数名必须用这个,不能用其他的。
PD0识别到有上升沿到来,就会打印这个消息
void EXTI0_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line0) != RESET)
{
// 处理中断事件,例如打印信息
printf("PD0 Rising Edge Interrupt Detected!\r\n");
// 清除中断标志位
EXTI_ClearITPendingBit(EXTI_Line0);
}
}
触发中断
连接到3.3V触发变化
- PD0接杜邦线接到3.3V去,接通的瞬间,就会触发一个上升沿,中断就被触发,打印消息。
- 当断开时,由于下拉电阻,电平会恢复到低电平,下一次再接通3.3V时,又会触发上升沿
通过按键控制输入
我们可以设置按键,按键触发另一个引脚PD1(或者其他引脚),PD1是作为GPIO输出的,使用按键来触发PD1的输出高或低电平,然后PD1也用杜邦线连接到PD0上。
按键可以参考STM32 按键输入检测 轮询和中断
按键引脚初始化代码
void KEY_Init(void) //IO初始化
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOE,ENABLE);//使能PORTA,PORTE时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4|GPIO_Pin_3;//KEY0-KEY1
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //设置成上拉输入
GPIO_Init(GPIOE, &GPIO_InitStructure);//初始化GPIOE4,3
//初始化 WK_UP-->GPIOA.0 下拉输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //PA0设置成输入,默认下拉
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.0
}
按键扫描代码
void Key_task()
{
vu8 key=0;
printf("Key_task start\r\n");
while(1)
{
key=KEY_Scan(0); //得到键值
if(key)
{
switch(key)
{
case WKUP_PRES: //控制蜂鸣器
printf("Task 2 WKUP_PRES\r\n");
break;
case KEY1_PRES:
printf("Task 2 KEY1_PRES\r\n");
break;
case KEY0_PRES:
printf("Task 2 KEY0_PRES\r\n");
break;
}
}
else
{
delay_ms(10);
}
}
}
按键触发PD1引脚的输出,从而影响PD0输入值
- 按下KEY1,PD1输出高电平,PD0就会输入高电平
- 按下KEY0,PD1输出低电平,PD0就会输入低电平
void Key_task(void *pvParameters)
{
vu8 key=0;
printf("Key_task start\r\n");
while(1)
{
key=KEY_Scan(0); //得到键值
if(key)
{
switch(key)
{
case WKUP_PRES: //控制蜂鸣器
BEEP=!BEEP;
break;
case KEY1_PRES:
printf("Task 2 KEY1_PRES\r\n");
GPIO_SetBits(GPIOD, GPIO_Pin_1);
break;
case KEY0_PRES:
printf("Task 2 KEY0_PRES\r\n");
GPIO_ResetBits(GPIOD, GPIO_Pin_1);
break;
}
}
else
{
delay_ms(10);
}
}
}
定时器中断
定时器初始化函数
配置定时器时基参数
- TIM_Prescaler = 7200 - 1:预分频器设置为 7199,将定时器时钟(72MHz)分频为 72MHz / 7200 = 10kHz(即计数频率为 10kHz,每计数 1 次耗时 0.1ms)。
- TIM_Period = 10000 - 1:自动重载值为 9999,计数器从 0 计数到 9999 后溢出(产生更新事件),耗时 10000 × 0.1ms = 1000ms = 1秒。
- TIM_CounterMode = TIM_CounterMode_Up:向上计数模式(从 0 到重载值循环)。
- TIM_ITConfig使能 TIM2 的更新中断(计数器溢出时触发中断)。
void TIM_Configuration(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
// 使能定时器时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
// 假设系统时钟频率为 72MHz,APB1 时钟频率为 36MHz,定时器时钟频率为 72MHz
// 配置定时器 1 秒定时
TIM_TimeBaseStructure.TIM_Period = 10000 - 1; // 自动重载值
TIM_TimeBaseStructure.TIM_Prescaler = 7200 - 1; // 预分频器值,72MHz / 7200 = 10kHz
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
// 使能定时器中断
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
// 配置 NVIC
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x03;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x03;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
// 使能定时器
TIM_Cmd(TIM2, ENABLE);
}
中断服务函数
每次定时器时间到,触发中断服务函数,打印信息
// 定时器 2 中断服务函数
void TIM2_IRQHandler(void)
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET)
{
printf("TIM2_IRQHandler() \r\n");
// 清除定时器中断标志
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}