欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

stm32学习-nvic的理解与使用

程序员文章站 2022-04-02 08:24:05
...

1、nvic的资料

Nested Vectored Interrupt Controller,全称嵌套向量中断控制器,见名知意,这是arm公司提供的中断控制的部件。

在线pdf文档

stm32学习-nvic的理解与使用

 

从pdf中可以看到nvic被归类为cortex-m4内核的外设,这个外设并非st公司参与设计,是arm公司设计的,所以在stm32参考手册里面nvic的资料写的非常少。

既然是外设就应该关注的是它的寄存器。

stm32学习-nvic的理解与使用
图1 中断寄存器在内存寻址的位置

nvic寄存器在寄存器统一编制中的位置是在最后一个块内存中。

下图为nvic在内核中的位置,由图可以看出nvic是一个处理器中的硬件,ARM公司将所有中断都认为是异常,所以中断和异常都交给nvic处理。注意到nvic将io端口与外设的中断分开了。

stm32学习-nvic的理解与使用
图2 nvic在内核中的位置

2、nvic的官方介绍

ARM的官方文档介绍了哪些nvic的内容:

stm32学习-nvic的理解与使用

4.2.1 通过使用CMSIS访问cortex-m4内核的nvic寄存器

CMSIS是ARM公司与多家不同的芯片和软件供应商一起紧密合作定义的,提供了内核与外设、实时操作系统和中间设备之间的通用接口。全称为Cortex Microcontroller Software Interface Standard,conrtex-m 软件接口标准。

这里告诉你可以调用哪些函数去控制寄存器。

4.2.2 中断使能寄存器

interrupt set-enable registers,该寄存器可以开启某个中断。

4.2.3 中断清除寄存器

interrupt clear-enable register,该寄存器可以清除某个中断。

4.2.4 中断挂起寄存器

interrupt set-pending registers ,将某个正在执行的中断挂起。

4.2.5 中断清除挂起寄存器

interrupt clear-pending registers ,将某个已经挂起的中断恢复。

4.2.6 中断活跃寄存器

interrupt active bit registers,该寄存器可以保存了每个中断当前是否活跃或活跃挂起的状态,可读。

4.2.7 中断优先级寄存器

interrupt priority registers,该寄存器配置每一个中断的优先级。

4.2.8 软件触发中断寄存器

software trigger interrupt register,往该寄存器写值可以用来触发某个中断,该寄存器仅有一个,在低9位写中断号就可以触发某个中断。

stm32学习-nvic的理解与使用
图3 软件中断触发寄存器

4.2.9 电平敏感和脉冲中断

Level-sensitive and pulse interrupts 暂时不知道干嘛的

4.2.10 nvic使用指南和技巧

3、寄存器的理解

在nvic介绍的开头

stm32学习-nvic的理解与使用
图4 nvic简介

 

其中提到范围可以是1-240个中断,这里的意思是这款芯片支持最多240个外部中断,soc厂商可以自行加外设进行设计,还有16个中断被这个内核所使用,所以0-15是内部中断,16-255是外部中断。这16个内部中断的声明如下所示。DCD指令用于申请一个空间按4字节对齐,在这个cortex-m4的芯片中初始化中断向量表在内存寻址的最低位置0x0000 0000,可见16个内部中断号也并没有用完,有些也作了保留。

在这里一定要清晰中断向量表和中断寄存器地址的区别,中断向量表保存在了4GB可寻址地址的最低为,中断寄存器保存在了4GB可寻址地址的尾部(也不是最高位可见图1和图6)。

stm32学习-nvic的理解与使用
图5 startup_stm32f40_41xxx.s中声明

 所以这款cortex-m4一共可以支持256个中断号,那么就需要8个32位寄存器来对这256个中断进行相应的操作,不过在stm32f407中外部中断只使用了82个,所以只需要操作前3个寄存器即可。32*3=96。在其他stm32f4的其他系列中可能用到更多或更少的中断号。但是要知道无论SOC厂商用多少个,ARM公司的cortex-m4的芯片最多能支持240个外部中断就可以了。

stm32学习-nvic的理解与使用
图6 寄存器功能表

 

有两个有点特殊的寄存器,中断优先级寄存器和软件触发中断寄存器,下面单独来说明。

4、两个特殊的寄存器

中断优先级寄存器和软件触发中断寄存器。

中断优先级寄存器

中断优先级寄存器有60个,先来看看中断优先级寄存器是如何为每个中断(256)配置优先级的。在这个芯片中每个中断的优先级用1个字节来配置,60个32位寄存器有240个字节,所以只能配置240个中断的优先级,这240个中断是之前提到的外部中断,那么还有16个内部中断的优先级如何配置,答案就在如下图。下图中有四个配置内部中断优先级的寄存器,

stm32学习-nvic的理解与使用
图7 配置内部中断优先级的寄存器
stm32学习-nvic的理解与使用
图8 SHPR1
stm32学习-nvic的理解与使用
图9 SHPR2
stm32学习-nvic的理解与使用
图10 SHPR3

 

 配置内部中断也不是16个都能配置优先级,内部中断也没有16个,有一些保留的编号,有一些中断是不能配置优先级的,下面是内部中断的表,这里有16个,中间的保留代表了4个。

stm32学习-nvic的理解与使用
图11 内部中断

还有一个剩余的问题,1个字节的优先级具体是如何分的呢?cortex-m4并没有简单将一个字节的数据标识优先级大小,而是把一个字节又继续划分成主优先级和次优先级,初始化一个中断的优先级时,要指出主优先级占多少位,如果主优先级占3位,那么次优先级占5位,在执行中断的时候,优先级的判断顺序是:主优先级->次优先级->中断编号。那么又引出一个问题,这个相当于子网掩码的主优先级前缀存在哪里?实际上并不需要存储这个前缀,在真正比较的时候,直接比较1个优先级字节的大小就可以了,这个主优先级和次优先级的概念只是逻辑上的,体现在代码上,在设置优先级时,首先将分组前缀PRIGROUP的代号写到Application Interrupt and Reset Control Register这个寄存器的[10:8]中,然后在真正设置中断优先级寄存器的值的时候,会去读取这个寄存器的值,作一些代码上的移位就编程了最后的优先级的值,再初始化到中断优先级寄存器中。

stm32学习-nvic的理解与使用
图12 优先级在cortex-m4中如何分组
stm32学习-nvic的理解与使用
图13 Application Interrupt and Reset Control 寄存器

 

 软件触发中断寄存器

这也是一个有一点特殊的寄存器,软件触发中断寄存器是作为这样的用途:将中断号写入软件触发中断寄存器中就可以触发某个中断。中断号只有256个占8位,所以写入低8位即可,剩下的保留。

stm32学习-nvic的理解与使用
图12 软件触发中断寄存器介绍

5、库函数

与nvic操作相关的库函数在misc.h与misc.c中进行声明与定义。

nvic的初始化结构体

相关数据与结构体:

typedef struct
{
  uint8_t NVIC_IRQChannel;                            //指定中断编号
  uint8_t NVIC_IRQChannelPreemptionPriority;          //指定中断主优先级的值
  uint8_t NVIC_IRQChannelSubPriority;                 //指定中断次优先级的值
  FunctionalState NVIC_IRQChannelCmd;                 //是否使能中断ENABLE或DISABLE
} NVIC_InitTypeDef;

 

stm32学习-nvic的理解与使用
图13 中断编号示意(stm32f4xx.h)

 调用示例:

NVIC_InitTypeDef NVIC_InitStructure;                        //开辟结构体空间
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;            //中断编号
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;   //主优先级,配合前缀别越界
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;          //次优先级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;             //使能

 

指定中断主优先级前缀长度

需要说明的是,stm32f407的库函数对cortex-m4内核提供的中断主优先级前缀并没有全部采用,只采用了0b011,0b100,0b101,0b110,0b111这5种中断主优先级前缀。

 

函数原型:

void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup);

相关数据与结构体:

#define NVIC_PriorityGroup_0         ((uint32_t)0x700) //前缀长度0位
#define NVIC_PriorityGroup_1         ((uint32_t)0x600) //前缀长度1位
#define NVIC_PriorityGroup_2         ((uint32_t)0x500) //前缀长度2位
#define NVIC_PriorityGroup_3         ((uint32_t)0x400) //前缀长度3位
#define NVIC_PriorityGroup_4         ((uint32_t)0x300) //前缀长度4位

调用示例:

NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);

nvic最后的初始化

在nvic结构体和前缀长度都设置好了以后,调用NVIC_Init函数进行写入寄存器对应的中断寄存器种。NVIC_Init由库函数提供,所在文件misc.c。

函数原型:

void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct)

相关数据与结构体:

 NVIC_InitTypeDef结构体

调用示例:

NVIC_Init(&NVIC_InitStructure);

6、最后

完成了初始化后在stm32f4xx_it.h和stm32f4xx_it.c中完成对应中断处理函数的声明和编写。中断处理函数的名字要参照startup_stm32f40xx.s文件给出的中断名。如图5所示。

后续再总结一下exti,现在还不清楚exti在中断中扮演着什么样的角色。