本文主要是介绍NVIC和EXTI,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
NVIC:Nest Vector Interrupt Controller,嵌套中断向量控制器,是用来管理中断嵌套的,核心任务在于其优先级的管理。NVIC给每个中断赋予先占优先级(抢占优先级)和次占优先级(响应优先级)。
CM3 内核支持256个中断,其中包含了16个内核中断和240个外部中断,并且具有256级的可编程中断设置。但STM32并没有使用CM3内核的全部东西,而是只用了它的一部分,STM32有76 个中断,包括16 个内核中断和60 个可屏蔽中断,具有16级可编程的中断优先级。而我们常用的就是这60个可屏蔽中断。
STM32将CM3内核的中断向量表进行了重新编排,将编号-3至6的中断向量定义为系统异常,编号为负的内核异常不能被设置优先级,从编号7开始为外部中断,这些中断优先级都是可以自行设置的。
EXTI:External Interrupt,外部中断,通过GPIO检查输入脉冲,引起中断时间,打断原来的代码执行流程,进入到中断服务函数中进行处理,处理完后再返回中断之前的代码中执行。
STM32 的EXTI控制器支持19个外部中断/事件请求。每个中断设有状态位,每个中断/事件都有独立的触发和屏蔽设置。STM32的19 个外部中断为 :
线 0~15:对应外部IO口的输入中断。
线 16:连接到PVD输出。
线 17:连接到RTC闹钟事件。
线 18:连接到USB唤醒事件。
EXTI中断步骤:
① 配置端口为输入模式
② 开启与该IO口相对应的线上中断/事件,并设置触发条件
③ 配置中断分组(NVIC)并使能中断
④ 中断函数
一、配置端口为输入模式
void GPIOA_Init(void)
{
GPIO_DeInit(GPIOA);
RCC->APB2ENR|=1<<2; //使能PORTA时钟
GPIOA->CRL&=0XFFFFFFF0;//PA0设置成上拉输入
GPIOA->CRL|=0X00000008;
}
在对端口进行任何操作之前,必须打开对应的时钟信号,其设置才能生效。这里使
用了GPIOA.00 端口作为中断0输入,作为输入时一般我们设置为上拉输入,如果要
设置成浮空输入的话,外部一定要加上拉电阻,这样对于过滤输入波动很有益处(假
设在电压在3.3-2.0之间进行波动,时间上没尝试按键操作,因为一旦有按键,就应
该为0,那么接了上拉的话,除非产生了低电平,否则小波动都会被拉高过滤掉)。
二、开启与该IO口相对应的线上中断/事件,并设置触发条件
这一步封装在函数void Ex_NVIC_Config(u8 GPIOx,u8 BITx,u8 TRIM)中,可以直接调用,例如:Ex_NVIC_Config(GPIO_A,0,RTIR); //设置PA(0)上升沿触发
Ex_NVIC_Config(GPIO_A,13,FTIR);//设置PA(13)下降沿触发
该函数为 Ex_NVIC_Config,该函数有3个参数:GPIOx为GPIOA~G(0~6),在sys.h里面有定义。代表要配置的IO口。BITx则代表这个IO口的第几位。TRIM为触发方式,低2位有效(0x01代表下降触发;0x02代表上升沿触发;0x03代表任意电平触发)。其代码如下:
/**************************
该函数只针对GPIOA~G;不包括PVD,RTC和USB唤醒这三个,参数:GPIOA~G(0~6),代表GPIOA~G;BITx:需要使能的位;TRIM:触发模式,1,上升沿;2,下降沿;3,任意电平触发;该函数一次只能配置1个IO 口,多个IO 口,需多次调用,该函数会自动开启对应中断,以及屏蔽线
***************************/
void Ex_NVIC_Config(u8 GPIOx,u8 BITx,u8 TRIM)
{
u8 EXTADDR;
u8 EXTOFFSET;
EXTADDR=BITx/4;//得到中断寄存器组的编号
EXTOFFSET=(BITx%4)*4;
RCC->APB2ENR|=0x01;//使能IO复用时钟
AFIO->EXTICR[EXTADDR]|=GPIOx<<EXTOFFSET;//EXTI.BITx映射到GPIOx.BITx
//自动设置
EXTI->IMR|=1<<BITx;//开启line BITx上的中断
EXTI->EMR|=1<<BITx;//不屏蔽lineBITx上的事件
if(TRIM&0x01)EXTI->FTSR|=1<<BITx;//lineBITx 上事件下降沿触发
if(TRIM&0x02)EXTI->RTSR|=1<<BITx;//lineBITx 上事件上升降沿触发
}
EXTICR(External interrupt configuration register)即外部中断配置寄存器。
因为STM32 任何一个IO口都可以配置成中断输入口,但是IO口的数目远大于中断线数(16个),所以我们需要选择哪一个中断是经由那个IO口输入的。于是STM32就这样设计,GPIOA~GPIOG的[15:0]分别对应中断线15~0。这样每个中断线对应了最多7个IO 口,以线0为例:它对应了GPIOA.0、PIOB.0、GPIOC.0、GPIOD.0、GPIOE.0、GPIOF.0、GPIOG.0。而中断线每次只能连接到1个IO 口上,这样就需要EXTICR来决定对应的中断线配置到哪个GPIO上了。寄存器EXTIR有四组,在书写时要注意。具体对应关系如下:0000: PA[x] pin,0001: PB[x] pin,0010: PC[x] pin,0011: PD[x] pin0100: PE[x] pin,0101: PF[x] pin,0110: PG[x] pin。假设我们需要设定外部中断0由GPIOA_0来控制,那么我就可以写成AFIO->EXTIR[0] |=0X00;
Ex_NVIC_Config,首先根据GPIOx的位得到中断寄存器组的编号,即EXTICR的编号,在EXTICR里面配置中断线应该配置到GPIOx的哪个位。然后使能该位的中断及事件,最后配置触发方式。这样就完成了外部中断的的配置了。从代码中可以看到该函数默认是开启中断和事件的。其次还要注意的一点就是该函数一次只能配置一个IO口,如果你有多个IO口需要配置,则多次调用这个函数就可以了。
三、配置中断分组(NVIC)并使能中断
这一步封装在函数void MY_NVIC_Init(u8 NVIC_PreemptionPriority,u8 NVIC_SubPriority,u8 NVIC_Channel,u8 NVIC_Group)里面可以直接调用,例如
MY_NVIC_Init(2,2,EXTI0_IRQChannel,2); //抢占2,子优先级2,组2
NVIC 设置函数MY_NVIC_Init,该函数有4个参数,分别为:NVIC_PreemptionPriority、NVIC_SubPriority、NVIC_Channel、NVIC_Group。第一个参数NVIC_PreemptionPriority为中断抢占优先级数值,第二个参数NVIC_SubPriority为中断子优先级数值,前两个参数的值必须在规定范围内,否则也可能产生意想不到的错误。第三个参数NVIC_Channel为中断的编号(范围为0~59),最后一个参数NVIC_Group为中断分组设置(范围为0~4)。
/********************设置NVIC****************
*NVIC_PreemptionPriority:抢占优先级
*//NVIC_SubPriority :响应优先级
*NVIC_Channel :中断编号
*NVIC_Group :中断分组 0~4
*注意优先级不能超过设定的组的范围!否则会有意想不到的错误
*组划分:
*组0:0 位抢占优先级,4 位响应优先级
*组1:1 位抢占优先级,3 位响应优先级
*组2:2 位抢占优先级,2 位响应优先级
*组3:3 位抢占优先级,1 位响应优先级
*组4:4 位抢占优先级,0 位响应优先级
*NVIC_SubPriority 和NVIC_PreemptionPriority的原则是,数值越小,越优先
************************************/
void MY_NVIC_Init(u8 NVIC_PreemptionPriority,u8 NVIC_SubPriority,u8NVIC_Channel,u8 NVIC_Group)
{
u32 temp;
u8 IPRADDR=NVIC_Channel/4; //每组只能存4个,得到组地址
u8 IPROFFSET=NVIC_Channel%4;//在组内的偏移
IPROFFSET=IPROFFSET*8+4; //得到偏移的确切位置
MY_NVIC_PriorityGroupConfig(NVIC_Group);//设置分组
temp=NVIC_PreemptionPriority<<(4-NVIC_Group);
temp|=NVIC_SubPriority&(0x0f>>NVIC_Group);
temp&=0xf;//取低四位
if(NVIC_Channel<32)NVIC->ISER[0]|=1<<NVIC_Channel;//使能中断位(要清除的话,相反操作就OK)
else
NVIC->ISER[1]|=1<<(NVIC_Channel-32);
NVIC->IPR[IPRADDR]|=temp<<IPROFFSET;//设置响应优先级和抢断优先级
}
MY_NVIC_PriorityGroupConfig为NVIC的分组函数,该函数的参数NVIC_Group为要设置的分组号,可选范围为0~4,总共5组:EXTI0、EXTI1、EXTI2、EXTI3、EXTI4为Line0~Line4、EXTI15_10为Line15~Line10、EXTI9_5为Line9~Line5。如果参数非法,将可能导致不可预料的结果。
/***************设置NVIC分组*****************
*NVIC_Group:NVIC 分组 0~4总共5 组
************************************/
void MY_NVIC_PriorityGroupConfig(u8 NVIC_Group)
{
u32 temp,temp1;
temp1=(~NVIC_Group)&0x07;//取后三位
temp1<<=8;
temp=SCB->AIRCR; //读取先前的设置
temp&=0X0000F8FF; //清空先前分组
temp|=0X05FA0000; //写入钥匙
temp|=temp1;
SCB->AIRCR=temp; //设置分组
}
MY_NVIC_PriorityGroupConfig 函数设置中断优先级分组的思路:STM32的5 个分组是通过设置SCB->AIRCR的BIT[10:8]来实现的,SCB->AIRCR的修改需要通过在高16位写入0X05FA这个密钥才能修改的,故在设置AIRCR之前,应该把密钥加入到要写入的内容的高16位,以保证能正常的写入AIRCR。在修改AIRCR的时候,我们一般采用读->改->写的步骤,来实现不改变AIRCR原来的其他设置。MY_NVIC_PriorityGroupConfig分组函数在每个系统里面只要设置一次就够了,设置多次,则是以最后的那一次为准。但是只要多次设置的组号都是一样,就没事。否则前面设置的中断会因为后面组的变化优先级会发生改变,这点在使用的时候要特别注意!一个系统代码里面,所有的中断分组都要统一,以上代码对要配置的中断号默认是开启中断的。也就是ISER中的值设置为1了。
四、中断函数
A. 中断函数名的书写有要求,否则会找不到中断入口。
stm32f10x_it.c是专门用来存放中断服务函数的。
在 3.5 库函数之前,在stm32f10x_it.c中就预先写好了个外部中断的函数名称,我们只要将对应的执行过程填充进去就可以。
在3.5库函数中,文件中默认只有几个福安与系统异常的中断服务函数,而且都是空函数,在需要的时候自行编写。但是中断服务函数名是不可以自己定义,中断服务函数的名字必须要与启动文件startup_stm32f10x_hd.s中的中断向量表中定义一致。
B. 在进入中断后我们需要做的动作有 2 部,一次执行中断过程,2 是清除中断挂起标识,因为执行完毕了。如果不清除中断挂起标识,则无法再次进入中断。
例程:
void EXTI0_IRQHandler(void)
{
delayMs(10);
if(((GPIOA->IDR)&0x01)==0)
{
while(((GPIOA->IDR)&0x01)==1);
GPIOA->ODR = ~(GPIOA->ODR&0X02);
EXTI->PR=1;
}
}
这里 EXTI->PR=1,即想中断挂起寄存器中写1,清除中断
这篇关于NVIC和EXTI的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!