IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 嵌入式 -> 带你模仿正点原子编程风格到深入学习寄存器并手把手编写STM32F103寄存器程序(外部中断--EXTI) -> 正文阅读

[嵌入式]带你模仿正点原子编程风格到深入学习寄存器并手把手编写STM32F103寄存器程序(外部中断--EXTI)

带你模仿正点原子到寄存器编写–外部中断EXTI_驱动按键

前言

对于大家来说学习STM32中断是学的比较迷的,这里是我学中断做的一些笔记希望对大家有所作用。

一、我们先了解一些中断的一些基础知识。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
要产生中断,必须先配置好并使能中断线。根据需要的边沿检测设置2个触发寄存器,同时在中断屏蔽寄存器的相应位写’1’允许中断请求。当外部中断线上发生了期待的边沿时,将产生一个中断请求,对应的挂起位也随之被置’1’。在挂起寄存器的对应位写’1’,将清除该中断请求。
如果需要产生事件,必须先配置好并使能事件线。根据需要的边沿检测通过设置2个触发寄存器,同时在事件屏蔽寄存器的相应位写’1’允许事件请求。当事件线上发生了需要的边沿时,将产生一个事件请求脉冲,对应的挂起位不被置’1’。
通过在软件中断/事件寄存器写’1’,也可以通过软件产生中断/事件请求。
这里我要强调一下一定要将挂起寄存器置’1’清除中断请求信号,不然在之后没有中断事件产生的时候它还一直将这个中断信号发给NVIC中断向量控制器使得MCU一直进行这个中断对应的中断服务程序。

在这里插入图片描述
这里我要提示一下EXTI0到EXRTI4中断线对应的中断服务子程序是EXTI0_IRQHandler到EXTI4_IRQHandler。而EXTI5到EXTI9中断线是共用一个中断服务子程序的EXTI9_5_IRQHandler 。而EXTI10到EXTI15中断线是共用一个中断服务子程序的EXTI15_10_IRQHandler。其它的你们可以看启动程序里的,这个不能自己随便命名的。

二、寄存器配置

STM32手册提供的配置外部中断:在这里插入图片描述
总结配置思路:

  1. 时钟配置:GPIO时钟、AFIO时钟(中断线与IO形成映射)。
  2. GPIO配置:IO初始化和中断线映射。
  3. 屏蔽位(EXTI_IMR):这个我们一般不配,因为我们就是想外部出现事件后能发生中断响应。
  4. EXIT配置:触发方式(EXTI_RTSR上升沿触发、EXTI_FTSR下降沿触发)、中断使能。
  5. NVIC配置:优先级分组,中断使能。
  6. 中断服务子程序:判断标志、清除标志。

手册的映射地址图:
在这里插入图片描述
在这里插入图片描述
根据上图即可配置出下图程序:

EXTI.h:

#ifndef __EXIT_H
#define __EXIT_H	

#include "gpio.h"
#include "nvic.h"
#include "key.h"

#define afio_base		(0x40010000)       	//复用寄存器基地址
#define EXTI_BASE		(0x40010400)		//中断寄存器基地址

typedef struct
{
  volatile unsigned int IMR;     	//中断屏蔽寄存器
  volatile unsigned int EMR;		//事件屏蔽寄存器
  volatile unsigned int RTSR;		//上升沿触发选择寄存器
  volatile unsigned int FTSR;		//下降沿触发选择寄存器
  volatile unsigned int SWIER;	//软件中断事件寄存器
  volatile unsigned int PR;		//挂起寄存器
} EXTI_Type;

typedef struct
{
  volatile unsigned int EVCR;		//事件控制寄存器
  volatile unsigned int MAPR;		//复用重映射和调试IO配置寄存器	
  volatile unsigned int EXTICR[4];	//外部中断配置寄存器
} afio_type;

#define afio 			((afio_type *) afio_base)  	//复用寄存器
#define EXTI			((EXTI_Type *) EXTI_BASE)	//

void exti_init(void);//外部中断初始化	
void ex_nvic_config(u8 gpio,u8 bit,u8 trim);

#endif

EXTI.c:

//外部中断配置函数
//gpio指端口位  就是说PA~G=0~6
//bit指使能位   就是说PA~G[bit]
//trim指触发模式   1为下降沿,2为上升沿
//设置的是PE2~4 和PA0

void ex_nvic_config(u8 gpio,u8 bit,u8 trim)
{
	u8 extino;     //这个是保存根据GPIO口的哪一位算出要使用的外部中断配置寄存器EXTICR
	u8 extioffset;  //这个保存的是偏移量,用来设置寄存器哪一位的
	extino=bit/4;  //得到中断寄存器组的编号
	extioffset=(bit%4)*4;  //偏移量
	RCC->RCC_APB2ENR|=0x01; //使能 AFIO 复用时钟
	afio->EXTICR[extino]&=~(0x000F<<extioffset); //清楚原来的设置
	afio->EXTICR[extino]|=gpio<<extioffset;  //将对应的gpio映射连接到中断线exti对应位
	EXTI->IMR|=1<<bit;  //开启来自线bit的中断
	//EXTI->EMR|=1<<BITx;//不屏蔽line BITx上的事件 (如果不屏蔽这句,在硬件上是可以的,但是在软件仿真的时候无法进入中断!)
	if(trim&0x01) EXTI->FTSR|=1<<bit; //自线bit上事件下降沿触发
	if(trim&0x02) EXTI->RTSR|=1<<bit; //自线bit上事件上升沿触发
}

大家可能对extioffset和extino这两个变量对寄存器进行配置不太懂,下图是这个配置的参考,大家看了应该就懂了。
在这里插入图片描述
配置完上面的我们需要将中断事件载入NVIC嵌套向量中断控制器中然后作出中断响应。
前面的外部中断配置的只是开启或者是禁止中断信号的进入内核罢了。而NVIC嵌套向量中断控制器是用于中断分组,而用于分配抢占优先级和响应优先级的。
还有中断分组功能不是在NVIC寄存器上的。

在这里插入图片描述
在这里插入图片描述
这里我先说明:
中断的使能与除能分别使用各自的寄存器来控制——这与传统的, 使用单一比特的两个
状态来表达使能与除能是不同的。 CM3 中可以有 240 对使能位/除能位,每个中断拥有一对。这 240 个对子分布在 8 对 32 位寄存器中(最后一对没有用完)。欲使能一个中断,你需要写 1 到对应 SETENA 的位中;欲除能一个中断,你需要写 1 到对应的 CLRENA 位中;如果往它们中写 0,不会有任何效果。通过这种方式,使能/除能中断时只需把“当事位”写成1,其它的位可以全部为零。再也不用像以前那样,害怕有些位被写入 0 而破坏其对应的中断设置(写 0 没有效果),从而实现每个中断都可以自顾地设置,而互不侵犯——只需单一的写指令,不再需要读‐改‐写。
含有60个IPR寄存器。
在这里插入图片描述
在这里插入图片描述
中断分组寄存器:
在这里插入图片描述
中文版的图:
在这里插入图片描述
有了上面的知识后:

nvic.h:

#ifndef __NVIC_H
#define __NVIC_H
#include "gpio.h"


//中断复位寄存器  设置分组的
#define scb_aircr (*(volatile unsigned int *)0xE000ED0C)

//NVIC基地址
#define nvic_base (0xE000E100)

typedef struct
{
  volatile unsigned int iser[8];                     	//中断使能设置寄存器 
       unsigned int RESERVED0[24];                                   
  volatile unsigned int icer[8];                      	//中断清除使能寄存器
       unsigned int RSERVED1[24];                                    
  volatile unsigned int ispr[8];                      	//中断挂起设置寄存器
       unsigned int RESERVED2[24];                                   
  volatile unsigned int icpr[8];                      	//中断清除挂起寄存器
       unsigned int RESERVED3[24];                                   
  volatile unsigned int iabr[8];                      	//中断激活状态位寄存器
       unsigned int RESERVED4[56];                                   
  volatile unsigned char  ipr[240];                    //中断优先级寄存器
       unsigned int RESERVED5[644];                                  
  volatile  unsigned int stir;                         //软件触发方式寄存器
}nvic_type; 

//设置NCIC的指针
#define nvic ((nvic_type *) nvic_base)

/** p抢占 s响应 no中断编号 优先级分组 **/
void NVIC_Init(u8 p,u8 s,u8 no,u8 group); 
	
#endif

nvic.c:

#include "nvic.h"

/**p:抢占优先级, n:响应优先级,  no:中断编号, group:分组号**/
void NVIC_Init(u8 p,u8 s,u8 no,u8 group)
{
	u32 t,t1,t2;
	t1=(~group)&0x07;   //取前三位
	t1<<=8;              //设置优先级分组的在其寄存器的8~10位
	t=scb_aircr;       //读取先前的设置
	t&=0X0000F8FF;     //清空先前分组对应的寄存器设置位
	t|=0X05FA0000; //写入钥匙 只有向寄存器高4字写入0X05FA0000才能写入生效
	t|=t1;	     //t=101 抢占和响应各两位 位置/**4~7**/
	scb_aircr=t;  //设置分组
	/**0组-编号7,1组-编号6,2组-编号5,3组-编号4,4组-编号0~3**/
	
	t2=p<<(4-group); //抢占有n位,所以用寄存器设置位的4位减去n位就是移动的位置了**/
	t2|=s&(0x0f>>group); //响应有n位,所以用寄存器设置位的4位减去n位就是移动的位置=group了**/
	t2&=0xf;   //取低四位 
	/**这里的iser用的是结构体且里面成员用数组表示每一个32位寄存器**/
	/**0~31号使能0号数组成员,32~63使用1号数组成员**/
	nvic->iser[no/32]|=(1<<no%32);//使能中断位(要清除的话,相反操作就OK)
	nvic->ipr[no]|=t2<<4;	//设置响应优先级和抢断优先级的等级,右移4位将设置内容放入对应的操作位
}



上面配置完了我们就可以写发生中断后进行的中断响应了(中断服务子程序)
void EXTI0_IRQHandler(void)
{
  if(GPIOA->GPIO_IDR&(1<<0))	//WK_UP按键
  {
		delay_ms(20);	//消抖
		if(GPIOA->GPIO_IDR&(1<<0))	//WK_UP按键
		{				 
			GPIOB->GPIO_ODR^=(1<<5);  //反转状态
		}
  }
	//清除中断挂起标志位,否则会被认为中断没有被处理而循环再次进入中断
	EXTI->PR|=1<<0;   // //清除LINE0上的中断标志位 
}	


void EXTI2_IRQHandler(void)
{
  if(!(GPIOE->GPIO_IDR&(1<<2)))	//key1按键
  {
		delay_ms(20);		//消抖
		if(!(GPIOE->GPIO_IDR&(1<<2)))	//key1按键
		{				 
			GPIOB->GPIO_ODR^=(1<<5);
		}
  }
	 EXTI->PR|=1<<2;  //清除LINE3上的中断标志位  
}

void EXTI3_IRQHandler(void)
{
  if(!(GPIOE->GPIO_IDR&(1<<3)))	//key1按键
  {
		delay_ms(20);		//消抖
		if(!(GPIOE->GPIO_IDR&(1<<3)))	//key1按键
		{				 
			GPIOE->GPIO_ODR^=(1<<5);
		}
  }
	EXTI->PR|=1<<3;  //清除LINE3上的中断标志位  
}
将GPIO口与EXTI外部中断联系起来,再将EXTI与NVIC联系起来:
//EXTI0编号为6,中断向量表有提供的
void exti_init(void)
{
  
	ex_nvic_config(0,0,2);
	ex_nvic_config(4,2,1);
	ex_nvic_config(4,3,1);
	NVIC_Init(2,0,6,2);  //按键对应不同的中断服务函数
	NVIC_Init(2,1,8,2);
	NVIC_Init(2,2,9,2);
}

这个GPIO的初始化:
void key_init(void)
{
	RCC->RCC_APB2ENR|=1<<2;  //使能GPIOA时钟
	RCC->RCC_APB2ENR|=1<<6;  //使能GPIOE时钟
	GPIOE->GPIO_CRL&=0XFFF000FF; 
	GPIOE->GPIO_CRL|=0X00088800; 
	//PE2~4上拉 因为默认是下拉的,需要用odr将其上拉
	GPIOE->GPIO_ODR|=7<<2;   
	
	GPIOA->GPIO_CRL&=0XFFFFFFF0;  //PA0设置成输入,默认下拉	
	GPIOA->GPIO_CRL|=0X00000008;
}

完整的程序

exti.h

#ifndef __EXIT_H
#define __EXIT_H	

#include "gpio.h"
#include "nvic.h"
#include "key.h"

#define afio_base		(0x40010000)       	//复用寄存器基地址
#define EXTI_BASE		(0x40010400)		//中断寄存器基地址

typedef struct
{
  volatile unsigned int IMR;     	//中断屏蔽寄存器
  volatile unsigned int EMR;		//事件屏蔽寄存器
  volatile unsigned int RTSR;		//上升沿触发选择寄存器
  volatile unsigned int FTSR;		//下降沿触发选择寄存器
  volatile unsigned int SWIER;	//软件中断事件寄存器
  volatile unsigned int PR;		//挂起寄存器
} EXTI_Type;

typedef struct
{
  volatile unsigned int EVCR;		//事件控制寄存器
  volatile unsigned int MAPR;		//复用重映射和调试IO配置寄存器	
  volatile unsigned int EXTICR[4];	//外部中断配置寄存器
} afio_type;

#define afio 			((afio_type *) afio_base)  	//复用寄存器
#define EXTI			((EXTI_Type *) EXTI_BASE)	//

void exti_init(void);//外部中断初始化	
void ex_nvic_config(u8 gpio,u8 bit,u8 trim);
void key_init(void);

#endif

exti.c

#include "exti.h"
#include "delay.h"

void EXTI0_IRQHandler(void)
{
  if(GPIOA->GPIO_IDR&(1<<0))	//WK_UP按键
  {
		delay_ms(20);	//消抖
		if(GPIOA->GPIO_IDR&(1<<0))	//WK_UP按键
		{				 
			GPIOB->GPIO_ODR^=(1<<5);  //反转状态
		}
  }
	//清除中断挂起标志位,否则会被认为中断没有被处理而循环再次进入中断
	EXTI->PR|=1<<0;   // //清除LINE0上的中断标志位 
}	


void EXTI2_IRQHandler(void)
{
  if(!(GPIOE->GPIO_IDR&(1<<2)))	//key1按键
  {
		delay_ms(20);		//消抖
		if(!(GPIOE->GPIO_IDR&(1<<2)))	//key1按键
		{				 
			GPIOB->GPIO_ODR^=(1<<5);
		}
  }
	 EXTI->PR|=1<<2;  //清除LINE3上的中断标志位  
}

void EXTI3_IRQHandler(void)
{
  if(!(GPIOE->GPIO_IDR&(1<<3)))	//key1按键
  {
		delay_ms(20);		//消抖
		if(!(GPIOE->GPIO_IDR&(1<<3)))	//key1按键
		{				 
			GPIOE->GPIO_ODR^=(1<<5);
		}
  }
	EXTI->PR|=1<<3;  //清除LINE3上的中断标志位  
}


//外部中断配置函数
//gpio指端口位  就是说PA~G=0~6
//bit指使能位   就是说PA~G[bit]
//trim指触发模式   1为下降沿,2为上升沿
//设置的是PE2~4 和PA0

void ex_nvic_config(u8 gpio,u8 bit,u8 trim)
{
	u8 extino;     //这个是保存根据GPIO口的哪一位算出要使用的外部中断配置寄存器EXTICR
	u8 extioffset;  //这个保存的是偏移量,用来设置寄存器哪一位的
	extino=bit/4;  //得到中断寄存器组的编号
	extioffset=(bit%4)*4;  //偏移量
	RCC->RCC_APB2ENR|=0x01; //使能 AFIO 复用时钟
	afio->EXTICR[extino]&=~(0x000F<<extioffset); //清楚原来的设置
	afio->EXTICR[extino]|=gpio<<extioffset;  //将对应的gpio映射连接到中断线exti对应位
	EXTI->IMR|=1<<bit;  //开启来自线bit的中断
	//EXTI->EMR|=1<<BITx;//不屏蔽line BITx上的事件 (如果不屏蔽这句,在硬件上是可以的,但是在软件仿真的时候无法进入中断!)?
	if(trim&0x01) EXTI->FTSR|=1<<bit; //自线bit上事件下降沿触发
	if(trim&0x02) EXTI->RTSR|=1<<bit; //自线bit上事件上升沿触发
}


//EXTI0编号为6
void exti_init(void)
{
  
	ex_nvic_config(0,0,2);
	ex_nvic_config(4,2,1);
	ex_nvic_config(4,3,1);
	NVIC_Init(2,0,6,2);  //按键对应不同的中断服务函数
	NVIC_Init(2,1,8,2);
	NVIC_Init(2,2,9,2);
}

void key_init(void)
{
	RCC->RCC_APB2ENR|=1<<2;  //使能GPIOA时钟
	RCC->RCC_APB2ENR|=1<<6;  //使能GPIOE时钟
	GPIOE->GPIO_CRL&=0XFFF000FF; 
	GPIOE->GPIO_CRL|=0X00088800; 
	//PE2~4上拉 因为默认是下拉的,需要用odr将其上拉
	GPIOE->GPIO_ODR|=7<<2;   
	
	GPIOA->GPIO_CRL&=0XFFFFFFF0;  //PA0设置成输入,默认下拉	
	GPIOA->GPIO_CRL|=0X00000008;
}

nvic.h

#ifndef __NVIC_H
#define __NVIC_H
#include "gpio.h"


//中断复位寄存器  设置分组的
#define scb_aircr (*(volatile unsigned int *)0xE000ED0C)

//NVIC基地址
#define nvic_base (0xE000E100)

typedef struct
{
  volatile unsigned int iser[8];                     	//中断使能设置寄存器 
       unsigned int RESERVED0[24];                                   
  volatile unsigned int icer[8];                      	//中断清除使能寄存器
       unsigned int RSERVED1[24];                                    
  volatile unsigned int ispr[8];                      	//中断挂起设置寄存器
       unsigned int RESERVED2[24];                                   
  volatile unsigned int icpr[8];                      	//中断清除挂起寄存器
       unsigned int RESERVED3[24];                                   
  volatile unsigned int iabr[8];                      	//中断激活状态位寄存器
       unsigned int RESERVED4[56];                                   
  volatile unsigned char  ipr[240];                    //中断优先级寄存器
       unsigned int RESERVED5[644];                                  
  volatile  unsigned int stir;                         //软件触发方式寄存器
}nvic_type; 

//设置NCIC的指针
#define nvic ((nvic_type *) nvic_base)

/** p抢占 s响应 no中断编号 优先级分组 **/
void NVIC_Init(u8 p,u8 s,u8 no,u8 group); 
	
#endif

nvic.c

#include "nvic.h"

/**p:抢占优先级, n:响应优先级,  no:中断编号, group:分组号**/
void NVIC_Init(u8 p,u8 s,u8 no,u8 group)
{
	u32 t,t1,t2;
	t1=(~group)&0x07;   //取前三位
	t1<<=8;             //设置优先级分组的在其寄存器的8~10位
	t=scb_aircr;       	//读取先前的设置
	t&=0X0000F8FF;     	//清空先前分组对应的寄存器设置位
	t|=0X05FA0000; 	//写入钥匙 只有向寄存器高4字写入0X05FA0000才能写入生效
	t|=t1;	     	//t=101 抢占和响应各两位 位置/**4~7**/
	scb_aircr=t;  		//设置分组
	/**0组-编号7,1组-编号6,2组-编号5,3组-编号4,4组-编号0~3**/
	//这样操作是因为使用不同的分组,抢占设置位和响应设置位都不一样的
	t2=p<<(4-group); 	//抢占有n位,所以用寄存器设置位的4位减去n位就是移动的位置了**/
	t2|=s&(0x0f>>group);//响应有n位,所以用寄存器设置位的4位减去n位就是移动的位置=group了**/
	t2&=0xf;   		//取低四位 
	/**这里的iser用的是结构体且里面成员用数组表示每一个32位寄存器**/
	/**0~31号使能0号数组成员,32~63使用1号数组成员**/
	nvic->iser[no/32]|=(1<<no%32);//使能中断位(要清除的话,相反操作就OK)
	nvic->ipr[no]|=t2<<4;	//设置响应优先级和抢断优先级的等级,右移4位将设置内容放入对应的操作位
}

感谢大家关注,希望对大家有所帮助。
下一篇写 “用寄存器方法写模拟串口程序”😁

  嵌入式 最新文章
基于高精度单片机开发红外测温仪方案
89C51单片机与DAC0832
基于51单片机宠物自动投料喂食器控制系统仿
《痞子衡嵌入式半月刊》 第 68 期
多思计组实验实验七 简单模型机实验
CSC7720
启明智显分享| ESP32学习笔记参考--PWM(脉冲
STM32初探
STM32 总结
【STM32】CubeMX例程四---定时器中断(附工
上一篇文章      下一篇文章      查看所有文章
加:2021-07-14 11:05:27  更:2021-07-14 11:08:50 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/6 13:02:23-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码