STM32F4 异常与中断

概述

  异常是导致程序流更改的事件,当一个异常发生,处理器会挂起当前正在执行的任务,并跳转执行响应的异常处理函数。在执行完异常处理函数后,处理器会恢复刚刚正常的程序执行流程。在ARM架构下,中断是异常的一部分,中断通常由外设或外部I/O引脚产生,在某些情况下,它们可以由软件触发。
  每个异常源都有一个异常号。异常号1 ~ 15表示系统异常,异常号16及以上表示中断。在Cortex-M3和Cortex-M4处理器中,NVIC的设计可以支持多达240个中断输入。然而,在实际中,在设计中实现的中断输入的数量要少得多,通常在16到100的范围内。这样设计的芯片尺寸就可以减少,也就降低了功耗。

  上图为在《stm32f4xx中文参考手册》中截取的异常向量表,异常向量表接下来就是中断向量表了,因为太长就不截取了,也可以去启动文件startup_stm32f40_41xxx.s中查看。

嵌套向量中断控制器


  如上图所示,NVIC属于系统外设,也就是属于Cortex-M处理器的一部分。它是可编程的,它的寄存器位于内存映射的系统控制空间(SCS)
NVIC处理异常和外部中断事件的配置、优先级设置和中断屏蔽。NVIC具有以下特点:
  1.灵活的异常和中断管理;
  2.嵌套异常/中断支持;
  3.向量异常/中断条目;
  4.中断屏蔽。
  系统重置后,所有中断都被禁用,并给出一个优先级值0,在使用任何中断之前,嵌套向量中断控制器需要进行以下设置:
    1.设置所需中断的优先级(这一步是可选的);
    2.在触发中断的外设中启用中断生成控制;
    3.在NVIC中启用中断。
《Cortex M3与M4权威指南》章节7.3 P235

Functions Usage
void NVIC_EnableIRQ (IRQn_Type IRQn) Enable an external interrupt
void NVIC_DisableIRQ (IRQn_Type IRQn) Disable an external interrupt
void NVIC_SetPriority (IRQn_Type IRQn,uint32_t priority) Set the priority of an interrupt
void __enable_irq(void) Clear PRIMASK to enable interrupts
void __disable_irq(void) Set PRIMASK to disable all interrupts
void NVIC_SetPriorityGrouping(uint32_t PriorityGroup) Set priority grouping configuration

异常/中断响应过程

  1.将当前程序状态寄存器CPSR的内容保存到将要执行的异常中断对应的SPSR寄存器中实现的,保存了处理器当前状态、中断屏蔽位以及各条件标志位。硬件还会自动把会被破坏的寄存器自动压栈;
  2.设置当前程序状态寄存器CPSR中相应的位。使处理器进入相应的执行模式、禁止IRQ中断等;
  3.将寄存器LR设置成需要返回地址;
  4.将程序计数寄存器PC的值,设置成该异常中断的中断向量地址,从而跳转到相应的异常中断处理程序处执行;
  5.当异常/中断函数执行完成后,恢复被中断的程序的处理器状态,即将当前的SPSR寄存器内容复制到当前程序状态寄存器CPSR中,恢复被中断的程序的处理器状态。最后返回到发生异常/中断的指令的下一条指令处执行,即将寄存器LR的内容复制到程序计数器PC中。

抢占优先级和响应优先级

  stm32除了中断向量表上的优先级属性,还有具有抢占优先级和响应优先级属性,抢占优先级可以理解会对中断处理进行抢夺,也就是当有一个中断处理函数正在执行时,NVIC接收到了一个中断处理请求其抢占优先级高于现在当前执行的中断(数字越小优先级越高),则会中断当前正在执行的中断处理函数,去执行新产生的中断处理请求的函数,当执行完毕后再继续执行之前的中断处理函数。
  而响应优先级呢,不会进行抢夺,如当一个中断处理函数正在执行时,NVIC接收到了一个中断处理请求其抢占优先级等于现在当前执行的中断,响应优先级高于现在的中断,但是并不会进行抢夺,等待先前的中断处理完毕,才处理当前新产生的中断,只有在它们两个同时达到时,才会优先处理响应优先级高的中断。
  抢占优先级和响应优先级共同使用4个bit位来表示,通过库函数NVIC_PriorityGroupConfig()进行设置,说直白就是设置用几个bit位表示抢占优先级,用几个位表示响应优先级。当有两个抢占优先级和响应优先级都相同的中断产生时,则按中断向量表的优先级进行处理。

外部中断/事件控制器


  上图是外部中断线或外部事件线产生的示意图,图中信号线上划有一条斜线,旁边标志23字样的注释,表示共有23条中断/事件线。图中的蓝色路线,标出了外部中断信号的传输路径,首先外部信号产生,通过边沿检测电路,与软件中断/事件寄存器相与后,如果相应位被置1则相应位的挂起请求寄存器会被清0表示中断产生,向NVIC中断控制器发出一个中断请求,当然前提是相应位的中断屏蔽寄存器未被清0,在中断处理函数中当任务处理完成,则需要手动将相应中断屏蔽寄存器置1,高速系统中断处理函数执行完成,等待下次中断进入,通过理解也可以得知只要将软件中断/事件寄存器需要的位置1便可以发生中断,即使输入线并没有相应的输入信号传输进来。
  图中红色的线是外部事件的传输路线,外部请求信号与软件中断/事件寄存器相与后,与事件屏蔽寄存器相或,这个与外部中断信号的传输路径相似,用于引入事件屏蔽寄存器的控制,最后脉冲发生器的一个跳变的信号转变为一个单脉冲,输出到芯片中的其它功能模块。从上图中我们也可以知道,从外部激励信号来看,中断和事件的产生源都可以是一样的。之所以分成两个部分,由于中断是需要CPU参与的,需要软件的中断服务函数才能完成中断后产生的结果,但是事件,是靠脉冲发生器产生一个脉冲,进而由硬件自动完成这个事件产生的结果,当然相应的联动部件需要先设置好。

  stm32f4一共有23条中断/事件线,每个I/O引脚号占用相应的中中断/事件线,如GPIOA引脚0和GPIOB引脚1,分别占用中断/事件线0和1,而GPIOA引脚0和GPIOB引脚0就共同占用中断/事件线0,因为每个端口最多有16根引脚,所以占用了16根中断/事件线,下面是剩下的7根:

• EXTI line 16 is connected to the PVD output
• EXTI line 17 is connected to the RTC Alarm event
• EXTI line 18 is connected to the USB OTG FS Wakeup event
• EXTI line 19 is connected to the Ethernet Wakeup event
• EXTI line 20 is connected to the USB OTG HS (configured in FS) Wakeup event
• EXTI line 21 is connected to the RTC Tamper and TimeStamp events
• EXTI line 22 is connected to the RTC Wakeup even

  因为相同的I/O引脚号,都是共用同一个中断/事件线的,所以一次最多只能让一个相同引脚号的I/O起作用,正如上图所示,具体由哪个端口的I/O引脚与中断线向连接,可在系统配置控制器中进行设置。

实验程序

  通过以上分析可知,如果需要配置GPIOA的引脚4,能够产生中断需要进行以下设置:
    1.打开GPIOA端口的时钟,初始化GPIOA的引脚4;
    2.设置系统配置控制器(SYSCFG),将GPIOA的引脚4与对应的中断/事件线4连接;
    3.设置嵌套向量中断控制器(NVIC),设置优先级,指定中断通道并使能;
    4.设置外部中断/事件控制器,设置中断/事件线,模式和触发方式,最后使能。
  GPIOA的引脚4我将其接上了一个蓝牙模块的stata引脚,当手机或者其他设备连接/断开蓝牙模块后,蓝牙模块的stata引脚电平将跳变,而GPIOA的引脚4被配置为了输入模式,上下沿触发方式,当检测到了引脚电平跳变就会产生一个中断,在中断中将LED1点亮/熄灭。
  根据本人随笔《LED和按键实验》和《Keil5环境搭建》创建和添加文件的方式进行。最先添加所需要的NVIC和EXTI的库函数,创建一个bluetooth.c,将添加在目录树的Device组中,创建头文件添加到目录树的Inc组中;
bluetooth.c

#include "stm32f4xx.h"
#include "common.h"
void led1cbt_signal_init()
{
	NVIC_InitTypeDef NVIC_InitStruct;
	EXTI_InitTypeDef EXTI_InitStruct;
	GPIO_InitTypeDef GPIO_InitStructure;
	//配置PA4
	GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_4;//指定第9根引脚 
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN ;//配置为输入模式
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz ;//配置引脚的响应时间=1/50MHz.
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP ;//推挽的输出模式,增加输出电流和灌电流的能力
	GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_NOPULL ;//不使能内部上下拉电阻
	GPIO_Init(GPIOA ,&GPIO_InitStructure);
	
	SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOA, EXTI_PinSource4);
	NVIC_InitStruct.NVIC_IRQChannel = EXTI4_IRQn; // 指定通道
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0; // 抢占优先级
	NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;// 响应优先级
	NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; // 打开通道
	NVIC_Init(&NVIC_InitStruct);

	EXTI_InitStruct.EXTI_Line = EXTI_Line4; 
	EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;  
	EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Rising_Falling; 
	EXTI_InitStruct.EXTI_LineCmd = ENABLE;  
	EXTI_Init(&EXTI_InitStruct);
}

void EXTI4_IRQHandler(void)
{
	//检查是否有中断请求
	if(EXTI_GetITStatus(EXTI_Line4) != RESET)
	{
		// 上升沿触发
		if (PAin(4)) //检测到高电平,表示有设备连接了蓝牙模块,点亮LED1
		{
			LED1ON; // 蓝牙连接,LED1亮
		}
		else //蓝牙连接被断开,LED1熄灭
		{
			LED1OFF; // 蓝牙断开
		}
		EXTI_ClearITPendingBit(EXTI_Line4);		
	}
}

main.c

#include "stm32f4xx.h"
#include "hwconf.h"
#include "common.h"
#include "bluetooth.h"
#include "led.h"
    
int main()
{
	init_board(); //打开相关端口的时钟
	led1_init();
	LED1OFF;
        NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //选择使用优先级组的类别
        led1cbt_signal_init();
}

总结

  1.需要注意区别异常,中断,事件的区别;
  2.配置一个中断需要初始化相关引脚,配置SYSCFG,配置NVIC,配置EXTI一个都不能少;
  3.注意序号是相同I/O引脚不能同时使用中断线。

posted @ 2021-02-19 23:34  ding-ding-light  阅读(692)  评论(0编辑  收藏  举报