STM32 Hal库版—NVIC、外部中断(EXTI)、AFIO

一、中断

1.1.什么是中断

STM32中的中断是指在程序正常运行时,外部事件或内部异常强行打断当前执行流程,转而执行相应处理程序,完成后再返回原流程的机制。在STM32中,中断是指当外设或处理器内部发生某些事件时,处理器暂停当前执行的程序,转而执行预定义的中断服务程序,处理完毕后再返回原程序继续执行的机制。

1.2.中断的意义与作用

中断的意义就是能高效处理紧急程序,不会一直占用CPU资源。
作用:

  • 实时控制:在确定时间内对相应事件做出响应。
  • 故障处理:检测到故障,需要第一时间处理。
  • 数据传输:为了不确定到来的数据提供保障

1.3.中断优先级基本概念

  • 抢占优先级(pre):高抢占优先级可以打断正在执行的低抢占优先中断
  • 响应优先级(sub):当抢占优先级相同时,响应优先级高的先执行,但是不能相互打断
  • 抢占和响应都相同的情况下,自然优先级越高的先执行
  • 自然优先级:中断向量表的优先级
  • 数值越小,表示优先级越高越先执行

1.4.中断优先级分组

以下表格是中断优先级分组,优先级分组为5组,是因为STM32的IPR(中断优先级寄存器)只使用高四位。

优先级分组AIRCR[10:8]IPRx bit[7:4]分配结果
0111None :[7:4]0位抢占优先级,4位响应优先级
1110[7] :[6:4]1位抢占优先级,3位响应优先级
2101[7:6] :[5:4]2位抢占优先级,2位响应优先级
3100[7:5] :[4]3位抢占优先级,1位响应优先级
4011[7:4] :None4位抢占优先级,0位响应优先级

下表为中断优先级举例:(假设分组是2)
首先看抢占优先级,抢占优先级数值越低,越先执行,因此编号3和7排到前二名;再看响应优先级,响应优先级数值越低,越先执行,因此编号7最先执行;再看编号6和-1,它们的抢占优先级和响应优先级都一样,就要看自然优先级,自然优先级数值越低,越先执行,因此编号-1要排到第3个执行,编号6要排到最后执行。总结,先考虑抢占优先级,再考虑响应优先级,最后考虑自然优先级。

编号自然优先级对应外设抢占响应执行顺序
310RTC212
613EXTI0304
714EXTI1201
-16Systick303

PS:如果EXTI0或Systick正在执行,RTC或EXTI1可以打断它;如果EXTI0正在执行,Systick不可以打断它。

二、NVIC

2.1.NVIC的基本概念

NVIC全称:Nested vectored interrupt controller(嵌套向量中断控制器)是STM32中用于管理和控制中断的核心组件,负责中断优先级分配、使能/禁用控制以及中断嵌套处理等功能。NVIC是STM32处理器中负责处理和管理中断的核心组件,它通过配置中断优先级、使能中断源以及处理中断嵌套等功能,确保系统能够高效响应各类中断请求。NVIC支持256个中断(16内核+240外部),支持256个优先级,并允许裁剪,ST公司把256个优先级裁剪为16个。下图是F1和F4型号的中断和中断优先级对比:

型号内核中断外部中断中断优先级
STM32F103xx106016
STM32F407xx108216

图中内核中断为10,意味着有6个中断被保留。无论是内核中断还是外部中断,它都有相应的中断服务函数,也就是中断发生时,CPU需要执行相应的中断程序,中断服务函数就是该中断程序的一个入口。这些中断服务函数被定义在中断向量表里。

2.2.中断向量表

中断向量表是指在一块固定的内存,以4字节对齐,存放各个中断服务函数程序的首地址,当发生中断时,CPU会自动执行对应的中断服务函数,main函数往往是最后执行的,也就是优先级最低。
下图是内核中断服务函数(10个):

在这里插入图片描述
在这里插入图片描述

下图是外部中断服务函数(60个):

在这里插入图片描述

2.3.NVIC相关寄存器

NVIC相关寄存器位数寄存器个数备注
中断使能寄存器(ISER)32832*8=240个外部中断,每个位控制一个中断置1
中断除能寄存器(ICER)32832*8=240个外部中断,每个位控制一个中断置0
应用程序中断及复位控制寄存器(AIRCR)321位[10:8]只有这三个位控制优先级分组
中断优先级寄存器(IPR)82408个位对应一个,而STM32只使用高4位

2.4.NVIC的工作原理

下图是NVIC的工作原理,假设有4个外部中断来影响程序执行,外部中断进入NVIC后首先经过ISER和ICER寄存器来决定是否给该中断使能,如下图可知只有外部中断2、3、4使能,并进入IPR(中断优先级寄存器),STM32只使用高4位来控制一个优先级,再由AIRCR(应用程序中断及复位控制寄存器)控制优先级分组,最后到CPU来处理,其中内核中断最后一个到来,内核中断不用经过使能或失能,直接来到SHPR(系统异常优先级寄存器),该寄存器与IPR同一级别,最后CPU再处理内核中断。

在这里插入图片描述

2.5.NVIC的使用

  • 设置中断分组:寄存器AIRCR[10:8],HAL_NVIC_SetPriorityGrouping
  • 设置中断优先级:寄存器IPRx bit[7:4],HAL_NVIC_SetPriority
  • 使能中断:寄存器ISERx,HAL_NVIC_EnableIRQ

2.5.1.HAL_NVIC_SetPriorityGrouping

设置中断分组,大概是通过以下流程来操控AIRCR寄存器来设置分组。

在这里插入图片描述
在这里插入图片描述

2.5.2.HAL_NVIC_SetPriority

该函数有三个参数,第一个填入外部中断的位置(该编号可查阅STM32F10xxx参考手册_V10(中文版)第132页);第二个填入抢占优先级;第三个填入响应优先级。主要操作的是IPR(中断优先级寄存器)。

在这里插入图片描述
在这里插入图片描述

2.5.3.HAL_NVIC_EnableIRQ

该函数主要实现请求使能该中断,通过ISER(中断使能寄存器)控制特定位置1使能。

在这里插入图片描述
在这里插入图片描述

三、EXTI

3.1.EXTI基本概念和主要特性

EXTI全称是External interrupt / event Controller,外部(扩展)中断事件控制器。可管理 20 个中断请求 / 生产事件线,即F1系列共有20条EXTI线,支持 GPIO 引脚及内部外设信号触发,实现中断响应或事件触发功能。每一条EXTI线都可以单独配置:选择类型(中断或事件)、触发方式(上升沿、下降沿或双边沿触发)、支持软件触发、开启 / 屏蔽、有挂起状态位。

3.1.1.中断和事件的理解

  • 中断:需要进入NVIC,有相应的中断服务函数,需要CPU处理
  • 事件:不需要进入NVIC,仅用于内部硬件自动控制,如:TIM、DMA、ADC

3.2.EXTI支持的外部中断 / 事件请求

  • EXTI线0 ~ 15:对应GPIO PIN0 ~ 15
  • EXTI线16:PVD输出
  • EXTI线17:RTC闹钟事件
  • EXTI线18:USB OTG FS唤醒事件
  • EXTI线19:以太网唤醒事件(互联型板子)

3.3.EXTI工作原理

下图是EXTI的工作原理图,当有一个外部信号由输入线进入EXTI,来到边沿检测电路,由上升沿或下降沿触发选择器寄存器决定,并在寄存器里相应位置置1来决定,双边沿触发就由两个寄存器相同位置置1,通过边沿检测电路,来到或门电路,无论是软件中断事件寄存器是否置1,只要有信号即可通过这个或门电路,该外部信号通过或门电路后来到请求挂起寄存器,并在相应位置置1,说明有中断来了,该中断是否能来到NVIC,由中断屏蔽寄存器来决定,因此需要经过一个与门电路;该外部型号也有可能来到事件屏蔽寄存器,是否让设备发生事件触发是由时间屏蔽寄存器来决定。

在这里插入图片描述

①:边沿检测
②:软件触发
③:中断屏蔽 / 清除
④:事件屏蔽

3.4.EXTI相应的寄存器

3.4.1.下降沿触发选择寄存器(EXTI_FTSR)

在这里插入图片描述
在这里插入图片描述

注意:

  • 外部唤醒线是边沿触发的,这些线上不能出现毛刺信号
  • 在写EXTI_FTSR寄存器时,在外部中断线上的上升沿信号不能被识别,挂起位也不会被置位
  • 在同一中断线上,可以同时设置上升沿和下降沿触发。即任一边沿都可触发中断

3.4.2.上升沿触发选择寄存器(EXTI_RTSR)

在这里插入图片描述
在这里插入图片描述

注意:

  • 外部唤醒线是边沿触发的,这些线上不能出现毛刺信号
  • 在写EXTI_RTSR寄存器时,在外部中断线上的上升沿信号不能被识别,挂起位也不会被置位
  • 在同一中断线上,可以同时设置上升沿和下降沿触发。即任一边沿都可触发中断

3.4.3.中断屏蔽寄存器(EXTI_IMR)

在这里插入图片描述
在这里插入图片描述

3.4.5.挂起寄存器(EXTI_PR)

该寄存器自动置1挂起等待中断屏蔽寄存器的允许,如果手动置1就是清除当前挂起。

在这里插入图片描述
在这里插入图片描述

四、AFIO

4.1.AFIO简介

AFIO全称Alternate Function IO,复用功能IO,主要用于重映射和外部中断映射配置。配置AFIO寄存器之前,要使能AFIO时钟:__HAL_RCC_AFIO_CLK_ENABLE();对应RCC_APB2ENR寄存器位0。下面是常用的AFIO寄存器:

  • 调试IO配置:AFIO_MAPR[26:24],这三个位配置JTAG / SWD的开关状态
  • 重映射配置:AFIO_MAPR,部分外设IO重映射配置
  • 外部中断配置:AFIO_EXTICR1~4,配置EXTI中断线0 ~ 15对应到哪个具体IO口

4.2.EXTI和IO

EXTI和IO口的关系如下图所示,AFIO_EXTICRx寄存器一共有4个(1 ~ 4),一个AFIO_EXTICRx寄存器里面只有4个EXTIx,因此4个AFIO_EXTICRx和4个EXTIx一共控制16个IO口(0 ~ 15),EXTI的尾缀与IO口的引脚号有关。

在这里插入图片描述

4.3.外部中断配置寄存器 1(AFIO_EXTICR1)

该寄存器高16位保留,只用到低16位,里面的EXTIx只用[3:0]这三位来控制A ~ G组的IO。还有剩下三个一样的寄存器以此类推。

在这里插入图片描述
在这里插入图片描述

AFIO_EXTICR1:EXTI0、EXTI1、EXTI2、EXTI3
AFIO_EXTICR2:EXTI4、EXTI5、EXTI6、EXTI7
AFIO_EXTICR3:EXTI8、EXTI9、EXTI10、EXTI11
AFIO_EXTICR4:EXTI12、EXTI13、EXTI14、EXTI15

五、使用中断

5.1.如何使用中断

下图是使用中断的大致步骤:

在这里插入图片描述

5.2.EXTI的配置步骤(GPIO外部中断)

  1. 使能GPIO时钟
  2. 设置GPIO输入模式:上拉/下拉/浮空
  3. 使能AFIO时钟
  4. 设置EXTI和IO对应关系:选择PA ~ PK组,和EXTI输入线,AFIO_EXTICR / SYSCFG_EXTICR
  5. 设置EXTI屏蔽,上/下沿
  6. 设置NVIC:设置优先级分组、设计优先级、使能中断
  7. 设计中断服务函数:编写对应中断的中断服务函数,清中断标志

步骤2 ~ 5使用HAL_GPIO_Init即可完成。

5.3.EXTI的HAL库设置步骤(GPIO外部中断)

  1. 使能GPIO时钟:使用__HAL_RCC_GPIOx_CLK_ENABLE
  2. GPIO / AFIO / EXTI:使用HAL_GPIO_Init,一步到位
  3. 设置中断分组:使用HAL_NVIC_SetPriorityGrouping,此函数仅需设置一次
  4. 设置中断优先级:使用HAL_NVIC_SetPriority
  5. 使能中断:使用HAL_NVIC_EnableIRQ
  6. 设计中断服务函数:编写EXTIx_IRQHandler,中断服务函数,清除中断标志

STM32仅有:EXTI0 ~ 4、EXTI9_5、EXTI15_10,7个外部中断服务函数。

5.4.HAL库中断回调处理机制介绍

在这里插入图片描述

六、通过按键来控制灯的亮灭

6.1.按键

以下是STM32F103zet6精英版的按键电路图,KEY_UP是按下输出高电平,上升沿,不按下的时候为高阻态,电平不稳定,因此要接一个下拉电阻;KEY0、KEY1与KEY_UP相反。

在这里插入图片描述

6.2.EXTI初始化函数

下面是代码EXTI.h,重新定义了一些引脚名称、中断编号和中断服务函数:

#ifndef __EXTI_H
#define __EXTI_H

#include "./SYSTEM/sys/sys.h"

#define KEY0_INT_GPIO_PORT              GPIOE
#define KEY0_INT_GPIO_PIN               GPIO_PIN_4
#define KEY0_INT_GPIO_CLK_ENABLE()      do{ __HAL_RCC_GPIOE_CLK_ENABLE(); }while(0)   
#define KEY0_INT_IRQn                   EXTI4_IRQn
#define KEY0_INT_IRQHandler             EXTI4_IRQHandler

#define KEY1_INT_GPIO_PORT              GPIOE
#define KEY1_INT_GPIO_PIN               GPIO_PIN_3
#define KEY1_INT_GPIO_CLK_ENABLE()      do{ __HAL_RCC_GPIOE_CLK_ENABLE(); }while(0)  
#define KEY1_INT_IRQn                   EXTI3_IRQn
#define KEY1_INT_IRQHandler             EXTI3_IRQHandler

#define WKUP_INT_GPIO_PORT              GPIOA
#define WKUP_INT_GPIO_PIN               GPIO_PIN_0
#define WKUP_INT_GPIO_CLK_ENABLE()      do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0) 
#define WKUP_INT_IRQn                   EXTI0_IRQn
#define WKUP_INT_IRQHandler             EXTI0_IRQHandler

void extix_init(void);

#endif

下面代码是EXTI.c:

void extix_init(void)
{
    GPIO_InitTypeDef gpio_init_struct;

    KEY0_GPIO_CLK_ENABLE();	//使能三个按键的时钟                          
    KEY1_GPIO_CLK_ENABLE();                                  
    WKUP_GPIO_CLK_ENABLE();                                 

    gpio_init_struct.Pin = KEY0_INT_GPIO_PIN;	   //KEY0 的初始化
    gpio_init_struct.Mode = GPIO_MODE_IT_FALLING;  //中断模式的下降沿触发         
    gpio_init_struct.Pull = GPIO_PULLUP;		   //上拉输入
    HAL_GPIO_Init(KEY0_INT_GPIO_PORT, &gpio_init_struct);  

    gpio_init_struct.Pin = KEY1_INT_GPIO_PIN;		//KEY1 的初始化
    gpio_init_struct.Mode = GPIO_MODE_IT_FALLING;   //中断模式的下降沿触发          
    gpio_init_struct.Pull = GPIO_PULLUP;			//上拉输入
    HAL_GPIO_Init(KEY1_INT_GPIO_PORT, &gpio_init_struct);    
    
    gpio_init_struct.Pin = WKUP_INT_GPIO_PIN;		//KEYUP 的初始化
    gpio_init_struct.Mode = GPIO_MODE_IT_RISING;    //中断模式上升沿触发          
    gpio_init_struct.Pull = GPIO_PULLDOWN;			//下拉输入
    HAL_GPIO_Init(WKUP_GPIO_PORT, &gpio_init_struct);        

	//下面优先级最高的是KEY0>KEY1>KEYUP
    HAL_NVIC_SetPriority(KEY0_INT_IRQn, 0, 2);              
    HAL_NVIC_EnableIRQ(KEY0_INT_IRQn);                      
    HAL_NVIC_SetPriority(KEY1_INT_IRQn, 1, 2);              
    HAL_NVIC_EnableIRQ(KEY1_INT_IRQn);                      
    HAL_NVIC_SetPriority(WKUP_INT_IRQn, 2, 2);               
    HAL_NVIC_EnableIRQ(WKUP_INT_IRQn);                    
}

三个中断函数共用一个回调函数,按下KEY0,实现LED0亮5秒钟然后熄灭;按下KEY1,实现LED0电平翻转;按下KEYUP,实现蜂鸣器电平翻转;由上面优先级可知,如果按下KEY0,在LED0亮的5秒之内,按其他两个按键是没有反应的;按下KEY1,再按下KEY0,KEY0就会抢占CUP操作权,这时再次按下KEY1也实现不了电平翻转了,蜂鸣器也同理。

void KEY0_INT_IRQHandler(void)						 //中断服务函数
{
    HAL_GPIO_EXTI_IRQHandler(KEY0_INT_GPIO_PIN);     //HAL库中断处理公共函数,里面包含HAL库数据处理回调函数
    __HAL_GPIO_EXTI_CLEAR_IT(KEY0_INT_GPIO_PIN);     //清除中断标志位
}

void KEY1_INT_IRQHandler(void)						 //中断服务函数
{ 
    HAL_GPIO_EXTI_IRQHandler(KEY1_INT_GPIO_PIN);	//HAL库中断处理公共函数,里面包含HAL库数据处理回调函数
    __HAL_GPIO_EXTI_CLEAR_IT(KEY1_INT_GPIO_PIN);	//清除中断标志位
}

void WKUP_INT_IRQHandler(void)						//中断服务函数
{ 
    HAL_GPIO_EXTI_IRQHandler(WKUP_INT_GPIO_PIN);    //HAL库中断处理公共函数,里面包含HAL库数据处理回调函数
    __HAL_GPIO_EXTI_CLEAR_IT(WKUP_INT_GPIO_PIN); 	//清除中断标志位      
}


void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
    delay_ms(20);    
    switch(GPIO_Pin)
    {
        case KEY0_INT_GPIO_PIN:
            if (KEY0 == 0)
            {
                HAL_GPIO_WritePin(LED1_GPIO_PORT, LED1_GPIO_PIN, GPIO_PIN_RESET);
                delay_ms(5000);
                HAL_GPIO_WritePin(LED1_GPIO_PORT, LED1_GPIO_PIN, GPIO_PIN_SET);
            }
            break;
        case KEY1_INT_GPIO_PIN:
            if (KEY1 == 0)
            {
                LED0_TOGGLE();  //LED0电平翻转
            }
            break;
        case WKUP_INT_GPIO_PIN:
            if (WK_UP == 1)
            {
                BEEP_TOGGLE(); 	//蜂鸣器电平翻转
            }
            break;
    }
}

6.3.主函数

#include "./stm32f1xx_it.h"
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "./BSP/BEEP/beep.h"
#include "./BSP/EXTI/exti.h"


int main(void)
{
    HAL_Init();                           
    sys_stm32_clock_init(RCC_PLL_MUL9);   
    delay_init(72);                       
    usart_init(115200);                    
    led_init();                           
    beep_init();                          
    extix_init();                        
    LED0(0);                               

    while (1)
    {
        printf("OK\r\n");
        delay_ms(1000);
    }
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值