一篇文章看懂STM32-EXTI外部中断(全网最详细!附上练习小项目详细注释)

NOTE

(1)外部中断配置过程

初始化配置(Init)

①配置RCC,把涉及的外设时钟都打开(GPIOB,AFIO,NVIC,EXTI)

②配置GPIO,选择端口为输入模式

③配置AFIO,选择需要用的GPIO连接EXTI

④配置EXTI,选择边沿触发方式以及响应方式(中断响应/事件响应)

⑤配置NVIC,选择合适的优先级

中断函数的路径

Keil中,在Start组的开启文件startup_stm32f10x_md.s文件里

(2)需要用到的库函数

1.中断介绍

1)什么是中断?

  在主程序运行过程中,出现了特定的中断触发条件(中断源),使得CPU暂停当前正在运行的程序,转而去处理中断程序处理完成后又返回原来被暂停的位置继续运行

  • 特定的中断触发条件:例如:①对于外部中断,引脚发生电平跳变;②对定时器来说,定时时间到了;③对串口通信来说,接收到了数据

  • 2)中断优先级

    当有多个中断源同时申请中断时,CPU会根据中断源的轻重缓急进行裁决,优先响应更加紧急的中断源

    STM32中断优先级基本概念

    1、抢占优先级(pre):高抢占优先级可以打断正在执行的低抢占优先级中断;

    2、响应优先级(sub):当抢占优先级相同时,响应优先级高的先执行,但是不能相互打断;

    3、抢占优先级和响应优先级都相同的情况下,自然优先级越高的先执行;

    4、自然优先级:中断向量表中的优先级;

    5、数值越小,表示优先级越高;                

    3)中断嵌套

    当一个中断程序正在运行时,又有新的更高优先级的中断源申请中断,CPU再次暂停当前中断程序,转而去处理新的中断程序,处理完成后依次进行返回

    4)中断执行流程

    5)STM32的中断

  • 68个可屏蔽中断通道,包含EXTI、TIM、ADC、USART、SPI、I2C、RTC等多个外设

  • 使用NVIC统一管理中断,每个中断通道都拥有16个可编程的优先等级,可对优先级进行分组,进一步设置抢占优先级和响应优先级

  • NVIC就是STM32用来管理中断,分配优先级的

  • 本次课程用到EXTI0-EXTI14、EXTI9_5、EXTI15_10

  • 6)NVIC的基本结构

    NVIC叫嵌套中断向量管理器,是一个内核外设,CPU的小助手

    7)NVIC优先级分组

    NV1C的中断优先级由优先级寄存器的4位(0-15)决定,这4位可以进行切分,分为高n位的抢占优先级和低4-n位的响应优先级。

    抢占优先级高的可以中断嵌套,响应优先级高的可以优先排队,抢占优先级和响应优先级均相同的按中断号排队(任何时候都是优先级高的先响应)

    2.外部中断EXTI

    1)EXTI简介

  • EXTI(Extern Interrupt)外部中断

  • EXTI的基本功能:EXTI可以监测指定GPIO口的电平信号,当其指定的GPIO口产生电平变化时,EXTI将立即向NVIC发出中断申请,经过NVIC裁决后即可中断CPU主程序,使CPU执行EXTI对应的中断程序

  • 简单来说就是:引脚电平发生变化,申请中断

  • 支持的触发方式:上升沿下降沿/双边沿/软件触发

  • 支持的GPIO口:所有GPIO口,但相同的Pin不能同时触发中断

  • 通道数:16个GPIO Pin,外加PVD输出、RTC闹钟、USB唤醒、以太网唤醒

  • 触发响应方式:中断响应/事件响应

  • 中断响应:申请中断,让CPU执行中断函数

  • 事件响应:当外部中断检测到引脚变化时,中断信号就不会通向CPU了,而是通向其他外设,用来触发其他外设的操作(ADC,DMA等),属于外设之间的联合工作

  • 2)EXTI基本结构

    3)AFIO复用IO口

  • AFIO主要用于引脚复用功能的选择和重定义

  • 在STM32中,AFIO主要完成两个任务:复用功能引脚重映射、中断引脚选择

  • 4)EXTI内部框图

    1. 3.旋转编码器介绍

    1)定义

    用来测量位置、速度或旋转方向的装置。当其旋转轴旋转时,其输出端可以输出与旋转速度和方向对应的方波信号,读取方波信号的频率和相位信息即可得知旋转轴的速度和方向

    2)硬件电路图

    1. 4.对射式红外传感器计次project

    硬件接线图

    代码

    CountSensor.c模块代码

    #include "stm32f10x.h"                  // Device header
    
    uint16_t CountSensor_Count;       //全局变量,用于计数
    
    /**
      * 函    数:计数传感器初始化
      * 参    数:无
      * 返 回 值:无
      */
    void CountSensor_Init(void)
    {
            /*开启时钟*/
            RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);  //开启GPIOB的时钟
            RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);   //开启AFIO的时钟,外部中断必须开启AFIO的时钟
            
            /*GPIO初始化*/
            GPIO_InitTypeDef GPIO_InitStructure;
            GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;   //上拉输入
            GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;
            GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
            GPIO_Init(GPIOB, &GPIO_InitStructure);       //将PB14引脚初始化为上拉输入
            
            /*AFIO选择中断引脚*/
            GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource14);//将外部中断的14号线映射到GPIOB,即选择PB14为外部中断引脚
            
            /*EXTI初始化*/
            EXTI_InitTypeDef EXTI_InitStructure;          //定义结构体变量
            EXTI_InitStructure.EXTI_Line = EXTI_Line14;   //选择配置外部中断的14号线
            EXTI_InitStructure.EXTI_LineCmd = ENABLE;     //指定外部中断线使能
            EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;      //指定外部中断线为中断模式
            EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;  //指定外部中断线为下降沿触发
            EXTI_Init(&EXTI_InitStructure);               //将结构体变量交给EXTI_Init,配置EXTI外设
            
            /*NVIC中断分组*/
            NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);    //配置NVIC为分组2
                                                                                                                          //若调用多次配置分组的代码,则后执行的配置会覆盖先执行的配
            /*NVIC配置*/
            NVIC_InitTypeDef NVIC_InitStructure;        //定义结构体变量
            NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;   //选择配置NVIC的EXTI15_10线
            NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;        //指定NVIC线路使能
            NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;   //指定NVIC线路的抢占优先级为1
            NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;     //指定NVIC线路的响应优先级为1
            NVIC_Init(&NVIC_InitStructure);             //将结构体变量交给NVIC_Init,配置NVIC外设
    }
    
    /**
      * 函    数:获取计数传感器的计数值
      * 参    数:无
      * 返 回 值:计数值,范围:0~65535
      */
    uint16_t CountSensor_Get(void)
    {
            return CountSensor_Count;
    }
    
    /**
      * 函    数:EXTI15_10外部中断函数
      * 参    数:无
      * 返 回 值:无
      * 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
      *           函数名为预留的指定名称,可以从启动文件复制
      *           请确保函数名正确,不能有任何差异,否则中断函数将不能进入
      */
    void EXTI15_10_IRQHandler(void)
    {
            if (EXTI_GetITStatus(EXTI_Line14) == SET)     //判断是否是外部中断14号线触发的中断
            {
                    /*如果出现数据乱跳的现象,可再次判断引脚电平,以避免抖动*/
                    if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_14) == 0)
                    {
                            CountSensor_Count ++;          //计数值自增一次
                    }
                    EXTI_ClearITPendingBit(EXTI_Line14);   //清除外部中断14号线的中断标志位
                                                           //中断标志位必须清除
                                                           //否则中断将连续不断地触发,导致主程序卡死
            }
    }
    
    main.c模块代码
    #include "stm32f10x.h"                  // Device header
    #include "Delay.h"
    #include "OLED.h"
    #include "CountSensor.h"
    
    int main(void)
    {
            /*模块初始化*/
            OLED_Init();          //OLED初始化
            CountSensor_Init();   //计数传感器初始化
            
            /*显示静态字符串*/
            OLED_ShowString(1, 1, "Count:");   //1行1列显示字符串Count:
            
            while (1)
            {
                    OLED_ShowNum(1, 7, CountSensor_Get(), 5);  //OLED不断刷新显示CountSensor_Get的返回值
            }
    }
    
    1. 5.旋转编码器计次project

    硬件接线图

    代码

    Encoder.c模块代码

    #include "stm32f10x.h"                  // Device header
    
    int16_t Encoder_Count;        //全局变量,用于计数旋转编码器的增量值
    
    /**
      * 函    数:旋转编码器初始化
      * 参    数:无
      * 返 回 值:无
      */
    void Encoder_Init(void)
    {
            /*开启时钟*/
            RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);     //开启GPIOB的时钟
            RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);     //开启AFIO的时钟,外部中断必须开启AFIO的时钟
            
            /*GPIO初始化*/
            GPIO_InitTypeDef GPIO_InitStructure;
            GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
            GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
            GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
            GPIO_Init(GPIOB, &GPIO_InitStructure);                     //将PB0和PB1引脚初始化为上拉输入
            
            /*AFIO选择中断引脚*/
            GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource0);//将外部中断的0号线映射到GPIOB,即选择PB0为外部中断引脚
            GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource1);//将外部中断的1号线映射到GPIOB,即选择PB1为外部中断引脚
            
            /*EXTI初始化*/
            EXTI_InitTypeDef EXTI_InitStructure;                       //定义结构体变量
            EXTI_InitStructure.EXTI_Line = EXTI_Line0 | EXTI_Line1;    //选择配置外部中断的0号线和1号线
            EXTI_InitStructure.EXTI_LineCmd = ENABLE;                  //指定外部中断线使能
            EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;        //指定外部中断线为中断模式
            EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;    //指定外部中断线为下降沿触发
            EXTI_Init(&EXTI_InitStructure);                        //将结构体变量交给EXTI_Init,配置EXTI外设
            
            /*NVIC中断分组*/
            NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);           //配置NVIC为分组2
     
            /*NVIC配置*/
            NVIC_InitTypeDef NVIC_InitStructure;                      //定义结构体变量
            NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;          //选择配置NVIC的EXTI0线
            NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;           //指定NVIC线路使能
            NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;        //指定NVIC线路的抢占优先级为1
            NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;               //指定NVIC线路的响应优先级为1
            NVIC_Init(&NVIC_InitStructure);                                  //将结构体变量交给NVIC_Init,配置NVIC外设
    
            NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn;                 //选择配置NVIC的EXTI1线
            NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;                  //指定NVIC线路使能
            NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;        //指定NVIC线路的抢占优先级为1
            NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;               //指定NVIC线路的响应优先级为2
            NVIC_Init(&NVIC_InitStructure);                                  //将结构体变量交给NVIC_Init,配置NVIC外设
    }
    
    /**
      * 函    数:旋转编码器获取增量值
      * 参    数:无
      * 返 回 值:自上此调用此函数后,旋转编码器的增量值
      */
    int16_t Encoder_Get(void)
    {
            /*使用Temp变量作为中继,目的是返回Encoder_Count后将其清零*/
            /*在这里,也可以直接返回Encoder_Count
              但这样就不是获取增量值的操作方法了
              也可以实现功能,只是思路不一样*/
            int16_t Temp;
            Temp = Encoder_Count;
            Encoder_Count = 0;
            return Temp;
    }
    
    /**
      * 函    数:EXTI0外部中断函数
      * 参    数:无
      * 返 回 值:无
      * 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
      *           函数名为预留的指定名称,可以从启动文件复制
      *           请确保函数名正确,不能有任何差异,否则中断函数将不能进入
      */
    void EXTI0_IRQHandler(void)
    {
            if (EXTI_GetITStatus(EXTI_Line0) == SET)                //判断是否是外部中断0号线触发的中断
            {
                    /*如果出现数据乱跳的现象,可再次判断引脚电平,以避免抖动*/
                    if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0)
                    {
                            if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0)    //PB0的下降沿触发中断,此时检测另一相PB1的电平,目的是判断旋转方向
                            {
                                    Encoder_Count --;                             //此方向定义为反转,计数变量自减
                            }
                    }
                    EXTI_ClearITPendingBit(EXTI_Line0);          //清除外部中断0号线的中断标志位
                                                                 //中断标志位必须清除
                                                                 //否则中断将连续不断地触发,导致主程序卡死
            }
    }
    
    /**
      * 函    数:EXTI1外部中断函数
      * 参    数:无
      * 返 回 值:无
      * 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
      *           函数名为预留的指定名称,可以从启动文件复制
      *           请确保函数名正确,不能有任何差异,否则中断函数将不能进入
      */
    void EXTI1_IRQHandler(void)
    {
            if (EXTI_GetITStatus(EXTI_Line1) == SET)                //判断是否是外部中断1号线触发的中断
            {
                    /*如果出现数据乱跳的现象,可再次判断引脚电平,以避免抖动*/
                    if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0)
                    {
                            if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0)                //PB1的下降沿触发中断,此时检测另一相PB0的电平,目的是判断旋转方向
                            {
                                    Encoder_Count ++;         //此方向定义为正转,计数变量自增
                            }
                    }
                    EXTI_ClearITPendingBit(EXTI_Line1);       //清除外部中断1号线的中断标志位
                                                              //中断标志位必须清除
                                                              //否则中断将连续不断地触发,导致主程序卡死
            }
    }
    
    main.c模块代码
    #include "stm32f10x.h"                  // Device header
    #include "Delay.h"
    #include "OLED.h"
    #include "Encoder.h"
    
    int16_t Num;     //定义待被旋转编码器调节的变量
    
    int main(void)
    {
            /*模块初始化*/
            OLED_Init();      //OLED初始化
            Encoder_Init();   //旋转编码器初始化
            
            /*显示静态字符串*/
            OLED_ShowString(1, 1, "Num:");     //1行1列显示字符串Num:
            
            while (1)
            {
                    Num += Encoder_Get();        //获取自上此调用此函数后,旋转编码器的增量值,并将增量值加到Num上
      //上面这一步最好不要在主程序和中断程序里,操作可能产生冲突的硬件
      //可以先在中断里操作变量或标志位,当中断返回时,再对这个变量进行显示操作
                    OLED_ShowSignedNum(1, 5, Num, 5);        //显示Num
            }
    }
    

    作者:choichoi201122

    物联沃分享整理
    物联沃-IOTWORD物联网 » 一篇文章看懂STM32-EXTI外部中断(全网最详细!附上练习小项目详细注释)

    发表回复