STM32HAL库-中断篇

中断

中断简介

中断是一种事件处理机制,可以暂停主程序的运行,转而处理特定事件程序。

中断的作用和意义:

实时控制 在确定事件内对响应事件做出相应

故障处理 检测到故障需要第一时间处理

数据传输 如串口通信,不确定数据何时会来

意义:高效处理紧急程序,不会一直占用cpu资源
GPIO外部中断简图

信号从外部进来,首先遇到的外设是GPIO

然后会走到SYSCFG系统配置控制器,将GPIOXy映射到EXTIy

SYSCFG系统配置控制器是一种常见外设,其作用是配置系统的各种控制和配置寄存器,用于管理系统的各种配置参数和功能。

接着信号进入到EXTI来判断是否触发响应

触发EXTI响应后会进入NVIC判断响应优先级

最后优先级高的先进入CPU去处理中断

NVIC

NVIC基本概念

Nested vectored interrupt controlelr,嵌套向量中断控制器,属于内核的一部分(M3/4/7)

NVIC支持256个中断(16个内核+240外部),支持256个优先级,允许裁剪

中断向量表

中断向量表是一块固定的内存,4字节对齐,存放各个中断对应的中断服务函数首地址

中断向量表定义在启动文件,也就是.s文件里,当发生中断,CPU会自动执行对应的中断服务函数

.s文件中__Vectors标注的代码块就是中断向量表开始的地方

DCD表示4字节对齐

NVIC相关寄存器介绍

中断使能寄存器 ISER,Interrupt Set-Enable Register

中断除能寄存器 ICER,Interrupt Clean-Enable Register

应用程序中断及复位控制寄存器 AIRCR,Application Interrupt and Reset Controller Register

中断优先级寄存器 IPR,Interrupt Priority Register

NVIC还有中断挂起、解挂、激活标志等不常用的功能,此处不做介绍

NVIC工作原理

经过EXTI判断后触发响应的外部中断会来到中断使能寄存器(ISER)中断失能寄存器(ICER),

再走到中断优先级寄存器(IPR),经过优先级判断后交由CPU处理

内核中断直接到达SHPR

SHPR,System Handler Priority Registers 系统处理器优先级寄存器

STM32中断优先级基本概念

1、抢占优先级(pre):高抢占优先级可以打断正在执行的低抢占优先级的中断

2、响应优先级(sub):当抢占优先级相同时,响应优先级高的先执行,但是不能互相打断

3、自然优先级:抢占和响应都相同的话,自然优先级高的先执行。自然优先级看向量表上到下

STM32中断优先级分组

NVIC中通过AIRCR的[10:8]位,可支持8种优先级分组

STM32只用到了其中5个。

如果优先级分组为0,

则IPR的[7:4]位全部用于响应优先级配置,也就是0个抢占优先级,2^4=16个响应优先级

如果分组为1,

则IPR的[7]位用于抢占优先级,[6,4]用于分配响应优先级,也就是2个抢占优先级,8个响应优先级

以此推类。

一个工程中一般只设置一次优先级分组。设置多次会以最后一次为准。

STM32 NVIC的使用

使用流程:

1、设置中断分组 AIRCR[10:8],HAL_NVIC_SetPriorityGrouping

2、设置中断优先级 IPRx bit[7:4],HAL_NVIC_SetPriority //STM32只用了IPR的高四位

3、使能中断 ISERx,HAL_NVIC_EnableIRQ

cpp 复制代码
 /* Set Interrupt Group Priority */
  HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_2);    //设置中断优先级分组为2
 /****************************************************************/
  HAL_NVIC_SetPriority(KEY0_INT_IRQn, 0, 2);               /* 抢占0,子优先级2 */
  HAL_NVIC_EnableIRQ(KEY0_INT_IRQn);                       /* 使能中断线3 */
cpp 复制代码
//设置中断优先级分组源代码
__STATIC_INLINE void __NVIC_SetPriorityGrouping(uint32_t PriorityGroup)
{
  uint32_t reg_value;
  uint32_t PriorityGroupTmp = (PriorityGroup & (uint32_t)0x07UL);             /* only values 0..7 are used          */

  reg_value  =  SCB->AIRCR;                                                   /* read old register configuration    */
  reg_value &= ~((uint32_t)(SCB_AIRCR_VECTKEY_Msk | SCB_AIRCR_PRIGROUP_Msk)); /* clear bits to change               */
  reg_value  =  (reg_value                                   |
                ((uint32_t)0x5FAUL << SCB_AIRCR_VECTKEY_Pos) |
                (PriorityGroupTmp << SCB_AIRCR_PRIGROUP_Pos)  );              /* Insert write key and priority group */
  SCB->AIRCR =  reg_value;
}
cpp 复制代码
//设置中断优先级源代码
__STATIC_INLINE void __NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority)
{
  if ((int32_t)(IRQn) >= 0)
  {
    NVIC->IP[((uint32_t)IRQn)]               = (uint8_t)((priority << (8U - __NVIC_PRIO_BITS)) & (uint32_t)0xFFUL);
  }
  else
  {
    SCB->SHP[(((uint32_t)IRQn) & 0xFUL)-4UL] = (uint8_t)((priority << (8U - __NVIC_PRIO_BITS)) & (uint32_t)0xFFUL);
  }
}

设置中断优先级源代码中的

NVIC->IP[IRQn] 代表设置外部中断优先级的 IPR 寄存器小知识,IP[0]就是WWG窗口看门口

SCB->SHP[IRQn] 代表设置内核中断优先级的SHPR寄存器

EXTI

EXTI基本概念

External(Extended) interrupt/event Controlelr 外部(扩展)中断事件控制器

如F4系列包含23个产生事件/中断请求的边沿检测器,即总共:23条EXTI线

中断和事件的区别:

中断要进入NVIC,有响应的中断服务函数,需要CPU处理

事件不进入NVIC,仅用于内部硬件自动控制,如:TIM、DMA、ADC
EXTI支持的外部中断/事件请求

EXTI主要特性

F1/F4/F7系列每条EXTI线都可以单独配置:

选择类型(中断或者事件)

触发方式(上升沿、下降沿、双边)

支持软件触发

开启/屏蔽

有挂起状态位

H7系列

由其他外设对EXTI产生的事件可分为可配置事件和直接事件

可配置事件:基本和F1/F4/F7类似

直接时间:固定上升沿触发、不支持软件触发、无挂起状态位(由其他外设提供)

EXTI工作原理(F4)

一个信号从EXTI线输入进来,首先到达****边沿检测线路

接着到达****软硬件触发选择线路

最后到达中断屏蔽/清除(挂起)线路或****事件屏蔽线路

**边沿检测线路:**通过EXTI_RTSR/EXTI_FTSR上升下降触发选择寄存器的配置来决定是否允许信号通过。比如上升下降两个选择寄存器对应位都置1,代表来了上升沿或者下降沿都允许通过

**软硬件触发选择:**然后信号经过或门,不管是选择软件触发还是硬件触发,只要触发了到左边就是1

**中断屏蔽/清除(挂起):**之后可以通过与门,也可以通过请求挂起寄存器,如果到达EXTI_PR请求挂起寄存器的信号是1,就会自动让EXTI_PR请求挂起寄存器置 1;如果EXTI_IMR屏蔽掉了,输出的就是0,与门得到的也是0,中断信号就无法到达NVIC也就无法产生中断

**事件屏蔽线路:**EXTI_EMR事件屏蔽寄存器如果屏蔽了信号,则与门会让信号无法到达脉冲发生器,也就无法使外设产生事件(触发外设功能)

上升/下降沿触发选择寄存器 EXTI_RTSR/EXTI_FTSR (Rising/Falling trigger selection register)

上升/下降沿触发选择器RTSR为32位,用到了20位,每一位TRx控制一个线x的上升沿中断/事件触发的允许和禁止

软件中断事件寄存器 EXTI_SWIER*(Software Interrupt Event Register)*

挂起寄存器 EXTI_PR*(Pending Register)*

请求挂起寄存器可以挂起/清除中断标志位。32位寄存器,20位有效,如果外部中断线上发生了选择的边沿事件,该位被置1,也就是硬件触发中断,清除时主动往该位写1,或改变边沿触发的极性(上升沿改下降沿)

中断屏蔽寄存器 EXTI_IMR*(Interrupt Mask Register)*

IMR事件屏蔽寄存器是32位,只用到了20位,想屏蔽或者开启某条线的中断置1获置0即可

事件屏蔽寄存器 EXTI_EMR*(Event Mask Register)*

EXTI_RTSR 和 EXTI_FTSR都是32位的,具体多少位有效就看有多少EXTI线,比如F1系列有20根EXTI线那么RTSR和FTSR就是20位有效,F4系列有23根EXTI线就是23位有效
STM32F4的EXTI控制器框图

EXTI和IO的映射关系

SYSCFG简介(F4/F7/H7)

System configuration controller,即系统配置寄存器,用于外部中断映射配置等功能*(F1为AFIO模块)*

SYSCFG模块的外部中断配置主要使用SYSCFG_EXTICR1~4,配置EXTI中断线0~15对应到哪个具体IO口

SYSCFG_EXTICR1~4 (Configuration Register)

特别注意:配置SYSCFG寄存器之前要使能SYSCFG时钟,方法如下:

__HAL_RCC_SYSCFG_CLK_ENABLE();

EXTI与IO对应关系

EXTI0与Px0对应

当Px0映射到EXTI0时,其他分组的Pin0就不能映射到EXTI0了

可以看到EXTI0与引脚号为0的IO口对应。SYSCFG有SYSCFG_EXTICR1~4共4个外部中断配置寄存器,即EXTI0~EXTI15,EXTIx寄存器的[3:0]位用来选择相同x引脚号的不同分组。以EXTICR0为例,外部中断配置寄存器EXTICR0为32位,高16位保留,低16位拆分为4个EXTI寄存器,控制不同分组外部中断引脚的映射。EXTI0可选择对应A0、B0~I0,EXTI1可选择A1、B1~I1,依次推类
SYSCFG外部中断配置寄存器1

如何使用中断

既然是GPIO外部中断,肯定首先是要使用GPIO的,

1、设置GPIO的输入模式,比如上拉、下拉、浮空

经过GPIO以后又和EXTI有一个映射关系,就需要

2、使用AFIO或SYSCFG配置IO引脚和EXTI线映射

3、之后针对EXTI可以设置中断的屏蔽、打开,上升下降双边沿触发方式

4、经过EXTI后需要使用NVIC来设置中断分组、中断优先级和中断使能

5、最后CPU会按照优先级顺序依次处理中断

需要到达EXTI中断的可以统称为EXTI中断

GPIO中断对应的是EXTI0~15这16根线,而EXTI16 ~23是来自其他外设,如RTC闹钟事件、USB唤醒事件等等,这些EXTI16之后的外部中断不经过SYSCFG直接到达EXTI

不经过EXTI直接到达NVIC的中断统称为外设中断

USART/TIM/SPI等外设中断则不需要经过EXTI,直接由外设自己的寄存器进行中断开启和触发方式选择,可以直接到达NVIC

EXTI的HAL库配置步骤(GPIO外部中断)

1、使能GPIO时钟**__HAL_RCC_GPIOx_CLK_ENABLE**

/*******************************************HAL_GPIO_Init() 一步到位**********************************/

2、设置GPIO输入模式 上/下拉/浮空输入

3、设置AFIO/SYSCFG时钟 设置AFIO/SYSCFG时钟开启寄存器

4、设置EXTI和IO对应关系 AFIO_EXTICR/SYSCFG_EXTICR

5、设置EXTI屏蔽,上下沿触发方式 IMR设置EXTI对应通道的屏蔽,、RTSR/FTSR设 置上升沿/下降沿触发方式

/*********************************************HAL_GPIO_Init()一步到位**********************************/

6、设置NVIC 设置优先级分组、设置优先级、中断使能****HAL_NVIC_SetPriorityGrouping

HAL_NVIC_EnableIRQ

7、设置中断服务函数 编写对应中断服务函数,清中断标志

EXTIx_IRQHandler

STM32仅有7个外部中断服务函数,EXTI 0~4有5个中断服务函数,EXTI 9_5共用一个中断服务函数,EXTI 15_10共用一个中断服务函数

通用外设驱动模型(四步法)

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

HAL库的中断回调处理异常复杂,知道大概原理即可,按需去看即可

大概流程就是从main()进入到中断服务函数HAL库中断处理公用函数HAL库数据处理回调函数,再依次往上回到上一级函数,最后回到main函数

通过外部中断控制一个灯亮灭

Key按键原理图

首先分析按键IO应该配置为什么模式

KEY_UP外接高电平,连接时应该给到一个上升沿触发,断开的时候是高阻态,高阻态电平不确定,因此为了确保稳定性初始状态应该配置为下拉输入

KEY0、KEY1、KEY2这三个按键是反过来的,按下时输入低电平,会给到一个下降沿触发,因此初始状态应该配置成上拉输入

当外部中断产生之后会调用中断服务函数,中断处理函数会调用中断处理公用函数,处理公用函数会调用数据处理回调函数

通常重写数据处理回调函数处理自己的业务逻辑

cpp 复制代码
#define KEY0_INT_IRQHandler             EXTI3_IRQHandler
#define __HAL_GPIO_EXTI_GET_IT(__EXTI_LINE__) (EXTI->PR & (__EXTI_LINE__))

/**
  * @brief  This function handles EXTI interrupt request.
  * @param  GPIO_Pin Specifies the pins connected EXTI line
  * @retval None
  */
//Pin口引脚与EXTI线由SYSCFG_EXTI1~3的EXTI0~3(共0~15)进行映射
//映射规则不可能有两条EXTI线映射到相同的Pin号引脚
//PR寄存器的20位有效每一位对应被映射到EXTI的引脚Pin号。因此Pin号确定PR寄存器的对应位也就可确定
void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin) //***中断处理公用函数***
{
  /* EXTI line interrupt detected */
  if(__HAL_GPIO_EXTI_GET_IT(GPIO_Pin) != RESET)
  {
    __HAL_GPIO_EXTI_CLEAR_IT(GPIO_Pin);
    HAL_GPIO_EXTI_Callback(GPIO_Pin);  //***数据处理回调函数***
  }
}

void KEY0_INT_IRQHandler(void)          //***中断服务函数***
{
    HAL_GPIO_EXTI_IRQHandler(KEY0_INT_GPIO_PIN);         /* 调用中断处理公用函数,清除中断标志位 */
    __HAL_GPIO_EXTI_CLEAR_IT(KEY0_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)
            {
                LED1_TOGGLE();                          /* LED1状态取反 */ 
                LED0_TOGGLE();                          /* LED0状态取反 */ 
            }
            break;

        case KEY1_INT_GPIO_PIN:
            if (KEY1 == 0)
            {
                LED1_TOGGLE();                          /* LED1 状态取反 */ 
            }
            break;

        case KEY2_INT_GPIO_PIN:
            if (KEY2 == 0)
            {
                LED0_TOGGLE();                          /* LED0 状态取反 */ 
            }
            break;

        case WKUP_INT_GPIO_PIN:
            if (WK_UP == 1)
            {
                LED1_TOGGLE();                          /* LED1状态取反 */

                if (HAL_GPIO_ReadPin(LED1_GPIO_PORT, LED1_GPIO_PIN) == 1)
                 {
                   LED0(0);
                 }
                 else 
                 {
                     LED0(1);
                 }
            }
            break;

        default : break;
    }
}
相关推荐
智商偏低3 小时前
单片机之helloworld
单片机·嵌入式硬件
青牛科技-Allen4 小时前
GC3910S:一款高性能双通道直流电机驱动芯片
stm32·单片机·嵌入式硬件·机器人·医疗器械·水泵、
森焱森6 小时前
无人机三轴稳定控制(2)____根据目标俯仰角,实现俯仰稳定化控制,计算出升降舵输出
c语言·单片机·算法·架构·无人机
白鱼不小白6 小时前
stm32 USART串口协议与外设(程序)——江协教程踩坑经验分享
stm32·单片机·嵌入式硬件
S,D7 小时前
MCU引脚的漏电流、灌电流、拉电流区别是什么
驱动开发·stm32·单片机·嵌入式硬件·mcu·物联网·硬件工程
芯岭技术10 小时前
PY32F002A单片机 低成本控制器解决方案,提供多种封装
单片机·嵌入式硬件
youmdt10 小时前
Arduino IDE ESP8266连接0.96寸SSD1306 IIC单色屏显示北京时间
单片机·嵌入式硬件
嘿·嘘10 小时前
第七章 STM32内部FLASH读写
stm32·单片机·嵌入式硬件
Meraki.Zhang10 小时前
【STM32实践篇】:I2C驱动编写
stm32·单片机·iic·驱动·i2c
几个几个n13 小时前
STM32-第二节-GPIO输入(按键,传感器)
单片机·嵌入式硬件