STM32震动感应控制继电器(使用循环VS使用外部中断EXTI和中断控制器NVIC)

参考:stm32的外部中断 震动感应 控制 继电器
作者:点灯小哥
发布时间: 2021-03-05 22:37:01
网址:https://blog.csdn.net/weixin_46016743/article/details/114417161

参考:STM32震动感应灯
作者:一只小阿大:)
发布时间: 2021-07-09 20:10:02
网址:https://blog.csdn.net/qq_44610809/article/details/118613689

EXTI外部中断和NVIC中断控制器

EXTI外部中断简介

在这里插入图片描述

在这里插入图片描述

STM32 的每个 IO 都可以作为外部中断的中断输入口,这点也是 STM32 的强大之处。STM32F103 的中断控制器NVIC支持19个外部中断/事件请求。每个中断设有状态位,每个中断/事件都有独立的触发屏蔽设置。STM32F103 的19 个外部中断为:

线 0~15:对应外部 IO 口的输入中断。
线 16:连接到 PVD 输出。
线 17:连接到 RTC 闹钟事件。
线 18:连接到 USB 唤醒事件

从上面可以看出,STM32 供 IO 口使用的中断线只有 16 个,但是 STM32 的 IO 口却远远不止 16 个,那么 STM32是怎么把 16 个中断线和 IO 口一一对应起来的呢?
STM32 这样设计的,GPIO 的管脚GPIOx.0-GPIOx.15(x=A,B,C,D,E,F,G)分别对应中断线 0~15。这样每个中断线对应了最多 7 个 IO 口,以线0为例:它对应了GPIOA.0、GPIOB.0、GPIOC.0、GPIOD.0、GPIOE.0、GPIOF.0、GPIOG.0,而中断线每次只能连接到 1个 IO 口上,这样就需要通过配置EXTI0[3:0]来决定对应的中断线配置到哪一个GPIO上了

下面我们看看 GPIO 跟中断线的映射关系图(第二张图片右侧):
在这里插入图片描述
在这里插入图片描述

中断触发方式(上升沿和下降沿)

  • 上升沿触发:数字电平从低电平(数字“0”)变为高电平(数字“1”)的那一瞬间叫作上升沿。 上升沿触发是当信号有上升沿时的开关动作,当电位由低变高而触发输出变化的就叫上升沿触发。也就是当测到的信号电位是从低到高也就是上升时就触发,叫做上升沿触发。
  • 下降沿触发:数字电路中,数字电平从高电平(数字“1”)变为低电平(数字“0”)的那一瞬间叫作下降沿。 [1] 下降沿触发是当信号有下降沿时的开关动作,当电位由高变低而触发输出变化的就叫下降沿触发。也就是当测到的信号电位是从高到低也就是下降时就触发,叫做下降沿触发。
  • 边沿触发:既可以上升触发也可以下降触发

上升沿触发 就是当电压从低变高时触发中断
下降沿触发 就是当电压从高变低时触发中断

EXTI外部中断结构体(19根中断线、触发方式、使能)

在这里插入图片描述

  • EXTI_Line:EXTI中断/事件线选择,可选EXTI0至EXTI19这20个中断线
    在这里插入图片描述

  • EXTI_Mode:EXTI模式选择,可选为产生中断模式/产生事件

  • EXTI_Trigger:上升沿/下降沿/双沿触发
    在这里插入图片描述

  • EXTI_LineCmd:使能OR失能

NVIC中断控制结构体(中断通道/源 – EXTI外部中断/串口中断、中断分组、抢占优先级、子优先级)

在misc.h文件中,属于内核寄存器
在这里插入图片描述

在这里插入图片描述
中断通道/中断源(EXTI外部中断源、串口中断源等,注意串口中断不属于EXTI外部中断):
在这里插入图片描述
NVIC中断分组、抢占优先级和子优先级:

在这里插入图片描述

在这里插入图片描述

STM32 将中断分为5 个组,组 0 ~ 4。该分组的设置是由 SCB->AIRCR 寄存器的 bit10 ~ 8 来定义的。NVIC中断分组使用如下的固件库函数:

NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置NVIC中断分组  配置成第二组

在这里插入图片描述

通过这个表,我们就可以清楚的看到组 0~4 对应的配置关系,例如组设置为 3,那么此时所有的 60 个中断,每个中断的中断优先寄存器的高四位中的最高 3 位是抢占优先级,低 1 位是响应优先级。每个中断,你可以设置抢占优先级为 0 ~ 7,响应优先级为 1 或 0。抢占优先级的级别高于响应优先级。而数值越小所代表的优先级就越高。

优先级原则:第一,如果两个中断的抢占优先级和响应优先级都是一样的话,则看哪个中断先发生就先执行;第二,高优先级的抢占优先级是可以打断正在进行的低抢占优先级中断的。而抢占优先级相同的中断,高优先级的响应优先级不可以打断低响应优先级的中断。

举例: 假定设置中断优先级组为 2,然后设置中断 3(RTC 中断)的抢占优先级为 2,响应优先级为 1。中断 6(外部中断> 0)的抢占优先级为 3,响应优先级为 0。中断 7(外部中断 1)的抢占优先级为 2,响应优先级为 0。那么这 3> 个中断的优先级顺序为:中断 7>中断 3>中断 6。 上面例子中的中断 3 和中断 7 都可以打断中断 6 的中断。而中断 7 和中断 3> 却不可以相互打断!

中断服务函数名(在启动头文件里)

在这里插入图片描述

中断函数配置步骤

IO 口外部中断的一般步骤:

  • 1、初始化 IO 口为输入。
  • 2、开启 AFIO(GPIO复用为中断)时钟 - - rcc.h
  • 3、设置 IO 口与中断线的映射关系(配置GPIOA1为中断源) - - gpio.h。
  • 4、初始化线上中断,设置触发条件等 - - exti.h。
  • 5、配置中断分组(NVIC),并使能中断 - - misc.h。
  • 6、编写中断服务函数 - - 启动头文件。
  • 7、判断中断标志位(上升沿/下降沿硬件自动更改标志位)。
  • 8、清除中断标志位(手动)。

通过以上几个步骤的设置,我们就可以正常使用外部中断了。

具体函数:

1、初始化IO的输入

GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);

2、开启IO口复用时钟

RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState);

3、设置IO口与中断线的映射关系

GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource);

4、初始化中断线、触发方式等

EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct);

5、配置中断分组(NVIC),并使能中断

NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct)

6、编写中断服务函数

EXTI4_IRQHandler()

7、判断中断线中断状态,是否发生

EXTI_GetITStatus(uint32_t EXTI_Line);

8、清除中断标志位

EXTI_ClearITPendingBit(uint32_t EXTI_Line);

硬件接线及开发环境

硬件平台

stm32最小系统
在这里插入图片描述
软件平台

Keil C51是美国Keil Software公司出品的51系列兼容单片机C语言软件开发系统,与汇编相比,C语言在功能上、结构性、可读性、可维护性上有明显的优势,因而易学易用。Keil提供了包括C编译器、宏汇编、链接器、库管理和一个功能强大的仿真调试器等在内的完整开发方案,通过一个集成开发环境(μVision)将这些部分组合在一起。运行Keil软件需要WIN98、NT、WIN2000、WINXP等操作系统。如果你使用C语言编程,那么Keil几乎就是你的不二之选,即使不使用C语言而仅用汇编语言编程,其方便易用的集成环境、强大的软件仿真调试工具也会令你事半功倍。

接线图
在这里插入图片描述

代码编写(使用循环VS使用外部中断)

继电器模块relay.c(PA3)

relay.c

#include "relay.h"
#include "stm32f10x.h"                  // Device header
 
void Relay_Init(void)
{
		GPIO_InitTypeDef Relay_init ;//结构体定义放到开时钟前面
		
		//1.使能GPIOA时钟    接入PA3口
		RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA ,  ENABLE);
		//2.GPIOA3结构体配置
		Relay_init.GPIO_Mode  = GPIO_Mode_Out_PP;//推挽输出
		Relay_init.GPIO_Pin   = GPIO_Pin_3;
		Relay_init.GPIO_Speed = GPIO_Speed_10MHz;
	
		GPIO_Init( GPIOA , &Relay_init);
 
}

单独新建立relay文件夹,里面新建relay.c和relay.h两个文件,先将relay.c文件添加到USER里面,写完第一句代码#include "relay.h"直接编译,relay.h会自动包含到relay.c的子目录下。
若头文件和.c文件不在同一个文件目录下编译将提示找不到头文件,需要手动添加头文件路径。

relay.h

#include "stm32f10x.h"
 
void Relay_Init(void);//先声明,再定义,再调用

震动感应模块shake.c(PA1)

shake.c

#include "shake.h"
#include "stm32f10x.h"                  // Device header
 
void Shake_Init(void)
{

	GPIO_InitTypeDef Shake_init;
	
	//1.打开我们的GPIOA时钟      连接PA1引脚
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	//2.配置GPIOA1的配置
	Shake_init.GPIO_Mode  = GPIO_Mode_IPD;  //震动传感器初始为高电平(无振动),有振动就为低电平 检测从高电平到低电平的变化  所以用下拉模式(这个表述不是很懂)
	Shake_init.GPIO_Pin   = GPIO_Pin_1;
	Shake_init.GPIO_Speed = GPIO_Speed_10MHz;
		
	 GPIO_Init( GPIOA, &Shake_init);
 
}

shake.h

#include "stm32f10x.h"
 
void Shake_Init(void);

EXTI外部中断配置exti.c(PA1为中断源)

在这里插入图片描述

exti.h

#include "stm32f10x.h"  
 
void Exti_Init(void);

在这里插入图片描述
在这里插入图片描述

exti.c

#include "exti.h"
#include "stm32f10x.h"  // Device header 固件库头文件
 
void Exti_Init(void)
{
	//1.配置GPIO A1
	GPIO_InitTypeDef Shake_init;//gpio.h  和shake.c一样的
	EXTI_InitTypeDef Exti_init;	//exti.h
	NVIC_InitTypeDef Nvic_init;	//misc.h
	//使能GPIO和GPIOA复用的时钟  如上图所示
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //使能GPIOA的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);  //使能GPIOA的复用时钟
	
    //设置GPIOA1为中断源 如上图所示
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,  GPIO_PinSource1); //gpio.h  配置的外部中断源是GPIOA1  是shake震动传感器(GPIOA pin1的复用)
	//配置NVIC中断组 选第2组(随便选)
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); 
	
	Shake_init.GPIO_Mode  = GPIO_Mode_IPD;  //震动传感器初始为高电平  所以用下拉模式 拉到低电平(不是很懂)
	Shake_init.GPIO_Pin   = GPIO_Pin_1;
	Shake_init.GPIO_Speed = GPIO_Speed_10MHz;
	GPIO_Init( GPIOA, &Shake_init);
	
	
	// 2、配置EXTI外部中断 
	Exti_init.EXTI_Line    = EXTI_Line1; //中断线     因为我们用到的中断源是GPIOA1 所以中断线选择Line1  
	Exti_init.EXTI_Mode    = EXTI_Mode_Interrupt;//中断模式  还一种是事件模式
	Exti_init.EXTI_Trigger = EXTI_Trigger_Falling;//振动传感器有振动源————>高电平到底电平 所以选择下降沿触发
	Exti_init.EXTI_LineCmd = ENABLE; //选使能  还一种是静止模式
	EXTI_Init(&Exti_init);//头文件最下面的函数
 
 
	///3、配置中断控制器(NVIC)
	Nvic_init.NVIC_IRQChannel = EXTI1_IRQn; //中断通道 因为我们用到的中断源是GPIOA1所以选择中断通道1
	Nvic_init.NVIC_IRQChannelCmd = ENABLE;  //使能  找到FunctionalState字眼 右键goto
	Nvic_init.NVIC_IRQChannelPreemptionPriority = 1;//因为只配置了一个中断 抢占优先级、子优先级随便都设为1
	NVIC_Init(&Nvic_init);//中断初始化函数
	
	//4.编写中断服务函数  写在main函数里
}

主函数(main.c)

main.c

#include "stm32f10x.h" // Device header
#include "relay.h"//这个会找不到头文件,需要手动添加头文件路径  点击魔术棒——选择C/C++——include path
#include "shake.h"//下同
#include "exti.h"
 
void delay(uint16_t time)//ms延时函数
{
		uint16_t i = 0;
		while(time--)
		{
			i=12000;
			while(i--);
		}
}
int main(void)
{

		LED_Init();
		Relay_Init();
		Shake_Init();
		Exti_Init();
		
		GPIO_SetBits(GPIOA , GPIO_Pin_3); //初始化继电器为关闭状态

		
下面这部分没有使用外部中断,直接While()死循环检测振动模块电平,从而控制灯的亮灭(继电器)		
//		while(1)
//		{
//              //if内部判断是否发生电平变化
//				if(GPIO_ReadInputDataBit( GPIOA,  GPIO_Pin_1) == 0) //如果发生震动
//				{
//						GPIO_ResetBits(GPIOA , GPIO_Pin_3);//拉低开灯
//						delay(1000);//延迟1秒											 //持续一秒
//						GPIO_SetBits(GPIOA , GPIO_Pin_3);//拉高关灯
//				}else{
//						GPIO_SetBits(GPIOA , GPIO_Pin_3);
//				} 			
//		}

}

//4.中断服务函数   中断的好处:不用一直while()死循环,一直占用CPU
void EXTI1_IRQHandler(void)//在启动文件里 函数名不能变
{	
	if (EXTI_GetITStatus( EXTI_Line1 )  != RESET) //判断是否发生了中断  右键 EXTI_GetITStatus goto 在exit.h定义 exit.c实现
	{
		GPIO_ResetBits(GPIOA , GPIO_Pin_3);  //打开继电器,开灯
		delay(1000);
		GPIO_SetBits(GPIOA , GPIO_Pin_3);	//关闭继电器,关灯
	}		
		EXTI_ClearFlag( EXTI_Line1);//清除中断标志 不然标志位一直都存在  在exit.h			
}
  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

行稳方能走远

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值