STM32 中断原理及外部中断的实现

  • NVIC 中断优先级管理
    • 中断寄存器
    • 库函数配置
    • 小结
  • STM32 外部中断
    • 基础知识
    • 库函数配置
    • 外部中断配置示例
    • 小结

内容较充实,作为个人的学习记录

NVIC 中断优先级管理

Nested Vectored Interrupt Controller,嵌套向量中断控制器。

CM3支持256个中断,16个内核中断,240个外部中断,256级可编程中断设置。

STM32使用了其中一部分,16个内核中断,107系列有68个可屏蔽中断(103系列只有60个),16级可编程的中断优先级。




中断寄存器

  • ISER[8],Interrupt Set-Enable Registers,中断使能寄存器组,用8个32位寄存器控制(256个可编程中断),每个位控制一个中断。由于STM32f103只有60个可屏蔽中断,于是只用了 ISER[0] 和 ISER[1] 64个中断中的前60位。
  • ISER[0] 的 bit0 至 bit31 对应中断0至31,ISER[1] 的 bit0 至 bit27 对应中断32至59,使能某个中断就将对应的 ISER 置1
  • ICER[8],Interrupt Clear-Enable Register,中断除能寄存器组。中断对应与ISER相同,但作用是置1清除中断使能
  • ISPR[8],Interrupt Set-Pending Register,中断挂起控制寄存器组。置1将正在进行的中断挂起,暂停等待执行,执行同级或更高级别的中断
  • ICPR[8],Interrupt Clear-Pending Register,中断解挂控制寄存器组,置1将挂起的中断解挂
  • IABR[8],Interrupt Active Bit Register,中断激活标志位寄存器组,只读寄存器,为1表示该位对应的中断正在被执行
  • IP[240],Interrupt Priority Registers,中断优先级控制寄存器组,相当重要! STM32的中断分组与其密切相关。
  • IP[240]有240个8bit寄存器组成,每个可屏蔽中断占用8bit,总共可表示240个可屏蔽中断,STM32f1使用其中60个,IP[59]至IP[0]分别对应中断59至0。每个可屏蔽中断只用高4位,分为抢占优先级和响应优先级,抢占优先级在前、响应优先级在后,各占几位由SCB->AIRCR中的中断分组设置。采用库函数==NVIC_PriorityGroupConfig () ==配置。

如果组设置为3,则每个中断的中断优先寄存器高4位中前3位是抢占优先级,优先级级别0-7;低1位是响应优先级,优先级级别0-1。

STM32的中断向量有抢占属性和响应属性,属性编号越小,优先级别越高。

抢占属性是打断其他中断的属性,有这个属性就有嵌套中断。由库函数NVIC_IRQChannelPreemptionPriority() 进行参数配置。

响应属性应用在抢占属性相同时(前提),两个中断向量的抢占优先级相同时,如果同时到达,则优先处理响应优先级高的中断。由库函数NVIC_IRQChannelSubPriority() 进行参数配置。


库函数配置

NVIC 中断管理函数声明在 misc.c 文件里

  1. 中断优先级分组函数 NVIC_PriorityGroupConfig()

此函数在系统工程中只能被调用一次!一旦确定好中断分组就不能更改!

目前了解到的在主函数中调用该函数

 F12 查看函数定义块:

void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup)
{
 	assert_param(IS_NVIC_PRIORITY_GROUP(NVIC_PriorityGroup));
	SCB->AIRCR = AIRCR_VECTKEY_MASK | NVIC_PriorityGroup;
}

可以看到,这个函数通过设置 SCB->AIRCR 寄存器设置中断分组。

补充:

assert_param(),断言机制函数,在stm32f10x_conf.h文件中可以看到 assert_param() 是一个宏定义,作用就是检测传递给函数的参数是否是有效的参数,返回值是 0假 1真。

中断优先组配置示例:分组值为2,2优先2抢占:

NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

暂时看到的此函数调用都是在主函数里,以后看到其他程序结构再补充:

2. 中断初始化函数 NVIC_Init

函数定义:

void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct)

查看结构体 NVIC_InitTypeDef 的成员变量:

typedef struct
{
 	uint8_t NVIC_IRQChannel; 
 	uint8_t NVIC_IRQChannelPreemptionPriority;
 	uint8_t NVIC_IRQChannelSubPriority; 
 	FunctionalState NVIC_IRQChannelCmd; 
} NVIC_InitTypeDef;
  • NVIC_IRQChannel,定义初始化的是哪个中断,例如 USART1_IRQn
  • NVIC_IRQChannelPreemptionPriority,定义这个中断的抢占优先级别,级别数不超过2的“抢占优先级位数”次方。(一个是位数,一个是级别数)
  • NVIC_IRQChannelSubPriority,定义这个中断的响应优先级别,级别数不超过2的“响应优先级位数”次方。
  • NVIC_IRQChannelCmd,该中断是否使能

IRQ全称为Interrupt Request,即是“中断请求”的意思

中断初始化配置示例:

NVIC_InitTypeDef NVIC_InitStructure;

NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
//串口 1 中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1 ;
// 抢占优先级为 1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
// 响应优先级为 2
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; 
//IRQ 通道使能
NVIC_Init(&NVIC_InitStructure); 
//根据上面指定的参数初始化 NVIC 寄存器

通常如果写外部中断时,此代码块放在 \exti.c\EXTI_Init() 函数内部,每个端口配置结束都需要一个NVIC_Init(&NVIC_InitStructure);




小结

中断优先级设置的步骤:

  1. 系统运行开始的时候设置中断分组。确定组号,也就是确定抢占优先级和子优先级的分配位数。调用函数为 NVIC_PriorityGroupConfig();
  2. 设置所用到的中断的中断优先级别,对每个中断调用函数为 NVIC_Init();





STM32 外部中断

基础知识

STM32 外部 IO 口的中断功能

文件: stm32f10x_exti.c/h

每个 IO 口都可以作为外部中断的中断输入口,支持19个外部中断/事件请求,每个中断设有状态位、独立的触发/屏蔽设置。

19个外部中断为:

  • 线 0-15:对应外部 IO 口的输入中断 (供 IO 口使用的中断线)
  • 线 16 :连接到PVD输出(Programmable Votage Detector,可编程电压监测器)
  • 线 17 :连接到RTC闹钟事件
  • 线 18 :连接到USB唤醒事件

映射方式:


每个中断线对应最多7个IO口,但每次只能连接到一个IO口上,利用选择器的配置来决定中断线配置到哪个IO口上去。



库函数配置

1. 映射关系配置 GPIO_EXTILineConfig()

外部中断端口映射配置示例 (GPIOE_2 与 EXTI_2):

GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_PinSource2);

2. 中断初始化 EXTI_Init()

void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct);
//结构体函数参数

查看一下结构体:

typedef struct
{
 	uint32_t EXTI_Line; 
 	EXTIMode_TypeDef EXTI_Mode; 
 	EXTITrigger_TypeDef EXTI_Trigger; 
 	FunctionalState EXTI_LineCmd; 
}EXTI_InitTypeDef;

有4个需要设置的参数:

  1. 中断线标号,EXTI_Line0~EXTI_Line15
  2. 中断模式,中断 EXTI_Mode_Interrupt 和事件 EXTI_Mode_Event
  3. 触发方式,下降沿触发 EXTI_Trigger_Falling,上升沿触发 EXTI_Trigger_Rising,任意电平(上升沿和下降沿)触发EXTI_Trigger_Rising_Falling
  4. 使能,ENABLE

中断事件的区别


简而言之,中断是需要经过芯片内部程序处理,然后跳转的中断程序
事件是检测到某一动作(电平边沿)触发事件发生了,进而由提前设置好的相应的联动硬件自动完成产生的结果


中断是红线

事件是绿线

外部中断初始化配置示例:

EXTI_InitTypeDef EXTI_InitStructure;
//定义结构体
EXTI_InitStructure.EXTI_Line=EXTI_Line4;
//中断线4
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
//中断模式
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
//下降沿触发
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
//使能
EXTI_Init(&EXTI_InitStructure); 
//根据 EXTI_InitStruct 中指定的参数初始化外设 EXTI 寄存器

3. 设置中断线2的中断优先级

优先级配置见第一部分NVIC的配置

NVIC_InitTypeDef NVIC_InitStructure;
//定义NVIC结构体
NVIC_InitStructure.NVIC_IRQChannel = EXTI2_IRQn; 
//使能按键外部中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02; 
//抢占优先级 2,
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x02; 
//响应优先级 2
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; 
//使能外部中断通道
NVIC_Init(&NVIC_InitStructure); 
//中断优先级分组初始化

4. 中断服务函数

STM32一共15个IO口外部中断线,共有6个中断函数:

EXPORT EXTI0_IRQHandler
EXPORT EXTI1_IRQHandler
EXPORT EXTI2_IRQHandler
EXPORT EXTI3_IRQHandler
EXPORT EXTI4_IRQHandler
EXPORT EXTI9_5_IRQHandler
EXPORT EXTI15_10_IRQHandler

0-4每个中断线对应一个中断函数,5-9共用EXTI9_5_IRQHandler,10-15共用EXTI15_10_IRQHandler

当多线共用一个处理函数的时候,就必须写EXTI_GetITStatus()判断到底是哪一线中断请求

  • 1. ITStatus EXTI_GetITStatus();

用在中断服务函数的开头,判断中断是否发生

EXTI_GetFlagStatus()与之的区别
EXTI_GetITStatus() 函数中会先判断这种中断是否使能,使能了才去判断中断标志位
而EXTI_GetFlagStatus() 直接用来判断状态标志位

  • 2. EXTI_ClearITPendingBit(EXTI_Line0);

中断服务函数结束之前调用,清除某中断线上的中断标志位

EXTI_ClearFlag(),与之功能相同

常用的中断服务函数格式:

void EXTI2_IRQHandler(void)
{
	if(EXTI_GetITStatus(EXTI_Line2)!=RESET)
	//判断某个线上的中断是否发生 
	{
		中断逻辑…
		
		EXTI_ClearITPendingBit(EXTI_Line2); 
		//清除 LINE 上的中断标志位 
	} 
}

外部中断配置示例

1. exit.c

包含4个函数:

  • EXTI_Init()

    外部中断初始化,(以按键功能为例):

void EXTIX_Init(void)
{
 EXTI_InitTypeDef EXTI_InitStructure;
 NVIC_InitTypeDef NVIC_InitStructure;
 //调用结构体
  1. 调用 KEY_Init 函数,来初始化外部中断输入的 IO 口。
KEY_Init();
//初始化按键对应 io 模式
  1. 调用RCC_APB2PeriphClockCmd()函数来使能复用功能时钟。
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
//外部中断,需要使能 AFIO 时钟
  1. 接着配置中断线和 GPIO 的映射关系。
//GPIOC.5 与中断线5映射
  	GPIO_EXTILineConfig(GPIO_PortSourceGPIOC,GPIO_PinSource5);
  1. 然后初始化中断线。
EXTI_InitStructure.EXTI_Line=EXTI_Line5;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;	
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
  1. 中断初始化、配置优先级
 NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;			
 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02;	
 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x02;					
 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;								
 NVIC_Init(&NVIC_InitStructure); 
  • EXTI0_IRQHandler()

中断处理函数

void EXTI0_IRQHandler(void)
{
  delay_ms(10);   
	if(WK_UP==1)//WK_UP在key.h文件中定义
	{	  
		LED0=!LED0;//LED0在led.h文件中定义
		LED1=!LED1;	
	}
	EXTI_ClearITPendingBit(EXTI_Line0); 
	//清除EXTI0线路挂起位
}
  • EXTI9_5_IRQHandler()
  • EXTI15_10_IRQHandler()





小结

使用 IO 口外部中断的一般步骤:

  1. 初始化 IO 口为输入
  2. 开启 IO 口复用时钟,设置 IO 口与中断线的映射关系。
  3. 初始化线上中断(EXTI),设置触发条件等。
  4. 配置中断分组(NVIC),并使能中断。
  5. 编写中断服务函数。