STM32·HAL库开发(五)EXTI中断——案例:按键、振动开关等(中断)控制输出(LED、继电器等)

目录

【什么是中断 ?】

【什么是EXTI ?】

EXTI初始化结构体

中断/事件线

EXTI模式

触发类型

EXTI控制

【什么是优先级 ?】

【什么是优先级分组 ?】

【什么是NVIC ?】

【什么是中断向量表 ?】

【demo · GPIO_EXTI】

【中断函数中使用HAL_delay】

【外部中断调用流程】

【小试牛刀:电动车报警项目】

【按键库 MultiButton】


【什么是中断 ?】

         中断是指计算机运行过程中,出现某些意外情况需主机干预时,机器能自动停止正在运行的
程序并转入处理新情况的程序,处理完毕后又返回原被暂停的程序继续运行

【什么是EXTI ?】

         外部中断 / 事件控制器 (EXTI) 管理了控制器的  23  个中断 / 事件线。每个中断 / 事件线都对应有一
个边沿检测器,可以实现输入信号的上升沿检测和下降沿的检测。  EXTI  可以实现对每个中断 /
件线进行单独配置,可以单独配置为中断或者事件,以及触发事件的属性
        
         EXTI 可分为两大部分功能:一个是产生中断,另一个是产生事件
        产生中断线路:把输入信号输入到 NVIC ,继而运行中断服务函数,是软件级的
        产生事件线路:传输一个脉冲信号给其他外设使用,且是电路级别的信号传输,是硬件级的

EXTI初始化结构体
typedef struct
{
//中断/事件线    
uint32_t EXTI_Line;                 /*!< Specifies the EXTI lines to be enabled or disabled.
                                    This parameter can be any combination value of @ref EXTI_Lines */
//EXTI 模式
EXTIMode_TypeDef EXTI_Mode;         /*!< Specifies the mode for the EXTI lines.
                                    This parameter can be a value of @ref EXTIMode_TypeDef */
//触发类型
EXTITrigger_TypeDef EXTI_Trigger;   /*!< Specifies the trigger signal active edge for the EXTI lines.
                                    This parameter can be a value of @ref EXTITrigger_TypeDef */
//EXTI 控制
FunctionalState EXTI_LineCmd;       /*!< Specifies the new state of the selected EXTI lines.
                                    This parameter can be set either to ENABLE or DISABLE */
}EXTI_InitTypeDef;
中断/事件线
#define EXTI_Line0 ((uint32_t)0x00001) /*!< External interrupt line 0 */
#define EXTI_Line1 ((uint32_t)0x00002) /*!< External interrupt line 1 */
#define EXTI_Line2 ((uint32_t)0x00004) /*!< External interrupt line 2 */
#define EXTI_Line3 ((uint32_t)0x00008) /*!< External interrupt line 3 */
#define EXTI_Line4 ((uint32_t)0x00010) /*!< External interrupt line 4 */
#define EXTI_Line5 ((uint32_t)0x00020) /*!< External interrupt line 5 */
#define EXTI_Line6 ((uint32_t)0x00040) /*!< External interrupt line 6 */
#define EXTI_Line7 ((uint32_t)0x00080) /*!< External interrupt line 7 */
#define EXTI_Line8 ((uint32_t)0x00100) /*!< External interrupt line 8 */
#define EXTI_Line9 ((uint32_t)0x00200) /*!< External interrupt line 9 */
#define EXTI_Line10 ((uint32_t)0x00400) /*!< External interrupt line 10 */
#define EXTI_Line11 ((uint32_t)0x00800) /*!< External interrupt line 11 */
#define EXTI_Line12 ((uint32_t)0x01000) /*!< External interrupt line 12 */
#define EXTI_Line13 ((uint32_t)0x02000) /*!< External interrupt line 13 */
#define EXTI_Line14 ((uint32_t)0x04000) /*!< External interrupt line 14 */
#define EXTI_Line15 ((uint32_t)0x08000) /*!< External interrupt line 15 */
#define EXTI_Line16 ((uint32_t)0x10000) /*!< External interrupt line 16
Connected to the PVD Output */
#define EXTI_Line17 ((uint32_t)0x20000) /*!< External interrupt line 17
Connected to the RTC Alarm event */
#define EXTI_Line18 ((uint32_t)0x40000) /*!< External interrupt line 18
Connected to the USB OTG FS Wakeup from suspend event */
#define EXTI_Line19 ((uint32_t)0x80000) /*!< External interrupt line 19
Connected to the Ethernet Wakeup event */
#define EXTI_Line20 ((uint32_t)0x00100000) /*!< External interrupt line 20
Connected to the USB OTG HS (configured in FS) Wakeup event */
#define EXTI_Line21 ((uint32_t)0x00200000) /*!< External interrupt line 21
Connected to the RTC Tamper and Time Stamp events */
#define EXTI_Line22 ((uint32_t)0x00400000) /*!< External interrupt line 22
Connected to the RTC Wakeup event */
EXTI模式
typedef enum
{
EXTI_Mode_Interrupt = 0x00,     // 产生中断
EXTI_Mode_Event = 0x04          // 产生事件
}EXTIMode_TypeDef;
触发类型
typedef enum
{
EXTI_Trigger_Rising = 0x08, //上升沿
EXTI_Trigger_Falling = 0x0C, //下降沿
EXTI_Trigger_Rising_Falling = 0x10 //上升沿和下降沿都触发
}EXTITrigger_TypeDef;
EXTI控制
ENABLE or DISABLE

【什么是优先级 ?】

        优先级分为        抢占优先级        和        响应优先级
        高优先级的抢占优先级可以打断正在进行的低抢占优先级中断
        抢占优先级相同的中断,高响应优先级不可以打断低响应优先级的中断
        抢占优先级相同的中断,当两个中断同时发生的情况下,哪个响应优先级高,哪个先执行
        如果两个中断的抢占优先级响应优先级都是一样的话,则看哪个中断先发生就先执行
       
         数字越小(含负数),优先级越高

【什么是优先级分组 ?】

        Cortex-M3允许具有较少中断源时使用较少的寄存器位指定中断源的优先级,因此 STM32 把指定中断优先级的寄存器位减少到4 位,这 4 个寄存器位的分组方式如下:
        第0 组:所有 4 位用于指定响应优先级
        第1 组:最高 1 位用于指定抢占式优先级,最低 3 位用于指定响应优先级
        第2 组:最高 2 位用于指定抢占式优先级,最低 2 位用于指定响应优先级
        第3 组:最高 3 位用于指定抢占式优先级,最低 1 位用于指定响应优先级
        第4 组:所有 4 位用于指定抢占式优先级

【什么是NVIC ?】

         STM32通过中断控制器NVIC(Nested Vectored Interrupt Controller)进行中断管理 
        NVIC是属于 Cortex 内核的器件,不可屏蔽中断( NMI )和外部中断都由它来处理,但是 SYSTICK 不是由NVIC 控制的。

【什么是中断向量表 ?】

         每个中断源都有对应的处理程序,即中断服务程序,其入口地址  称为  中断向量
        所有中断的中断服务程序入口地址构成一个表,称为中断向量表
        也有的机器把中断服务程序入口的跳转指令构成一张表,称为中断向量跳转表

【demo · GPIO_EXTI】

 

 

注意:关于.h的引用问题:
         ●     main.h中引入stm32f1xx_hal.h(CubeMx会自动生成)
         ●     main.c中引入用户所有自建的.h文件
         ●     每个用户自建的.h文件仅需引用main.h
         ●     用户自建.c文件仅需引用对应自身的.h
         ●     用户自建.c文件中所有函数都应该在对应.h文件中声明
         ●     共享变量定义在.c中,并在对应的.h中使用extern声明(extern时不可赋值)
                共享的宏定义一般直接定义在.h中
                需要引用该共享变量的.c文件需引用该.h文件


         ●     若在.c中需要用另一个.c的函数,可使用extern声明,或在.c中引用目标.c文件的.h文件

                如:在a.c中需要用b.c的函数led(),则a.c中写入:#include<b.h>
                       或者:在a.c前面声明extern void led(); 后面再调用led()

         ●     若在.c中需要用另一个.c的变量(定义在.c中而不是.h),可使用extern声明

                如:在a.c中需要用b.c的变量int m,则在a.c前面声明extern int m; 后面再使用m

/* main.h */
#ifndef __MAIN_H
#define __MAIN_H
 
#include "stm32f1xx_hal.h"

 
#define LED_Pin GPIO_PIN_13
#define LED_GPIO_Port GPIOC
#define KEY_Pin GPIO_PIN_1
#define KEY_GPIO_Port GPIOA
 
#define KEY_OFF 0
#define KEY_ON  1
 
#endif /* __MAIN_H */
/* key_EXTI.h */
#ifndef __KEY_H__
#define __KEY_H__
 
#include "main.h"
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin);
 
#endif /*__ KEY_H__ */
/* led.h */
#ifndef __LED_H__
#define __LED_H__
 
#include "main.h"
void led(uint8_t KEY_FLAG);
 
#endif /*__ LED_H__ */
/* key_EXTI.c */
 
#include "key_EXTI.h"
 
#include "led.h"

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
	switch(GPIO_Pin)
	{
		case KEY_Pin:
			led(KEY_ON);
	}
}
/* led.c */
 
#include "led.h"
 
void led(uint8_t KEY_FLAG)
{
	if(KEY_FLAG == KEY_ON)
		{
			HAL_GPIO_TogglePin(LED_GPIO_Port,LED_Pin);  
		}
}

main.c中无需添加任何函数,因为中断是自动调用,回调函数已经写在key_EXTI.c中

【中断函数中使用HAL_delay】

上面  /* key_EXTI.c */  中的按键判断函数没有消抖,下面进行消抖

若要在中断函数中使用HAL_delay,则需在NVIC中更改优先级,因为HAL_delay用了滴答定时器

/* key_EXTI.c */
 
#include "key_EXTI.h"
 
#include "led.h"

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
	switch(GPIO_Pin)
	{
		case KEY_Pin:
			HAL_Delay(60);
			if(HAL_GPIO_ReadPin(KEY_GPIO_Port,KEY_Pin) == GPIO_PIN_RESET)
				led(KEY_ON);
		break;
	}
}

此处的switch case语句也可替换为if判断

/* key_EXTI.c */
 
#include "key_EXTI.h"
 
#include "led.h"

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
    if(GPIO_Pin == KEY_Pin) //一根中断线上接有多个中断源,判断中断源是否来自目标引脚
    {
	    HAL_Delay(60);
		if(HAL_GPIO_ReadPin(KEY_GPIO_Port,KEY_Pin) == GPIO_PIN_RESET)
        {
		    led(KEY_ON);
        }
	}
}    

更详细讲解:STM32 HAL库 中断处理中使用延时函数(HAL_Delay)_Dir_xr的博客-优快云博客

扩展说明:

① 按键模块可以直接替换为振动模块、触摸模块等(本质和按键一样,都是人为施加信号时,模块信号引脚输出低电平),程序中仅需根据实际情况调整消抖的延迟时间

② LED可替换为继电器模块,下图所用的继电器规格为DC 5V,Low level trigger

继电器:

【外部中断调用流程】

①        stm32f1xx_it.c中的  EXTIX_IRQHandler(void)
②        stm32f1xx_hal_gpio.c中的  HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin)
③        自己找位置重写的  HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)



【小试牛刀:电动车报警项目】

项目功能:

①若检测到PA4被拉低(小偷偷车)且警报模式打开:将PB7拉低,继电器通电,喇叭一直响
②若检测到PA5被拉高(按下按键A):开启警报模式(ON),将PB7拉低2秒(喇叭响2秒)
③若检测到PA6被拉高(按下按键B):关闭警报模式(OFF),将PB7
拉低1秒(喇叭响1秒)

#define J_ON 1
#define J_OFF 0
//重写中断服务函数,如果检测到EXTI中断请求,则进入此函数
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
    static int mark = J_OFF;
    switch(GPIO_Pin)
    {
        case GPIO_PIN_4:
            //如果检测到PA4被拉低(小偷偷车),并且警报模式打开
            if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_4) == GPIO_PIN_RESET && mark == J_ON)
            {
                //则将PB7拉低,继电器通电,喇叭一直响
                HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_RESET);
            }
        break;
        // 如果检测到PA5被拉高(按键A按下),设定为开启警报模式
        case GPIO_PIN_5:
            if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_5) == GPIO_PIN_SET)
            {
                // 则将PB7拉低(喇叭响),2秒后恢复电平(喇叭不响),表示进入警报模式
                HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_RESET);
                HAL_Delay(2000);
                HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_SET);
                // 同时将标志位设置为ON
                mark = J_ON;
            }
        break;
        // 如果检测到PA6被拉高(按键B按下),设定为关闭警报模式
        case GPIO_PIN_6:
        if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_6) == GPIO_PIN_SET)
            {
            // 则将PB7拉低(喇叭响),1秒后恢复电平(喇叭不响),表示关闭警报模式
            HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_RESET);
            HAL_Delay(1000);
            HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_SET);
            // 同时将标志位设置为OFF
            mark = J_OFF;
            }
        break;
    }
}

注意:mark变量要设置为静态变量

        static修饰的静态变量存储在全局数据区而不是栈中,这意味着该变量在程序运行期间始终存在,因此能够保存变量的值

参考: 

STM32 HAL库 STM32CubeMx -- 外部中断_gpio pullup和pulldown_Dir_xr的博客-优快云博客
STM32 中断NVIC应用讲解(标准库andHAL库结合代码对比讲解,)-优快云博客

【按键库 MultiButton】

MultiButton: 简介MultiButton 是一个小巧简单易用的事件驱动型按键驱动模块,可无限量扩展按键,按键事件的回调异步处理方式可以简化你的程序结构,去除冗余的按键处理硬编码,让你的按键业务逻辑更清晰。

MultiButton | 一个小巧简单易用的事件驱动型按键驱动模块-优快云博客

【多功能按键MultiButton模块使用解析,按键扫描,按键事件】_multibutton处理按键冲突-优快云博客

三行代码按键消抖 独立按键 矩阵按键 长按 短按 双击_三行按键法原理-优快云博客

可以使用STM32的GPIO模块来控制LED灯和继电器开关。具体步骤如下: 1. 配置GPIO模块:选择需要使用的GPIO引脚并设置为输出模式。 ```c /* LED控制引脚配置 */ GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.Pin = LED_Pin; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; // 推挽输出模式 GPIO_InitStruct.Pull = GPIO_NOPULL; // 不使用上下拉电阻 GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; // GPIO时钟频率设置 HAL_GPIO_Init(LED_GPIO_Port, &GPIO_InitStruct); /* 继电器控制引脚配置 */ GPIO_InitStruct.Pin = Relay_Pin; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(Relay_GPIO_Port, &GPIO_InitStruct); ``` 2. 按键检测:使用GPIO的中断功能检测按键的状态。 ```c /* 按键检测引脚配置 */ GPIO_InitStruct.Pin = KEY_Pin; GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING; // 下降沿触发中断 GPIO_InitStruct.Pull = GPIO_PULLUP; // 使用上拉电阻 GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(KEY_GPIO_Port, &GPIO_InitStruct); /* 按键中断回调函数 */ void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if (GPIO_Pin == KEY_Pin) { // 按键被按下 // 在这里控制LED灯和继电器开关 } } ``` 3. 控制LED灯和继电器开关:根据按键状态控制GPIO引脚的电平。 ```c if (HAL_GPIO_ReadPin(KEY_GPIO_Port, KEY_Pin) == GPIO_PIN_RESET) { // 按键被按下 HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_SET); // LED灯亮 HAL_GPIO_WritePin(Relay_GPIO_Port, Relay_Pin, GPIO_PIN_SET); // 继电器吸合 } else { // 按键未被按下 HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET); // LED灯灭 HAL_GPIO_WritePin(Relay_GPIO_Port, Relay_Pin, GPIO_PIN_RESET); // 继电器断开 } ``` 以上就是使用STM32控制LED灯和继电器的基本步骤。需要注意的是,需要在main函数中调用HAL_Init()HAL_GPIO_Init()初始化函数,并且要在while(1)循环中调用HAL_GPIO_ReadPin()函数检测按键状态。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值