STM32F1开发指南笔记5----STM32 NVIC中断优先级管理 & 寄存器地址名称映射分析
NVIC中断优先级管理
CM3内核支持256个中断,其中包含了16个内核中断和240个外部中断,并且具有256级的可编程中断设置。但STM32并没有使用CM3内核的全部东西,而是只用了它的一部分。STM32有84个中断,包括16个内核中断和68个可屏蔽中断,具有16级可编程的中断优先级。而我们常用的就是这68个可屏蔽中断,但是STM32的68个可屏蔽中断,在STM32F103系列上面,又只有60个(在107系列才有68个)。因为我们开发板选择的芯片是STM32F103系列的,所以我们就只针对STM32F103系列这60个可屏蔽中断进行介绍。
STM32 的中断在这些寄存器的控制下有序的执行的。只有了解这些中断寄存器,才能方便的使用 STM32 的中断。下面重点介绍这几个寄存器:
ISER[8]:ISER 全称是:Interrupt Set-Enable Registers,这是一个中断使能寄存器组。上面说了 CM3 内核支持 256 个中断,这里用 8 个 32 位寄存器来控制,每个位控制一个中断。但是STM32F103 的可屏蔽中断只有 60 个,所以对我们来说,有用的就是两个(ISER[0]和 ISER[1]),总共可以表示 64 个中断。而 STM32F103 只用了其中的前 60 位。ISER[0]的 bit0-bit31 分别对应中断应中断 0-31。ISER[1]的 bit0-27 对应中断32-59;这样总共 60 个中断就分别对应上了。你要使能某个中断,必须设置相应的ISER 位为 1,使该中断被使能(这里仅仅是使能,还要配合中断分组、屏蔽、IO 口映射等设置才算是一个完整的中断设置)。具体每一位对应哪个中断,请参考 stm32f10x.h 里面的第 140 行处(针对编译器 MDK5 来说)。
ICER[8]:全称是:Interrupt Clear-Enable Registers,是一个中断除能寄存器组。该寄存器组与 ISER 的作用恰好相反,是用来清除某个中断的使能的。其对应位的功能,也和 ISER 一样。这里要专门设置一个 ICER 来清除中断位,而不是向 ISER 写 0 来清除,是因为 NVIC 的这些寄存器都是写 1 有效的,写 0 是无效的。具体为什么这么设计,请看《CM3 权威指南》第 125 页,NVIC 概览一章。
ISPR[8]:全称是:Interrupt Set-Pending Registers,是一个中断挂起控制寄存器组。每个位对应的中断和 ISER 是一样的。通过置 1,可以将正在进行的中断挂起,而执行同级或更高级别的中断。写 0 是无效的。
ICPR[8]:全称是:Interrupt Clear-Pending Registers,是一个中断解挂控制寄存器组。其作用与 ISPR 相反,对应位也和 ISER 是一样的。通过设置 1,可以将挂起的中断解挂。写 0 无效。
IABR[8]:全称是:Interrupt Active Bit Registers,是一个中断**标志位寄存器组。对应位所代表的中断和 ISER 一样,如果为 1,则表示该位所对应的中断正在被执行。这是一个只读寄存器,通过它可以知道当前在执行的中断是哪一个。在中断执行完了由硬件自动清零。
IP[240]:全称是:Interrupt Priority Registers,是一个中断优先级控制的寄存器组。这个寄存器组相当重要!STM32 的中断分组与这个寄存器组密切相关。IP 寄存器组由 240 个 8bit 的寄存器组成,每个可屏蔽中断占用 8bit,这样总共可以表示 240 个可屏蔽中断。而 STM32 只用到了其中的前 60 个。IP[59]-IP[0]分别对应中断 59-0。而每个可屏蔽中断占用的 8bit 并没有全部使用,而是 只用了高 4 位。这 4 位,又分为抢占优先级和子优先级。抢占优先级在前,子优先级在后。而这两个优先级各占几个位又要根据 SCB->AIRCR 中的中断分组设置来决定。
这里需要注意两点:第一,如果两个中断的抢占优先级和响应优先级都是一样的话,则看哪个中断先发生就先执行;第二,高优先级的抢占优先级是可以打断正在进行的低抢占优先级中断的。而抢占优先级相同的中断,高优先级的响应优先级不可以打断低响应优先级的中断。
结合实例说明一下:假定设置中断优先级组为 2,然后设置中断 3(RTC 中断)的抢占优先级为 2,响应优先级为 1。中断 6(外部中断 0)的抢占优先级为 3,响应优先级为 0。中断 7(外部中断 1)的抢占优先级为 2,响应优先级为 0。那么这 3 个中断的优先级顺序为:中断 7>中断 3>中断 6。
上面例子中的中断 3 和中断 7 都可以打断中断 6 的中断。而中断 7 和中断 3 却不可以相互打断!
通过以上介绍,我们熟悉了 STM32 中断设置的大致过程。接下来我们介绍如何使用库函
数实现以上中断分组设置以及中断优先级管理,使得我们以后的中断设置简单化。NVIC 中断管理函数主要在 misc.c 文件里面。
这里我们讲解了中断的分组的概念以及设定优先级值的方法,至于每种优先级还有一些关于清除中断,查看中断状态,这在后面我们讲解每个中断的时候会详细讲解到。最后我们总结一下中断优先级设置的步骤:
1、系统运行开始的时候设置中断分组。确定组号,也就是确定抢占优先级和子优先级的分配位数。调用函数为NVIC_PriorityGroupConfig();
2、设置所用到的中断的中断优先级别。对每个中断调用函数为NVIC_Init();
MDK中寄存器地址名称映射分析
之所以要讲解这部分知识,是因为经常会遇到客户提到不明白MDK中那些结构体是怎么与寄存器地址对应起来的。这里我们就做一下简要的分析吧。
首先我们看看51中是怎么做的。51单片机开发中经常会引用一个reg51.h的头文件,下面我们看看他是怎么把名字和寄存器联系起来的:
sfr P0 = 0x80;
sfr也是一种扩充数据类型,点用一个内存单元,值域为0-255.利用它可以访问51单片机内部的所有特殊功能寄存器。如果sfr P1 = 0x90;这一句定义P1为P1端口在片内的寄存器。然后我们往地址为0x80的寄存器设置的方法是:P0=value;
那么在STM32中,是否也可以这样做呢?答案是肯定的。肯定也可以通过同样的方法来做,但是STM32因为寄存器太多太多,如果一一以这样的方式列出来,那要好大的篇幅,既不方便开发,也显得太杂乱无序的感觉。所以MDK采用的方式是通过结构体来将寄存器组织在一起。下面我们就讲解MDK是怎么把结构体和地址对应起来的,为什么我们修改结构体成员变量的值就可以达到操作对应寄存器的值。这些事情都是在stm32f10x.h文件中完成的。我们通过GPIOA的几个寄存器的地址来讲解吧。
首先我们可以查看《STM32中文参考手册V10》中的寄存器地址映射表(P159):
从这个表我们可以看出,GPIOA的7个寄存器都是32位的,所以每个寄存器占有4个地址,一共占用28个地址,地址偏移范围为(000h-01Bh)。这个地址偏移是相对GPIOA的基地址而言的。GPIOA的基地址是怎么算出来的呢?因为GPIO都是挂载在APB2总线之上,所以它的基地址是由APB2总线的基地址+GPIOA在APB2总线上的偏移地址决定的。同理依次类推,我们便可以算出GPIOA基地址了。这里涉及到总线的一些知识,我们在后面会讲到。下面我们打开stm32f10x.h定位到GPIO_TypeDef定义处:
上面我们已经知道GPIOA的基地址,那么那些GPIOA的7个寄存器的地址又是怎么算出来的呢?在上面我们讲过GPIOA的各个寄存器对于GPIOA基地址的偏移地址,所以我们自然可以算出来每个寄存器的地址。
GPIOA的寄存器的地址=GPIOA基地址+寄存器相对GPIOA基地址的偏移值。这个偏移值在上面的寄存器地址映像表中可以查到。
那么结构体里面这些寄存器又是怎么与地址一一对应的呢?这里就涉及到结构体的一个特征,那就是结构体存储的成员他们的地址是连续的。上面讲到GPIOA是指向GPIO_Type类型的指针,又由于GPIO_TypeDef是结构体,所以自然而然我们就可以算出GPIOA指向的结构体成员变量对应地址了。
我们可以把GPIO_TypeDef的定义中的成员变量的顺序和GPIOx寄存器地址映像对比可以发现,他们的顺序是一致的,如果不一致,就会导致地址的混乱。
这就是为什么固件库里面:GPIO->BRR=value;就是设置地址为0x40010800+0x014(BRR偏移量)=0x40010814的寄存器BRR的值了。它和51里面P0=value是设置地址为0x80的P0寄存器的值是一样的道理。
看到这里你是否学起来踏实一点呢?STM32使用的方式虽然和51单片机不一样,但是原理都是一致的。