零死角玩转stm32初级篇5-中断

一.中断相关概念知识

1.STM32 的中断和异常

Cortex 内核具有强大的异常响应系统,它把能够打断当前代码执行流程的事件分为异常(exception) 和 中断(interrupt) ,并把它们用一个表管理起来,编号为 0~15 的称为 内核异常 ,而 16 以上的则称为 外部中断(这里的外,相对内核而言) ,这个表就称为 中断向量表 。而 STM32 对这个表重新进行了编排,把编号从-3 至 6 的中断向量定义为系统异常, 编号为负 的内核异常不能被设置优先级,如复位(Reset)、不可屏蔽中断 (NMI)、硬错误(Hardfault)。从编号 7 开始的为外部中断,这些中断的优先级都是可以自行设置的。根据编程手册STM32F10xxx产品(小容量、中容量和大容量)的向量表如下图所示。( 中断:暂停当前任务转而去执行中断任务,当中断任务执行完后再来处理暂停任务;中断触发后,CPU就会停下来去执行中断任务,所以中断任务的处理时间一般非常快,对于一些耗时任务不要交给中断进行处理,中断任务中也尽量不要使用延时函数 )

在这里插入图片描述

在这里插入图片描述

2.NVIC 中断控制器

STM32 的中断如此之多,配置起来并不容易,因此,我们需要一个强大而方便的中断控制器 NVIC (Nested Vectored Interrupt Controller)。NVIC 是属于Cortex 内核的器件,不可屏蔽中断 (NMI)和外部中断都由它来处理,而SYSTICK 不是由 NVIC 来控制的。

在这里插入图片描述

3.NVIC 结构体成员

当我们要使用 NVIC 来配置中断时,自然想到 ST 库肯定也已经把它封装成库函数了。查找库帮助文档,发现在 Modules->STM32F10x_StdPeriph_Driver->misc 查找到一个 NVIC_Init() 函数,对 NVIC 进行初始化,首先要定义并填充一个NVIC_InitTypeDef 类型的结构体。

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

这个结构体的成员参数的描述如下表,成员参数的第一个参数为选择指定的中断向量,第二个参数为中断使能,这里的使能指的是总开关使能,只要当总开关使能,中断才有效;第三个参数和第四个参数都是中断的优先级,中断可能出现嵌套的情况如果二个中断同时响应,那应该执行哪一个呢?这个时候就由优先级来决定。

成员参数描述
NVIC_IRQChannel需要配置的中断向量
NVIC_IRQChannelCmd使能或关闭相应中断向量的中断响应
NVIC_IRQChannelPreemptionPriority配置相应中断向量抢占优先级
NVIC_IRQChannelSubPriority配置相应中断向量的响应优先级
4.抢占优先级和响应优先级

STM32 的中断向量具有两个属性,一个为抢占属性 ,另一个为响应属性 ,其属性编号越小 ,表明它的优先级别越高 ( 编号越小优先级越高 )。

  • 抢占优先级
    抢占,是指打断其它中断的属性,即因为具有这个属性,会出现嵌套中断(在执行中断服务函数 A 的过程中被中断 B 打断,执行完中断服务函数 B 再继续执行中断服务函数 A),抢占属性由 NVIC_IRQChannelPreemptionPriority 的参数配置。

  • 响应优先级
    响应属性则应用在抢占属性相同的情况下,当两个中断向量的抢占优先级相同时,如果两个中断同时到达,则先处理响应优先级高的中断,响应属性由 NVIC_IRQChannelSubPriority 的参数配置。

例如,如下图所示,有三个中断向量:若内核正在执行 C 的中断服务函数,则它能被抢占优先级更高的中断A打断,由于B和C的抢占优先级相同,所以 C 不能被 B 打断。但如果 B 和C中断是同时到达的,内核就会首先响应响应优先级别更高的B中断。

中断向量抢占优先级响应优先级
A00
B10
C11
5.NVIC 的优先级组

在配置优先级的时候,还要注意一个很重要的问题,中断种类的数量。NVIC只可以配置16 种中断向量的优先级,也就是说,抢占优先级和响应优先级的数量由一个4位的数字来决定,把这个 4 位数字的位数分配成抢占优先级部分和响应优先级部分。有 5 组分配方式 ( 优先级的顺序是抢占优先级(主)>响应优先级(子)>硬件中断编号,当有一个相同时,依次比较下一个 ):

在这里插入图片描述

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

6.EXTI 外部中断

STM32 的所有 GPIO 都引入到 EXTI 外部中断线上,使得所有的 GPIO 都能作为外部中断的输入源。

在这里插入图片描述

观察上图可知, PA0~PG0 连接到 EXTI0 、 PA1~ PG1 连接到EXTI1 、 ……、 PA15~PG15 连接到 EXTI15 ( 通过这种方式就可以实现16种中断优先级控制所有GPIO引脚 )。这样的话 PAx~PGx端口的中断事件都连接到了 EXTIx ,即同一时刻 EXTx 只能响应一个端口的事件触发,不能够同一时间响应所有 GPIO 端口的事件,但可以分时复用。它可以配置为上升沿触发,下降沿触发或双边沿触发。EXTI 最普通的应用就是接上一个按键,设置为下降沿触发,用中断来检测按键。

7.中断服务函数

中断服务函数就是当中断触发后执行任务的函数;在stm32f10x_it.c 文件是专门用来存放中断服务函数的。文件中默认只有几个关于系统异常的中断服务函数,而且都是空函数,在需要的时候自已进行编写。那么中断服务函数名是不是可以自己定义呢?不可以。中断服务函数的名字必须要跟启动文件 startup_stm32f10x_md.s 中的中断向量表定义一致。如下图所示。

在这里插入图片描述

对于多个中断共用一个函数,怎么知道是哪一个引起的呢,可以通过调用库函数 EXTI_GetITStatus() 来重新检查是否产生了 EXTI_Line 中断,比如EXTI_GetITStatus(EXTI_Line9)!=RESET 然后通过GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_9)来读取引脚的电平情况,为低电平时说明是EXTI_Line9引起的中断。

在这里插入图片描述

中断任务执行完毕后需要将中断标志清除后再退出中断服务函数,可以使用EXTI_ClearITPendingBit() 清除中断标置位。比如 EXTI_ClearITPendingBit(EXTI_Line9); 清除EXTI_Line8的中断标志。

在这里插入图片描述

二.按键点灯的二种实现方式

1.按键和LED的原理图以及各种输入模式(浮空输入,上拉输入,下拉输入和模拟输入)
  • 按键原理图(这里选择sw1按钮)

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

  • led灯原理图

在这里插入图片描述

  • 浮空输入,上拉输入,下拉输入和模拟输入

stm32的引脚有四种输入模式浮空输入,上拉输入,下拉输入和模拟输入,下面对这四种模式进行介绍。
在这里插入图片描述

在STM32中,模拟输入指的是模拟到数字转换器(ADC)的输入。这些输入通常来自于外部传感器、电位器等模拟信号源,并且需要将它们转换为数字信号以便于微控制器进行处理。(图片来源于:https://blog.csdn.net/scarecrow_sun/article/details/120287852

在这里插入图片描述

上拉,GPIO的输入线(IN)通过一个上拉电阻连接到VCC(上拉电阻开关闭合,下拉电阻开关断开),当没有外部信号连接时,输入线会被拉高到高电平(VCC)状态,当引脚输入高电平时,读取到的也是高电平(但是无法区分是上拉电阻连接的VCC还是引脚的高电平 ),当引脚输入低电平时,读取到的是低电平,此时就能够明确知道是输入的低电平。

在这里插入图片描述

同样,下拉输入使用的电路也是类似的(上拉电阻开关断开,下拉电阻开关闭合),只不过电阻连接到地(GND),当IO口不输入时,读取到的就是低电平,当引脚输入低电平时,读取到的也是低电平,但是此时是无法知道是下拉电阻GND的低电平还是引脚输入的低电平;但是当输入的是高电平时,就能够明显读取到高电平,说明引脚输入了高电平。

在这里插入图片描述

浮空输入,和上面二种不同的是上拉电阻开关和下拉电阻开关都断开,此时读取的引脚电平漂浮不定,电平会处于一个跳变的状态,一会高,一会低。只有输入了一个高/低电平才会确定下来( 采用浮空输入表名输出情况有外部来决定 )。

在这里插入图片描述

在STM32中,可以通过使用内部上拉/下拉电阻或外部上拉/下拉电阻来实现上拉/下拉输入,由于我的原理图设计并没有设计外部的上拉和下拉,所以这里需要采用内部上拉,因为如果采用内部下拉的话,低电平是无法识别的,所以应该采用内部的上拉电阻(内部的上拉都是弱上拉,不是很稳定,所以在设计的时候尽量采用外部的上拉或下拉,并且可以在按键上并联一个电容,这样可以实现消抖的作用)。如果采用外部上拉的话在外部接一个上拉电阻即可,如下图所示。

在这里插入图片描述

2.软件轮询方式

软件方式实现按键控制LED就是通过获取用户按键的电平信息,来判断用户是否按下,由于按钮采用上拉方式,当按键没有按下时输入的是高电平,当按下时,输入的是低电平;通过这种方式就可以获取用户按键的开和关;需要注意的是当按键按下时会有10ms的跳变电平,所以需要进行消抖处理,消抖处理有二种方式,硬件方式和软件方式;软件方式就是通过软件代码实现10ms的休眠,从而实现消抖,如果要实现较为准确的延时建议采用定时器来实现。

  • key.h
#ifndef __key_h
#define __key_h
#include "stm32f10x.h"


#define KEY_G_GPIO_PIN    GPIO_Pin_9
#define KEY_G_GPIO_PORT	 GPIOB
#define KEY_G_GPIO_CLK  RCC_APB2Periph_GPIOB
#define KEY_ON   1
#define KEY_OFF 0

void keyInit(void);
uint8_t keyScan(GPIO_TypeDef *,uint16_t);
#endif /* __key_h */
  • key.c
#include "key.h"
#include "stm32f10x.h"



// 初始化按钮
void keyInit(void){
	// PB上的时钟使能
	RCC_APB2PeriphClockCmd(KEY_G_GPIO_CLK,ENABLE);
	// 初始化GPIO
	GPIO_InitTypeDef  GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Pin = KEY_G_GPIO_PIN; 
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;// 上拉输入
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_10MHz;
	GPIO_Init(KEY_G_GPIO_PORT,&GPIO_InitStruct);// 初始化GPIO
}


// 按键扫描
uint8_t keyScan(GPIO_TypeDef *  GPIOx,uint16_t  GPIO_Pin){
		// 如果没有并联电容的话,可以考虑进行消抖,消抖时长10ms
		// 获取引脚的值
	  if(GPIO_ReadInputDataBit( GPIOx,GPIO_Pin)  == KEY_ON){
			// 松手检测
			while(GPIO_ReadInputDataBit( GPIOx,GPIO_Pin)== KEY_ON);
			return KEY_ON;
		}else{
			return KEY_OFF;
		}
}

  • led.h
#ifndef __led_h
#define __led_h
#define LED_G_GPIO_PIN    GPIO_Pin_10
#define LED_G_GPIO_PORT	 GPIOB
#define LED_G_GPIO_CLK  RCC_APB2Periph_GPIOB
#define LED_ON 1
#define LED_OFF 0
#define LED_G(state) if(state) GPIO_ResetBits(LED_G_GPIO_PORT,LED_G_GPIO_PIN); else GPIO_SetBits(LED_G_GPIO_PORT,LED_G_GPIO_PIN);

void ledInit(void);
void ledOnOrOff(int state);
void ledToggle(void);
#endif /* __led_h */
  • led.c
#include "led.h"
#include "stm32f10x.h"


// 初始化LED
void ledInit(){
	// PB上的时钟使能
	RCC_APB2PeriphClockCmd(LED_G_GPIO_CLK,ENABLE);
	// 初始化GPIO
	GPIO_InitTypeDef  GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Pin = LED_G_GPIO_PIN;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_10MHz;
	GPIO_Init(LED_G_GPIO_PORT,&GPIO_InitStruct);// 初始化GPIO
}

// 控制LED的开关
void ledOnOrOff(int state){
	// 打开LED
	if(state == LED_ON){
		// 置低电平
		GPIO_ResetBits(LED_G_GPIO_PORT,LED_G_GPIO_PIN);
	}else if(state == LED_OFF){
		// 置高电平
		GPIO_SetBits(LED_G_GPIO_PORT,LED_G_GPIO_PIN);
	}
}

// 状态反转
void ledToggle(){
	// 通过odr寄存器
	LED_G_GPIO_PORT->ODR ^=LED_G_GPIO_PIN;//  ODR:0000 LED_G_GPIO_PIN:0x0400   (主要看第二位)进行异或后:0000与0100异或结果为0100;ODR=0100 再次与LED_G_GPIO_PIN(0100)异或,异或后0000;依次循环实现状态反转
}
  • main.c
#include "stm32f10x.h"
#include "key.h"
#include "led.h"



int main(void){
	// 【1】通过按键轮询的方式点亮LED
	// 初始化Led和key
	keyInit();
	ledInit();
	while(1){
		if(keyScan(KEY_G_GPIO_PORT,KEY_G_GPIO_PIN) == KEY_ON){//判断用户是否按下
			// led的状态反转
			ledToggle();
		}
	}
}

3.中断方式

采用软件轮训的方式适合响应速度要求不高的场景,如果对于响应要求有一定需求的话,还是建议采用中断方式进行实现,因为采用中断方式可以更快得到响应,采用中断方式需要采用如下几步进行实现:

在这里插入图片描述

使用中断也需要开启中断的时钟,时钟必须采用AFIO 。AFIO (alternate-function I/O)指 GPIO 端口的复用功能,GPIO 除了用作普通的输入输出( 主功能 ),还可以作为片上外设的复用输入输出,如串口,ADC,这些就是复用功能。大多数 GPIO 都有一个 默认复用功能 ,有的 GPIO 还有 重映射功能 , 重映射功能是指把原来属于 A 引脚的默认复用功能,转移到了B 引脚进行使用,前提是 B 引脚具有这个重映射功能当把 GPIO 用作 EXTI 外部中断 或使用 重映射功能 的时候,必须开启 AFIO时钟,而在使用 默认复用功能 的时候,就不必开启 AFIO 时钟了;从下图可知AFIO在APB2上,所以开启时钟的时候开启APB2上的AFIO时钟即可。

在这里插入图片描述

中断的初始化需要先初始化中断输入线,,所谓的中断输入线就是用于连接外部触发源(如按键、传感器等)到单片机的引脚(GPIOB_9对应的中断线就是EXTI_Line9)。当外部触发源发生相应事件(如按键按下、传感器检测到信号等),会通过这根输入线向单片机发送一个中断请求信号,触发相应的中断服务程序执行,中断触发方式需要用户进行设置比如下降沿触发(高->低),上升沿触发(低->高),至于何时产生中断请求或事件请求由软件中断事件寄存器决定;如果多个事件同时触发这时候就由他们的优化级来决定谁先执行,谁挂起,至于中断的优先级采用NVIC进行控制。

在这里插入图片描述

  • key.c
// 配置中断优先级
static void EXTI_NVIC_Config(){
	NVIC_InitTypeDef   NVIC_InitStruct;
	NVIC_InitStruct.NVIC_IRQChannel =EXTI9_5_IRQn;// IRQn通道使能,在stm32f10x.h中可以查找到
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority =1;// 设置抢占优先级
	NVIC_InitStruct.NVIC_IRQChannelSubPriority= 1;// 设置响应优先级
	NVIC_InitStruct.NVIC_IRQChannelCmd= ENABLE;// 使能
	
	// 设置优先级的分组
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_3);//3位抢占优先级,1位响应优先级
	NVIC_Init(&NVIC_InitStruct);// 初始化NVIC
}

// 中断方式初始化KEY
void keyByExti(){
	// 临时变量	
	GPIO_InitTypeDef  GPIO_InitStruct;
	
	
	
	// 【1】初始化GPIO
	RCC_APB2PeriphClockCmd(KEY_G_GPIO_CLK,ENABLE);// 开启时钟
	GPIO_InitStruct.GPIO_Pin = KEY_G_GPIO_PIN; 
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;// 上拉输入
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_10MHz;
	GPIO_Init(KEY_G_GPIO_PORT,&GPIO_InitStruct);// 初始化GPIO
	
	// 【2】初始化EXTI
	EXTI_InitTypeDef   EXTI_InitStruct;
	//开启时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);// EXTI在APB2上,开启的是AFIO时钟
	GPIO_EXTILineConfig( GPIO_PortSourceGPIOB,  GPIO_PinSource9); // 开启PB9的时钟线
	
	// 中断初始化
	EXTI_InitStruct.EXTI_Line = EXTI_Line9;// 中断线
	EXTI_InitStruct.EXTI_Mode =EXTI_Mode_Interrupt;// 中断模式
	EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Falling;// 触发方式:下降沿(高->低)
	EXTI_InitStruct.EXTI_LineCmd = ENABLE;// 打开中断开关
	EXTI_Init(&EXTI_InitStruct);
	
	// 【3】配置中断优先级
	EXTI_NVIC_Config();
}




  • stm32f10x_it.c

上面的三步配置完毕后,第四步就是编写相应的中断服务函数,如下:

在这里插入图片描述

中断服务汉最好放在stm32f10x_it.c文件中,但也有放在main文件下的(不建议):

在这里插入图片描述

按键中断的配置完毕后,只需要在main函数中进行引入和初始化即可。

  • key.h

在这里插入图片描述

  • mian.c

在这里插入图片描述

三.源码下载

微信公众号,回复 中断源码 既可以获取本篇博文的源代码,如果有什么问题,后台留言我看见会第一时间回复你的喔。

在这里插入图片描述

  • 8
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
### 回答1: 野火是一家致力于开发和销售嵌入式开发板的公司,他们的产品系列中包括了很多基于STM32-F103芯片的开发板。这款芯片是意法半导体推出的一款低功耗、高性能的32位微控制器,广泛应用于工业控制、汽车电子、消费电子等领域。 野火的开发板采用了精美的设计和丰富的功能,使得学习和开发STM32-F103变得更加简单和有趣。它们不仅提供了各种接口和外设,如GPIO、串口、I2C、SPI等,还具备了丰富的软件资源,包括示例代码、库函数和开发工具等。这些资源可以帮助开发者快速上手,并且能够实现各种功能,如LED闪烁、蜂鸣器发声、温度传感器读取等。 野火开发板的另一个亮点是它们的死角设计。无论是电源、传感器、扩展接口还是USB接口,野火都考虑到了用户的需求,并且尽可能地做到了完善和完整。这样一来,用户就可以在不受限制的情况下进行各种创意的嵌入式开发,不再为接口不够或功能不完善而烦恼。 与此同时,野火开发板也提供了丰富的学习资源和教学视频,帮助初学者了解和掌握STM32-F103的基本原理和应用。无论是学生、工程师还是爱好者,都可以通过野火的开发板来深入了解嵌入式系统的开发和应用,进一步提升自己的技能和能力。 总之,野火的开发板可以说是死角玩转STM32-F103的理想选择。它们的丰富功能、完善的接口和外设,以及全面的学习资源,都使得开发者能够轻松地进行嵌入式系统的开发和应用。无论是初学者还是专业人士,都能够通过野火的产品来探索和实践嵌入式技术的魅力。 ### 回答2: 野火 死角玩转STM32-F103是一本关于如何全面掌握野火STM32-F103开发板的书籍。 首先,野火STM32-F103是一款功能强大的开发板,搭载了STM32F103C8T6的微控制器芯片。该开发板具有丰富的外设资源,如LED指示灯、按键、蜂鸣器、ADC、串口等,可用于各种实际应用。在这本书中,通过详细的实例和案例,作者教读者如何充分利用这些外设实现各种功能。 此外,书中还详细介绍了STM32开发环境的搭建和使用。从软件安装到简单的“Hello World”程序的编写,再到更复杂的项目开发,都有详细的步骤和指导。读者可以根据书中的指导逐步学习和掌握STM32的开发工具和编程语言。 在书的后面章节中,还介绍了一些高级应用,如通过外部中断、定时器和PWM等方式控制外设,以及使用WiFi、蓝牙等无线通信模块进行远程控制等。这些内容可以帮助读者更加深入地理解STM32的应用场景,并扩展自己的知识和技能。 总之,野火 死角玩转STM32-F103是一本全面而实用的书籍,适合初学者和有一定基础的读者。通过学习这本书,读者可以轻松地掌握STM32的开发技术,将其应用于自己的项目中。无论是做电子制作、嵌入式系统设计还是其他领域,这本书都会成为读者的得力助手。 ### 回答3: 野火是一款基于STM32-F103开发板的强大工具,它集成了丰富的硬件资源和强大的软件支持,让开发者能够充分发挥STM32-F103的潜力。野火开发板具有死角的设计,使得开发者可以以各种方式玩转STM32-F103。 首先,野火开发板提供了丰富的外设接口,包括GPIO、UART、SPI、I2C、ADC等等,这些接口可以连接各种外部设备,如传感器、显示屏、通信模块等。同时,野火还配备了大容量存储器,包括FLASH和RAM,可以存储大量的代码和数据。 其次,野火开发板支持多种开发环境和编程语言,包括keil、IAR、STM32CubeMX等,可以满足不同开发者的需求。野火还提供了丰富的软件库和示例代码,开发者可以轻松地使用这些库和代码进行开发,加快开发进度。 最重要的是,野火具有强大的性能和稳定性,能够在各种应用场景下稳定运行。它采用了STM32-F103芯片,配备了高速处理器和丰富的内存,能够处理各种复杂的任务。此外,野火还支持多种调试功能,如JTAG、SWD和串口调试,方便开发者进行调试和测试。 总而言之,野火开发板死角玩转STM32-F103,不仅提供了丰富的硬件资源和软件支持,还具有强大的性能和稳定性,能够满足开发者的各种需求。无论是初学者还是有经验的开发者,都可以通过野火开发板来开发出高质量的STM32-F103应用。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

嘟嘟的程序员铲屎官

你的鼓励将是我最大的动力。

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

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

打赏作者

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

抵扣说明:

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

余额充值