STM32开发手册:标准库底层实现之NVIC与EXTI入门指南
NVIC
嵌套向量中断控制器,是用于管理和控制中断的组件。
NVIC在stm32开发手册中没有太多介绍,需要跳转到Cortex-M3手册
NVIC有ISER[2](使能中断)、ICER[2](禁用中断)、ISPR[2](设置中断挂起)、ICPR[2](清除中断挂起)、IABR[2](检测中断状态)、IPR[68](中断优先级配置)、STIR(软件触发中断)
在NVIC_IPR寄存器中,有IPRn和IP[n]。
IP[n]是储存优先级信息,n为中断编号(如EXTI3中断编号为9,对应IP[9])IP[n]有八位,0-3位未使用总是读0,4-7位是定义中断的优先级。高四位中,0写最高级,15写最低级。
ISER到IABR都有0、1、2三组,根据对应的中断优先级向量表,第0组对应IPR寄存器中的0-31位,第1组对应IPR寄存器中的32-63位。中断优先级就是通过配置对应的IPR寄存器相关位实现。位数越低,优先级越高。
ISER等都是32位寄存器:ISER[0]:覆盖中断0至31。ISER[1]:覆盖中断32至63。ISER[2]:覆盖中断64至67。
也就是说stm32芯片一共可以有68个中断,每个中断都有一组ISER+ICER+ISPR+ICPR+IABR控制,对应一个IP,所有中断再由IPR分配优先级。每一组都对应一个外设,ISER[9]这组对应的外设就是EXTI3.
通过查阅stm32开发手册上的中断向量表
可以看到,stm32上的外设基本上都已经被默认分配中断优先级,大部分的优先级都是可以根据用户需要自己设置的。
如要重新设置EXTI3的优先级。EXTI3的中断号为EXTI3_IRQn(也就是表中的9)
//标准库操作
NVIC_SetPriority(EXTI3_IRQn, 3);
//寄存器操作
// 计算EXTI3在IPR寄存器中的位置
uint32_t ipr_index = EXTI3_IRQn / 4; // 计算IPR寄存器索引
uint32_t ipr_offset = (EXTI3_IRQn % 4) * 8; // 计算IPR寄存器中的位偏移
// 设置EXTI3的优先级
// 假设优先级分组为0,即全部8位用于优先级
uint32_t priority = 3; // 设置的优先级
NVIC->IPR[ipr_index] = (NVIC->IPR[ipr_index] & ~(0xFF << ipr_offset)) |
系统滴答校准寄存器SysTick
SysTick是一个24位自动递减计数器,可以被编程为以不同时钟频率进行计数。当计数器减到0时,会触发一个中断,可以在中断处理函数SysTick_Handler中进行相应操作。
看起来和定时中断很像。但区别是,SysTick只有一个,TIM有多个,而且SysTick配置更为简单,几乎不占用多少硬件资源。
SysTick在stm32的中文参考上没有过多介绍,需要到Cortex-M3权威指南第八章寻找。
SysTick有四个寄存器
CTRL |
控制和状态寄存器 |
![]() |
LOAD |
自动重装载除值寄存器 |
![]() |
VAL |
当前值寄存器 |
![]() |
CALIB |
校准值寄存器 |
![]() |
常用来编写精准的Delay函数,如编写us级和ms级的的延迟函数
void Delay_us(uint32_t cnt)
{//假设系统时钟为72MHZ,也就是每秒计数72000000次,每us计数72次
SysTick->LOAD = 72 * cnt;//设置定时器重装值,cnt us
SysTick->VAL = 0x00;//清空当前计数值
SysTick->CTRL = 0x00000005;//设置时钟源为HCLK,启动定时器
while(!(SysTick->CTRL & 0x00010000));//等待计数到0
SysTick->CTRL = 0x00000004;//关闭定时器
}
void Delay_ms(uint32_t cnt)
{//假设系统时钟为72MHZ,也就是每秒计数72000000次,每ms计数72000次
SysTick->LOAD = 72000 * cnt;//设置定时器重装值,cnt ms
SysTick->VAL = 0x00;//清空当前计数值
SysTick->CTRL = 0x00000005;//设置时钟源为HCLK,启动定时器
while(!(SysTick->CTRL & 0x00010000));//等待计数到0
SysTick->CTRL = 0x00000004;//关闭定时器
}
void SysTick_Handler(void)//如无特殊操作需求,为空就可以了,因为CTRL会自动自减
{
}
外部中断/事件控制器(EXTI)
EXTI是通过能产生中断请求的边沿检测器,检测上升沿/下降沿等方式,来挂起中断标志位,进入中断。在进入中断后,需要对挂起的中断标志位置0,以表示出中断。
EXTI属于APB2总线的外设,可以通过、SysTick、TIM、DMA、I2C、USART、SPI等触发中断。
产生中断的配置过程为:配置并使能中断线——>配置触发寄存器——>在中断屏蔽寄存器相应位写“1”允许中断
EXTI寄存器
中断屏蔽寄存器(EXTI_IMR)
中断屏蔽寄存器IMR是用于选择使能中断的线路,0-19位,共20条线路。置“1”代表开启。共20条线路,意味着软件触发+上升沿|下降沿触发一共只能有20个中断。20条线就是这三类中断的线不能重合。软件触发选了第1条,那么其它两种中断就不能选第一条线。
事件屏蔽寄存器EMR是用于使能事件中断,和IMR同样20条线路,且三种中断方式的线路不能重合。
区别:IMR是通过检测上升沿下降沿,如按下按键导致引脚高低电平变化,以此触发中断。EMR是通过软件,例如某个变量的值a>=10,触发中断。事件中断所对应的“脉冲发生器”
上升沿触发选择寄存器(EXTI_RTSR)
下降沿触发选择寄存器(EXTI_FTSR)
上升沿触发器和下降沿触发器一致,用于配置中断触发模式。不同于软件中断寄存器,在20条线中,同一线路上升沿和下降沿的寄存器都可以置“1”,也就是上升沿和下降沿都触发中断。
0-15是对应GPIO的0-15号引脚,分别触发EXTI0-EXTI15。如RTSR和FTSR的0位都是GPIOA-GPIOG的Pin0,触发EXTI0。对这16个中断线的选择,就是AFIO的功能之一。
需要注意的是,在stm32c8t6中,EXTI9-5、EXTI10-15的中断函数是同一个,也就是说,EXTI8和EXTI6会触发同一个中断函数。
软件中断事件寄存器(EXTI_SWIER)
软件中断和上升沿下降沿触发中断之间是或门,因此不建议和其它中断寄存器位重复。上升下降沿一般占用0-15位,因此建议软件中断选择16-19位。
挂起寄存器(EXTI_PR)
当触发中断时(20条线路的软件触发、上升沿触发、下降沿触发),PR相应线路的位置就会置“1”,置“1”后就会进入到中断函数中,如9号线路触发EXTI9_5_IRQHandler。当标志位置1,并执行完中断函数后,需要在中断函数最后将标志位手动清零。
代码实现
配置中断的流程为
GPIO引脚选择——>AFIO中断引脚选择——>EXTI边沿检测——>NVIC优先级设置
标准库配置
例如在GPIOB_Pin10上放了个按钮,按下输入高电平。即GPIOB_Pin10上升沿触发中断
void GPIO_EXTI_Init(void)
{
//GPIO引脚选择
// 使能GPIOB时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
//AFIO中断引脚选择,配置Pin10对应中断线10
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource10);
//EXTI边沿检测触发中断
EXTI_InitTypeDef EXTI_InitStructure;
EXTI_InitStructure.EXTI_Line = EXTI_Line10;//中断线路10
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;//中断模式
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;// 上升沿触发
EXTI_InitStructure.EXTI_LineCmd = ENABLE;//使能
EXTI_Init(&EXTI_InitStructure);
//中断优先级分组
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
//配置中断优先级为3
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = EXTI10_IRQn;//配置为EXTI10线路
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; // 抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; // 子优先级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//NVIC使能
NVIC_Init(&NVIC_InitStructure);
}
//中断执行函数
void EXTI10_IRQHandler(void)
{
//执行的程序
//清除EXTI10中断标志位
EXTI_ClearITPendingBit(EXTI_Line10);
}
这里可以看看标准库中的函数是如何实现的(省略断言)
typedef struct
{
uint32_t EXTI_Line;//中断线选择
EXTIMode_TypeDef EXTI_Mode;//中断模式(软件/上下沿)
EXTITrigger_TypeDef EXTI_Trigger;//中断触发模式
FunctionalState EXTI_LineCmd;//中断线路使能
}EXTI_InitTypeDef;
void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct)
{
uint32_t tmp = 0;
tmp = (uint32_t)EXTI_BASE;//tmp设置为中断寄存器基地址
if (EXTI_InitStruct->EXTI_LineCmd != DISABLE)//如果使能中断线
{
/* 设置EXTI中断线路配置 */
EXTI->IMR &= ~EXTI_InitStruct->EXTI_Line;//使能/失能中断线路
EXTI->EMR &= ~EXTI_InitStruct->EXTI_Line;//失能/使能事件线路
、
tmp += EXTI_InitStruct->EXTI_Mode;//通过地址偏移,设置tmp为中断模式地址
*(__IO uint32_t *) tmp |= EXTI_InitStruct->EXTI_Line;
/* 配置上升沿或下降沿触发 */
EXTI->RTSR &= ~EXTI_InitStruct->EXTI_Line;//上升沿
EXTI->FTSR &= ~EXTI_InitStruct->EXTI_Line;//下降沿
/* 选择外部中断触发器 */
if (EXTI_InitStruct->EXTI_Trigger == EXTI_Trigger_Rising_Falling)
{//如果是上升沿或者下降沿触发
/* 上升沿或下降沿触发 */
EXTI->RTSR |= EXTI_InitStruct->EXTI_Line;
EXTI->FTSR |= EXTI_InitStruct->EXTI_Line;
}
else//如果是事件触发
{
tmp = (uint32_t)EXTI_BASE;
tmp += EXTI_InitStruct->EXTI_Trigger;
*(__IO uint32_t *) tmp |= EXTI_InitStruct->EXTI_Line;
}
}
else//如果失能中断线
{
tmp += EXTI_InitStruct->EXTI_Mode;
/* 清除中断线 */
*(__IO uint32_t *) tmp &= ~EXTI_InitStruct->EXTI_Line;
}
}
typedef struct
{
uint8_t NVIC_IRQChannel;//中断号
uint8_t NVIC_IRQChannelPreemptionPriority;//抢占优先级
uint8_t NVIC_IRQChannelSubPriority;//响应优先级
FunctionalState NVIC_IRQChannelCmd;//中断使能
} NVIC_InitTypeDef;
void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct)
{
uint32_t tmppriority = 0x00, tmppre = 0x00, tmpsub = 0x0F;
if (NVIC_InitStruct->NVIC_IRQChannelCmd != DISABLE)//如果要使使能中断
{
/* 计算IRQ优先级 */
tmppriority = (0x700 - ((SCB->AIRCR) & (uint32_t)0x700))>> 0x08;
tmppre = (0x4 - tmppriority);//计算抢占优先级位移
tmpsub = tmpsub >> tmppriority;//计算响应优先级位移
tmppriority = (uint32_t)NVIC_InitStruct->NVIC_IRQChannelPreemptionPriority << tmppre;
tmppriority |= NVIC_InitStruct->NVIC_IRQChannelSubPriority & tmpsub;
tmppriority = tmppriority << 0x04;
/* 将优先级位移计算结构传给IPR */
NVIC->IP[NVIC_InitStruct->NVIC_IRQChannel] = tmppriority;
/* 使能中断 */
NVIC->ISER[NVIC_InitStruct->NVIC_IRQChannel >> 0x05] =
(uint32_t)0x01 << (NVIC_InitStruct->NVIC_IRQChannel & (uint8_t)0x1F);
}
else
{
/* 失能中断 */
NVIC->ICER[NVIC_InitStruct->NVIC_IRQChannel >> 0x05] =
(uint32_t)0x01 << (NVIC_InitStruct->NVIC_IRQChannel & (uint8_t)0x1F);
}
}
寄存器配置
寄存器配置就简洁了许多
void GPIO_EXTI_Init(void)
{
//GPIO引脚选择
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIOB->CRH |= ((uint32_t)0x0000001A << 4*(10-8))//0000 0000 0000 0000 0000 0110 0000 0000
//EXTI配置
EXTI->IMR |= (1<<10);//使能通道10,即EXTI10
EXTI->RTSR |=(1<<10);//通道10配置为上升沿触发
EXTI->EMR &= ~(1<<10);//关闭EMR事件使能,防止冲突
//NVIC配置
NVIC->ISER[0] |= (1<<10);//使能EXTI中断
NVIC->IPR[4] = (NVIC->IPR[4] & ~(0xFF<<8)) | (3<<8);//配置EXTI10的优先级为3
}
void EXTI10_IRQHandler(void)
{
//EXTI10要执行的程序
//清除EXTI10中断挂起标志位
EXTI->PR |= (1<<10);
}
作者:水拨千斤