参考: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外部中断简介
- 中断触发方式(上升沿和下降沿)
- EXTI外部中断结构体(19根中断线、触发方式、使能)
- NVIC中断控制结构体(中断通道/源 -- EXTI外部中断/串口中断、中断分组、抢占优先级、子优先级)
- 中断服务函数名(在启动头文件里)
- 中断函数配置步骤
- 硬件接线及开发环境
- 代码编写(使用循环VS使用外部中断)
- 继电器模块relay.c(PA3)
- 震动感应模块shake.c(PA1)
- EXTI外部中断配置exti.c(PA1为中断源)
- 主函数(main.c)
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 headervoid 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 headervoid 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 A1GPIO_InitTypeDef Shake_init;//gpio.h 和shake.c一样的EXTI_InitTypeDef Exti_init; //exti.hNVIC_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所以选择中断通道1Nvic_init.NVIC_IRQChannelCmd = ENABLE; //使能 找到FunctionalState字眼 右键gotoNvic_init.NVIC_IRQChannelPreemptionPriority = 1;//因为只配置了一个中断 抢占优先级、子优先级随便都设为1NVIC_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
}