STM32 外部中断 和 定时器中断

发布于:2025-07-31 ⋅ 阅读:(21) ⋅ 点赞:(0)

Overivew

外部中断

功能

外部中断是由芯片外部引脚的电平变化(上升沿、下降沿或双边沿)触发的中断,主要用于响应外部硬件事件(如按键触发、传感器信号变化、外部设备状态改变等)。

特点

  1. 触发源
    由 GPIO 引脚的电平变化触发,每个 GPIO 引脚可通过配置映射到相应的外部中断线(EXTI)。STM32 中外部中断线数量有限(通常为 16 条),多个 GPIO 引脚可复用同一条中断线(需通过 AFIO 配置)。

  2. 响应场景
    适用于处理异步外部事件,如按键按下、传感器触发、外部设备中断请求等,实时响应外部信号变化的场景。

  3. 触发方式
    可配置为上升沿触发、下降沿触发或双边沿触发。

  4. 优先级
    支持中断优先级配置,可通过 NVIC(嵌套向量中断控制器)设置抢占优先级和子优先级,确保高优先级事件优先响应。

定时器中断

功能

定时器中断由芯片内部定时器模块触发,通过配置定时器的计数溢出、比较匹配等事件产生中断,主要用于实现定时任务、周期性操作或精确计时。

特点

  1. 触发源
    由内部定时器(如 TIM1-TIM17 等)的计数事件触发,例如计数器溢出(更新事件)、比较通道匹配、输入捕获等。定时器的时钟源可来自内部时钟(APB 总线时钟)、外部时钟或其他定时器触发信号。

  2. 响应场景
    适用于需要周期性执行的任务,如定时采样数据、刷新显示、产生 PWM 信号、延时控制等,依赖精确时间间隔的场景。

  3. 定时精度
    定时精度高,可通过配置定时器的预分频系数和自动重装载值,实现从微秒级到秒级的精确定时(例如:定时时间 = (预分频值 + 1) × (自动重装载值 + 1) / 定时器时钟频率)。

  4. 灵活性
    支持多种工作模式(向上计数、向下计数、中心对齐计数),可配合比较输出、输入捕获等功能实现复杂时序控制,如电机调速、频率测量等。

  5. 资源特性
    每个定时器独立工作,可同时运行多个定时器中断,互不干扰;中断优先级同样通过 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)。

外部输入引脚的配置

  1. 开启GPIOD和AFIO时钟(我用的是PD0引脚)
  2. 设置PD0为下拉输入(因为会配置为上升沿触发,所以默认应该为低电平)
  3. 关联PD0到外部中断线0
  4. 配置中断线0为上升沿触发
  5. 配置并使能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触发变化
  1. PD0接杜邦线接到3.3V去,接通的瞬间,就会触发一个上升沿,中断就被触发,打印消息。
  2. 当断开时,由于下拉电阻,电平会恢复到低电平,下一次再接通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);
    }
}


网站公告

今日签到

点亮在社区的每一天
去签到