通用中断知识铺垫1:
完整的CM4有256个可编程中断(16个内核中断和240个外部中断),而stm32f40x共有92个中断(10内+82可编程),意思是说STM32F40X这个单片机没有完全释放CM4内核的资源。
CM4内核的中断是由NVIC来控制的,就像是GIC这样的东西~它一共有几个寄存器用来管理这256个中断的各种行为,包括:
中断使能寄存器(ISER[8],这是一个32位数组,共8个。32*8个位控制着256个中断)
失能寄存器(ICER[8],看名字就知道了,但是很神奇哈,为啥芯片不设计成写1开写0关)
挂起寄存器(ISPR[8],因为中断可以被更高级的中断打断,这个是用来暂时挂起被打断的中断)
清除挂起寄存器(ICPR[8],这两个估计不用我们操心,单片机内部机制可能已经实现了)
活跃位寄存器(IABR[8],这个是用来显示,那些中断正在执行)
优先级寄存器(IP[240],是个8位寄存器,是配置240个可编程中断优先级的寄存器,每8bit只用到高4位,而这高四位有n位是设置抢占优先级的,有m位是设置设置响应优先级(也叫子优先级),m+n = 4)
上面这个可能还需要再解释一下,先看一下分组寄存器(SCB->AIRCR 寄存器的 bit10~8 )
例如,如果中断分组设为3,即 抢:响 = 3:1,也就是说任意一个中断的抢占优先级可以设为0~7(即2^3-1),响应优先级可以设为0~1。数字越小,优先级越高。那么什么是抢占什么是响应优先级呢?
抢占优先级就是高抢断的中断任务发生时,能打断正在执行的低抢断的中断。而响应优先级就不行了,如果两个任务的抢占优先级一样,其中一个响应优先级比另外一个高,也不能打断,只能乖乖排队。响应优先级高有一个优点就是如果两个同抢不同响的中断同时在排队(等一个比它们抢占优先级高的中断任务完成),那么响应优先级高的会排在前面先执行。
现在应该能够理解分组、抢占、响应这三个概念了,其实中断相关的编程就是这样,核心三要素1.开关2.分组3.优先级,这样配置完就能写中断服务函数了。
通用中断代码分析:
领会了三要素,我们再来看正点原子的这段代码就很清楚了:
//设置NVIC
//NVIC_PreemptionPriority:抢占优先级
//NVIC_SubPriority :响应优先级
//NVIC_Channel :中断编号
//NVIC_Group :中断分组 0~4
void MY_NVIC_Init(u8 NVIC_PreemptionPriority抢占优先级,u8 NVIC_SubPriority响应优先级,u8 NVIC_Channel中断号,u8 NVIC_Group分组)
{
u32 temp;
MY_NVIC_PriorityGroupConfig(NVIC_Group);//设置分组
temp=NVIC_PreemptionPriority<<(4-NVIC_Group);
temp|=NVIC_SubPriority&(0x0f>>NVIC_Group);
temp&=0xf; //取低四位
NVIC->ISER[NVIC_Channel/32]|=1<<NVIC_Channel%32;//使能中断位(要清除的话,设置ICER对应位为1即可)
NVIC->IP[NVIC_Channel]|=temp<<4; //设置响应优先级和抢断优先级
}
其中MY_NVIC_PriorityGroupConfig(NVIC_Group);函数就不贴出来了,里面其实就是给分组寄存器幅值(SCB->AIRCR 寄存器的 bit10~8)。
下面NVIC->ISER是根据传入的中断号打开中断(中断号其实就是一个宏定义整数,表明是哪个中断而已,例如MY_NVIC_Init(3,2,EXTI2_IRQn,2)和MY_NVIC_Init(3,3,USART1_IRQn,2),里面的EXTI2_IRQn和USART1_IRQn就是中断号,用于表面要打开的是哪个中断)NVIC->IP则是设置两种优先级~
值得一提的是,中断分组在一个程序里,要设置一致,不要说串口中断分组为2,外部中断又分组为3,这样是不行滴,这是代码混乱的体现也是没搞明白原理的体现,差异在于优先级,而不在于分组。
上面是所有中断都要做的操作,使能、分组、设优先级,外部中断属于上面但是要实现外部中断还有一些步骤!且看下面理论铺垫2.
外部中断理论铺垫2:
外部中断由于是单片机这个芯片之外的事件触发了中断(如某个引脚的由于外部的原因产生了上升沿/下降沿,RTC相关事件,以太网相关事件。说起来RTC,以太网等事件本质也是电平变化的组合,总之都是外部引起的就是了),而很显然,定时器中断,串口中断这类的属于单片机这个芯片内部产生的中断,则无需后面这些额外的操作了。
stm32f4xx的外部中断,由NVIC+EXTI一起管理,一共23个中断号:
0~15号是负责GPIO的,GPIO这个有意思,它是举个例子就很明白了,PA_1,PB_1,PC_1等IO引脚是1的,共用中断号1,PA_0,PB_0,PC_0等IO引脚是0的,共用中断号0。但是的但是,一共只有7个外中断服务函数,它们的名字分别是:EXTI0_IRQHandler,EXTI1_IRQHandler,EXTI2_IRQHandler,EXTI3_IRQHandler,EXTI4_IRQHandler,EXTI9_5_IRQHandler,EXTI15_10_IRQHandler,大家应该看明白了,凡是引脚是0,1,2,3,4的,对应前5个中断服务函数,凡是引脚是5,6,7,8,9的,对应EXTI9_5_IRQHandler,凡是引脚是10,11,12,13,14,15的,对应EXTI15_10_IRQHandler,这叫一个错综复杂,所以在中断函数里我们要额外判断具体是哪个GPIO组的IO触发了中断。值得一题的是,虽然是共用,但是一根线一次只能连接到一个IO上,所以说一个程序中如果用了PA_0作为外部中断引脚,就表示它占用了0号线,如果再用将PB_0或者PC_0之类GPIO的开启外部中断,那程序就乱套了。再再值得一提的是,使用hal库开发就不会有这个问题,因为hal库的中断处理都是放回调函数来搞的,和硬件上的中断设计可以说关系不大,所以也没有这个限制,如果不是为了通用地学习单片机,还是用hal库开发st的单片机香。
16~22号是负责RTC,以太之类的,它们有各自的中断号,也有各自的中断服务函数
NVIC我们都知道了(负责开关/分组/优先级),而EXTI也需要学习然后总结一下:
它有以下寄存器:
中断屏蔽寄存器(IMR):给哪个中断号勾上哪个中断就无效了
事件屏蔽寄存器(EMR):给哪个事件勾上哪个事件就不触发中断了(很别扭啊,直接设计成哪种事件能够触发不就得了,还搞个屏蔽。。。这个是和RTC,以太等外部中断有关的,和IO上升沿下降沿触发没有关系)
上升沿寄存器(RTSR):给外部中断选择触发边沿咯
下降沿寄存器(FTSR):同上
挂起寄存器(PR): 这个寄存器是指示作用的,有外部中断执行就会置1而且中断服务函数结束不会自动清零,所以~在外部中断完事后要手动清零。
除了以上寄存器,外部中断还和SYSCFG->EXTICR寄存器有关(留意要使能SYSCFG时钟),这个寄存器就是将GPIO各个组的引脚和EXTI的中断号关联起来,就如我们上面所说的PA_0对应外部中断号EXTI0,PB_0也对应外部中断号EXTI0.如下图:
其他几个EXTICR类似,不用想的太复杂记住它的作用是将GPIO各个组的引脚和EXTI的中断号关联起来即可
外部中断代码分析:
好好好,终于进入了轻松愉悦的代码环节:
//外部中断配置函数
//只针对GPIOA~I;不包括PVD,RTC,USB_OTG,USB_HS,以太网唤醒等
void Ex_NVIC_Config(u8 GPIOx哪组GPIO,u8 BITxGPIO引脚,u8 TRIM触发方式)
{
u8 EXTOFFSET=(BITx%4)*4;
RCC->APB2ENR|=1<<14; //使能SYSCFG时钟
SYSCFG->EXTICR[BITx/4]&=~(0x000F<<EXTOFFSET);//清除原来设置!!!
SYSCFG->EXTICR[BITx/4]|=GPIOx<<EXTOFFSET; //EXTI.BITx映射到GPIOx.BITx
//自动设置
EXTI->IMR|=1<<BITx; //开启line BITx上的中断(如果要禁止中断,则反操作即可)
if(TRIM&0x01)EXTI->FTSR|=1<<BITx; //line BITx上事件下降沿触发
if(TRIM&0x02)EXTI->RTSR|=1<<BITx; //line BITx上事件上升降沿触发
}
很好理解,关联GPIO引脚和外部中断号,设置中断触发方式,调用方式是:
Ex_NVIC_Config(GPIO_E,4,FTIR); //PE_4,下降沿触发
Ex_NVIC_Config(GPIO_A,0,RTIR); //PA_0,上升沿触发
下面补一下外部中断的中断服务函数,因为里面有个小知识点(清楚中断标识位)
例如外部中断号EXTI4的服务函数是这样的:中断结束时要清除
//外部中断4服务程序
void EXTI4_IRQHandler(void)
{
delay_ms(10); //消抖
if(KEY0==0)
{
LED0=!LED0;
LED1=!LED1;
}
EXTI->PR=1<<4; //清除LINE4上的中断标志位
}
再例如外部中断号6的服务函数:是EXTI9_5_IRQHandler,与外部中断号5,7,8,9共用,所以用完后也要清除(假如这里是外部中断号6触发的)
//外部中断5~9的服务程序
void EXTI9_5_IRQHandler(void)
{
EXTI->PR=1<<6; //清除LINE6上的中断标志位
}
总结:
普通中断配置NVIC(分组,中断号,优先级):
MY_NVIC_Init(抢占优先级,响应优先级,中断号,分组);
外部中断配置NVIC(分组,中断号,优先级)+EXTI+SYSFG(关联,触发方式):
MY_NVIC_Init(抢占优先级,响应优先级,中断号,分组);
Ex_NVIC_Config(GPIO组,GPIO引脚,触发方式);
还有一点需要注意的是,中断的配置和IO初始化没有关系哈,例如KEY,LED,它们的IO该上拉上拉该下拉下拉,和中断的配置是分割开的。
完~