历史上的今天
返回首页

历史上的今天

今天是:2025年02月18日(星期二)

正在发生

2019年02月18日 | STM32_EXTI外部中断学习笔记

2019-02-18 来源:eefocus

参考资料:《STM32F4xx中文参考手册》系统配置控制器以及中断和事件章节。


EXTI( External interrupt /evet controller)


之前接触过51单片机的都了解到51单片机有两个外部中断 ,分别为外部中断0、1。用来实时地处理外部事件的一种内部机制。当某种外部事件发生时,单片机的中断系统将迫使CPU暂停正在执行的程序,转而去进行中断事件的处理;中断处理完毕后.又返回被中断的程序处,继续执行下去。而STM32的则有与之功能相同的外部中断事件控制器。外部中断/事件控制器(EXTI)管理了控制器的 23个中断/事件线。每个中断/事件线都对应有一个边沿检测器,可以实现输入信号的上升沿检测和下降沿的检测。EXTI可以实现对每个中断/事件线进行单独配置,可以单独配置为中断或者事件,以及触发事件的属性。


23个中断事件线包含了16个与I/O有关的外部中断/事件线和其余七根 EXTI 线

由上图可以看出PA-PI口的各个位对应EXT0~EXTI15 共16个外部中断/事件线。


可以通过配置SYSCFG_EXTICR1~CR4来选择用于外部中断事件的I/O口。

其余7个如下图所示

下表即为所列的外部中断事件线

下图即为 外部中断/ 事件控制器框图

图中左下角的输入线即I/O口的输入状态,通过配置上升沿触发或下降沿触发选择寄存器来选择输入线的触发状态(可以是只有上升沿触发、只有下降沿触发或者上升沿和下降沿都触发),如果检测到有边沿跳变就输出有效信号 1,否则就输出无线信号0,出来的信号作为一路或门电路的输入,另外一输入来自软件中断事件寄存器(EXTI_SWIER)。EXTI_SWIER 允许我们通过程序控制就可以启动中断/事件线,当或门电路输入中有一路信号为1,那么就会输出信号1,或门电路输出的信号出来分两路,下面路作为与门的输入,通过配置事件屏蔽寄存器,两个都为1则输出为1,脉冲发生器则输出脉冲,这个脉冲信号,就是产生事件的线路最终的产物,可以给其他外设电路使用,比如定时器 TIM、模拟数字转换器 ADC等等。或门电路出来的上面一路通过配置中断屏蔽寄存器,然后同样经过一个与门电路后输出,作为挂起请求寄存寄存器的输入,如果中断屏蔽寄存器和挂起请求寄存器同时为1,那么就可以输出作为NVIC中断控制器的输入了,即可以实现系统中断事件控制。产生中断线路目的是把输入信号输入到NVIC,进一步会运行中断服务函数,实现功能,这样是软件级的。而产生事件线路目的就是传输一个脉冲信号给其他外设使用,并且是电路级别的信号传输,属于硬件级的。中断在嵌入式应用中占有非常重要的地位,几乎每个控制器都有中断功能。中断对保证紧急事件得到第一时间处理是非常重要的,接下来通过一个小实验要操作一下外部中断


硬件电路图如下所示


我要实现的操作是当我每按下一次开关,LED灯改变一次状态(可以参考固件库点亮LED灯和输入按键检测以及NVIC概述)。


要利用外部中断使LED灯改变状态,首先就是需要检测I/O口的状态,当I/O口满足触发条件,就进入中断,执行中断服务程序。


在进行编程之前我们先理一理编程的思路


1、首先要初始化按键和LED相关的GPIO


2、接下来要初始化EXTI相关寄存器用来产生中断


3、初始化NVIC,用于处理中断。


4、编写中断服务函数


5、主函数


1、所以我们第一步就应该配置KEY1,KEY2的端口,那么应该配置KEY1、KEY2的GPIO为哪种状态才能与中断产生联系呢,

通过查阅手册了解到上图所表达的意思就是要使用外部中断线的话,端口必须被配置成输入模式,所以我们就要开始对GPIO进行初始化初始化包括按键GPIO端口的初始化与LED灯GPIO的初始化。首先我先把与硬件相关的宏定义放在定义好的bsp_exti.h与bsp_led.h文件中,方便程序的移植与可读性,代码如下


//引脚定义

/*******************************************************/

#define KEY1_INT_GPIO_PORT                GPIOA         //按键1端口宏定义

#define KEY1_INT_GPIO_CLK                 RCC_AHB1Periph_GPIOA //按键1端口总线时钟宏定义

#define KEY1_INT_GPIO_PIN                 GPIO_Pin_0         //按键1端口引脚宏定义

#define KEY1_INT_EXTI_PORTSOURCE          EXTI_PortSourceGPIOA //按键1中断端口宏定义

#define KEY1_INT_EXTI_PINSOURCE           EXTI_PinSource0         //按键1中断端口引脚宏定义

#define KEY1_INT_EXTI_LINE                EXTI_Line0                 //按键1外部中断线宏定义

#define KEY1_INT_EXTI_IRQ                 EXTI0_IRQn //按键1的中断请求类型宏定义

 

#define KEY1_IRQHandler                   EXTI0_IRQHandler //按键1的中断服务函数名宏定义

 

#define KEY2_INT_GPIO_PORT                GPIOC

#define KEY2_INT_GPIO_CLK                 RCC_AHB1Periph_GPIOC

#define KEY2_INT_GPIO_PIN                 GPIO_Pin_13

#define KEY2_INT_EXTI_PORTSOURCE          EXTI_PortSourceGPIOC

#define KEY2_INT_EXTI_PINSOURCE           EXTI_PinSource13

#define KEY2_INT_EXTI_LINE                EXTI_Line13

#define KEY2_INT_EXTI_IRQ                 EXTI15_10_IRQn

 

#define KEY2_IRQHandler                   EXTI15_10_IRQHandler

#include "stm32f4xx_gpio.h"

 

#define GPIO_R_Pin  GPIO_Pin_10

#define GPIO_R_Port GPIOH

 

#define GPIO_G_Pin  GPIO_Pin_11

#define GPIO_G_Port GPIOH

 

#define GPIO_B_Pin  GPIO_Pin_12

#define GPIO_B_Port GPIOH

 

/************************控制LED灯亮灭的宏***************/

 

/*直接操作寄存器的方法控制IO*/

 

#define  LED_PORT_OUT_HI(p,i) { p->BSRRL = i ;} //输出为高电平

#define  LED_PORT_OUT_LO(p,i) { p->BSRRH = i ;} //输出为低电平

#define  LED_PORT_OUT_Toggle(p,i)         { p->BSRRL ^= i ;}        //输出为反状态

 

/*定义控制IO的宏*/

 

#define LED_R_Toggle         LED_PORT_OUT_Toggle(GPIO_R_Port,GPIO_R_Pin)

#define LED_R_ON LED_PORT_OUT_LO(GPIO_R_Port,GPIO_R_Pin)

#define LED_R_OFF LED_PORT_OUT_HI(GPIO_R_Port,GPIO_R_Pin)

 

#define LED_G_Toggle         LED_PORT_OUT_Toggle(GPIO_G_Port,GPIO_G_Pin)

#define LED_G_ON LED_PORT_OUT_LO(GPIO_G_Port,GPIO_G_Pin)

#define LED_G_OFF LED_PORT_OUT_HI(GPIO_G_Port,GPIO_G_Pin)

 

#define LED_B_Toggle         LED_PORT_OUT_Toggle(GPIO_B_Port,GPIO_B_Pin)

#define LED_B_ON LED_PORT_OUT_LO(GPIO_B_Port,GPIO_B_Pin)

#define LED_B_OFF LED_PORT_OUT_HI(GPIO_B_Port,GPIO_B_Pin)

 

#define LED_RGBOFF   LED_R_OFF;\

LED_G_OFF;\

LED_B_OFF

接下来开始初始化按键的GPIO与LED的端口引脚,在初始化按键的GPIO引脚时因为要与外部中断产生联系,所以通过查阅数据手册,所以要将按键初始化为输入模式,代码分别如下。

void bsp_exti_key_gpio_config()

{

GPIO_InitTypeDef GPIO_InitStructure;


/*开启按键GPIO口的时钟*/

RCC_AHB1PeriphClockCmd(KEY1_INT_GPIO_CLK|KEY2_INT_GPIO_CLK ,ENABLE);

/* 选择按键1的引脚 */ 

        GPIO_InitStructure.GPIO_Pin = KEY1_INT_GPIO_PIN;

        /* 设置引脚为输入模式 */ 

        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;    

        /* 设置引脚不上拉也不下拉 */

        GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;

        /* 使用上面的结构体初始化按键 */

        GPIO_Init(KEY1_INT_GPIO_PORT, &GPIO_InitStructure);


/* 选择按键2的引脚 */ 

        GPIO_InitStructure.GPIO_Pin = KEY2_INT_GPIO_PIN;  

        /* 其他配置与上面相同 */

        GPIO_Init(KEY2_INT_GPIO_PORT, &GPIO_InitStructure); 

 

}

void LED_Config(void)

{

GPIO_InitTypeDef GPIO_LED_Struct;


RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOH , ENABLE);  



GPIO_LED_Struct.GPIO_Mode  =  GPIO_Mode_OUT;

GPIO_LED_Struct.GPIO_Speed =  GPIO_Fast_Speed;

GPIO_LED_Struct.GPIO_OType =  GPIO_OType_PP;

GPIO_LED_Struct.GPIO_PuPd  =  GPIO_PuPd_UP;              


GPIO_LED_Struct.GPIO_Pin   =  GPIO_R_Pin;

GPIO_Init(GPIO_R_Port, &GPIO_LED_Struct);        

 

GPIO_LED_Struct.GPIO_Pin   =  GPIO_B_Pin;

GPIO_Init(GPIO_B_Port, &GPIO_LED_Struct);


GPIO_LED_Struct.GPIO_Pin   =  GPIO_G_Pin;

GPIO_Init(GPIO_G_Port, &GPIO_LED_Struct);


LED_RGBOFF;                        

}

 


关于按键及LED的GPIO配置可以参考STM32按键输入检测和STM32固件库点亮LED灯


2、当我们配置好按键的GPIO后,接下来我们要进行EXTI的有关配置,由于23个外部中断事件线相关的外设都是挂载在APB2总线上的,所以们要线打开APB2总线上的时钟,因为STM32的外设设计的都是很棒的,当外设不工作时,都处于休眠状态,要想配置使用相关的外设,首先要打开对应的时钟(关于时钟配置这块可以参考STM32时钟树介绍)

  /* 使能 SYSCFG 时钟 ,使用GPIO外部中断时必须使能SYSCFG时钟*/

  RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);

打开时钟后,首先要做的是什么?,肯定是要先先配置EXTI的输入线,我们要把按键的GPIO端口与EXTI联系起来,这里我们使用了STM32自带的库函数SYSCFG_EXTILineConfig函数,这个函数用来选择GPIO的引脚作为EXTI的中断事件线,函数有两个参数,一个是GPIO的端口(A~I)另一个是端口的哪个引脚(0~15),无返回值。函数说明大致意思就是这样。

  /* 连接 EXTI 中断源 到key1引脚 */

  SYSCFG_EXTILineConfig(KEY1_INT_EXTI_PORTSOURCE,KEY1_INT_EXTI_PINSOURCE);

  /* 连接 EXTI 中断源 到key2 引脚 */

  SYSCFG_EXTILineConfig(KEY2_INT_EXTI_PORTSOURCE,KEY2_INT_EXTI_PINSOURCE);

配置完输入后,接下来就要开始对EXTI进行配置,在官方的固件库exti.h文件中可以找到EXTI_InitTypeDef,进行配置,STM32的固件库非常的好用,齐全,每一种外设在对应的.h文件中都可以找到xx_InitTypeDef这个初始化结构体和xx_Init初始化配置函数,非常棒。


  EXTI_InitTypeDef EXTI_InitStructure; 

 

  /* 选择 EXTI 中断源 */

  EXTI_InitStructure.EXTI_Line = KEY1_INT_EXTI_LINE;

  /* 中断模式 */

  EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;

  /* 下降沿触发 */

  EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;  

  /* 使能中断/事件线 */

  EXTI_InitStructure.EXTI_LineCmd = ENABLE;

  EXTI_Init(&EXTI_InitStructure);

 

  /* 选择 EXTI 中断源 */

  EXTI_InitStructure.EXTI_Line = KEY2_INT_EXTI_LINE;

  EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;

  /* 上升沿触发 */

  EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;  

  EXTI_InitStructure.EXTI_LineCmd = ENABLE;

  EXTI_Init(&EXTI_InitStructure);

好,上面的就已经把EXTI前面的准备(红色方框里的)工作都已经做好了。


3、当中断发生后,EXTI会产生一个中断请求,接来下就要配置NVIC的中断优先级等来处理中断信号,在讲配置NVIC时说,首先要使能中断源(就上是我上面配置EXTI的操作)已经完成,接下来要进行初始化 NVIC_InitTypeDef结构体。


 void NVIC_Configuration(void)

{

  NVIC_InitTypeDef NVIC_InitStructure;

  

  /* 配置NVIC为优先级组1 */

  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);

  

  /* 配置中断源:按键1 */

  NVIC_InitStructure.NVIC_IRQChannel = KEY1_INT_EXTI_IRQ;

  /* 配置抢占优先级:1 */

  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;

  /* 配置子优先级:1 */

  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;

  /* 使能中断通道 */

  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;

  NVIC_Init(&NVIC_InitStructure);

  

  /* 配置中断源:按键2,其他使用上面相关配置 */  

  NVIC_InitStructure.NVIC_IRQChannel = KEY2_INT_EXTI_IRQ;

  NVIC_Init(&NVIC_InitStructure);

}

4、配置完NVIC后我们接下来就要进行第四步编写中断服务函数,中断服务函数的函数名在启动文件中都已经定义好了,注意引用的时候中断服务函数的名字不要打错,否则编译器不会报错,然后进入一个死循环当中,有关中断服务函数我建议以后所有的中断服务函数都放在一个文件中,避免出错。在编写中断服务函数时,我们首先要检查中断是否真的发生,然后执行中断程序,执行完程序后记得消除中断标志位为了下次进入中断。程序如下。


void KEY1_IRQHandler(void)

{

          //确保是否产生了EXTI Line中断

if(EXTI_GetITStatus(KEY1_INT_EXTI_LINE) != RESET) 

{

// LED红色取反

LED_R_Toggle;

                //清除中断标志位

EXTI_ClearITPendingBit(KEY1_INT_EXTI_LINE);     

}  

}

 

void KEY2_IRQHandler(void)

{

          //确保是否产生了EXTI Line中断

if(EXTI_GetITStatus(KEY2_INT_EXTI_LINE) != RESET) 

{

// LED蓝色取反

LED_B_Toggle;

                //清除中断标志位

EXTI_ClearITPendingBit(KEY2_INT_EXTI_LINE);     

}  

}

5、接下来我们就编写主函数来实现实验最终现象


int main(void)

{

/* LED 端口初始化 */

LED_GPIO_Config();

 

/* 初始化EXTI中断,按下按键会触发中断,

        *  触发中断会进入stm32f4xx_it.c文件中的函数

*  KEY1_IRQHandler和KEY2_IRQHandler,处理中断,反转LED灯。

*/

         bsp_exti_key_gpio_config();

         EXTI_Key_Config();

         NVIC_Configuration() ;


/* 等待中断,由于使用中断方式,CPU不用轮询按键 */

while(1)                            

{

}

}

   好了,以上就是我在学习过程中的感悟与总结,写的不好敬请见谅。

推荐阅读

史海拾趣

Atlantic Microwave Ltd公司的发展小趣事

近年来,电子行业面临着技术更新迅速、市场竞争日益激烈等挑战。Atlantic Microwave Ltd公司敏锐地察觉到行业变革的趋势,及时调整了公司的战略方向。公司加大了对新兴技术的研发投入,积极拥抱数字化转型,优化生产流程和管理模式,提高了公司的运营效率和市场竞争力。

法思特(FAST)公司的发展小趣事
延时时间应根据电网容量、热水器功率和数量等因素进行合理设计,以确保电网稳定和设备安全运行。
Densitron公司的发展小趣事

为了扩大市场份额,Densitron公司制定了一系列市场拓展策略。公司首先分析了市场需求和竞争态势,确定了目标市场。然后,通过加强品牌宣传、优化销售渠道、提高客户服务质量等手段,不断提升品牌知名度和客户满意度。此外,公司还积极开展国际合作,与全球知名电子企业建立了紧密的合作关系,共同开拓市场。这些策略的实施,使得Densitron公司的市场份额逐年上升,成为行业内的领军企业。

思瑞浦微电子科技(3PEAK INCORPORATED)公司的发展小趣事

为了扩大市场份额,Densitron公司制定了一系列市场拓展策略。公司首先分析了市场需求和竞争态势,确定了目标市场。然后,通过加强品牌宣传、优化销售渠道、提高客户服务质量等手段,不断提升品牌知名度和客户满意度。此外,公司还积极开展国际合作,与全球知名电子企业建立了紧密的合作关系,共同开拓市场。这些策略的实施,使得Densitron公司的市场份额逐年上升,成为行业内的领军企业。

Analog Microwave Design公司的发展小趣事

随着通信技术的不断进步,对微波器件的性能要求也越来越高。Analog Microwave Design公司敏锐地捕捉到了这一市场变化,开始致力于研发更高性能的微波器件。经过多次试验和改进,公司成功开发出了一款具有低损耗、高稳定性的微波放大器,这一创新产品在市场上引起了热烈反响。公司凭借这一技术突破,迅速扩大了市场份额,并树立了行业内的技术领先地位。

Allegro公司的发展小趣事

Analog Microwave Design公司一直注重企业文化的建设和传承。公司倡导创新、务实、合作的企业精神,鼓励员工敢于挑战、勇于创新。同时,公司还注重员工的培训和发展,为员工提供了良好的职业晋升通道和学习机会。这种积极向上的企业文化不仅激发了员工的工作热情和创新精神,还为公司的长期发展提供了有力保障。

以上五个故事分别从初创时期的挑战与机遇、技术创新的突破、国际化战略的实施、产品线的丰富与完善以及企业文化的建设与传承等方面,展现了Analog Microwave Design公司在电子行业发展的历程和成就。这些故事不仅反映了公司的成长轨迹,也体现了公司在面对市场变化和技术挑战时所采取的积极态度和应对策略。

问答坊 | AI 解惑

verilog设计实例,均有原代码!!

实例分为两部分,第1部分连续输入数据处理的电路设计是我们的课程设计,有详细的设计过程(分析过程,数据流图,以及状态机,源代码仿真和综合电路图),另一个是老师给的例题,有100多个例子吧,不过只有源代码和题目。希望能给大家做一个参考!!…

查看全部问答>

西门子s7200编程问题

为什么M1.0变为1时不能点亮q0.2,而当M1.1变为1时,Q0.2点亮…

查看全部问答>

三维加速器设计。。资料

这是我淘的三维加速器资料。。。…

查看全部问答>

求portel99 LQFP100封装库

求portel99 LQFP100封装库…

查看全部问答>

TCPMP修改(有经验的朋友帮帮忙)

我先说一下我的需求,就是要在播放影片的画面上面显示影片的码率之类的. 假设现在影片的码率是700kbps,我要在影片的某个位置显示出来,比如说左上角,这个怎么做出来呢? 或者给我指条明路. 谢谢 PS: 现在TCPMP能在我的机器上面跑了,也能正常解码.…

查看全部问答>

传感器检测问题.求教大家!

功能描述: 用单片机实现传感器检测功能,通过两个按键和一个LED显示模块来实现设置检测阀值的大小,2个按键分别为"加1"和"减1"功能,LED上显示的当前阀值大小.读入的传感器信号与设置的阀值进行比较,达到所设阀值大小输出报警.请 ...…

查看全部问答>

windows ce 新手请教

一直从事asp.net开发,对wince开发一无所知。 目前有一个gps设备,需要开发征对此设备的应用程序开发。该设备是wince 5.0操作系统。 我使用的是vs 2008做为开发工具(C#)。 我在vs2008中新建项目-智能设备 右侧仅有智能设备选项,请问我还需要安 ...…

查看全部问答>

对M128串口缓冲区逐位处理中的问题(含程序)

现在的问题是,程序无法检测到其中的位数,并做出相应的处理 表象就是:在串口调试助手中输入B****E,无任何返回值 程序如下: #include            //工作频率7.3728Mhz #include unsigned ch ...…

查看全部问答>

不用BOOTLOADER如何启动Wince系统?

通常我们加载wince系统,总是编译好eboot然后下载到flash里面,根据菜单提示下载NK。而实际的产品中,可以不用BOOTLOADER,那它是怎么实现系统加载的? 还有个问题,就是OAL的代码也可以完成所有的bootloader的功能,为什么一定要写个bootloader? ...…

查看全部问答>

【求助】想在嵌入式板(S3C2410)用java语言编写程序在LCD上显示图像和视频,我该如何入手?

最近在学习嵌入式,想在嵌入式板(S3C2410)用java语言编写程序在LCD上显示图像和视频,到现在没有一点头绪,不知道从什么地方入手。在嵌入式linux上的java虚拟机用什么?…

查看全部问答>