STM32F103五分钟入门系列外部中断大汇总

Posted 自信且爱笑‘

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了STM32F103五分钟入门系列外部中断大汇总相关的知识,希望对你有一定的参考价值。

学习板:STM32F103ZET6

强推系列:
STM32F103五分钟入门系列(一)跑马灯(库函数+寄存器)+加编程模板+GPIO总结

STM32F103五分钟入门系列(二)GPIO的七大寄存器+GPIOx_LCKR作用和配置

STM32F103五分钟入门系列(三)GPIO的常用库函数使用方法总结+一个网络上的误区

参考:

51单片机(四)定时器中断(+数码管—24小时制钟表)

STM32F103五分钟入门系列(八)SysTick滴答定时器+SysTick中断实现跑马灯

STM32F103五分钟入门系列(十)NVIC中断优先级管理

本博会用到:

STM32F103五分钟入门系列(一)跑马灯(库函数+寄存器)+加编程模板+GPIO总结

STM32F103五分钟入门系列(四)蜂鸣器实验(库函数+寄存器)

STM32F103五分钟入门系列(五)按键实验(库函数+寄存器)

前言

上一博客总结了NVIC中断优先级管理,本博总结一下外部中断的一些内容。本博会用外部中断的方法来实现之前博客:STM32F103五分钟入门系列(四)蜂鸣器实验(库函数+寄存器)中所举例子。

一、外部中断

(一)外部中断的数量及引脚映射关系

1、外部中断数量

在STM32F103中有19个外部中断:

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

在底层中的定义:

在这里插入图片描述

2、外部中断与GPIO的映射关系GPIO_EXTILineConfig()函数

在这里插入图片描述

这个图详细的表述了外部中断与GPIO的映射关系:

外部中断EXTI0——>GPIOA.0、GPIOB.0、GPIOC.0…GPIOG.0
外部中断EXTI1——>GPIOA.1、GPIOB.1、GPIOC.1…GPIOG.1
.
.
.
外部中断EXTI15——>GPIOA.15、GPIOB.15、GPIOC.15…GPIOG.15

如EXTI0可以映射到PA0~PG0,那么EXTI0究竟映射到哪个引脚呢?当然是用到:GPIO_EXTILineConfig()函数了。

在stm32f10x_gpio.h头文件中:
在这里插入图片描述
在这里插入图片描述

第一个参数:
在这里插入图片描述

第二个参数:

在这里插入图片描述

通过这个函数,将外部中断线与GPIO的引脚映射起来。

如对KEY_UP:
在这里插入图片描述
在这里插入图片描述

中断映射:

	GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0 )

从参数中也能看到PA0…那只能是外部中断线0与PA0映射。

(三)外部中断初始化函数 EXTI_Init()

该函数设置某个中断的触发方式及使能,在stm32f10x_exti.h头文件中。
在这里插入图片描述
打开函数体:

在这里插入图片描述

这个函数传递的参数是一个结构体,这种函数的配置应该见过N遍了。

第一个参数:MODE
在这里插入图片描述

表示模式是中断还是事件。

第二个参数:

在这里插入图片描述

表示触发方式是上升沿、下降沿、还是双边沿

第三个参数:
在这里插入图片描述

表示哪个引脚的中断,之前的GPIO_EXTILineConfig()已经将中断线与引脚对应起来了,所以这里只需选择是哪条中断线即可。

第四个参数:使能和失能

在这里插入图片描述

这类型函数设置方法之前有过基础,直接举例:

例:设置刚刚的KEY_UP:

①要使用中断,所以第一个参数MODE为EXTI_Mode_Interrupt

②按下KEY_UP后,PA0变为高电平,所以为上升沿触发,第二个参数为:EXTI_Trigger_Rising

③引脚连接在PA0,所以第三个参数为:EXTI_Line0

④使能中断,所以第四个参数为:ENABLE

直接附代码:

	EXTI_InitTypeDef  EXTI_InitStructure;
	
	EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;
	EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Rising;
	EXTI_InitStructure.EXTI_Line=EXTI_Line0;
	EXTI_InitStructure.EXTI_LineCmd=ENABLE;
	EXTI_Init(&EXTI_InitStructure);

(三)外部中断解除函数EXTI_DeInit()

在这里插入图片描述

与外部中断相关的寄存器在中文参考手册中,该函数将IMR、EMR、RTSR、FTSR寄存器都置0,对PR寄存器写1来清零。
在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述
在这里插入图片描述

注意该函数没有参数的,即调用该函数后,所有的外部中断都被屏蔽掉,所以该函数要慎用。

如果想清除某一个外部中断,而保留其他外部中断,则直接用寄存器的方法来设置。

例:清除前面例子中KEY_UP的外部中断。

①屏蔽线0上中断,所以IMR寄存器位0置0(事件模式时可不设置)

	EXTI->IMR&=0xfffffffe;//屏蔽线0中断

②屏蔽线0上的事件请求,所以EMR寄存器位0置0(中断模式时可不设置)

	EXTI->EMR&=0xfffffffe;//屏蔽线0事件

③禁止线0上升沿触发中断和事件,所以RTSR寄存器位0置0(下降沿触发中断时可不设置,双边沿触发需要设置)

	EXTI->RTSR&=0xfffffffe;//禁止线0上升沿触发中断和事件

④禁止线0下降沿触发中断和事件,所以FTSR寄存器位0置0(上升沿沿触发中断时可不设置,双边沿触发需要设置)

	EXTI->FTSR&=0xfffffffe;//禁止线0下降沿触发中断和事件

⑤挂起线0的中断,所以PR寄存器位0软件置1来清除改位。

	EXTI->PR|=1;//挂起线0的中断和事件

(四)外部中断参数初始化函数EXTI_StructInit()

在这里插入图片描述

该函数将所有中断设置的参数初始化为:
①中断线:无
②模式:中断
③触发方式:下降沿
④失能

通过这种方法也能关闭全部外部中断:(慎用)

	EXTI_InitTypeDef EXTI_InitStructure;
	EXTI_StructInit(& EXTI_InitStructure);
	EXTI_Init(&EXTI_InitStructure);//失能全部外部中断

(五)软件中断函数EXTI_GenerateSWInterrupt()

在这里插入图片描述

该函数是可以通过软件来触发对应中断线产生中断。该函数对SWIER寄存器直接操作。
在这里插入图片描述

如果IMR和EMR寄存器允许中断和事件,即对应中断线上的中断或事件没有被屏蔽,不论设置的中断线是上升沿、下降沿还是双边沿触发,调用该函数后,都会产生一个中断。

(六)外部中断状态函数EXTI_GetFlagStatus()

在这里插入图片描述

如果发生中断,PR寄存器的对应位被硬件置1所以可以检测PR寄存器对应位是否为1来获得某外部中断线是否发生中断。

注意的是:
在这里插入图片描述

该函数返回的RESET为0,但是SET不是1,而是!0,所以最好不要这样写:

	if(EXTI_GetFlagStatus(EXTI_Line0)==1)//发生中断
	{
	
	}

而应该:

	if(EXTI_GetFlagStatus(EXTI_Line0))//发生中断
	{
	
	}

或者:

	if(EXTI_GetFlagStatus(EXTI_Line0)==SET)//发生中断
	{
	
	}

(七)中断状态清除函数EXTI_ClearFlag()

在这里插入图片描述

该函数也是对PR寄存器的设置,将寄存器对应软件位置1后清除该位

在这里插入图片描述

(八)外部中断状态函数EXTI_GetITStatus()

在这里插入图片描述

该函数获取中断的状态,返回RESET和SET,与EXTI_GetFlagStatus()函数一致,不过该函数还判断了IMR是否屏蔽了中断请求。所以EXTI_GetITStatus()EXTI_GetFlagStatus()更严谨,以后习惯用这个函数就行。

(九)清除中断标志位函数EXTI_ClearITPendingBit()

在这里插入图片描述

该函数与EXTI_ClearFlag()一模一样,所以俩个函数可以随便用。

二、外部中断的中断服务函数

(一)外部中断服务函数数量

STM32的外部中断服务函数只有6个:

线0:EXTI0_IRQHandler
线1:EXTI1_IRQHandler
线2:EXTI2_IRQHandler
线3:EXTI3_IRQHandler
线4:EXTI4_IRQHandler
线5~9:EXTI9_5_IRQHandler
线10~15:EXTI15_10_IRQHandler

中断服务函数定义在启动文件中:

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

线0~4各用一个中断服务函数,线5 ~9共用一个中断服务函数,线10 ~15共用一个中断服务函数。

(二)外部中断服务函数编写规则

在中断服务函数中写的代码主要是发生中断后要执行的代码,比如之前SysTick这一博客中,用它的中断实现跑马灯,则SysTick中断服务函数中就写LED灯的亮、灭。

在中断服务函数中,第一步要检测是否发生中断,如果发生中断,就执行中断后想要执行的代码,执行完中断服务函数后,需要清除中断标志,如果不清除中断标志,就一直显示处于中断中,一直会执行中断后的代码,导致程序跑飞。

代码格式:

	void EXTIX_IRQHandler(void)
	{
	if(EXTI_GetITStatus(EXTI_Line3)!=RESET)//判断某个线上的中断是否发生 
	{
	  //中断逻辑…
	EXTI_ClearITPendingBit(EXTI_Line3); //清除 LINE 上的中断标志位 
	}
   }

或者有时候把导致中断的举动写进中断服务函数中,如按下按键后,执行led亮、蜂鸣器响等。

	void EXTI0_IRQHandler(void)
	{
		delay_ms(10);//消抖
		if(WK_UP==1)	 	 //WK_UP按键
		{				 
			BEEP=!BEEP;	
		}
		EXTI_ClearITPendingBit(EXTI_Line0); //清除LINE0上的中断标志位  
	}

当然,如上述代码,之前是检测到PA0有上升沿的,所以才会触发中断进入中断服务函数。然后再消抖后重新检查KEY_UP是否按下,确定按下后再执行蜂鸣器发声。不论消抖后检测到按下还是没按下,它都的的确确的发生了中断,即只要检测到上升沿就会发生中断,发生中断就会进入中断服务函数,PR寄存器对应位会被置1,所以中断服务函数后面也必须得对PR寄存器写1清零。

(三)外部中断代码的编写顺序

(1)初始化IO时钟
既然要用到线0~15的中断,所以GPIO的时钟必须先使能,不论是复用还是不复用。

(2)初始化IO为输入
既然是外部中断,那么可能是外部的一个信号触发中断,所以IO需设置为输入,至于是上拉、下拉还是浮空输入,需要看外围电路。

(3)开启复用时钟

GPIO正常情况下是只做输入输出的,要实现其他功能就需要GPIO的复用。IO口复用可以查看《STM32中文参考手册》第八章GPIO和AFIO

(4)设置IO口与中断的映射关系
即对GPIO_EXTILineConfig()函数的设置

(5)初始化线上中断,设置触发条件等
即EXTI_Init()函数的设置

(6)配置中断分组(NVIC),并使能中断
当然除了设置中断分组外,还需要设置外部中断的优先级,这些都在上一博客中总结了。

(7)编写中断服务函数

三、举例

(一)题

现用STM32F103五分钟入门系列(五)按键实验(库函数+寄存器)的例子:

①按下key_up后LED0、LED1交替闪烁,每0.5s闪烁一次,取消按下后,两个灯全灭。
②按下key0后,LED0常亮、蜂鸣器每隔0.5s间断发声。
③按下key2后,LED1常亮,蜂鸣器每隔0.5s间断发声。
④按下key1后,LED0、LED1同时亮,同时灭,且同时灭的时候蜂鸣器发声,同时亮的时候蜂鸣器不发声;间隔1s

因为有延时,会用到:STM32F103五分钟入门系列(九)延时函数(自己重写的底层)(上限:477218ms和477218588us)

(二)搞清楚IO和工作的高低电平

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

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

KEY_UP:接PA0、按下后IO为高电平,下拉输入(未按下时输入未知,拉到低电平)

KEY2:接PE2、按下后IO为低电平,上拉输入(未按下时输入未知,拉到高电平)

KEY1:接PE3、按下后IO为低电平,上拉输入(未按下时输入未知,拉到高电平)

KEY0:接PE4、按下后IO为低电平,上拉输入(未按下时输入未知,拉到高电平)

LED1:接PE5、输出低电平后灯亮、通用推挽输出

LED0:接PB5、输出低电平后灯亮、通用推挽输出

BEEP:接PB8、输出高电平发声、通用推挽输出

(三)代码编写

1、led.h和led.c

之前的几篇博客都讲解过,直接附代码:

led.h代码:

1	//led.h
2	#ifndef LED_H
3	#define LED_H
4	void LED_Init(void);
5
6	#endif
7

led.c代码:

1 	//led.c
2 	#include "sys.h"
3 	#include "stm32f10x.h"
4 	#include "led.h"
5 	void LED_Init(void)
6 	{
7 		GPIO_InitTypeDef GPIO_InitStruct_B;
8 		GPIO_InitTypeDef GPIO_InitStruct_E;
9 		RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOE ,ENABLE);
10		
11		GPIO_InitStruct_B.GPIO_Mode=GPIO_Mode_Out_PP;
12		GPIO_InitStruct_B.GPIO_Pin=GPIO_Pin_5;
13		GPIO_InitStruct_B.GPIO_Speed=GPIO_Speed_50MHz;
14		GPIO_Init(GPIOB,&GPIO_InitStruct_B);
15		
16		GPIO_InitStruct_E.GPIO_Mode=GPIO_Mode_Out_PP;
17		GPIO_InitStruct_E.GPIO_Pin=GPIO_Pin_5;
18		GPIO_InitStruct_E.GPIO_Speed=GPIO_Speed_50MHz;
19		GPIO_Init(GPIOE,&GPIO_InitStruct_E);
20		
21		GPIO_SetBits(GPIOB, GPIO_Pin_5);//PB5置高电平
22		//GPIO_WriteBit(GPIOB, GPIO_Pin_5,1);
23		//GPIO_Write(GPIOB,0x0020);              //慎用
24		//PBout(5)=1;
25		GPIO_SetBits(GPIOE, GPIO_Pin_5);//PE5置高电平
26		//GPIO_WriteBit(GPIOE, GPIO_Pin_5,1);
27		//GPIO_Write(GPIOE,0x0020);              //慎用
28		//PEout(5)=1;
29 }

2、beep.h和beep.c

之前的几篇博客都讲解过,直接附代码:

头文件:beep.h:

//beep.h
#ifndef BEEP_H
#define  BEEP_H

void BEEP_Init();
#endif

beep.c代码:

1 	//beep.c
2 	#include "beep.h"
3 	#include "stm32f10x.h"
4 	#include "sys.h"
5 
6 	void BEEP_Init(void)
7 	{
8 		GPIO_InitTypeDef GPIO_InitStruct;
9 		RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);//使能GPIOB时钟
10		
11		GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP;
12		GPIO_InitStruct.GPIO_Pin=GPIO_Pin_8;
13		GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;//PB8 IO配置
14		GPIO_Init(GPIOB,&GPIO_InitStruct);
15		
16		GPIO_ResetBits(GPIOB,GPIO_Pin_8);     //初始状态置低电平,关闭蜂鸣器
17		//GPIO_WriteBit(GPIOB,GPIO_Pin_8, 0);//初始状态置低电平,关闭蜂鸣器
18		//GPIO_Write(GPIOB,0);          //初始状态置低电平,关闭蜂鸣器 
19		//PBout(8)=0;                         //初始状态置低电平,关闭蜂鸣器
20	}

3、key.h和key.c

之前的几篇博客都讲解过,直接附代码:

头文件key.h:

#ifndef KEY_H
#define KEY_H

void KEY_Init(void);

#endif

key.c文件:

//key.c
#include "stm32f10x.h"
#include "sys.h"
#include "key.h"
void KEY_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStruct_A;
	GPIO_InitTypeDef GPIO_InitStruct_E;
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOE , ENABLE);//使能GPIOA和GPIOE(PA0 PE2、3、4)
	
	GPIO_InitStruct_A.GPIO_Mode=GPIO_Mode_IPD;
	GPIO_InitStruct_A.GPIO_Pin=GPIO_Pin_0;
	GPIO_InitStruct_A.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStruct_A);//PA0 key_up  下拉输入
	
	GPIO_InitStruct_E.GPIO_Mode=GPIO_Mode_IPU;
	GPIO_InitStruct_E.GPIO_Pin=GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4;
	GPIO_InitStruct_E.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOE, &GPIO_InitStruct_E);//PE2、3、4 key0、key1、key2 上拉输入
}

4、delay.h和delay.c

之前博客重写过,直接附代码:

头文件:delay.h:

	//delay.h
	#ifndef _DELAY_
	#define _DELAY_
	#include  "sys.h"
	void delay_ms(u32 nms);
	void delay_Init(void);
	#endif

delay.c:

 	//delay.c
	#include "delay.h"
	void delay_Init(void)
	{
		SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); //选择外部时钟源72MHZ/8=9MHZ
		 //SysTick->CTRL &=0xFFFFFFFB
		
		SysTick->VAL=0; //清空计数器
	}
	
	void delay_ms(u32 n)
	{
		u32 a;//商
		u32 b;//余数
		u32 temp;
		a=(9000*n/0xffffff);//2^24 -1=16777215=0xffffff
		b=(9000*n)%0xffffff;
		while(a)//商大于0时
		{
			SysTick->LOAD=(u32)0xffffff; //装载最大可装载值
			SysTick->VAL=0; //清空计数器
			SysTick->CTRL|=1;       //使能计数器,开始计数
			do
			{
				temp=SysTick->CTRL;
			}while((temp&0x01)&&!(temp&(0x10000)));//while(计数器未使能&&(!CTRL计数器倒计数到0))
			a--;
			if(!a)//a为0,表示装载最大可装载值的次数用完
			{
				SysTick->CTRL&=0xfffffffe;//关闭计数器
			}
		}
		
		SysTick->LOAD=b;//装载余数
		SysTick->VAL =0x00;	//清空计数器
		SysTick->CTRL|=1;       //重新使能计数器,开始计数
		do
		{
			temp=SysTick->CTRL;
		}while((temp&0x01)&&!(temp&(0x10000)));//while(计数器未使能&&(!CTRL计数器倒计数到0)) 
		
		SysTick->CTRL&=0xfffffffe;//关闭计数器
		SysTick->VAL =0X00;       //清空计数器	 
	}

5、添加.c和.h文件到工程

以LED为例:

(1)工程目录下新建文件夹LED
在这里插入图片描述
点击工程名——>【manage project…】
在这里插入图片描述
在这里插入图片描述

新建text文件,并保存
在这里插入图片描述
并保存为led.c

在这里插入图片描述

同理保存led.h
在这里插入图片描述

添加.c文件:
在这里插入图片描述在这里插入图片描述
在这里插入图片描述

添加头文件.h
在这里插入图片描述在这里插入图片描述
在这里插入图片描述

同理将其他文件添加进来:
在这里插入图片描述

6、exti.h

exti.h还是老规矩:

#ifndef _EXTI_
#define _EXTI_

void exti_Init(void);
#endif

7、exti.c

因为之前的key中都使能了GPIO时钟和设置了GPIO,所以现在直接开启复用时钟。

由于中断服务函数要在exti.c文件中写,所以把lexd.h、key.h、beep.h、delay.h包含进来

代码:

	#include "stm32f10x.h"
	#include "led.h"
	#include "key.h"
	#include "beep.h"
	#include "delay.h"
	#include "exti.h"
	void exti_Init()
	{
		RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
	}

然后接下来就是中断线与IO的映射:
KEY_UP——>线0——>PA0
KEY2——>线2——>PE2
KEY1——>线3——>PE3
KEY0——>线4——>PE4

	GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);外部中断与PA0映射
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOE, GPIO_PinSource2);
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOE, GPIO_PinSource3);
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOE, GPIO_PinSource4);

然后就是外部中断初始化函数:

#include "stm32f10x.h"
#include "led.h"
#include "key.h"
#include "beep.h"
#include "delay.h"
#include "exti.h"
void exti_init()
{
	EXTI_InitTypeDef  EXTI_InitStructure;
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
	
		//外部中断与PE2、PE3、PE4映射
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOE, GPIO_PinSource2);
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOE, GPIO_PinSource3);
	GPIO_EXTILineConfig(GPIO_Port

以上是关于STM32F103五分钟入门系列外部中断大汇总的主要内容,如果未能解决你的问题,请参考以下文章

STM32F103五分钟入门系列定时器中断

STM32F103五分钟入门系列SysTick滴答定时器+SysTick中断实现跑马灯

STM32F103(二十七)超长篇解读STM32访问外部flash

STM32F103(二十七)超长篇解读STM32访问外部flash

STM32F103五分钟入门系列(十五)输入捕获(精雕细琢-.-)

STM32F103五分钟入门系列(十六)输入捕获(精雕细琢-.-)