STM32 外部中断 和 定时器中断
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);// 配置外部中断线 0EXTI_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);// 配置 NVICNVIC_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-KEY1GPIO_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 = 10kHzTIM_TimeBaseStructure.TIM_ClockDivision = 0;TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);// 使能定时器中断TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);// 配置 NVICNVIC_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);}
}