STM32_按键点灯学中断

1、首先回答下什么是中断?
中断就是让芯片停下当前正执行的程序,去执行另一个程序,举一个形象一点的例子,就是你正在家里面打游戏,突然有人敲门,这时,你不得不暂停游戏,去开门,这个过程就叫做中断,敲门可以理解为中断触发(中断源)。

Cortex-M3在内核上搭载了一个异常响应系统,支持为数众多的系统异常和外部中断。其中,编号为1-15的对应系统异常(注意:没有编号为0的异常,即使为0也不执行异常),大于等于16的则全是外部中断。除了个别异常的优先级被定死外,其它异常的优先级都是可编程的。
在这里插入图片描述

每个外设都可以产生中断,可以将中断与异常等价,不要深究它们到底有什么区别
16个系统异常清单如下:
在这里插入图片描述
60个外部中断:
在这里插入图片描述

有关具体的系统异常和外部中断可在标准库文件stm32f10x.h 这个头文件查询到,在IRQn_Type (中断/异常类型)这个结构体里面包含了F103 系列全部的异常声明。

下图为STM32的外部中断系统有哪些部分组成?

那么系统的中断又什么控制?

1. 先来讲解NVCI,在系统内部中如何处理中断?
NVIC(Nested Vectored Interrupt Controller):嵌套向量中断控制器,不可屏蔽中断(NMI)和外部中断都由它处理,但是SYSTICK(系统滴答定时器)却不是由NVIC来控制。

在这里插入图片描述
NVIC的作用就是:控制中断是否产生,比如你打游戏正上头,突然有人敲门,但是你却不想去开门,也就没有产生中断,这里人就相当与一个中断控制器,决定着中断是否产生。

理解了中断控制器,那就开始像配置GPIO那样来配置NVIC吧。
首先要了解下NVIC的构成,这样才能对每一项参数进行设定。
下图是在ST官方提供的固件库文件(misc.h)中找到的:
在这里插入图片描述
在这里插入图片描述
下面通过一幅图来理解NVIC中断控制器每一个成员所扮演的角色可能更加形象:
在这里插入图片描述
下面为EXTI功能框图,就不做过多说明,其实说的就是EXTI有两大功能,传递中断和事件,软件本质上是通过控制或门或者与门(硬件)来实现对中断和事件的控制。
在这里插入图片描述
下面再对NVIC结构体中出现的其他生词进行阐述:

①IRQ(Interrupt Request ):中断请求。
比如想要执行“紧急事件”必须向处理器提出申请(发一个电脉冲信号),要求“中断”,即要求处理器先停下“自己手头的工作”先去处理“紧急事件”,这一“申请”过程,称为——中断请求。

②什么叫抢占优先级和响应优先级(子优先级)?
这两个优先级发挥的作用就是决断,触发中断后,决断哪个更重要(优先级更高)就优先执行哪一个,也就是优先重要不紧急的事,这也是一种高效率做事的方式。
STM32 的中断向量具有两个属性,一个为抢占属性,另一个为响应属性,其属性编号越小,表明它的优先级别越高。

抢占:是指打断其他中断,执行优先级更高的程序,所以会出现嵌套中断,即在执行中断服务函数A 的过程中被中断B 打断,那么此时就要停下来,开始执行中断服务函数B,执行完B后再继续执行原来的中断服务函数A,抢占属性由NVIC_IRQChannelPreemptionPriority 的参数配置。

响应:在抢占优先级相同且同时到达的情况下,则先处理响应优先级高的中断, 响应属性由NVIC_IRQChannelSubPriority 参数配置。

总结一下:就是中断一次性可能有多个发生,就比如你正在打游戏,可能让你停下打游戏的突发事情有很多,而且还可能同时出现,那么此时就要理清先做哪一个,后做哪一个,其实本质还是优先“重要不紧急”的事。

③优先级是如何定义的呢?
在NVIC 有一个专门的寄存器:中断优先级寄存器NVIC_IPRx,用来配置外部中断的优先级,IPR宽度为8bit,原则上每个外部中断可配置的优先级为0~255,即存在256个中断,优先级数值越小,优先级越高。但是绝大多数CM3 芯片都会精简设计,以致实际上支持的优先级数减少,上面F103的外部中断清单图可知,F103只有60个外部中断,同时在F103 中,只使用了高4bit,即高四位有效。
在这里插入图片描述
用于表达优先级的这4bit,又被分组成抢占优先级和子优先级。如果有多个中断同时响应,抢占优先级高的就会抢占抢占优先级低的优先得到执行,如果抢占优先级相同,就比较子优先级。如果抢占优先级和子优先级都相同的话,就比较他们的硬件中断编号,编号越小,优先级越高。

中断优先级比较:多个中断触发时,先比较它们的抢占优先级,再比较响应优先级,最后比较编号(一定不会相同)。

优先级被分为了5组,通过这5组可以将中断设置为主或者子优先级属性,也就是可编程的。

在这里插入图片描述
在这里对表格做个解释,不然这里很容易存在一个误区:就是错把0bit当作是第0位,4bit当作是第4位,其实这是错误的,在官方手册的编程说明它会有bit和bits的区分,在这里主-0bit指的是高四位中没有一个可以配置主优先级,子-4bit指的是这四位都可以用来配置子优先级

第0组:所有4位用于配置响应优先级
第1组:最高1位用于配置抢占式优先级,最低3位用于配置响应优先级
第2组:最高2位用于配置抢占式优先级,最低2位用于配置响应优先级
第3组:最高3位用于配置抢占式优先级,最低1位用于配置响应优先级
第4组:所有4位用于配置抢占式优先级

这里要求我们学会调用官方库文件就可以,设置优先级分组可调用库函数NVIC_PriorityGroupConfig() 实现,有关NVIC 中断相关的库函数都在库文件misc.c 和misc.h 中。

misc文件是存放杂文件的固件库

  1. EXTI和NVIC的关系是什么?(以按键触发中断为例)
    在这里插入图片描述
    EXTI相当于接收外部中断信号,然后传递给NVIC进行处理,然后NVIC再传递给主核,告诉它要执行中断了,停下手头正在做的事,去做另一件事。

3. 解释EXTI:
EXTI:External interrupt/event controller,外部中断/事件控制器,管理了控制器的20 个中断/事件线。每个中断/事件线都对应有一个边沿检测器,可以实现输入信号的上升沿检测和下降沿的检测。

①那么EXTI如何接收中断信号呢?
在STM32中,所用的GOIO都与EXTI外部中断线相连,因此所有的GPIO都可以配置成外部中断,即通过GPIO可以接收中断信号源(按键按下去这一信息)。

GPIO与EXTI的连接方式:
在这里插入图片描述

由上图可知,PA0~PG0连接到EXTI0;以此类推,PA1-PG1连接到EXTI1、…PA15-PG15连接到EXTI15。

EXTI 有20 个中断/事件线,每个GPIO 都可以被设置为输入线,占用EXTI0 至EXTI15
还有另外四根用于特定的外设事件:
在这里插入图片描述

由GPIO和EXTI连接图可知,这里要使用GPIO的复用功能(AFIO),将GPIO引脚设置为接收中断的引脚,通过AFIO_EXTICRx配置GPIO线上的外部中断/事件,首先必须先使能AFIO时钟。

所以这里也要配置EXTI,选择GPIO的复用功能,根据EXTI初始化结构体,可知它包含哪些参数,并对其进行设定。
下图EXTI_InitTypeDef结构体包含的函数参数:

typedef struct {
uint32_t EXTI_Line; // 中断/事件线
EXTIMode_TypeDef EXTI_Mode; // EXTI 模式
EXTITrigger_TypeDef EXTI_Trigger; // 触发类型
FunctionalState EXTI_LineCmd; // EXTI 使能
} EXTI_InitTypeDef;

在这里插入图片描述
下面实验案例为:按键控制中断实现点灯,按下按键,触发中断,LED灯亮(与前一篇按键控制点灯实现功能一样),按键原理图如下:
在这里插入图片描述

先针对EXTI代码中的函数名做个简单的阐述:
GPIO_EXTILineConfig()函数的作用是:引脚选择,指明当前系统中使用哪一个引脚作为外部中断触发的引脚,就是EXTIx要与哪一个GPIOx连到一起。
在这里插入图片描述
什么是中断服务函数?
当中断发生时,要执行的就是中断服务函数中的程序,存放在stm32f10x_it.c文件中,需要自己编写。
注意:中断服务函数名是不可以自己定义的,必须要与启动文件startup_stm32f10x_hd.s中断向量表定义的一致。
具体内容我已在官方提供的固件库文件中标了出来:在这里插入图片描述
在这里插入图片描述在这里插入图片描述
下面我直接上代码进行实验,每一行代码都有相应的解释,没有解释的,那就说明在以前的文章中有解释,想知道为什么可以看前面写的文章,也随时欢迎与我进行探讨:

exti.h

#ifndef __EXTI_H
#define __EXTI_H

#include "stm32f10x.h"

//引脚定义
#define KEY1_INT_GPIO_CLK			(RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO) //GPIOA的时钟,GPIOA的复用
#define KEY1_INT_GPIO_PORT			GPIOA	//按键中断GPIO组
#define KEY1_INT_GPIO_PIN			GPIO_Pin_0
#define KEY1_INT_EXTI_PORTSOURCE	GPIO_PortSourceGPIOA //选择GPIO组
#define KEY1_INT_EXTI_PINSOURCE		GPIO_PinSource0	//选择GPIO引脚
#define KEY1_INT_EXTI_LINE			EXTI_Line0			//选择EXTI线
#define KEY1_INT_EXTI_IRQ			EXTI0_IRQn 			//中断通道

#define KEY1_IRQHandler				EXTI0_IRQHandler	//中断服务函数


#define KEY2_INT_GPIO_CLK			(RCC_APB2Periph_GPIOC|RCC_APB2Periph_AFIO) //GPIOA的时钟,GPIOA的复用
#define KEY2_INT_GPIO_PORT			GPIOC	//按键中断GPIO组
#define KEY2_INT_GPIO_PIN			GPIO_Pin_13
#define KEY2_INT_EXTI_PORTSOURCE	GPIO_PortSourceGPIOC //选择GPIO组
#define KEY2_INT_EXTI_PINSOURCE		GPIO_PinSource13	//选择GPIO引脚
#define KEY2_INT_EXTI_LINE			EXTI_Line13			//选择EXTI线
#define KEY2_INT_EXTI_IRQ			EXTI15_10_IRQn 			//中断通道

#define KEY2_IRQHandler				EXTI15_10_IRQHandler	//中断服务函数

void EXTI_KEY_Config(void);

#endif

exti.c

#include "exti.h"

/*配置嵌套向量中断控制器NVIC*/
static void NVIC_Config(void)
{
	//1.引入两个结构体
	NVIC_InitTypeDef NVIC_InitStructure;
	
	//2.配置NVIC为优先级组1 
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
	
	//3.配置中断源:按键1+按键2
	NVIC_InitStructure.NVIC_IRQChannel=KEY1_INT_EXTI_IRQ;
	
	//4.配置抢占优先级
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;
	
	//5.配置响应优先级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;
	
	//6.使能中断
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	
	//7.初始化NVIC中断
	NVIC_Init(&NVIC_InitStructure);
	
	/* 配置中断源:按键2,其他使用上面相关配置 */  
	NVIC_InitStructure.NVIC_IRQChannel = KEY2_INT_EXTI_IRQ;
	NVIC_Init(&NVIC_InitStructure);
	
}

/*配置IO口为EXTI中断口,并设置中断优先级*/
void EXTI_KEY_Config(void)
{	
	//1.引入GPIO和EXTI两个结构体
	GPIO_InitTypeDef GPIO_InitStructure;
	EXTI_InitTypeDef EXTI_InitStructure;
	
	//2.打开GPIO的时钟
	RCC_APB2PeriphClockCmd(KEY1_INT_GPIO_CLK,ENABLE);
	
	//3.引入NVIC函数
	NVIC_Config();
	
	//4.配置KEY1
	GPIO_InitStructure.GPIO_Pin=KEY1_INT_GPIO_PIN;//选择按键引脚
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING; //选择引脚为浮空输入模式
	GPIO_Init(KEY1_INT_GPIO_PORT,&GPIO_InitStructure);//调用库函数,初始化GPIO
	
	GPIO_EXTILineConfig(KEY1_INT_EXTI_PORTSOURCE,KEY1_INT_EXTI_PINSOURCE);//选择EXTI信号源,也就是EXTI要连接GPIOtx
	EXTI_InitStructure.EXTI_Line=KEY1_INT_EXTI_LINE;//初始化EXTI_LINE
	
	EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;//EXTI设置为中断模式
	EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Rising;//设置EXTI为上升沿触发中断
	EXTI_InitStructure.EXTI_LineCmd=ENABLE;//使能中断
	EXTI_Init(&EXTI_InitStructure);//初始化中断
	
	//5.配置KEY2
	GPIO_InitStructure.GPIO_Pin=KEY2_INT_GPIO_PIN;//选择按键引脚
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING; //选择引脚为浮空输入模式
	GPIO_Init(KEY2_INT_GPIO_PORT,&GPIO_InitStructure);//调用库函数,初始化GPIO
	
	GPIO_EXTILineConfig(KEY2_INT_EXTI_PORTSOURCE,KEY2_INT_EXTI_PINSOURCE);//选择EXTI信号源,也就是EXTI要连接GPIOtx
	EXTI_InitStructure.EXTI_Line=KEY2_INT_EXTI_LINE;//初始化EXTI_LINE
	
	EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;//EXTI设置为中断模式
	EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Falling;//设置EXTI为下降沿触发中断
	EXTI_InitStructure.EXTI_LineCmd=ENABLE;//使能中断
	EXTI_Init(&EXTI_InitStructure);//初始化中断
	
}

led.h

#ifndef __LED_H
#define __LED_H

#include "stm32f10x.h"

//R-红色
#define LED1_GPIO_CLK	RCC_APB2Periph_GPIOB
#define LED1_GPIO_PORT	GPIOB
#define LED1_GPIO_PIN	GPIO_Pin_5

//G-绿色
#define LED2_GPIO_CLK	RCC_APB2Periph_GPIOB
#define LED2_GPIO_PORT	GPIOB
#define LED2_GPIO_PIN	GPIO_Pin_0

//B-蓝色
#define LED3_GPIO_CLK	RCC_APB2Periph_GPIOB
#define LED3_GPIO_PORT	GPIOB
#define LED3_GPIO_PIN	GPIO_Pin_1

#define ON 	0
#define OFF    1

/* 使用标准的固件库控制IO*/
#define LED1(a)	if (a)	\
					GPIO_SetBits(LED1_GPIO_PORT,LED1_GPIO_PIN);\
					else		\
					GPIO_ResetBits(LED1_GPIO_PORT,LED1_GPIO_PIN)

#define LED2(a)	if (a)	\
					GPIO_SetBits(LED2_GPIO_PORT,LED2_GPIO_PIN);\
					else		\
					GPIO_ResetBits(LED2_GPIO_PORT,LED2_GPIO_PIN)

#define LED3(a)	if (a)	\
					GPIO_SetBits(LED3_GPIO_PORT,LED3_GPIO_PIN);\
					else		\
					GPIO_ResetBits(LED3_GPIO_PORT,LED3_GPIO_PIN)
					
					

/* 直接操作寄存器的方法控制IO */
#define	digitalHi(p,i)		 {p->BSRR=i;}	 //输出为高电平		
#define   digitalLo(p,i)		 {p->BRR=i;}	 //输出低电平
#define   digitalToggle(p,i)   {p->ODR ^=i;} //输出反转状态


/* 定义控制IO的宏 */
#define LED1_TOGGLE		 digitalToggle(LED1_GPIO_PORT,LED1_GPIO_PIN)
#define LED1_OFF		 digitalHi(LED1_GPIO_PORT,LED1_GPIO_PIN)
#define LED1_ON		 digitalLo(LED1_GPIO_PORT,LED1_GPIO_PIN)

#define LED2_TOGGLE		 digitalToggle(LED2_GPIO_PORT,LED2_GPIO_PIN)
#define LED2_OFF		 digitalHi(LED2_GPIO_PORT,LED2_GPIO_PIN)
#define LED2_ON		 digitalLo(LED2_GPIO_PORT,LED2_GPIO_PIN)

#define LED3_TOGGLE		 digitalToggle(LED3_GPIO_PORT,LED3_GPIO_PIN)
#define LED3_OFF		 digitalHi(LED3_GPIO_PORT,LED3_GPIO_PIN)
#define LED3_ON		 digitalLo(LED3_GPIO_PORT,LED3_GPIO_PIN)				

void LED_GPIO_Config(void);

#endif

led.c

#include "led.h"

void LED_GPIO_Config(void)
{
	//1.引入两个GPIO结构体
	GPIO_InitTypeDef GPIO_InitStructure;
		
	//2.打开GPIO时钟
	RCC_APB2PeriphClockCmd(LED1_GPIO_CLK|LED2_GPIO_CLK|LED3_GPIO_CLK,ENABLE);
	
	//3.选择引脚
	GPIO_InitStructure.GPIO_Pin=LED1_GPIO_PIN|LED2_GPIO_PIN|LED3_GPIO_PIN;
	
	//4.配置引脚模式
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
	
	//5.配置引脚速度
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	
	//6.初始化GPIO
	GPIO_Init(LED1_GPIO_PORT,&GPIO_InitStructure);
	GPIO_Init(LED2_GPIO_PORT,&GPIO_InitStructure);
	GPIO_Init(LED3_GPIO_PORT,&GPIO_InitStructure);
	
	//7.关闭所有LED灯,从最初状态开始运行
	GPIO_SetBits(GPIOB,GPIO_Pin_All);
	
}

stm32f10x_it.c

#include "led.h"
#include "exti.h"


void KEY1_IRQHandler(void)
{
	if(EXTI_GetITStatus(KEY1_INT_EXTI_LINE)!=RESET) //获得中断位,重新检查是否产生中断,
	{
		LED1_TOGGLE;//LED1取反,点亮LED
		
		//清除中断位,退出中断
		EXTI_ClearITPendingBit(KEY1_INT_EXTI_LINE);
	}	
}

void KEY2_IRQHandler(void)
{
	if(EXTI_GetITStatus(KEY2_INT_EXTI_LINE)!=RESET) //获得中断位,重新检查是否产生中断,
	{
		LED2_TOGGLE;//LED2取反,点亮LED
		
		//清除中断位,退出中断
		EXTI_ClearITPendingBit(KEY2_INT_EXTI_LINE);
	}	
}

main.c

#include "stm32f10x.h"
#include "led.h"
#include "exti.h"

int main(void)
{
	LED_GPIO_Config();//调用LED函数
	LED3(ON);//点亮蓝灯,可以实现R/G/B混合成白灯,这里使用库函数操作,ON=0

	EXTI_KEY_Config(); //调用KEY函数
	
	/* 等待中断,由于使用中断方式,CPU不用轮询按键 */
	while(1)
	{
	}
}

再说一下关于魔术棒配置:
在这里插入图片描述
STM32F10X_HD,USE_STDPERIPH_DRIVER这两个宏定义代表的含义:

USE_STDPERIPH_DRIVER:是告诉编译器,我们需要使用ST官方库;
STM32F10X_HD:表示我们使用的芯片是大容量的stm32,添加之后就可以使用库文件里面专为大容量芯片定义的寄存器。
注意:两个宏定义之间是英文逗号。

最后,我还是有些地方没有搞清楚:比如为什么要用空while等待中断,如果去掉这一行,灯则不会亮;
对使用官方库函数的控制GPIO端口解释一下:
在这里插入图片描述
这里有一点不理解的是:\的作用是什么?如果去掉的话,则会出错

说明:因为我使用的是野火开发板,所以选取的资料大多来源于野火,一般我都是自己弄懂之后才会写出来,这样也助于提高自己,而且这篇文章的目的也不是点灯,而是要通过按键点灯学会中断,因为中断的用途非常大,经常使用,所以一定要学会使用中断。

作者能力水平有限,文章难免存在错误和纰漏,请大佬不吝赐教,非常欢迎大家与小白杨进行技术交流,希望在此能遇到志同道合的朋友,一起学习技术。

  • 11
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值