中断是单片机非常重要的功能,也是一个难点,本节单独讲下NVIC,以及NVIC的配置。
一.关于NVIC
NVIC: Nested Vectored Interrupt Controller 内嵌向量中断控制器 是M3内核的一个外设
是用来总控中断的,例如中断优先级设置,中断使能等
下面看下《STM32F10xxx Cortex-M3编程手册-英文版》中关于NVIC的描述:
这部分描述了内嵌向量中断控制器以及寄存器的使用,NVIC支持:
- 最多81个中断(具体数量取决于STM32的型号,请参考数据手册)
- 每个中断都支持0-15个可编程的优先级。等级越高(标号越大),优先级越低,所以0是优先级最高的。
- 中断支持电平检测以及边沿检测
- Dynamic reprioritization of interrupts 中断的动态重新分配优先级(暂且这么翻译)
- 优先级支持编组,并且分为主优先级和抢占优先级
- 咬尾中断
- 1个外部不可屏蔽中断NMI Non-Mask Interrurt
说了这么多,其实有用的就只有3点:
中断优先级支持0-15优先级,编号越小,优先级越高
中断优先级又分为两组,分别是主优先级和抢占优先级
中断支持电平触发和边沿触发(应该是说外部中断EXIT)
上面2和1是不是有点矛盾?不急我们先往下说:
看下NVIC的相关寄存器,这部分比较无聊,且用固件库编程,无需知道这些,可以略过:
NVIC的寄存器按照功能分为6大类(常用标红):
- Set-enable 使能
- Clear-enable 失能
- Set-pending 设置挂起
- Clear-pending 清除挂起
- Active Bits
- Interrupt Priority Registers 中断优先级寄存器
那么对应的,用以下简写,以及数组来匹配相应的寄存器;
在手册里是这么描述的,每个数组将对应其寄存器,例如使能,ISER[0]对应ISER0
完整描述看下面:
功能 寄存器全名数组寄存器Set-enable 使能Interrupt Set Enable RigisterISER[0],ISER[1],ISER[2]ISER0,ISER1,ISER2Clear-enable 失能Interrupt Clear Enable RigisterICER[0],ICER[1],ICER[2]ICER0,ICER1,ICER2Set-pending 设置挂起
Interrupt Set Pending
Rigister
ISPR[0],ISPR[1],ISPR[2]ISPR0,ISPR1,ISPR2Clear-pending 清除挂起Interrupt Clear Pending RigisterICPR[0],ICPR[1],ICPR[2]ICPR0,ICPR1,ICPR2Active BitsInterrupt Active Bits RigisterIABR[0],IABR[1],IABR[2]IABR0,IABR1,IABR2
例如常用的Set-enable 使能ISER,Clear-enable 失能ICER解释如下:
根据core_cm3.h的定义,每个ISER数组都是32位的,每一位控制1个中断的状态,并且是可读可写的。
1是使能,0是无效。复位后全部都是0,即复位后,中断默认全关,要省电嘛~~~
ISCR同理
中断优先级寄存器Interrupt Priority Registers,简称IP,M3内核的MCU有8位,理论上可以支持2的8次方,0~255,共256级,但是根本用不完,所以厂家阉割了,STM32只用了8位中的高4位用来定义优先级,就是2的4次方即16,所以中断优先级最多支持0-15级。不是16个中断的意思,多个中断可以排在同一优先级上。
但是Air32F103只用了3位定义优先级!!!所以优先级支持0~7,共8级
第二节关于中断源列出了所有中断表,Air32F103一共67个可编程中断,所以到IP[80]足够用了。
/**
\ingroup CMSIS_core_register
\defgroup CMSIS_NVIC Nested Vectored Interrupt Controller (NVIC)
\brief Type definitions for the NVIC Registers
@{
*/
/**
\brief Structure type to access the Nested Vectored Interrupt Controller (NVIC).
*/
typedef struct
{
__IOM uint32_t ISER[8U]; /*!< Offset: 0x000 (R/W) Interrupt Set Enable Register */
uint32_t RESERVED0[24U];
__IOM uint32_t ICER[8U]; /*!< Offset: 0x080 (R/W) Interrupt Clear Enable Register */
uint32_t RESERVED1[24U];
__IOM uint32_t ISPR[8U]; /*!< Offset: 0x100 (R/W) Interrupt Set Pending Register */
uint32_t RESERVED2[24U];
__IOM uint32_t ICPR[8U]; /*!< Offset: 0x180 (R/W) Interrupt Clear Pending Register */
uint32_t RESERVED3[24U];
__IOM uint32_t IABR[8U]; /*!< Offset: 0x200 (R/W) Interrupt Active bit Register */
uint32_t RESERVED4[56U];
__IOM uint8_t IP[240U]; /*!< Offset: 0x300 (R/W) Interrupt Priority Register (8Bit wide) */
uint32_t RESERVED5[644U];
__OM uint32_t STIR; /*!< Offset: 0xE00 ( /W) Software Trigger Interrupt Register */
} NVIC_Type;
core.cm3.h定义了非常多的中断,IP一共240个数组,能定义240个中断的优先级,牛
关于优先级分组:
Air32F103和STM32103有很大不同,并且固件库里关于分组是错误的!
STM32支持16级中断优先级(4Bits用于设置分组),AIR32F103只支持8级(3Bits用于设置分组),那么固件库里关于分组的信息就是错误的
照抄STM32改个头文件名字就全拿来用了。。。
/** @defgroup MISC_Private_Functions
* @{
*/
/**
* @brief Configures the priority grouping: pre-emption priority and subpriority.
* @param NVIC_PriorityGroup: specifies the priority grouping bits length.
* This parameter can be one of the following values:
* @arg NVIC_PriorityGroup_0: 0 bits for pre-emption priority
* 4 bits for subpriority
* @arg NVIC_PriorityGroup_1: 1 bits for pre-emption priority
* 3 bits for subpriority
* @arg NVIC_PriorityGroup_2: 2 bits for pre-emption priority
* 2 bits for subpriority
* @arg NVIC_PriorityGroup_3: 3 bits for pre-emption priority
* 1 bits for subpriority
* @arg NVIC_PriorityGroup_4: 4 bits for pre-emption priority
* 0 bits for subpriority
* @retval None
*/
void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup)
{
/* Check the parameters */
assert_param(IS_NVIC_PRIORITY_GROUP(NVIC_PriorityGroup));
/* Set the PRIGROUP[10:8] bits according to NVIC_PriorityGroup value */
SCB->AIRCR = AIRCR_VECTKEY_MASK | NVIC_PriorityGroup;
}
吐槽完,咱们看下优先级该怎么办。。。
首先了解下中断分组,分组分为抢占优先级和子优先级
抢占优先级:中断之间互相打断用的,优先级高的打断优先级低的去执行。如果优先级相同呢,看下面;
子优先级:抢占优先级相同时比较,子优先级,同样遵循标号越低优先级越高原则。
如果抢占优先级和子优先级全部相同时,比较中断标号,标号越低,优先级越高。
CM3权威指南-宋岩
原则上,CM3支持3个固定的高优先级和多达256级的可编程优先级,并且支持128级抢占(128的来历请见下文分解——译注)。但是,绝大多数CM3芯片都会精简设计,以致实际上支持的优先级数会更少,如8级,16级,32级等。它们在设计时会裁掉表达优先级的几个低端有效位,以减少优先级的级数(可见,不管使用多少位来表达优先级,都是以MSB对齐的——译者注)。
这是本好书啊,值得读10遍。书中举例用3位表示优先级正好对应Air32F103的情况。
也就是说,Air32F103用了高3位来表示优先级,优先级从高倒地依次为0x00,0x20,0x40,0x80,0xA0,0xC0,0xE0
0x1110 00000xE00x1100 00000xC00x1010 00000xA00x1000 00000x800x0110 00000x600x0101 00000x400x0010 00000x200x0000 00000x00
分组咋分呢。。。
分组是在SCB->AIRCR寄存器里的
这里有强调:AIRCR是用来提供分组控制的,如果想要写寄存器,必须在VECTKEY这部分中写入0X05FA,否则芯片将忽略写操作。
VECTKEY是【31:16】位,高16位的值固定了,05FA
【15】:写0
【14:11】:写0
【10:8】:用来进行分组,这里PRIGROUP的值很有意思,乍一看不好理解,但是如果我们把这里的所有值都列出来,对比下就清晰了。
分组位置
PRIGROUP
[10:8]
Binary Points
表达抢占优先级的位段
Group priority bits
表达子优先级的位段
Subpriority bits
0b0000bxxxxxxx.y[7:1][0:0]0b0010bxxxxxx.yy[7:2][1:0]0b0020bxxxxx.yyy[7:3][2:0]0b0030bxxxx.yyyy[7:4][3:0]0b0040bxxx.yyyyy[7:5][4:0]0b0050bxx.yyyyyy[7:6][5:0]0b0060bx.yyyyyyy[7:7][6:0]0b0070byyyyyyyy无[7:0](所有位)
其实这里的值代表了,子优先级的位数。
例如当【10:8】全部写0,即0b000,表示子优先级是【0:0】位,即只有0位用来表示子优先级,那么抢占优先级就是【7:0】位了。即分组值0b000,=0时,抢占优先级共7位,子优先级共1位。
但是Air32F103一共才3位用来表示优先级,所以【10:8】位,写0,1,2,3,4结果都是一样的,即抢占优先级用全部高3位表示,子优先级1位也没,所以抢占优先级共二的三次方共8级,子优先级0.
Bit 7Bit 6Bit 5Bit 4Bit 3Bit 2Bit 1Bit 0抢占优先级
那我们根据Air32F103的实际情况再重新写一下:
分组位置
PRIGROUP
[10:8]
Binary Points
表达抢占优先级的位段
Group priority bits
抢占优先级
表达子优先级的位段
Subpriority bits
子优先级0b0040bxxx[7:5]8无00b0050bxx.y[7:6]4[5:5]20b0060bx.yy[7:7]2[6:5]40b0070byyy无0[7:5](所有位)8
至此【10:8】的值,我们就能确定了
这里以IP共4位来进行分组示例(因为是STM32的手册)作为对比参考。
【7:3】:保留了,需要清除。即写0。
【1】:VECTCLRACTIVE,写时必须置0.
【0】:VECTRESET,写时必须置0.
至此我们能确定AIRCR寄存器的值了,只有【10:8】这三位的值是变化的,其余都是固定的。
我们再看下固件库:misc.h,这个头文件定义了几个分组的值,这些是错误的。
分组0,值是7,0位抢占优先级,4位子优先级
分组1,值是6,1位抢占优先级,3位子优先级
分组2,值是5,2位抢占优先级,2位子优先级
分组3,值是4,3位抢占优先级,1位子优先级
分组4,值是3,4位抢占优先级,0位子优先级
#define NVIC_PriorityGroup_0 ((uint32_t)0x700) /*!< 0 bits for pre-emption priority
4 bits for subpriority */
#define NVIC_PriorityGroup_1 ((uint32_t)0x600) /*!< 1 bits for pre-emption priority
3 bits for subpriority */
#define NVIC_PriorityGroup_2 ((uint32_t)0x500) /*!< 2 bits for pre-emption priority
2 bits for subpriority */
#define NVIC_PriorityGroup_3 ((uint32_t)0x400) /*!< 3 bits for pre-emption priority
1 bits for subpriority */
#define NVIC_PriorityGroup_4 ((uint32_t)0x300) /*!< 4 bits for pre-emption priority
0 bits for subpriority */
我们根据上面的表格,写个对的:
#define NVIC_PriorityGroup_0 ((uint32_t)0x700) /*!< 0 bits for pre-emption priority
3 bits for subpriority */
#define NVIC_PriorityGroup_1 ((uint32_t)0x600) /*!< 1 bits for pre-emption priority
2 bits for subpriority */
#define NVIC_PriorityGroup_2 ((uint32_t)0x500) /*!< 2 bits for pre-emption priority
1 bits for subpriority */
#define NVIC_PriorityGroup_3 ((uint32_t)0x400) /*!< 3 bits for pre-emption priority
0 bits for subpriority */
也就是说,我们可以使用分组0,1,2,3,分别对应
分组0,值是7,0位抢占优先级,3位子优先级
分组1,值是6,1位抢占优先级,2位子优先级
分组2,值是5,2位抢占优先级,1位子优先级
分组3,值是4,3位抢占优先级,0位子优先级
关于分组设定的库函数,我们看下原型:
1.先用断言检查了下中断分组库函数的形参是否在范围内,超范围会警告,严谨。
2.SCB->AIRCR = AIRCR_VECTKEY_MASK | NVIC_PriorityGroup;
这句是在配置SCB-AIRCR的值,这个将决定中断分组。
我们查下AIR_VECKEY_MASK的值
这个掩码的值太熟悉了,0x05FA是手册中要求我们写AIRCR寄存器时,必须要写在高16位的值,并且除了【10:8】位,其余位都为0。
所以NVIC_PriorityGroup的值或上AIRCR_VECTKEY_MASK的值,就是我们将要配置的结果。
换句话说,NVIC_PriorityGroup定义了【10:8】的值,其余无关位用掩码的形式和他或在一起即可,简单可靠。
中断分组设置只需要设置1次即可,一般放在main函数里:
例如,我们想要设置中断优先级分组为0组,即0位抢占优先级,3位子优先级。
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);
二.关于中断源
Air32F103 有8个可编程的优先等级(使用了3位中断优先级)
中断源及中断优先级如下:红字为内核中断,其余为芯片厂商的外设中断(自己设计的,即使都用M3内核,但每个厂商的中断可能不同,具体要看用户手册)
位置优先级优先级类型名称说明地址--保留 0x0000_0000-3固定Reset复位 0x0000_0004-2固定 NMI不可屏蔽中断 RCC时钟安全系统(CSS)连接到NMI 向量0x0000_0008-1固定硬件失效 (HardFault)所有类型的失效0x0000_000C0可设置存储管理 (MemManage)存储器管理0x0000_00101可设置总线错误 (BusFault)预取指失败,存储器访问失败0x0000_00142可设置错误应用 (UsageFault)未定义的指令或非法状态0x0000_0018---保留
0x0000_0018
~0x0000_002B
3可设置SVCall通过SWI指令的系统服务调用0x0000_002C4可设置调试监控 (DebugMonitor)调试监控器0x0000_0030---保留0x0000_00345可设置PendSV可挂起的系统服务0x0000_00386可设置SysTick系统滴答定时器0x0000_003C07可设置WWDG窗口看门狗中断0x0000_004018可设置PVD连接到EXTI的电源电压检测(PVD) 中断0x0000_0044忽略忽略忽略忽略忽略5966可设置DMA2通道4_5DMA2通道4和DMA2通道5全局中断0x0000_012C
我数了一下,一共有70个中断源,除了前三个是固定的优先级,剩下67个都是可自己设置使用的。
结合上一节:
Air32F103学习笔记-4.看看SDK中startup_air32f10x.s, .sct, .map-CSDN博客
我看下启动文件关于中断向量表的地址:
; Vector Table Mapped to Address 0 at Reset
AREA RESET, DATA, READONLY //开辟了1个区域,名字叫REST,数据类型,只读
EXPORT __Vectors
EXPORT __Vectors_End
EXPORT __Vectors_Size
__Vectors DCD __initial_sp ; Top of Stack
DCD Reset_Handler ; Reset Handler
DCD NMI_Handler ; NMI Handler
DCD HardFault_Handler ; Hard Fault Handler
DCD MemManage_Handler ; MPU Fault Handler
DCD BusFault_Handler ; Bus Fault Handler
DCD UsageFault_Handler ; Usage Fault Handler
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD SVC_Handler ; SVCall Handler
DCD DebugMon_Handler ; Debug Monitor Handler
DCD 0 ; Reserved
DCD PendSV_Handler ; PendSV Handler
DCD SysTick_Handler ; SysTick Handler
发现没,启动文件中关于中断的,关于中断向量的顺序和手册里的一模一样,因为启动文件是根据手册写的。
AREA 定义一块代码段,段名字是RESET,READONLY 表示只读。
还记得上一节的分散加载吗? RESET段放在最前面,从哪里开始?从外部存储器,即FLASH的起始地址开始放中断向量表。起始地址是0x8000 0000
#1.sct
LR_IROM1 0x08000000 0x00040000 { ; load region size_region
ER_IROM1 0x08000000 0x00040000 { ; load address = execution address
*.o (RESET, +First) //RESET段放在最前面
*(InRoot$$Sections)
.ANY (+RO)
}
RW_IRAM1 0x20000000 0x00018000 { ; RW data
.ANY (+RW +ZI)
}
}
例如:
启动文件,第一行: DCD __initial_sp ; Top of Stack 栈顶地址
手册第二行,保留,地址是0x8000 0000,会被映射到0x0000 0000,作为启动后的第一条命令,即MSP的地址。上一节我们知道了,MSP指向了0x2000 0400,即栈顶地址;
启动文件,第二行: DCD Reset_Handler ; Reset Handler 复位中断
手册第二行,复位,地址是0x8000 0004,会被映射到0x0000 0004,作为启动后读取的第二条命令,指向了Reset_Handler,即0x8000 023D
以此类推,剩余的中断向量表的名字,依次+4字节往下顺序移动
版权归原作者 Suliang2013 所有, 如有侵权,请联系我们删除。