您现在的位置是:首页 >学无止境 >009 - STM32学习笔记 - 中断网站首页学无止境

009 - STM32学习笔记 - 中断

宥小稚 2024-07-19 18:01:01
简介009 - STM32学习笔记 - 中断

009 - STM32学习笔记 - 中断

这节的内容,野火的官方视频我反复看了好几次,但是感觉火哥在这块讲解的特别绕,理解起来很吃力,后来在看了一下其他老师的视频,结合一些书本资料和官方手册,才搞清楚STM32中断该怎么使用。

1、中断概念

什么是中断?主线程序正常运行过程中,当中断信号产生时,系统先暂停主线程序,转去执行中断程序,当中断程序执行完后,在转回主线程序继续运行。

举个比较通俗的例子来说,加入一个人正在田里干活,家里人过来喊吃中午饭,此人就将手里的活放下回家吃饭,等吃完饭后在回来继续劳作。这里面田里干活就是主线程序,回家吃饭就是中断程序,而对应的家里人过来喊的这个动作,就是中断信号。

所以主线程序我们可以认为是正常运行的程序,中间插入的一些事情我们认为是突发的异常,只不过异常产生的时间不确定而已,那么在正常运行的程序过程中,可能会产生很多突发异常,可能有些异常是同时产生的,那么当出现多个同时产生的异常时,系统该如何处理呢?

用上一个例子来说明,正在田里干活,突然觉得肚子不舒服急需解手(例子而已,请勿介意),而此时家里人过来喊吃饭,那么该优先做那件事呢?当然是优先解手,等完事后再去吃饭(毕竟吃饭可以晚一会再去,肚子不舒服当然先解决咯)。那么事情就可以理顺了,先解手,在吃饭,吃完饭后再下地干活(解手后记得洗手哈!)。

所以这里就引出来一个概念,优先,当多个中断同时出现时,优先处理哪件事,就需要确定这些事的优先级

看一下STM32中提供出来的中断,这里将stm32提供的中断打断分为两部分说明,如下表:

系统异常(内核)

位置优先级优先级 类型名称说明地址
---保留0x0000 0000
-3固定Reset复位0x0000 0004
-2固定NMI不可屏蔽中断,时钟安全系统0x0000 0008
-1固定HardFault所有类型的错误0x0000 000C
0固定MemManageMPU 不匹配0x0000 0010
1可设置BusFault预取指失败,存储器访问失败0x0000 0014
2可设置UsageFault未定义的指令或非法状态0x0000 0018
---保留0x0000 001C - 0x0000 002B
3可设置SVCall通过 SWI 指令调用的系统服务0x0000 002C
4可设置Debug Monitor调试监控器0x0000 0030
--保留0x0000 0034
5可设置PendSV可挂起的系统服务0x0000 0038
6可设置Systick系统嘀嗒定时器0x0000 003C

外部中断(外设)

位置优先级优先级 类型名称说明地址
07可设置WWDG窗口看门狗中断0x0000 0040
18可设置PVD连接到 EXTI 线的可编程电压检测 (PVD) 中断0x0000 0044
29可设置TAMP_STAMP连接到 EXTI 线的入侵和时间戳中断0x0000 0048
310可设置RTC_WKUP连接到 EXTI 线的 RTC 唤醒中断0x0000 004C
411可设置FLASHFlash 全局中断0x0000 0050
512可设置RCCRCC 全局中断0x0000 0054
613可设置EXTI0EXTI 线 0 中断0x0000 0058
714可设置EXTI1EXTI 线 1 中断0x0000 005C
815可设置EXTI2EXTI 线 2 中断0x0000 0060
916可设置EXTI3EXTI 线 3 中断0x0000 0064
1017可设置EXTI4EXTI 线 4 中断0x0000 0068
1118可设置DMA1_Stream0DMA1 流 0 全局中断0x0000 006C
1219可设置DMA1_Stream1DMA1 流 1 全局中断0x0000 0070
1320可设置DMA1_Stream2DMA1 流 2 全局中断0x0000 0074
1421可设置DMA1_Stream3DMA1 流 3 全局中断0x0000 0078
1522可设置DMA1_Stream4DMA1 流 4 全局中断0x0000 007C
1623可设置DMA1_Stream5DMA1 流 5 全局中断0x0000 0080
1724可设置DMA1_Stream6DMA1 流 6 全局中断0x0000 0084
1825可设置ADCADC1、 ADC2 和 ADC3 全局中断0x0000 0088
1926可设置CAN1_TXCAN1 TX 中断0x0000 008C
2027可设置CAN1_RX0CAN1 RX0 中断0x0000 0090
2128可设置CAN1_RX1CAN1 RX1 中断0x0000 0094
2229可设置CAN1_SCECAN1 SCE 中断0x0000 0098
2330可设置EXTI9_5EXTI 线 [9:5] 中断0x0000 009C
2431可设置TIM1_BRK_TIM9TIM1 刹车中断和 TIM9 全局中断0x0000 00A0
2532可设置TIM1_UP_TIM10TIM1 更新中断和 TIM10 全局中断0x0000 00A4
2633可设置TIM1_TRG_COM_TIM11TIM1 触发和换相中断与 TIM11 全局 中断0x0000 00A8
2734可设置TIM1_CCTIM1 捕获比较中断0x0000 00AC
2835可设置TIM2TIM2 全局中断0x0000 00B0
2936可设置TIM3TIM3 全局中断0x0000 00B4
3037可设置TIM4TIM4 全局中断0x0000 00B8
3138可设置I2C1_EVI2C1 事件中断0x0000 00BC
3239可设置I2C1_ERI2C1 错误中断0x0000 00C0
3340可设置I2C2_EVI2C2 事件中断0x0000 00C4
3441可设置I2C2_ERI2C2 错误中断0x0000 00C8
3542可设置SPI1SPI1 全局中断0x0000 00CC
3643可设置SPI2SPI2 全局中断0x0000 00D0
3744可设置USART1USART1 全局中断0x0000 00D4
3845可设置USART2USART2 全局中断0x0000 00D8
3946可设置USART3USART3 全局中断0x0000 00DC
4047可设置EXTI15_10EXTI 线 [15:10] 中断0x0000 00E0
4148可设置RTC_Alarm连接到 EXTI 线的 RTC 闹钟( A 和 B) 中断0x0000 00E4
4249可设置OTG_FS WKUP连接到 EXTI 线的 USB On-The-Go FS 唤醒中断0x0000 00E8
4350可设置TIM8_BRK_TIM12TIM8 刹车中断和 TIM12 全局中断0x0000 00EC
4451可设置TIM8_UP_TIM13TIM8 更新中断和 TIM13 全局中断0x0000 00F0
4552可设置TIM8_TRG_COM_TIM14TIM8 触发和换相中断与 TIM14 全局 中断0x0000 00F4
4653可设置TIM8_CCTIM8 捕获比较中断0x0000 00F8
4754可设置DMA1_Stream7DMA1 流 7 全局中断0x0000 00FC
4855可设置FSMCFSMC 全局中断0x0000 0100
4956可设置SDIOSDIO 全局中断0x0000 0104
5057可设置TIM5TIM5 全局中断0x0000 0108
5158可设置SPI3SPI3 全局中断0x0000 010C
5259可设置UART4UART4 全局中断0x0000 0110
5360可设置UART5UART5 全局中断0x0000 0114
5461可设置TIM6_DACTIM6 全局中断, DAC1 和 DAC2 下溢错误中断0x0000 0118
5562可设置TIM7TIM7 全局中断0x0000 011C
5663可设置DMA2_Stream0DMA2 流 0 全局中断0x0000 0120
5764可设置DMA2_Stream1DMA2 流 1 全局中断0x0000 0124
5865可设置DMA2_Stream2DMA2 流 2 全局中断0x0000 0128
5966可设置DMA2_Stream3DMA2 流 3 全局中断0x0000 012C
6067可设置DMA2_Stream4DMA2 流 4 全局中断0x0000 0130
6168可设置ETH以太网全局中断0x0000 0134
6269可设置ETH_WKUP连接到 EXTI 线的以太网唤醒中断0x0000 0138
6370可设置CAN2_TXCAN2 TX 中断0x0000 013C
6471可设置CAN2_RX0CAN2 RX0 中断0x0000 0140
6572可设置CAN2_RX1CAN2 RX1 中断0x0000 0144
6673可设置CAN2_SCECAN2 SCE 中断0x0000 0148
6774可设置OTG_FSUSB On The Go FS 全局中断0x0000 014C
6875可设置DMA2_Stream5DMA2 流 5 全局中断0x0000 0150
6976可设置DMA2_Stream6DMA2 流 6 全局中断0x0000 0154
7077可设置DMA2_Stream7DMA2 流 7 全局中断0x0000 0158
7178可设置USART6USART6 全局中断0x0000 015C
7279可设置I2C3_EVI2C3 事件中断0x0000 0160
7380可设置I2C3_ERI2C3 错误中断0x0000 0164
7481可设置OTG_HS_EP1_OUTUSB On The Go HS 端点 1 输出全局 中断0x0000 0168
7582可设置OTG_HS_EP1_INUSB On The Go HS 端点 1 输入全局 中断0x0000 016C
7683可设置OTG_HS_WKUP连接到 EXTI 的 USB On The Go HS  唤醒中断0x0000 0170
7784可设置OTG_HSUSB On The Go HS 全局中断0x0000 0174
7885可设置DCMIDCMI 全局中断0x0000 0178
7986可设置CRYPCRYP 加密全局中断0x0000 017C
8087可设置HASH_RNG哈希和随机数发生器全局中断0x0000 0180
8188可设置FPUFPU 全局中断0x0000 0184
8289可设置UART7UART 7 全局中断0x0000 0188
8390可设置UART8UART 8 全局中断0x0000 018C
8491可设置SPI4SPI 4 全局中断0x0000 0190
8592可设置SPI5SPI 5 全局中断0x0000 0194
8693可设置SPI6SPI 6 全局中断0x0000 0198
a、优先级

在上面的表中,需要注意的是,系统已经给各种中断(异常)给出了默认优先级,数字越小,优先级越高。而且再优先级类型中明确说明了哪些优先级可以设置,哪些优先级不可设置。也就是说,凡是标明了中断优先级类型为可设置的,则表明用户可以通过修改优先级级别,从而决定中断执行的先后顺序。

b、中断地址

当单片机产生中断信号的时候,系统需要响应这个中断,对应的在表中最后一栏,看到每个对应的中断均有一个地址,需要注意的是,这个地址不是绝对地址,而是偏移地址,这里牵扯到地址重映射,后面学到了在分享。

c、中断服务子程序

在启动代码那一节中提到中断服务程序,当启动代码执行后,会映射所有中断服务程序,即我们说的中断向量表,在向量表中包含了所有内核和外设的中断服务函数名,这里需要注意,后续在用到中断服务程序时,一定要确保编写的中断服务程序名和向量表中的程序名一致,否则程序不会报错,但是当中断产生时,并不会去执行写好的中断服务程序,而是执行系统默认的程序,而系统默认的为无限空循环,那程序就会死到那里,不会出现响应。

2、中断向量控制器(NVIC)

中断向量控制器(Nested vectored interrupt controller)属于内核外设,主要作用是管理包括内核和片上所有外设的中断相关功能。

使用到的两个库文件为:core_cm4.hmisc.h,NVIC寄存器位于core_cm4.h中,固件库中NVIC预留了很多位,我猜大概率时为了后期扩展使用,目前我用到的固件库版本还比较老,一共才90多个中断,有兴趣的话可以去找一找最新的固件库看一下。

typedef struct
{
  __IO uint32_t ISER[8];                 /*!< 偏移量: 0x000 (读/写)  中断使能寄存器 			*/
       uint32_t RESERVED0[24];
  __IO uint32_t ICER[8];                 /*!< 偏移量: 0x080 (读/写)  中断清除寄存器         	*/
       uint32_t RSERVED1[24];
  __IO uint32_t ISPR[8];                 /*!< 偏移量: 0x100 (读/写)  中断使能悬起寄存器		  */
       uint32_t RESERVED2[24];
  __IO uint32_t ICPR[8];                 /*!< 偏移量: 0x180 (读/写)  中断清除悬起寄存器         */
       uint32_t RESERVED3[24];
  __IO uint32_t IABR[8];                 /*!< 偏移量: 0x200 (读/写)  终端有效位寄存器           */
       uint32_t RESERVED4[56];
  __IO uint8_t  IP[240];                 /*!< 偏移量: 0x300 (读/写)  中断优先级寄存器           */
       uint32_t RESERVED5[644];
  __O  uint32_t STIR;                    /*!< 偏移量: 0xE00 (只 读)  软件触发中断寄存器         */
}  NVIC_Type;

这里学习一下几个常用的寄存器

中断优先级寄存器(NVIC_IPRx)

在这里插入图片描述

这个图中,按照中断优先级分为IP0-IP80(实际目前我使用的内核中断已经增加到93,看过野火官方出的教材上,中断源已经增加到97了),每个中断的IPx寄存器都有8位位宽,原则来说,每个中断的优先级可配置范围位0~255,其值越小,优先级越高,但是实际起到作用的只有4位。

在这里插入图片描述
此表中,可以看到实际起到作用的只有高4位,低4位并没有使用,STM32官方为了表示这4位,将优先级分为主(抢占)优先级和子优先级,两个中断同时触发时,主优先级高的先抢占执行权,主优先级相同时,则子优先级高的限制性,当然也可能存在主优先级和子优先级相同的情况,这个STM32官方也给出了解决办法,就是硬件编号越小的,优先级越高。

因此根据主优先级和子优先级的分类,STM32将优先级分为5组分别如下
在这里插入图片描述
可以用中标准库函数NVIC_PriorityGroupConfig() 来实现优先级的分组

3、EXTI外部中断/事件控制器

在STM32中还提供了一个EXTI外部中断/事件控制器,用于产生事件/中断请求的边沿检测,数量有23个,每个输入线都可以进行单独配置,可以通过寄存器选择中断/事件类型,和相应的触发条件(上升沿触发、下降沿触发和边沿触发),并且也可以根据需求单独进行屏蔽。
在这里插入图片描述

EXTI控制器框图
typedef struct
{
      uint32_t EXTI_Line;               /* 选择输入线 */
      EXTIMode_TypeDef EXTI_Mode;       /*!< 触发模式:可选为外部中断或者事件模式 */
      EXTITrigger_TypeDef EXTI_Trigger; /*!< 触发方式:上升沿、下降沿、边沿检测 */
      FunctionalState EXTI_LineCmd;     /*!< 使能位 */ 
}EXTI_InitTypeDef;

以中断触发按键检测为例,对EXTI进行分析:

a、EXTI_Line外部中断/事件线

F429中168个GPIO通过下面的方式连接到16个外部中断/事件控制器上

在这里插入图片描述
其中将GPIOA-GPIO中以引脚号从0-15分别挂在在EXTI0-EXIT15

我使用的开发板上,可以操作的按键电路GPIO有两个,分别为PA0和PC13,PA0是连接到EXTI0,PC13连接到EXTI13上,因此PA0的外部中断/事件线应该选择EXTI_Line0,而PC13则选择EXTI_Line13。

b、EXTI_Mode,外部中断/事件模式控制器
typedef enum
{
      EXTI_Mode_Interrupt = 0x00,	//中断模式
      EXTI_Mode_Event = 0x04		//事件模式
}EXTIMode_TypeDef;

这个直接选择中断模式EXTI_Mode_Interrupt就行。

c、EXTI_Trigger外部中断/事件触发方式
typedef enum
{
      EXTI_Trigger_Rising = 0x08,			//上升沿触发
      EXTI_Trigger_Falling = 0x0C,  		//下降沿触发
      EXTI_Trigger_Rising_Falling = 0x10	//边沿触发
}EXTITrigger_TypeDef;

这里我选择的是上升沿触发EXTI_Trigger_Rising

在将上面几个寄存器设置好后,就可以使用中断使能控制器``EXTI_LineCmd`对外部中断/事件进行使能。

下面将程序关键部分代码贴上来,里面详细的可以看一下注释,程序内容是:按键按下,触发中断,控制LED灯亮灭。

关于LED灯的配置和使用,可以看一下之前关于LED灯的文章,这里着重说一下按键中断的相关配置和程序。

创建bsp_exti_key.c和bsp_exti_key.h

bsp_exti_key.h

#ifndef __BSP_KEY_H__
#define __BSP_KEY_H__

#include "stm32f4xx.h"
#include "stm32f4xx_gpio.h"
#include "stm32f4xx_rcc.h"

#define KEY1_PORT               GPIOA					//宏定义KEY1按键端口
#define KEY1_PIN                GPIO_Pin_0				//宏定义KEY1按键引脚号
#define KEY1_CLK                RCC_AHB1Periph_GPIOA	 //宏定义KEY1时钟

#define KEY1_EXTI_IRQ           EXTI0_IRQn				//配置KEY1中断源

#define KEY2_PORT               GPIOC					//宏定义KEY1按键端口
#define KEY2_PIN                GPIO_Pin_13				//宏定义KEY1按键引脚号
#define KEY2_CLK                RCC_AHB1Periph_GPIOC	 //宏定义KEY1时钟

#define KEY2_EXTI_IRQ           EXTI15_10_IRQn			//配置KEY2中断源

#define KEY1_EXTI_PROTSOURCE    EXTI_PortSourceGPIOA	 //宏定义KEY1端口源
#define KEY1_EXTI_PINSOURCE     EXTI_PinSource0			//宏定义KEY1引脚源	
#define KEY1_EXTI_LINE          EXTI_Line0				//宏定义KEY1输入线

#define KEY2_EXTI_PROTSOURCE    EXTI_PortSourceGPIOC	//宏定义KEY2端口源
#define KEY2_EXTI_PINSOURCE     EXTI_PinSource13		//宏定义KEY2引脚源	
#define KEY2_EXTI_LINE          EXTI_Line13				//宏定义KEY2输入线

#define KEY1_Handler    EXTI0_IRQHandler				//宏定义KEY1中断服务函数

#define KEY2_Handler    EXTI15_10_IRQHandler			//宏定义KEY2中断服务函数

#define KEY_ON  1
#define KEY_OFF 0

void EXTI_Key_Config(void);
uint8_t Key_Scan(GPIO_TypeDef *GPIOx,uint16_t GPIO_Pin);

#endif  /*__BSP_KEY_H__*/

这里需要注意的是在中断源的配置上,看到PA0的中断源是EXTI0_IRQn,而PC13的中断源配置为EXTI15_10_IRQn,这里可以看一下stm32f4xx.h中对于IRQn_Type结构体定义,这里就不大篇幅贴源码了。

bsp_exti_key.h

#include "bsp_exti_key.h"

static void NVIC_Config(void)				//配置中断向量表
{
    /*创建NVIC_InitTypeDef结构体*/
    NVIC_InitTypeDef NVIC_InitStructure;
    
    /*设置NVIC为优先级组1*/
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
    
    /*设置主优先级为1*/
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    
    /*设置子优先级为1*/
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    
    /*使能中断通道*/
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    
    /*配置中断源为按键1*/
    NVIC_InitStructure.NVIC_IRQChannel = KEY1_EXTI_IRQ;
    
    /*初始化中断*/
    NVIC_Init(&NVIC_InitStructure);
    
    /*配置中断源为按键2,其余沿用上面的配置*/
    NVIC_InitStructure.NVIC_IRQChannel = KEY2_EXTI_IRQ;
    
    /*初始化中断*/
    NVIC_Init(&NVIC_InitStructure);
}

void EXTI_Key_Config(void)
{
    /*--------------------------------GPIO引脚功能配置--------------------------------------*/
    GPIO_InitTypeDef KEY_InitStruct;
    /* 定义EXTI结构体 */
    EXTI_InitTypeDef EXTI_InitStructure;
    
    RCC_AHB1PeriphClockCmd(KEY1_CLK|KEY2_CLK,ENABLE);
    
    KEY_InitStruct.GPIO_Mode = GPIO_Mode_IN;
    
    KEY_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
    
    KEY_InitStruct.GPIO_Pin = KEY1_PIN;
    
    GPIO_Init(KEY1_PORT,&KEY_InitStruct);

    KEY_InitStruct.GPIO_Pin = KEY2_PIN;
    
    GPIO_Init(KEY2_PORT,&KEY_InitStruct);
    /************************************配置NVIC向量控制器*********************************/
    NVIC_Config();
    
    /******************************配置EXTI外部中断/事件控制器*******************************/
    /*使能SYSCFG时钟,使用GPIO外部中断时,必须使能SYSCFG时钟 */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG,ENABLE);
    
    /* 将EXTI连接到按键1引脚 */
    SYSCFG_EXTILineConfig(KEY1_EXTI_PROTSOURCE,KEY1_EXTI_PINSOURCE);
    
    /* 将EXTI连接到按键1引脚 */
    SYSCFG_EXTILineConfig(KEY2_EXTI_PROTSOURCE,KEY2_EXTI_PINSOURCE);
    
    /* 设置EXTI模式为外部中断模式 */
    EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
    
    /* 设置EXTI触发模式为上升沿 */
    EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
    
    /* 使能EXTI */
    EXTI_InitStructure.EXTI_LineCmd = ENABLE;
    
    /* 设置引脚为KEY1 */
    EXTI_InitStructure.EXTI_Line = KEY1_EXTI_LINE;
    
    /* 初始化KEY1 */
    EXTI_Init(&EXTI_InitStructure);
    /* 设置引脚为KEY1,其余沿用上面的配置*/
    EXTI_InitStructure.EXTI_Line = KEY2_EXTI_LINE;
    /* 初始化KEY1 */
    EXTI_Init(&EXTI_InitStructure);
}

在完成以上设置后,就可以写我们的中断服务函数了,中断服务函数是放在stm32f4xx_it.c中,具体实现如下:

/* 中断服务程序名需要和启动文件startup_stm32f429_439xx.s中中断向量表的中断服务程序名一致,否则系统会跑去执行默认的程序,即启动代码中的B .程序进入死循环,这里用宏定义将中断服务程序改为KEY1/KEY2_Handler,目的是为了方便看出来这个中断服务程序是用来做什么的 */
void KEY1_Handler(void)
{
    /* 当中断触发后,会将中断标志位状态置位,只需要读取中断标志位就可以知道终端是否触发 */
    if(EXTI_GetITStatus(KEY1_EXTI_LINE) != RESET)
    {
        LED_G_TOGGLE;					//中断触发后,用户程序
    }
    /* 中断触发后,需要将中断标志位清除,后续会等待中断再次触发 */
    EXTI_ClearITPendingBit(KEY1_EXTI_LINE);
}
void KEY2_Handler(void)
{
    if(EXTI_GetITStatus(KEY2_EXTI_LINE) != RESET)
    {
        LED_R_TOGGLE;
    }
    EXTI_ClearITPendingBit(KEY2_EXTI_LINE);
}

main.c

#include "stm32f4xx.h"
#include "bsp_led.h"
#include "bsp_exti_key.h"

int main(void)
{
    LED_Config();				//初始化LED
    EXTI_Key_Config();			//初始化GPIO、EXTI及NVIC,之后就是进入死循环等待中断触发并执行中断服务程序
    while(1)
    {
    }
}

总结:

关于中断的使用,编程内容总结如下几个:

1、初始化用来产生中断的GPIO;

2、初始化EXTI外部中断/事件控制器;

3、配置NVIC中断向量表;

4、编写中断服务程序。

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。