本篇博文目录:
- 一.中断相关概念知识
- 1.STM32 的中断和异常
- 2.NVIC 中断控制器
- 3.NVIC 结构体成员
- 4.抢占优先级和响应优先级
- 5.NVIC 的优先级组
- 6.EXTI 外部中断
- 7.中断服务函数
- 二.按键点灯的二种实现方式
- 1.按键和LED的原理图以及各种输入模式(浮空输入,上拉输入,下拉输入和模拟输入)
- 2.软件轮询方式
- 3.中断方式
- 三.源码下载
一.中断相关概念知识
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中断。
中断向量 | 抢占优先级 | 响应优先级 |
---|---|---|
A | 0 | 0 |
B | 1 | 0 |
C | 1 | 1 |
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中,模拟输入指的是模拟到数字转换器(ADC)的输入。这些输入通常来自于外部传感器、电位器等模拟信号源,并且需要将它们转换为数字信号以便于微控制器进行处理。(图片来源于:https://blog.csdn.net/scarecrow_sun/article/details/120287852)
上拉,GPIO的输入线(IN)通过一个上拉电阻连接到VCC(上拉电阻开关闭合,下拉电阻开关断开),当没有外部信号连接时,输入线会被拉高到高电平(VCC)状态,当引脚输入高电平时,读取到的也是高电平(但是无法区分是上拉电阻连接的VCC还是引脚的高电平 ),当引脚输入低电平时,读取到的是低电平,此时就能够明确知道是输入的低电平。
同样,下拉输入使用的电路也是类似的(上拉电阻开关断开,下拉电阻开关闭合),只不过电阻连接到地(GND),当IO口不输入时,读取到的就是低电平,当引脚输入低电平时,读取到的也是低电平,但是此时是无法知道是下拉电阻GND的低电平还是引脚输入的低电平;但是当输入的是高电平时,就能够明显读取到高电平,说明引脚输入了高电平。
浮空输入,和上面二种不同的是上拉电阻开关和下拉电阻开关都断开,此时读取的引脚电平漂浮不定,电平会处于一个跳变的状态,一会高,一会低。只有输入了一个高/低电平才会确定下来(
采用浮空输入表名输出情况有外部来决定
)。
在STM32中,可以通过使用内部上拉/下拉电阻或外部上拉/下拉电阻来实现上拉/下拉输入,由于我的原理图设计并没有设计外部的上拉和下拉,所以这里需要采用内部上拉,因为如果采用内部下拉的话,低电平是无法识别的,所以应该采用内部的上拉电阻(内部的上拉都是弱上拉,不是很稳定,所以在设计的时候尽量采用外部的上拉或下拉,并且可以在按键上并联一个电容,这样可以实现消抖的作用)。如果采用外部上拉的话在外部接一个上拉电阻即可,如下图所示。
2.软件轮询方式
软件方式就是通过获取用户按键的电平信息,来判断用户是否按下,由于按钮采用上拉方式,当按键没有按下时是输入的是高电平,当按下时,输入的是低电平;通过这种方式就可以获取用户按键的开和关;需要注意的是当按键按下时会有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;
}
}
// 配置中断优先级
static void EXTI_NVIC_Config(){
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel =EXTI15_10_IRQn;// IRQn通道使能,在stm32f10x.h中可以查找到
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority =1;// 设置抢占优先级
NVIC_InitStruct.NVIC_IRQChannelSubPriority= 1;// 设置响应优先级
NVIC_InitStruct.NVIC_IRQChannelCmd= ENABLE;// 使能
// 设置优先级的分组
NVIC_PriorityGroupConfig(3);//3位抢占优先级,1位响应优先级
NVIC_Init(&NVIC_InitStruct);// 初始化NVIC
}
- 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.中断方式
采用中断方式需要采用如下几步:
中断的初始化需要先初始化中断输入线,然后初始化NVIC中断控制器
使用中断也需要开启中断的时钟,时钟必须采用AFIO 。AFIO (alternate-function I/O)指 GPIO 端口的复用功能,GPIO 除了用作普通的输入输出( 主功能 ),还可以作为片上外设的复用输入输出,如串口,ADC,这些就是复用功能。大多数 GPIO 都有一个 默认复用功能 ,有的 GPIO 还有 重映射功能 , 重映射功能是指把原来属于 A 引脚的默认复用功能,转移到了B 引脚进行使用,前提是 B 引脚具有这个重映射功能当把 GPIO 用作 EXTI 外部中断 或使用 重映射功能 的时候,必须开启 AFIO时钟,而在使用 默认复用功能 的时候,就不必开启 AFIO 时钟了。
- 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;
EXTI_InitTypeDef EXTI_InitStruct;
// 配置中断优先级
EXTI_NVIC_Config();
// 初始化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
// 初始化EXTI
//开启时钟
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);
}
- key.h
- mian.c
- stm32f10x_it.c
三.源码下载
微信公众号,回复
中断源码
既可以获取本篇博文的源代码,如果有什么问题,后台留言我看见会第一时间回复你的喔。