[应用相关] STM32 外部中断 和 定时器中断

[复制链接]
125|0
wowu 发表于 2025-8-13 14:12 | 显示全部楼层 |阅读模式
Overivew
外部中断
功能
外部中断是由芯片外部引脚的电平变化(上升沿、下降沿或双边沿)触发的中断,主要用于响应外部硬件事件(如按键触发、传感器信号变化、外部设备状态改变等)。

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

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

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

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

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

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

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

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

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

资源特性:
每个定时器独立工作,可同时运行多个定时器中断,互不干扰;中断优先级同样通过 NVIC 配置,便于管理多个定时任务的执行顺序。

核心区别与应用场景




程序设计
外部中断
我用的是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);
    }
}


————————————————
版权声明:本文为CSDN博主「扣篮发型不乱」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/KawhiLeo/article/details/149784771

您需要登录后才可以回帖 登录 | 注册

本版积分规则

136

主题

4344

帖子

2

粉丝
快速回复 在线客服 返回列表 返回顶部