外部中断(EXTI) - 按键控制LED

本文详细介绍了STM32中EXTI(外部中断/事件控制器)的工作原理、结构、GPIO外部中断线路映像,以及如何利用EXTI控制LED灯,包括硬件消抖(RS触发器和电容滤波)、软件消抖(延时函数和定时器)的应用。
摘要由CSDN通过智能技术生成

一、外部中断/事件控制器(EXTI)结构图

1、结构图分析

外部中断主要由外部中断/事件控制器(External interrupt/event controller, EXTI)控制,它管理了外部中断或者事件的使能与否、触发方式等功能。

 ( 外部中断/事件控制器(EXTI)结构图 )

下图为中文翻译版

图中有两条从右向左走向的线路,红色线路用于产生中断,黄色线路产生事件

如上图,信号输入后首先会经过边沿检测电路,这个检测电路会通过查看 上边沿触发 和下边沿触发 寄存器的值,去判断信号到底在哪个边沿采集。(所谓上边沿和下边沿,即电平从低->高 or 从高->低 变化时的那条竖直的边)

经过边沿检测电路之后,就来到上图标号3的位置。这里是个或门连接着输入信号和软件中断事件寄存器。也就是说,除了信号触发中断,我们还可以控制相关的寄存器从而产生中断事件。

过了步骤3或门这一步之后,产生中断和产生事件的流程就有所区别了。

我们先来看看中断的产生过程:
首先中断请求信号会先进入 请求挂起寄存器 。这个寄存器的存在意义在于,如果中断发生时,正在处理同级或高优先级中断,则中断不能立即得到响应,此时中断被悬起。悬挂意味着等待而不是舍去,当优先级高的或者同等级先发生的中断完成后,被挂起的中断才会执行。

落实到stm32这里,可以看到在标号4的位置,是一个与门,所以,中断请教什么时候推送到NVIC控制器执行,就是中断屏蔽寄存器应该控制的事了。

同理,对于事件发生来说,事件是否生成,就是事件屏蔽寄存器的事了。不同点在于,事件的产生没有优先级的概念,所有无需挂起寄存器这么个东西。

2、外部中断线路映像

EXTI控制器支持多达20个软件的中断/事件请求。

我们单片机的外部中断通常是由GPIO的电平跳变引起的中断。
在STM32中,每一个GPIO都可以作为外部中断的触发源,外部中断通用I/O映像一共有16条线,对应着GPIO的0-15引脚,每一条外部中断都可以与任意一组的对应引脚相连,但不能重复使用

还有另外四个EXTI线的连接方式如下:
EXTI线16连接到PVD输出
EXTI线17连接到RTC闹钟事件
EXTI线18连接到USB唤醒事件
EXTI线19连接到以太网唤醒事件(只适用于互联型产品)

3、中断模式

外部中断支持GPIO的三种电平模式:
上升沿中断:当GPIO的电平从低->高时,引发外部中断。
下降沿中断:当GPIO的电平从高->低时,引发外部中断。
上升沿和下降沿中断(双边沿中断):当GPIO的电平从低->高和从高->低,都能引发外部中断。

二、外部中断控制LED灯

1、原理图

如下图,我们把三个LED灯分别连接在PA0、PA1、PA2上,按键 KEY1 连接在 PA3 上。

2、中断消抖

考虑到由于按键的机械结构具有弹性,按下时开关不会立刻接通,断开时也不会立刻断开,这就导致按键的输入信号在按下和断开时都会存在抖动。
如果不先将抖动问题进行处理,则读取的按键信号可能会出现错误。

因此为了消除这一问题,我们可以通过软件消抖或者硬件消抖两种方式来实现。

2.1 硬件消抖

硬件消抖一般有两种实现方式:“RS触发器”、“电容滤波器”

2.11 RS触发器

在RS触发器中,R是RESET的意思,代表复位,S是SET的意思,代表置位。

利用RS触发器来吸收按键的抖动。当按键未按下时,输出为0,一旦有键按下,触发器立即翻转,输出为1,触电的抖动便不会再对输出产生影响,起到抗抖作用,按键释放时也一样。RS触发电路消抖电路图如下。

注:详细分析可学习数电相关知识

2.12 电容滤波器

将电容并联在按键的两端,利用电容两端的电压不能突变的特性以及放电的延时特性。将产生抖动的电平通过电容吸收掉。从而达到消抖的作用,电容消抖电路图如原理图所示。

具体分析:

按键按下(高电平->低电平):按键按下,电容与按键形成回路,电容开始放电,当电容放电结束后,抖动就基本结束了。在放电期间,PA3处一直向外输出高电平。

主要是因为按键的电阻很小,电容和按键的并联电阻也被拉小,大部分回路电压会加到电阻上,电容电压变小,又由于电容两端电压不能突变,根据 I=dq/dt,带入电容公式q=uc,得出I=C*du/dt,当du/dt<0时,电容电荷减少,即电容放电,放电维持电平至按键抖动结束。

按键松开(低电平->高电平):按键松开,电容左极板接电源,右极板接地,开始充电,当电容充电结束后,抖动也就基本结束了。在电容充电期间,PA3处一直向外输出为低电平。

按键松开时,回路电压基本加在电容端,电容本来两边都是低电平,现在一边接高电平,一边接低电平,就开始充电,充电维持电平至按键抖动结束。

2.2 软件消抖
2.21 延时函数按键消抖

优点:简单方便
缺点:程序在空跑浪费CPU资源、不够精准

值得注意的是:由于我们这里按键是用中断的方式实现,所以不能在中断服务函数里面使用延时函数,因为中断服务函数最基本的要求就是快进快出。

2.22 定时器按键消抖

优点:节约CPU资源
缺点:消耗一个定时器

原理:按键采用中断驱动方式,当按键按下以后触发按键中断,在按键中断中开启一个定时器,定时周期为()ms,当定时时间到了以后就会触发定时器中断,最后在定时器中断处理函数中读取按键的值,如果按键值还是按下状态那就表示这是一次有效的按键。

定时器详情可参考STM32定时器TIM控制_沉默的道路的博客-CSDN博客

3、消抖选择

或许有人觉得用硬件电容滤波器消抖容易点,就焊接一个电容,但其实也是根据实际情况而定。

1. 电容也是钱啊,如果做产品,需求大量时也是一笔消费,主打一个省钱。
2. 中断闲着也是闲着,合理规划好代码也不乱,逻辑也不会变复杂。再复杂的逻辑,拆分好,规划好,都可以条理清晰,一目了然。
3. 再打比方如果在产品中,加电容在按键这玩意上,只要出现一次不可靠,人家就会认为你做的整个东西不可靠。
4. 不能100%依靠硬件消抖,这样究竟要选多大的电容合理,小了没有作用,大了反应慢,充电过程中可不可能存在临界状态,就是1,0的边沿。

当然不管硬件消抖也好软件消抖也罢,只要是人按的都不是绝对可靠,你消抖比如50ms,我乱按总可能抖出那么一两个。还是根据实际情况而定吧。

综上,如果能硬件+软件防抖就比较理想了! 

三、程序代码

1、按键中断在CubeMX中配置

将PA3号引脚设置为按键的输入引脚,将其设置为外部中断模式。
将GPIO模式设置为双边沿触发的外部中断,电阻设置为上拉电阻,最后设置用户标签为KEY1。

在NVIC中,勾选开启外部中断。

2、模块化封装led

2.1 宏定义与预处理-led
#ifndef __LED_DRIVER_H__
#define __LED_DRIVER_H__


#include "main.h"
void LED_Init(void);


/*********************
 * 开启时钟宏定义(便于代码的移植修改)
**********************/
#define LED_CLK_ENABLE()  __HAL_RCC_GPIOA_CLK_ENABLE();//使用前面的宏(LED_CLK_ENABLE),代替后面的函数
//#define LED2_CLK_ENABLE()  __HAL_RCC_GPIOA_CLK_ENABLE();    //如果是不同的引脚
//#define LED3_CLK_ENABLE()  __HAL_RCC_GPIOA_CLK_ENABLE();


/*********************
 * 引脚宏定义
**********************/
#define LED1_Pin		GPIO_PIN_0 //使用前面的宏(LED1_Pin),代替后面的函数
#define LED2_Pin		GPIO_PIN_1
#define LED3_Pin		GPIO_PIN_2


/*********************
 * 函数宏定义
**********************/
#define LED1_ON 		HAL_GPIO_WritePin(GPIOA,GPIO_PIN_0,GPIO_PIN_SET);//定义一个宏,通过使用前面的宏(LED1_ON),代替后面的函数,点亮对应灯
#define LED2_ON 		HAL_GPIO_WritePin(GPIOA,GPIO_PIN_1,GPIO_PIN_SET);
#define LED3_ON 		HAL_GPIO_WritePin(GPIOA,GPIO_PIN_2,GPIO_PIN_SET);
#define LED1_OFF 		HAL_GPIO_WritePin(GPIOA,GPIO_PIN_0,GPIO_PIN_RESET);//定义一个宏,通过使用前面的宏(LED1_OFF),代替后面的函数,关闭对应灯
#define LED2_OFF		HAL_GPIO_WritePin(GPIOA,GPIO_PIN_1,GPIO_PIN_RESET);
#define LED3_OFF		HAL_GPIO_WritePin(GPIOA,GPIO_PIN_2,GPIO_PIN_RESET);


#endif
2.2 主函数-led
#include "led_driver.h"


void LED_Init(void)
{
	/*定义GPIO的结构体变量*/
	GPIO_InitTypeDef GPIO_InitStruct = {0};
	
	/*使能LED的GPIO对应的时钟,开启时钟(GPIOA)*/
	LED_CLK_ENABLE() //用.h文件中定义的宏代替_ _HAL_RCC_GPIOA_CLK_ENABLE();
	
	/*设置参数*/
	GPIO_InitStruct.Pin = LED1_Pin|LED2_Pin|LED3_Pin;//选择LED1 LED2 LED3的引脚
	GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP ;//设置为推挽输出模式
	GPIO_InitStruct.Pull = GPIO_NOPULL;   //没有上下拉
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;//引脚输出速度设置为慢
	
	/*初始化引脚配置,让设置的参数生效*/
	HAL_GPIO_Init(GPIOA,&GPIO_InitStruct);//&GPIO_InitStruct定义的结构体

	
}

3、模块化封装Exti

3.1 宏定义与预处理-EXti
#ifndef __EXTI_DRIVER_H__
#define __EXTI_DRIVER_H__

#include "main.h" //#include "stm32f1xx_hal.h"
void EXTI_Init(void);
	
#define KEY1_GPIO_CLK_EN()    __HAL_RCC_GPIOA_CLK_ENABLE();
 
#define KEY1_Pin		 GPIO_PIN_3


uint8_t Getkey1val(void); //返回key1按键状态 


#endif

3.2 主函数-Exti
#include "Exti_driver.h"

uint8_t key_val = 0; //定义按键返回值变量

void EXTI_Init(void)
{
	/*定义GPIO的结构体变量*/
	GPIO_InitTypeDef GPIO_InitStruct = {0};
	
	/*使能LED的GPIO对应的时钟,开启时钟(GPIOA)*/
	KEY1_GPIO_CLK_EN() //用.h文件中定义的宏代替__HAL_RCC_GPIOA_CLK_ENABLE();
	
	/*设置参数*/
	GPIO_InitStruct.Pin = KEY1_Pin;//选择按键引脚
	GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING_FALLING	;//设置为双边沿触发外部中断
	GPIO_InitStruct.Pull = GPIO_PULLUP;   //默认上拉
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;//引脚输出速度设置为高速
	
	/*初始化引脚配置,让设置的参数生效*/
	HAL_GPIO_Init(GPIOA,&GPIO_InitStruct);//&GPIO_InitStruct定义的结构体
	

	HAL_NVIC_SetPriority(EXTI3_IRQn, 0, 0);// 设置外部中断优先级
	HAL_NVIC_EnableIRQ(EXTI3_IRQn);// 使能外部中断
	
}

	//外部中断3中断处理函数
void EXTI3_IRQHandler(void)
{
   HAL_GPIO_EXTI_IRQHandler(KEY1_Pin);
}

//外部中断回调函数。中断触发后,进入此函数,处理该函数中的代码之后,才会退出中断,此函数称为回调函数
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)// 回调函数
{
	if(GPIO_Pin == KEY1_Pin) //确定是否是KEY1键按下
	{
		key_val = HAL_GPIO_ReadPin(GPIOA, KEY1_Pin);
	}
	
}

//返回key1按键值
uint8_t Getkey1val(void)
{
	return key_val;
}



3.21 中断处理/回调函数

在HAL库的底层驱动说明中,有这么两段说明

大概就是,每当产生外部中断时,程序首先会进入外部中断服务函数。在stm32f1xx_it.c中,可以找到函数EXTI0_IRQHandler,它通过调用函数HAL_GPIO_EXTI_IRQHandler()对中断类型进行判断,并对涉及中断的寄存器进行处理,在处理完成后,它将调用中断回调函数。HAL_GPIO_EXTI_Callback(),在中断回调函数中编写在此次中断中需要执行的功能。

4、主函数

/* USER CODE BEGIN Includes */
#include "Exti_driver.h"
#include "led_driver.h"
/* USER CODE END Includes */
//把两个头文件包含进去
  /* USER CODE BEGIN 2 */
	LED_Init();
	EXTI_Init();
  /* USER CODE END 2 */
//初始化
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
		//如果按下了,就亮
		if(Getkey1val() == 0)
		{
			LED1_ON
			LED2_ON
			LED3_ON
		}
		//如果松开了,就熄灭
		else
		{
			LED1_OFF
			LED2_OFF
			LED3_OFF
		}
  }
  /* USER CODE END 3 */
}

四、运行效果

保存、编译、下载、烧录、上电。
可以看到一开始LED为点亮状态,当按键按下时,灯亮;当按键松开时,灯灭。

STM32中,外部中断可以通过GPIOx_EXTIy_IRQn中断线路来实现。其中,x表示GPIO端口号,y表示引脚号。例如,PA0引脚的外部中断线路为GPIOA_EXTI0_IRQn。 以下是通过PA0引脚实现按键外部中断的步骤: 1. 配置GPIOA的PA0引脚为输入模式,并使能GPIOA时钟。 2. 配置PA0引脚为外部中断触发源,可以选择上升沿、下降沿、双边沿触发。 3. 使能PA0引脚的外部中断线路,并使能对应的中断向量。 4. 在中断处理函数中添加相应的处理代码。 下面是具体的代码实现: ```c #include "stm32f10x.h" void EXTI0_IRQHandler(void) { if(EXTI_GetITStatus(EXTI_Line0) != RESET) { //处理按键中断事件 //...... EXTI_ClearITPendingBit(EXTI_Line0); } } int main(void) { //使能GPIOA时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //配置PA0引脚为输入模式 GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &GPIO_InitStructure); //配置PA0引脚为外部中断触发源,下降沿触发 EXTI_InitTypeDef EXTI_InitStructure; EXTI_InitStructure.EXTI_Line = EXTI_Line0; EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; EXTI_InitStructure.EXTI_LineCmd = ENABLE; EXTI_Init(&EXTI_InitStructure); //使能PA0引脚的外部中断线路,并使能对应的中断向量 NVIC_InitTypeDef NVIC_InitStructure; NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x00; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x00; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); while(1) { //主循环代码 //...... } } ``` 在中断处理函数中,需要先判断是哪个外部中断触发了中断请求,然后执行相应的处理代码。最后,需要调用EXTI_ClearITPendingBit函数清除相应的中断标志位,以便下一次中断请求能够被正确地触发。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值