stm32学习第7天


记录中断笔记

一、什么是中断

一般来说,没啥特别说明。异常就是中断,中断就是异常
中断有两种类型
系统异常:体现在内核水平
外部中断:体现在外设水平

二、什么是NVIC

NVIC 是嵌套向量中断控制器,控制着整个芯片中断相关的功能,它跟内核紧密耦合,是内核里面的一个外设。但是各个芯片厂商在设计芯片的时候会对Cortex-M3内核里面的NVIC进行裁剪,把不需要的部分去掉,所以说 STM32的 NVIC是 Cortex-M3 的 NVIC 的一个子集。

2.1 NVIC寄存器简介

我们在固件库里面找到源代码
翻译一下

typedef struct {
 	__IO uint32_t ISER[8]; 			// 中断使能寄存器
	uint32_t RESERVED0[24];
	__IO uint32_t ICER[8]; 			// 中断清除寄存器
	uint32_t RSERVED1[24];
	__IO uint32_t ISPR[8]; 			// 中断使能悬起寄存器
	uint32_t RESERVED2[24];
	__IO uint32_t ICPR[8];			 // 中断清除悬起寄存器
	uint32_t RESERVED3[24];
	__IO uint32_t IABR[8]; 			// 中断有效位寄存器
	uint32_t RESERVED4[56];
	__IO uint8_t IP[240]; 			// 中断优先级寄存器(8Bit wide)
	uint32_t RESERVED5[644];
	__O uint32_t STIR; 				// 软件触发中断寄存器
 } NVIC_Type;

请注意,中断优先级寄存器是8位的。。
在配置中断的时候我们一般只用 ISER、ICER 和 IP 这三个寄存器,ISER 用来使能中
断,ICER用来失能中断,IP 用来设置中断优先级。

三、优先级设定

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

①假如设置中断优先级分组为2,timer中断的抢占优先级为0,响应优先级为1,而uart中断的抢占优先级为1,响应优先级为0
我们可以发现timer中断的抢占优先级高
则当uart中断在执行的时候,timer中断刚好也来了,这时由于timer中断的抢占优先级比uart中断的抢占优先级高,因此可以打断其运行而执行自己的代码。
②假如设置中断优先级分组为2,timer中断的抢占优先级为0,响应优先级为1,而uart中断的抢占优先级为0,响应优先级为0
我们可以发现两者中断的抢占优先级相同,而uart中断的响应优先级高
则当timer中断在执行的时候,uart中断来了,是不能打断timer中断执行的,而当timer中断和uart中断同时来的时候,先执行响应优先级高的uart中断。

四 中断编程

4.2 中断编程的顺序

1、使能中断请求
2、配置优先级分组
3、配置NVIC寄存器,初始化NVIC_InitTypedef
3、编写中断服务函数

4.2 中断编程要点

1、使能外设某个中断,这个具体由每个外设的相关中断使能位控制。比如串口有发送完成中断,接收完成中断,这两个中断都由串口控制寄存器的相关中断使能位控制。

2、初始化 NVIC_InitTypeDef 结构体,配置中断优先级分组,设置抢占优先级和子优先级,使能中断请求。NVIC_InitTypeDef 结构体在固件库头文件 misc.h 中定义。

typedef struct {
	uint8_t NVIC_IRQChannel; 									// 中断源
	uint8_t NVIC_IRQChannelPreemptionPriority;		// 抢占优先级
	uint8_t NVIC_IRQChannelSubPriority; 					// 子优先级
	FunctionalState NVIC_IRQChannelCmd; 					// 中断使能或者失能
} NVIC_InitTypeDef

五、EXTI框图讲解

EXTI:(External interrupt/event controller)—外部中断/事件控制器.管理了控制器的 20个中断/事件线。
每个中断/事件线都对应有一个边沿检测器,可以实现输入信号的上升沿检测和下降沿的检测。EXTI 可以实现对每个中断/事件线进行单独配置,可以单独配置为中断或者事件,以及触发事件的属性。
在这里插入图片描述

首先我们看红色线路图:

  1. 编号 1 是输入线,EXTI 控制器有 19 个中断/事件输入线,这些输入线可以通过寄存器设置为任意一个 GPIO,也可以是一些外设的事件。
  2. 编号 2 是一个边沿检测电路,它会根据上升沿触发选择寄存器(EXTI_RTSR)和下降沿触发选择寄存器(EXTI_FTSR)对应位的设置来控制信号触发。边沿检测电路以输入线作为信号输入端,如果检测到有边沿跳变就输出有效信号 1 给编号 3 电路,否则输出无效信号0。
  3. 编号 3 电路实际就是一个或门电路,它一个输入来自编号 2 电路,另外一个输入来自软件中断事件寄存器(EXTI_SWIER)。我们知道或门的作用就是有 1 就为 1,所以这两个输入随便一个有有效信号 1就可以输出 1 给编号 4和编号 6电路。
  4. 编号 4 电路是一个与门电路,它一个输入是编号 3 电路,另外一个输入来自中断屏蔽寄存器(EXTI_IMR)。 与门电路要求输入都为 1 才输出 1,导致的结果是如果 EXTI_IMR 设置为 0 时,那不管编号 3 电路的输出信号是 1 还是 0,最终编号 4 电路输出的信号都为 0;如果EXTI_IMR设置为1时,最终编号4电路输出的信号才由编号3电路的输出信号决定,这样我们可以简单的控制 EXTI_IMR 来实现是否产生中断的目的。编号 4 电路输出的信号会被保存到挂起寄存器(EXTI_PR)内,如果确定编号 4 电路输出为 1 就会把 EXTI_PR 对应位置 1。
  5. 编号 5 是将 EXTI_PR 寄存器内容输出到 NVIC 内,从而实现系统中断事件控制。
  6. 编号6电路是一个与门,它一个输入来自编号 3 电路,另外一个输入来自事件屏蔽寄存器(EXTI_EMR)。如果 EXTI_EMR设置为 0时,那不管编号 3电路的输出信号是 1还是 0,最终编号 6 电路输出的信号都为 0;如果 EXTI_EMR 设置为 1 时,最终编号 6 电路输出的信号才由编号 3 电路的输出信号决定,这样我们可以简单的控制 EXTI_EMR 来实现是否产生事件的目的。
  7. 编号 7 是一个脉冲发生器电路,当它的输入端,即编号 6 电路的输出端,是一个有效信号 1 时就会产生一个脉冲;如果输入端是无效信号就不会输出脉冲。
  8. 编号 8 是一个脉冲信号,就是产生事件的线路最终的产物,这个脉冲信号可以给其他外设电路使用,比如定时器 TIM、模拟数字转换器 ADC等等,这样的脉冲信号一般用来触发 TIM 或者 ADC开始转换。

框图主要是对应不同的寄存器,我们需要去查找相关的寄存器。

六、中断事件表

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

七、EXTI结构体详解

在这里插入图片描述
翻译一下

 typedef struct {
	uint32_t EXTI_Line; 								// 中断/事件线
	EXTIMode_TypeDef EXTI_Mode; 				// EXTI 模式
	EXTITrigger_TypeDef EXTI_Trigger; 	// 触发类型
	FunctionalState EXTI_LineCmd; 			// EXTI 使能
 } EXTI_InitTypeDef;
  1. EXTI_Line:EXTI中断/事件线选择,可选 EXTI0 至 EXTI19
  2. EXTI_Mode:EXTI 模式选择,可选为产生中断(EXTI_Mode_Interrupt)或者产生事件(EXTI_Mode_Event)。
  3. EXTI_Trigger:EXTI 边沿触发事件,可选上升沿触发(EXTI_Trigger_Rising)、下降 沿 触 发 ( EXTI_Trigger_Falling) 或 者 上 升 沿 和 下 降 沿 都 触 发( EXTI_Trigger_Rising_Falling)。
  4. EXTI_LineCmd:控制是否使能 EXTI 线,可选使能 EXTI 线(ENABLE)或禁用(DISABLE)。

八、外部中断控制实验

同样的方法添加两个文件夹
在这里插入图片描述
采用之前的点灯程序(学习第五天)
bsp_led.h

#ifndef 	_BSP_LED_H
#define 	_BSP_LED_H

#include 	"stm32f10x.h"

#define LED_GPIO_PORT 	GPIOB
#define LED_GPIO_CLK 		RCC_APB2Periph_GPIOB
#define LED_GPIO_PIN		GPIO_Pin_1

#define LED_TOGGLE {LED_GPIO_PORT->ODR ^= LED_GPIO_PIN;}

void LED_CONFIG(void);

#endif	 /*_BSP_LED_H*/

bsp_led.c

#include "bsp_led.h"

void LED_CONFIG(void)
{
		/*定义一个 GPIO_InitTypeDef 类型的结构体*/
		GPIO_InitTypeDef LED_CONFIG;
		/*打开APB2上 GPIOB的时钟*/
		RCC_APB2PeriphClockCmd(LED_GPIO_CLK, ENABLE);
		/*配置GPIO口的输出*/
		LED_CONFIG.GPIO_Mode  = GPIO_Mode_Out_PP;
		LED_CONFIG.GPIO_Pin   =	LED_GPIO_PIN;
		LED_CONFIG.GPIO_Speed = GPIO_Speed_50MHz;
		GPIO_Init(LED_GPIO_PORT, &LED_CONFIG);
}

开始写中断函数

  1. 我们首先配置一下嵌套向量中断控制器 NVIC ,设置按下KEY1这个键来进行外设中断
    原理和使用固件库点灯是一样的,写一个函数,然后配置内容,最后使能初始化。

/*定义一个函数用来控制NVIC*/
static void EXTI_NVIC_CONFIG(void)
{
	/*定义一个NVIC_InitTypeDef的结构体*/
	NVIC_InitTypeDef NVIC_CONFIG;
	/* 配置 NVIC 为优先级组 1 */
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
	/* 配置中断源:按键 1 */
	NVIC_CONFIG.NVIC_IRQChannel = EXTI0_IRQn;
	/* 配置抢占优先级:1 */
	NVIC_CONFIG.NVIC_IRQChannelPreemptionPriority = 1;
	/* 配置子优先级:1 */
	NVIC_CONFIG.NVIC_IRQChannelSubPriority = 1;
	/* 使能中断通道 */
	NVIC_CONFIG.NVIC_IRQChannelCmd = ENABLE;
	/* 调用库函数 初始化 NVIC */
	NVIC_Init(&NVIC_CONFIG);
}

不要问我在哪里去找的这个库,我发现调库的等级在于你要知道这个库在哪里。多调用几次就熟悉了。

  1. 配置EXTI。同样的道理
void EXTI_KEY_CONFIG(void)
{
		GPIO_InitTypeDef LED_CONFIG;
		EXTI_InitTypeDef EXTI_CONFIG;
	
		/*打开APB2上 GPIOB的时钟*/
		RCC_APB2PeriphClockCmd(KEY1_TNT_GPIO_CLK, ENABLE);
		//配合中断优先级
		EXTI_NVIC_CONFIG();
		/*配置GPIO口的输出*/
		LED_CONFIG.GPIO_Mode  = GPIO_Mode_IN_FLOATING;
		LED_CONFIG.GPIO_Pin   =	KEY1_TNT_GPIO_PIN;
		GPIO_Init(KEY1_TNT_GPIO_PORT, &LED_CONFIG);
		/*配置EXTI口的输出*/
		GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource0);
		EXTI_CONFIG.EXTI_Line = EXTI_Line0;
		EXTI_CONFIG.EXTI_Mode = EXTI_Mode_Interrupt;
		EXTI_CONFIG.EXTI_Trigger = EXTI_Trigger_Rising;
		EXTI_CONFIG.EXTI_LineCmd = ENABLE;
		EXTI_Init(&EXTI_CONFIG);

}
  1. 写中断函数
    中断有20个线,对应不同的GPIO和外设,我们这里配置GPIO0,所以选择对应的EXTI0;
    这里补充一下,EXTI并不是像之前的表格一样,完全对应0-20而是存在共用的
    在这里插入图片描述
    0-4单独对应,5-9共用,10-15共用。这里只对应GPIO口,无关外设。
    中断函数只需要我们按照对应的样子写就行,我这里仿照野火写在stm32f10x_it.c里面,因为其余地方我试了下不行,可能是我自己没有理解好这个EXTI吧
void EXTI0_IRQHandler(void)
{
	/*确保是否产生了 EXTI Line0 中断*/
	if( EXTI_GetITStatus(EXTI_Line0) != RESET)
	{
		//取反,控制熄或者亮
		LED_TOGGLE;
	}
	/*清除中断标志*/
	EXTI_ClearITPendingBit(EXTI_Line0);
}

在这里插入图片描述
记得将头文件添加进去!!!

8.1 完整代码

  1. 点灯程序
    bsp_led.h
#ifndef 	_BSP_LED_H
#define 	_BSP_LED_H

#include 	"stm32f10x.h"

#define LED_GPIO_PORT 	GPIOB
#define LED_GPIO_CLK 		RCC_APB2Periph_GPIOB
#define LED_GPIO_PIN		GPIO_Pin_1

#define LED_TOGGLE {LED_GPIO_PORT->ODR ^= LED_GPIO_PIN;}

void LED_CONFIG(void);

#endif	 /*_BSP_LED_H*/

bsp_led.c

#include "bsp_led.h"

void LED_CONFIG(void)
{
		/*定义一个 GPIO_InitTypeDef 类型的结构体*/
		GPIO_InitTypeDef LED_CONFIG;
		/*打开APB2上 GPIOB的时钟*/
		RCC_APB2PeriphClockCmd(LED_GPIO_CLK, ENABLE);
		/*配置GPIO口的输出*/
		LED_CONFIG.GPIO_Mode  = GPIO_Mode_Out_PP;
		LED_CONFIG.GPIO_Pin   =	LED_GPIO_PIN;
		LED_CONFIG.GPIO_Speed = GPIO_Speed_50MHz;
		GPIO_Init(LED_GPIO_PORT, &LED_CONFIG);
}

  1. 中断程序
    bsp_exti.h
#ifndef _BSP_EXTI_H
#define	_BSP_EXTI_H

#include "stm32f10x.h"

#define KEY1_TNT_GPIO_PORT 		GPIOA
#define KEY1_TNT_GPIO_CLK 		RCC_APB2Periph_GPIOA
#define KEY1_TNT_GPIO_PIN			GPIO_Pin_0

void EXTI_KEY_CONFIG(void);
static void EXTI_NVIC_CONFIG(void);
#endif /*_BSP_EXTI_H*/

bsp_led.c

#include "bsp_exti.h"
#include "bsp_led.h"
/*定义一个函数用来控制NVIC*/
static void EXTI_NVIC_CONFIG(void)
{
	/*定义一个NVIC_InitTypeDef的结构体*/
	NVIC_InitTypeDef NVIC_CONFIG;
	/* 配置 NVIC 为优先级组 1 */
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
	/* 配置中断源:按键 1 */
	NVIC_CONFIG.NVIC_IRQChannel = EXTI0_IRQn;
	/* 配置抢占优先级:1 */
	NVIC_CONFIG.NVIC_IRQChannelPreemptionPriority = 1;
	/* 配置子优先级:1 */
	NVIC_CONFIG.NVIC_IRQChannelSubPriority = 1;
	/* 使能中断通道 */
	NVIC_CONFIG.NVIC_IRQChannelCmd = ENABLE;
	/* 调用库函数 初始化 NVIC */
	NVIC_Init(&NVIC_CONFIG);
}
void EXTI_KEY_CONFIG(void)
{
		GPIO_InitTypeDef LED_CONFIG;
		EXTI_InitTypeDef EXTI_CONFIG;
	
		/*打开APB2上 GPIOB的时钟*/
		RCC_APB2PeriphClockCmd(KEY1_TNT_GPIO_CLK, ENABLE);
		//配合中断优先级
		EXTI_NVIC_CONFIG();
		/*配置GPIO口的输出*/
		LED_CONFIG.GPIO_Mode  = GPIO_Mode_IN_FLOATING;
		LED_CONFIG.GPIO_Pin   =	KEY1_TNT_GPIO_PIN;
		GPIO_Init(KEY1_TNT_GPIO_PORT, &LED_CONFIG);
		/*配置EXTI口的输出*/
		GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource0);
		EXTI_CONFIG.EXTI_Line = EXTI_Line0;
		EXTI_CONFIG.EXTI_Mode = EXTI_Mode_Interrupt;
		EXTI_CONFIG.EXTI_Trigger = EXTI_Trigger_Rising;
		EXTI_CONFIG.EXTI_LineCmd = ENABLE;
		EXTI_Init(&EXTI_CONFIG);

}

确保中断程序

void EXTI0_IRQHandler(void)
{
	/*确保是否产生了 EXTI Line0 中断*/
	if( EXTI_GetITStatus(EXTI_Line0) != RESET)
	{
		//取反,控制熄或者亮
		LED_TOGGLE;
	}
	/*清除中断标志*/
	EXTI_ClearITPendingBit(EXTI_Line0);
}

main.c

#include "stm32f10x.h"    //默认是在当前文件夹
#include "bsp_led.h"
#include "bsp_exti.h"

int main(void)
{

	LED_CONFIG();
	EXTI_KEY_CONFIG();
	//循环等待,必要条件
	while(1)                            
	{
	}
}

这样我们按KEY1就能控制灯的亮灭了,亮的时候按下就灭,灭的时候按下就亮。

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

永不秃头的程序员

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值