STM32 NVIC 中断

***************************** STM32 NVIC 中断
一、STM32 的中断分组:STM32 将中断分为 5 个组,组 0~4。该分组的设
置是由 SCB->AIRCR 寄存器的 bit10~8 来定义的。具体的分配关系如表所示:
组…,…,. bit[7:4]分配情况 …,…,…,…分配结果
0 …,…,…,…,…,…0:4 …,…,…,…0 位抢占优先级,4 位响应优先级
1 …,…,…,…,…,…1:3 …,…,…,…1 位抢占优先级,3 位响应优先级
2 …,…,…,…,…,…2:2 …,…,…,…2 位抢占优先级,2 位响应优先级
3…,…,…,…,…,…3:1…,…,…,… ,3 位抢占优先级,1 位响应优先级
4…,…,…,…,…,…4:0…,…,…,… , 4位抢占优先级,0 位响应优先级
…,…,…,…,…,…,…,…,表 . AIRCR 中断分组设置表
例如组设置为 3,那么此时所有的 60 个中断,每个中断的中断优先寄存器的高四位中的最高 3 位是抢占优先级,低 1 位是响应优先级。每个中断,你可以设置抢占优先级为 0~7,响应优先级为 1 或 0。抢占优先级的级别高于响应优先级。而数值越小所代表的优先级就越高。

这里需要注意两点:第一,如果两个中断的抢占优先级和响应优先级都是一样的话,则看哪个中断先发生就先执行;第二,高优先级的抢占优先级是可以打断正在进行的低抢占优先级中断的。而抢占优先级相同的中断,高优先级的响应优先级不可以打断低响应优先级的中断。

二、如何使用库函数实现以上中断分组设置以及中断优先级管理
NVIC 中断管理函数主要在 misc.c 文件里面。
(1).首先是中断优先级分组函数 :void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup);这个函数的作用是对中断的优先级进行分组,这个函数在系统中只能被调用一次,一旦分组确定就最好不要更改。(这个函数是放在主函数里的)

int main(void) {	
	delay_init();	    	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);  //设置NVIC优先级分组为2
	uart_init(115200);	
 	LED_Init();		
	TIM3_Int_Init(4999,7199);
   	while(1){
		LED0=!LED0;
		delay_ms(200);		   
	}	 
}	 

(2)设置好了系统中断分组,下面设置中断初始化函数 NVIC_Init。
NVIC_InitTypeDef 结构体中间的三个成员变量的作用是:

1) NVIC_IRQChannel:定义初始化的是哪个中断,这个我们可以在 stm32f10x.h 中找到每个中断对应的名字。例如 USART1_IRQn、 TIM3_IRQn、EXTI9_5_IRQn、I2C1_EV_IRQn、SPI1_IRQn等等(还有好多)。

2) NVIC_IRQChannelPreemptionPriority:定义这个中断的抢占优先级别。
3) NVIC_IRQChannelSubPriority:定义这个中断的子优先级别。
4) NVIC_IRQChannelCmd:该中断是否使能。

三、 STM32 外部中断
(一)、重要概念
(1) *代码主要分布在固件库的 stm32f10x_exti.h 和 stm32f10x_exti.c 文件中。
STM32F103 的19 个外部中断为:
线 0~15:对应外部 IO 口的输入中断。
线 16:连接到 PVD 输出。
线 17:连接到 RTC 闹钟事件。
线 18:连接到 USB 唤醒事件。

(2)STM32 是怎么把 16 个中断线和 IO 口一一对应起来的呢?
(这个概念相当重要)

{STM32 就这样设计,GPIO 的管脚 GPIOx.0~GPIOx.15(x=A,B,C,D,E,F,G)分别对应中断线 0~15。这样每个中断线对应了最多 7 个 IO 口,以线 0 为例:它对应了 GPIOA.0、GPIOB.0、GPIOC.0、GPIOD.0、GPIOE.0、GPIOF.0、GPIOG.0。而中断线每次只能连接到 1 个 IO 口上,这样就需要通过配置来决定对应的中断线配置到哪个 GPIO 上了。}

(二)、程序编写
(1)主要步骤:
1)初始化 GPIO 口;
2)开启 AFIO 时钟;
3)设置好中断线和 GPIO 映射关系,配置 GPIO 与中断线的映射关系的函数 GPIO_EXTILineConfig();
4)设置好了中断的触发模式等初始化参数,中断线上中断的初始化是通过函数 EXTI_Init();
5)设置 NVIC 中断优先级,并使能中断;
6)编写中断服务函数。(这里需要说明一下,STM32 的 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。)

(2)中断服务函数会经常使用到两个函数:
第一个函数是判断某个中断线上的中断是否发生(标志位是否置位):ITStatus EXTI_GetITStatus(uint32_t EXTI_Line);这个函数一般使用在中断服务函数的开头判断中断是否发生。
另一个函数是清除某个中断线上的中断标志位:void EXTI_ClearITPendingBit(uint32_t EXTI_Line);这个函数一般应用在中断服务函数结束之前,清除中断标志位。

void KEY_Init(void){         //GPIO初始化程序
 	GPIO_InitTypeDef GPIO_InitStructure;
 
 	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE,ENABLE);

	GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_2;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; 
 	GPIO_Init(GPIOE, &GPIO_InitStructure);
}



//外部中断0服务程序
void EXTIX_Init(void){
 	EXTI_InitTypeDef EXTI_InitStructure;
 	NVIC_InitTypeDef NVIC_InitStructure;
    //	1)初始化 GPIO 口
    KEY_Init();	 
    
    //  2)开启 AFIO 时钟;使能复用功能时钟
  	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);	

    //  3)将中断线 2 与 GPIOE 映射起来,那么很显然是 GPIOE.2 与 EXTI2 中断线连接了  	
  	GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_PinSource2);

    //  4)设置好了中断的   触发模式   等初始化参数
  	EXTI_InitStructure.EXTI_Line=EXTI_Line2;	//第一个参数是中断线的标号,取值范围0~15
  	EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;//第二个参数是中断模式,可选值为中断 EXTI_Mode_Interrupt 和事件 EXTI_Mode_Event	
  	EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;//第三个参数是触发方式,可以是下降沿触发 EXTI_Trigger_Falling,上升沿触发 EXTI_Trigger_Rising,或者任意电平(上升沿和下降沿)触发EXTI_Trigger_Rising_Falling
  	EXTI_InitStructure.EXTI_LineCmd = ENABLE;
  	EXTI_Init(&EXTI_InitStructure);	 

    //  5)设置 NVIC 中断优先级,并使能中断
  	NVIC_InitStructure.NVIC_IRQChannel = EXTI2_IRQn;	
  	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02;	
  	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x03;					
  	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			
  	NVIC_Init(&NVIC_InitStructure); 
}


//6)编写中断2服务函数
void EXTI2_IRQHandler(void){
	delay_ms(10); //按键 消抖
	if(KEY2==0){	  //第一个函数,按下按键   即判断某个中断线上的中断是否发生,也可用函数:ITStatus EXTI_GetITStatus(uint32_t EXTI_Line)
		LED0=!LED0;
	}		 
	EXTI_ClearITPendingBit(EXTI_Line2);  //另一个函数是  清除某个中断线上的中断标志位
}

//note:具体源码,在正点原子,实验5  外部中断实验,那边一下设置了好几个外部中断

四、 STM32 定时器中断
(一)、重要概念
(1)、STM32F1 的定时器功能十分强大,有 TIME1 和 TIME8 等高级定时器,也有 TIME2~TIME5 等通用定时器,还有 TIME6和 TIME7 等基本定时器
(2)、STM32 通用定时器简介
STM32F1 的通用定时器是一个通过可编程预分频器(PSC)驱动的 16 位自动装载计数器(CNT)构成。可以被用于:测量输入信号的脉冲长度(输入捕获)或者产生输出波形(输出比较和 PWM)等。 使用定时器预分频器和 RCC 时钟控制器预分频器,脉冲长度和波形周期可以在几个微秒到几个毫秒间调整。

(3)、STM3F1 的通用 TIMx (TIM2、TIM3、TIM4 和 TIM5)定时器功能包括:
1)16 位向上、向下、向上/向下自动装载计数器(TIMx_CNT)。
2)16 位可编程(可以实时修改)预分频器(TIMx_PSC),计数器时钟频率的分频系数为 1~65535 之间的任意数值。
3)4 个独立通道(TIMx_CH1~4),这些通道可以用来作为:
A.输入捕获;
B.输出比较;
C.PWM 生成(边缘或中间对齐模式) ;
D.单脉冲模式输出。

4)可使用外部信号(TIMx_ETR)控制定时器和定时器互连(可以用 1 个定时器控制另外一个定时器)的同步电路。
5)如下事件发生时能产生中断/DMA
A.更新:计数器向上溢出/向下溢出,计数器初始化(通过软件或者内部/外部触发)
B.触发事件(计数器启动、停止、初始化或者由内部/外部触发计数)
C.输入捕获
D.输出比较
E.支持针对定位的增量(正交)编码器和霍尔传感器电路
F.触发输入作为外部时钟或者按周期的电流管理

(4)、通用定时器的寄存器
1)首先是控制寄存器 1(TIMx_CR1),TIMx_CR1 的最低位,也就是计数器使能位,该位必须置 1,才能让定时器开始计数。
2)第二个与我们这 章密切相关的寄存器:DMA/中断使能寄存器(TIMx_DIER)。同样仅关心它的第 0 位,该位是更新中断允许位,本章用到的是定时器的更新中断,所以该位要设置为 1,来允许由于更新事件所产生的中断。
3)第三个与我们这章有关的寄存器:预分频寄存器(TIMx_PSC)。该寄存器用
设置对时钟进行分频,然后提供给计数器,作为计数器的时钟。
4)最后,要介绍的寄存器是:状态寄存器(TIMx_SR)。该寄存器用来标记当前与定时器相关的各种事件/中断是否发生。
顺带介绍一下
5)TIMx_CNT 寄存器,该寄存器是定时器的计数器,该寄存器存储了当前
定时器的计数值。
6)自动重装载寄存器(TIMx_ARR)。根据 TIMx_CR1 寄存器中 APRE 位的设置:APRE=0 时,预装载寄存器的内容可以随时传送到影子寄存器,此时 2者是连通的;而 APRE=1 时,在每一次更新事件(UEV)时,才把预装在寄存器的内容传送到影子寄存器。

(5)、定时器的时钟来源有 4 个:
1)内部时钟(CK_INT)
2)外部时钟模式 1:外部输入脚(TIx)
3)外部时钟模式 2:外部触发输入(ETR)
4)内部触发输入(ITRx):使用 A 定时器作为 B 定时器的预分频器(A 为 B 提供时钟)。
这些时钟,具体选择哪个可以通过 TIMx_SMCR 寄存器的相关位来设置。

这里的 CK_INT时钟是从 APB1 倍频的来的,即通用定时器 TIMx 的时钟是 APB1 时钟的 2 倍;除非 APB1 的时钟分频数设置为 1,即 APB1 的时钟不分频的时候,通用定时器 TIMx 的时钟就等于 APB1的时钟。这里还要注意的就是高级定时器的时钟不是来自 APB1,而是来自 APB2 的。

(6)、如何设置分频系数(psc)、自动重载计数周期值(arr)
在前面时钟系统部分我们讲解过,系统初始化的时候在默认的系统初始化函数 SystemInit 函数里面已经初始化 APB1 的时钟为 2 分频,所以 APB1 的时钟为 36M,而从 STM32 的内部时钟树图得知:
1.当 APB1 的时钟分频数为 1(即psc=1时) 的时候,TIM2 ~ 7 的时钟为 APB1 的时钟**(即36M)
2.而如果 APB1 的时钟分频数不为 1(psc!= 1时),那么 TIM2~7 的时钟频率将为 APB1 时钟的
两倍(即72M)**。
因此,TIM3 的时钟为 72M,再根据我们设计的 arr 和 psc 的值,就可以计算中断时间了。

计算公式如下:
Tout= ((arr+1) * (psc+1))/Tclk。
例如 Tout= ((4999+1) * ( 7199+1))/72=500000us=500ms。
其中:
计数器时钟频率的分频系数(即psc)为 1~65535 之间的任意数值。
Tclk:TIM3 的输入时钟频率(单位为 Mhz)。
Tout:TIM3 溢出时间(单位为 us)。

int main(void) {		
	delay_init();	    	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); 
	uart_init(115200);	
 	LED_Init();			
	TIM3_Int_Init(4999,7199);  //当 定时器计数的值 (即TIM3_CNT)等于 4999 的值的时候,就会产生 TIM3 的更新中断
   	while(1){
		LED0=!LED0;
		delay_ms(200);		   
	}	 
}	 

此段代码对 TIM3 进行初始化之后,进入死循环等待 TIM3溢出中断,当 TIM3_CNT 的值等于 TIM3_ARR 的值的时候,就会产生 TIM3 的更新中断,然后在中断里面取反 LED1,TIM3_CNT 再从 0 开始计数。根据上面的公式,我们可以算出中断溢出时间为 500ms,即 Tout= ((4999+1)*( 7199+1))/72=500000us=500ms。

(二)、程序编写
(1)主要步骤:
1)TIM3 时钟使能。(比较 1)和 6),都是关于时钟的设置)RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
2)初始化定时器参数,设置自动重装值,分频系数,计数方式等。
3) 定时器的初始化参数。
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
4)指明使能的定时器中断的类型。
TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE );
在 中断服务函数(void TIM3_IRQHandler(void))中,用于 读取中断状态寄存器的值 判断是否有中断发生。
5)TIM3 中断优先级设置。
NVIC_InitStructure
6)允许 TIM3 工作,也就是使能 TIM3。
TIM_Cmd(TIM3, ENABLE);

7)编写中断服务函数。
void TIM3_IRQHandler(void)
(这里还需要补充说明一下,固件库还提供了两个函数用来判断定时器状态以及清除定时器状态标志位的函数 TIM_GetFlagStatus 和 TIM_ClearFlag)

void TIM3_Int_Init(u16 arr,u16 psc){
    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	
    // 1)TIM3 时钟使能。
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);//TIM3 是挂载在 APB1 之下,所以我们通过 APB1 总线下的使能使能函数来使能 TIM3
	// 2)初始化定时器参数,设置自动重装值,分频系数,计数方式等。
	TIM_TimeBaseStructure.TIM_Period = arr; //是设置  自动重载计数周期值
	TIM_TimeBaseStructure.TIM_Prescaler =psc;  // TIM_Prescaler 是用来设置  分频系数的
	TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //是用来设置时钟分频因子
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //数 TIM_CounterMode 是用来设置计数方式,可以设置为向上计数,向下计数方式还有中央对齐计数方式。
    // 3) 定时器的初始化参数是通过初始化函数 TIM_TimeBaseInit 实现
	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); // 第一个参数是确定是哪个定时器。第二个参数是定时器初始化参数结构体指针 ,结构体类型为 TIM_TimeBaseInitTypeDef。这个结构体一共有 5 个成员变量,要说明的是,对于通用定时器只有前面四个参数有用,最后一个参数 TIM_RepetitionCounter 是高级定时器才有用的,这里不多解释。
    // 4)指明使能的定时器中断的类型
	TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE ); //第二个参数非常关键,是用来指明使能的定时器中断的类型,定时器中断的类型有很多种,包括 TIM_IT_Update,TIM_IT_Trigger,输入捕获中断等等。
    // 5)TIM3 中断优先级设置。
	NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;  
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;  
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;  
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; 
	NVIC_Init(&NVIC_InitStructure); 
   // 6)允许 TIM3 工作,也就是使能 TIM3。
	TIM_Cmd(TIM3, ENABLE); 		 
}

// 7)编写中断服务函数。
void TIM3_IRQHandler(void){
	if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET){   //读取中断状态寄存器的值判断中断类型的函数
		TIM_ClearITPendingBit(TIM3, TIM_IT_Update  );    //清除定时器 TIMx 的中断 TIM_IT 标志位
		LED1=!LED1;
	}
}

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值