嵌入式--->异常,中断

本篇文章需要就着ARM Cortex-M3 Cortex-M4 权威指南STM32F7参考手册来阅读,
最好有现成的Stm32F7的工程,当然其他的也可以

文章目录

  • 中断
    • 中断的管理
      • 中断优先级(NVIC)
      • 中断优先级分组
    • 中断的屏蔽
      • PRIMASK & FAULTMASK
        • PRIMASK
        • FAULTMASK
      • BASEPRI
  • STM32F7的外部中断(EXIT)
    • 使用的介绍
      • 1.使能IO时钟&基本配置
      • 2.设置中断优先级(NVIC)
      • 3.中断服务函数
      • 4.编写中断处理的回调函数HAL_GPIO_EXTI_Callback
  • 相关概念
    • 事件、中断事件、中断
    • 中断服务函数使用的注意事项
  • 附录
    • 程序代码

中断

  • Cortex-M3/Cortex-M4内核支持256个中断,其中包含16个内核中断(系统异常)和240个外部中断(IRQ),并且都具有256级可编程中断设置

    • 下图为系统异常列表,编号1~15嵌入式--->异常,中断_第1张图片
  • Cortex-M处理器都会提供一个用于中断处理的嵌套向量中断控制器(NVIC);除了中断还有其他需要服务的事件称之为异常,按照ARM的说法中断也是一种异常

  • 各种异常源如下图
    嵌入式--->异常,中断_第2张图片

  • 在参考手册中搜索向量表,可查看所有中断,1~15共16个内核中断(内核中断也可以称之为系统异常),其中复位,NMI,硬件错误中断优先级为(-3 ~ -1),-3的优先级最高

  • 外部中断(中断编号16 ~ 255),不同的厂商对外部中断的定义可能是不用的,STM32F4/F7并没有使用CM4/7内核Dev全部东西,而是使用了其中的一部分,STM32F7x一共有118个中断,10个内核中断和108个可屏蔽中断,可以在stm32f767xx.h中看到结构体IRQn_Type,其中就是对外部中断的定义

中断的管理

STM32对中断进行分组0~4,不同的分组有不同的抢占优先级和相应优先级长度,

中断优先级(NVIC)

  • 中断优先级由优先级寄存器控制,宽度为3 ~ 8位,下图为只实现了3位的优先级寄存器
    在这里插入图片描述
  • 下图为启用3/5/8位优先级寄存器的可用优先级,3位有2^3 = 8个优先级,5位有2^5 = 32个优先级,8位有2^8 = 256个优先级
    嵌入式--->异常,中断_第3张图片
  • 支持的优先级数量越少,芯片功耗越低速度越快
  • 可编程优先级的实际数量由芯片厂商确定的,多数的Cortex-M3或Coretex-M4芯片所支持的优先等级较少
  • 优先级的值越低级别越高,其中复位,NMI,硬件错误的优先级是最高的

中断优先级分组

  • 抢占优先级(组优先级):高抢占式的优先级的中断会打断当前主程序及中断程序运行,俗称中断嵌套。
  • 子优先级(响应优先级):在抢占式优先级相同的情况下,高响应优先级的中断优先被响应;
  • 在抢占优先级相同的情况下才会去比较子优先级
  • 下图为,不同优先级分组下,抢占优先级和子优先级的区域定义
    嵌入式--->异常,中断_第4张图片
  • 默认使用优先级分组0,Cortex-M3&Cortex-M4也使用了分组0,抢占优先级的域为[7:1],因此抢占优先级也就是128个等级
  • 如下图为优先级分组为5,并且启用3位中断优先级寄存器的情况
    在这里插入图片描述

中断的屏蔽

PRIMASK & FAULTMASK

PRIMASK
  • PRIMASK用于屏蔽除复位,NMI,HardFault外的所有异常和中断,写1屏蔽中断,写0使能中断
  • 汇编变成可以使用CPS(修改处理器状态)指令修改PRIMASK寄存器数值
    CPSIE 		I; 	//清除PRIMASK(使能中断)
    CPSID		I;	//设置PRIMASK(禁止中断)
    
  • MRS和MSR指令访问如下
    MOVS	R0, #1
    MSR		PRIMASK,	R0	;//将1写入PRIMASK禁止所有中断
    MOVS	R0, #0
    MSR		PRIMASK,	R0	;//将0写入PRIMASK使能所有中断	
    
FAULTMASK
  • FAULTMASK可以将HardFault中断也屏蔽掉
  • 汇编变成可以使用CPS(修改处理器状态)指令修改PRIMASK寄存器数值
    CPSIE 		F; 	//清除FAULTMASK(使能中断)
    CPSID		F;	//设置FAULTMASK(禁止中断)
    
  • MRS和MSR指令访问如下
    MOVS	R0, #1
    MSR		FAULTMASK,	R0	;//将1写入PRIMASK禁止所有中断
    MOVS	R0, #0
    MSR		FAULTMASK,	R0	;//将0写入FAULTMASK使能所有中断	
    

BASEPRI

  • BASEPRI是一个灵活的中断屏蔽寄存器,它可以屏蔽低于某个优先级值得中断
    MOVS	R0, #0X60
    MSR		BASEPRI,	R0	;//将60写入BASEPRI禁止优先级低于60的所有中断
    MOVS	R0, #0
    MSR		BASEPRI,	R0	;//将0写入FAULTMASK使能优先级低于60的所有中断	
    
  • FreeRTOS的中断开关控制就是使用BASEPRI来实现的

STM32F7的外部中断(EXIT)

  • STM32F7 的每个 IO 都可以作为外部中断的中断输入口,STM32F7 的中断控制器支持 22 个外部中断/事件请求。每个中断设有状态位,每个中断/事件都有独立的触发和屏蔽设置。STM32F7的 23 个外部中断为:
    端口 对应中断
    EXTI 线 0~15: 对应外部 IO 口的输入中断。
    EXTI 线 16: 连接到 PVD 输出。
    EXTI 线 17: 连接到 RTC 闹钟事件。
    EXTI 线 18: 连接到 USB OTG FS 唤醒事件。
    EXTI 线 19: 连接到以太网唤醒事件。
    EXTI 线 20: 连接到 USB OTG HS(在 FS 中配置)唤醒事件。
    EXTI 线 21: 连接到 RTC 入侵和时间戳事件。
    EXTI 线 22: 连接到 RTC 唤醒事件。
    EXTI 线 23: 连接到 LPTIM1 异步事件
  • 上表可知,中断线 0-15 对应外部 IO 口的输入中断,一共是 16 个外部中断线,但是有
  • GPIO的引脚 GPIOx.0~GPIOx.15(x=A,B,C,D,E,F,G,H,I)分别对应中断线 0~15。这样每个中断线对应了最多 9 个 IO 口,以线 0 为例:它对应了 GPIOA.0、GPIOB.0、GPIOC.0、GPIOD.0、GPIOE.0、GPIOF.0、GPIOG.0,GPIOH.0,GPIOI.0。
     而中断线每次只能连接到 1 个 IO 口上,这样就需要通过配置来决定对应的中断线配置到哪个 GPIO 上了。GPIO 和中断线映射关系是在寄存器 SYSCFG_EXTICR1~ SYSCFG_EXTICR4 中配置的。

使用的介绍

因为STM32的中断非常多,所以必须设置中断的优先级,设置完优先级之后编写中断服务函数,进行中断服务函数的挂载。
中断的使用基本流程为
1)使能 IO 口时钟。
2)调用函数 HAL_GPIO_Init 设置 IO 口模式,触发条件,使能 SYSCFG 时钟以及设置 IO
口与中断线的映射关系。
3)配置中断优先级(NVIC),并使能中断。
4)在中断服务函数中调用外部中断共用入口函数 HAL_GPIO_EXTI_IRQHandler。
5)编写外部中断回调函数 HAL_GPIO_EXTI_Callback 实现控制逻辑。
下文为过程详细介绍

1.使能IO时钟&基本配置

首先,我们要使用 IO 口作为中断输入,所以我们要使能相应的 IO 口时钟,之后
设置 IO 口模式,触发条件,开启 SYSCFG 时钟,设置 IO 口与中断线的映射关系

__HAL_RCC_GPIOA_CLK_ENABLE(); //开启 GPIOA 时钟	
GPIO_InitTypeDef  GPIO_Initure;
GPIO_Initure.Pin=GPIO_PIN_0; //PA0
GPIO_Initure.Mode=GPIO_MODE_IT_RISING; //外部中断,上升沿触发
GPIO_Initure.Pull=GPIO_PULLDOWN; //默认下拉
HAL_GPIO_Init(GPIOA,&GPIO_Initure);

之后设置中断优先级

2.设置中断优先级(NVIC)

HAL_NVIC_SetPriority(EXTI0_IRQn,2,1); //抢占优先级为 2,子优先级为 1
HAL_NVIC_EnableIRQ(EXTI0_IRQn); //使能中断线 2

3.中断服务函数

们配置完中断优先级之后,接着要做的就是编写中断服务函数。中断服务函数的名字是
在 HAL 库中事先有定义的。有 7 个,分别为:

void EXTI0_IRQHandler(); 
void EXTI1_IRQHandler(); 
void EXTI2_IRQHandler(); 
void EXTI3_IRQHandler(); 
void EXTI4_IRQHandler(); 
void EXTI9_5_IRQHandler(); 
void EXTI15_10_IRQHandler();

中断线 0-4 每个中断线对应一个中断函数,中断线 5-9 共用中断函数EXTI9_5_IRQHandler,中断线 10-15 共用中断函数 EXTI15_10_IRQHandler。一般情况下,我们可以把中断控制逻辑直接编写在中断服务函数中,但是 HAL 库把中断处理过程进行了简单封装,也就是中断处理的回调函数。

//使用如下
//中断服务函数
void EXTI0_IRQHandler(void)
{
	 HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0); //调用中断处理公用函数
}

4.编写中断处理的回调函数HAL_GPIO_EXTI_Callback

在使用 HAL 库的时候,我们也可以跟使用标准库一样,在中断服务函数中编写控制逻辑。但 是 HAL 库 为 了 用 户 使 用 方 便 , 它 提 供 了 一 个 中 断 通 用 入 口 函 数HAL_GPIO_EXTI_IRQHandler,在该函数内部直接调用回调函数HAL_GPIO_EXTI_Callback。
HAL_GPIO_EXTI_IRQHandler 函数定义:

void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin)
{
    if(__HAL_GPIO_EXTI_GET_IT(GPIO_Pin) != RESET)
    {
   	    __HAL_GPIO_EXTI_CLEAR_IT(GPIO_Pin);
    	HAL_GPIO_EXTI_Callback(GPIO_Pin);
	}
}

该函数实现的作用非常简单,通过入口参数 GPIO_Pin 判断中断来自哪个 IO 口,然后清除相应的中断标志位,最后调用回调函数 HAL_GPIO_EXTI_Callback()实现控制逻辑。所以我们编写中断控制逻辑将跟串口实验类似,在所有的外部中断服务函数中直接调用外部中断共用处理函数 HAL_GPIO_EXTI_IRQHandler,然后在回调函数HAL_GPIO_EXTI_Callback 中通过判断中断是来自哪个 IO 口编写相应的中断服务控制逻辑。

相关概念

事件、中断事件、中断

 事件、中断事件 中断三个概念或术语。这三个概念彼此关联,有时会让人有点混淆或犯迷糊。
 拿一件生活中的事情打比方对上述三个概念做个基本的粗略理解,比如一老师在教室里给学生们上课。课堂上的学生可能做出各种行为动作,比方做笔记、打哈气、翻书包、讲小话等,我们把这些行为统称为事件,其中有些行为老师往往只是视而不见,继续他的上课;而有些行为可能导致老师的上课中止,比方讲小话,并对学生的相关行为予以警告、批评或纠正等,然后继续上课。我们把老师因为学生的某些行为而中止授课,并产生后续动作,之后接着上课的这个过程理解为中断或中断响应。我们把可能导致老师上课中断的学生行为理解为中断事件。
 结合上面的比方,不难理解中断事件是一种可以导致中断发生的事件,中断则是因为中断事件的发生而导致的后续行为过程。事件与中断事件是包含关系,即事件可分为中断事件或非中断事件。而中断事件与中断之间属于前后关联的因果关系,虽有关联,但二者在时序上、行为上并不一样。

中断服务函数使用的注意事项

中断服务函数名是固定的,在启动代码里面已经定下来了。
书写中断服务函数的时候注意的问题:

  1. 中断服务函数名尽量用复制,不要自己写,因为只要你写错一个字母,这个函数就变成普通函数了。
  2. (如果中断服务函数是公共入口)进入到中断服务函数后先要查询是哪种中断
  3. 先清中断标志,然后再做中断处理,不要把清中断标志放在函数的最后。(如果把清除中断标志放在中断服务函数的最后,会出现当发出清中断标志指令后,硬件还没有把相关标志清除掉,程序就已经跳出了中断服务函数,这个时候NVIC又会识别到标志是1,出现重复中断)。—可以清除中断标志命令发出后,等待清除成功再往下执行
  4. 中断服务函数应该尽量简短,一般是做一些标识,不要在中断中做延时之类的占用CPU很长时间的工作。----快进快出
  5. 中断服务函数不会被任何一个函数调用,当中断条件满足后(在启动文件中有中断服务函数的调用),NVIC控制把CPU拉到中断服务函数中执行。

附录

程序代码

//本程序是通过外部中断实现,四个按键按下从而控制LED灯呈现不同效果的程序
#include "exti.h"
#include "delay.h"
#include "led.h"
#include "key.h"

//中断初始化
void EXTI_Init(void)
{
    GPIO_InitTypeDef GPIO_Initure;
    
    //几个按键相关的时钟
    __HAL_RCC_GPIOA_CLK_ENABLE();               //开启时钟GPIOA
    __HAL_RCC_GPIOC_CLK_ENABLE();               //开启时钟GPIOC
    __HAL_RCC_GPIOH_CLK_ENABLE();               //开启时钟GPIOH
    
    GPIO_Initure.Pin=GPIO_PIN_0;                //PA0
    GPIO_Initure.Mode=GPIO_MODE_IT_RISING;      //上升沿触发
    GPIO_Initure.Pull=GPIO_PULLDOWN;			//下拉
    HAL_GPIO_Init(GPIOA,&GPIO_Initure);
    
    GPIO_Initure.Pin=GPIO_PIN_13;               //PC13
    GPIO_Initure.Mode=GPIO_MODE_IT_FALLING;     //下降沿触发
    GPIO_Initure.Pull=GPIO_PULLUP;				//上拉
    HAL_GPIO_Init(GPIOC,&GPIO_Initure);
    
    GPIO_Initure.Pin=GPIO_PIN_2|GPIO_PIN_3;     //PH2,3 下降沿触发,上拉
    HAL_GPIO_Init(GPIOH,&GPIO_Initure);
    
    //中断线0优先级设置
    HAL_NVIC_SetPriority(EXTI0_IRQn,2,0);       //优先级为2,子优先级为1
    HAL_NVIC_EnableIRQ(EXTI0_IRQn);             //使能中断线0
    
    //中断线2优先级设置
    HAL_NVIC_SetPriority(EXTI2_IRQn,2,1);       
    HAL_NVIC_EnableIRQ(EXTI2_IRQn);             
    
    //中断线3优先级设置
    HAL_NVIC_SetPriority(EXTI3_IRQn,2,2);       
    HAL_NVIC_EnableIRQ(EXTI3_IRQn);        
    
    //中断线13优先级设置
    HAL_NVIC_SetPriority(EXTI15_10_IRQn,2,3);   
    HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);       
}


//中断服务函数
void EXTI0_IRQHandler(void)
{
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);   //调用中断公用处理函数
}

void EXTI2_IRQHandler(void)
{
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_2);    //调用中断公用处理函数
}

void EXTI3_IRQHandler(void)
{
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_3);    //调用中断公用处理函数
}

void EXTI15_10_IRQHandler(void)
{
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_13);   //调用中断公用处理函数
}

//中断处理公用函数的回调函数
//中断处理公用函数在stm32f7xx_hal_gpio.c中定义,并且调用HAL_GPIO_EXTI_Callback(GPIO_Pin);
//在hal库中所有外部中断服务函数都会调用此函数
//Pram:GPIO_Pin-中断引脚
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
    static u8 led0sta=1,led1sta=1;
    delay_ms(50);      //消抖
    switch(GPIO_Pin)
    {
        case GPIO_PIN_0:
            if(WK_UP==1)	//控制LED0 LED1 互斥点亮
            {
                led1sta=!led1sta;
                led0sta=!led1sta;
                LED1(led1sta);
                LED0(led0sta);
            }
            break;
        case GPIO_PIN_2:
            if(KEY1==0) 	//同时控制 LED1
            {
                led1sta=!led1sta;
                LED1(led1sta);	
            };
            break;
        case GPIO_PIN_3:
            if(KEY0==0)  	//同时控制LED0 LED1
            {
                led1sta=!led1sta;
                led0sta=!led0sta;
                LED1(led1sta);
                LED0(led0sta); 
            }
            break;

        case GPIO_PIN_13:
            if(KEY2==0)  	//LED0翻转
            {
                led0sta=!led0sta;
                LED0(led0sta);
            }
            break;
    }
}

你可能感兴趣的:(#,嵌入式经验,中断,STM32F767,HAL)