当前位置: 首页 > news >正文

stm32之EXIT外部中断详解

目录

  • 1.引入: STM32F103
    • 1.1 中断路径上的3个部件
    • 1.2 STM32F103的GPIO中断
      • 1.1.1 GPIO控制器 -- AFIO
      • 1.1.2 EXTI
      • 1.1.3 NVIC
      • 1.1.4 CPU
        • 1. PRIMASK
        • 2. FAULTMASK
        • 3. BASEPRI
    • 1.3 中断执行流程
  • 2.旋转编码器介绍
  • 3.实验
    • 3.1 EXIT
      • 3.1.1 结构体
      • 3.1.2 函数
    • 3.2 NVIC
      • 3.2.1 结构体
      • 3.2.2 函数
    • 3.3 混淆
    • 3.4 中断函数
    • 3.5 对射式红外传感器
    • 3.6 旋转编码器计次

img

1.引入: STM32F103

在主程序运行过程中,出现了特定的中断触发条件(中断源),使得CPU暂停当前正在运行的程序,转而去处理中断程序,处理完成后又返回原来被暂停的位置继续运行

  • 68个可屏蔽中断通道,包含EXTI、TIM、ADC、USART、SPI、I2C、RTC等多个外设
  • 使用NVIC统一管理中断,每个中断通道都拥有16个可编程的优先等级,可对优先级进行分组,进一步设置抢占优先级和响应优先级

img

img

img

img

1.1 中断路径上的3个部件

● 中断源

  • 中断源指引发中断信号的硬件模块,如GPIO、定时器、串口(UART)、DMA控制器等。这些模块都有各自的寄存器,可以设置中断相关的参数,例如:

    • 使能中断:启用或禁用该模块的中断功能。
    • 中断状态:检查该模块是否发生了中断。
    • 中断类型:设置中断的触发条件,例如上升沿触发、下降沿触发或电平触发等。
  • 中断源可以根据其设置的状态来决定是否向中断控制器发送中断请求信号。

● 中断控制器

  • 中断控制器汇集所有中断源的信号,将其发送至CPU。中断控制器可以:

    • 设置优先级:为不同的中断源设置优先级,以便在多重中断同时发生时,确定先响应哪个中断。当有多个中断源同时申请中断时,CPU会根据中断源的轻重缓急进行裁决,优先响应更加紧急的中断源
    • 向CPU发出中断信号:中断控制器根据优先级和屏蔽设置向CPU发送中断请求信号。CPU通过读取中断控制器寄存器来判断哪个中断源需要响应。
  • 中断控制器的实现因芯片平台而异,例如:

    • STM32F103 使用 NVIC(嵌套向量中断控制器)来处理中断。
    • ARM9 平台中通常由芯片厂商设计的特定中断控制器。
    • Cortex A7 采用 GIC(通用中断控制器),具有更加复杂的中断管理功能。

● CPU

  • CPU会在每条指令执行后检查是否有中断请求。
  • CPU内部的寄存器提供了中断的总开关,可以通过这些寄存器来使能或禁用中断,从而实现对中断响应的控制。

img

1.2 STM32F103的GPIO中断

对于GPIO中断,STM32F103又引入了External interrupt/event controller (EXTI)。用来设置GPIO的中断类型,如下图:

img

在STM32F103中,GPIO中断通过 EXTI(外部中断/事件控制器)模块来处理。EXTI允许我们配置GPIO中断的类型,如上升沿、下降沿或电平触发等。EXTI模块可以向NVIC(嵌套向量中断控制器)提供多达16个中断信号(EXTI0到EXTI15),以便将特定的GPIO中断事件传递到NVIC处理。

1.1.1 GPIO控制器 – AFIO

img

STM32F103的GPIO控制器中有AFIO_EXTICR1~AFIO_EXTICR4一共4个寄存器

名为:External interrupt configuration register,外部中断配置寄存器。

用来选择某个外部中断EXTIx的中断源,示例如下:

img

注意:从上图可知,EXTI0只能从PA0、……、PG0中选择一个,这也意味着PA0、……、PG0中只有一个引脚可以用于中断。这跟其他芯片不一样,很多芯片的任一GPIO引脚都可以同时用于中断。

img

AFIO主要用于引脚复用功能的选择和重定义

在STM32中,AFIO主要完成两个任务:复用功能引脚重映射、中断引脚选择

16个EXIT中各自的PA1、PA2、…、PA15,这些都是对应到GPIOA模块的16根引脚;同理PB1、PB2、…、PB15则是对应到GPIOB模块的16根引脚。

因此对于一个EXITx中的PAx、PBx、…、PGx,只能选择一个对应到某一个模块,有点抽象,就是你要配置GPIOA的PA1引脚作为中断引脚,那么就得将EXIT1映射到GPIOA模块,而此时你还想配置GPIOB的PB1作为中断源是不行的,因此EXIT1已经被GPIOA的PA1占用了。

同时对于D、E、F、G我们是可以不用去管的,它们是对应到PVD、RTC、USB、ETH,唤醒相关的。主要是关注A、B、C的中断线,江科大的stm32的板子我们观察IO引脚其实是之后GPIOA、GPIOB、GPIOC三个模块的引脚的。

1.1.2 EXTI

img

  • EXTI(Extern Interrupt)外部中断

  • EXTI可以监测指定GPIO口的电平信号,当其指定的GPIO口产生电平变化时,EXTI将立即向NVIC发出中断申请,经过NVIC裁决后即可中断CPU主程序,使CPU执行EXTI对应的中断程序

  • 支持的触发方式:上升沿/下降沿/双边沿/软件触发

  • 支持的GPIO口:所有GPIO口,但相同的Pin不能同时触发中断通道数:16个GPIO_Pin,外加PVD输出、RTC闹钟、USB唤醒、以太网唤醒

  • 触发响应方式:中断响应/事件响应

    • 中断响应是正常的流程,引脚电平触发中断
    • 事件响应不会触发中断,而是触发别的外设操作,属于外设之间的联合工作

在GPIO控制器中,可以设置某个GPIO引脚作为中断源,给EXTI提供中断信号。但是,这个中断的触发方式是怎么的?高电平触发、低电平触发、上升沿触发、下降沿触发?这需要进一步设置。EXTI框图如下:

img

沿着上面框图中的红线,我们要设置:

  • 选择哪些GPIO可以发出中断(EXITmux)
  • Falling trigger selection register:是否选择下降沿触发(EventTrigger)
  • Rising trigger selection register:是否选择上升沿触发(EventTrigger)
  • …(EventTrigger)
  • Interrupt mask register:是否屏蔽中断,允许某个EXTI中断(Masking)

当发生中断时,可以读取下列寄存器判断是否发生了中断、发生了哪个中断:

  • Pending reqeust register

要使用EXTI,流程如下:

  • 配置EXTI_IMR(中断屏蔽寄存器)以允许EXTI发出中断信号。
  • 配置EXTI_RTSR和EXTI_FTSR寄存器,选择触发方式。
  • 在NVIC中配置相关寄存器,允许NVIC将中断传递给CPU。

img

img

1.1.3 NVIC

img

NVIC的中断优先级由优先级寄存器的4位(0~15)决定,这4位可以进行切分,分为高n位的抢占优先级和低4-n位的响应优先级

抢占优先级高的可以中断嵌套,响应优先级高的可以优先排队,抢占优先级响应优先级均相同的按中断号排队

分组方式抢占优先级响应优先级
分组00位,取值为04位,取值为0~15
分组11位,取值为0~13位,取值为0~7
分组22位,取值为0~32位,取值为0~3
分组33位,取值为0~71位,取值为0~1
分组44位,取值为0~150位,取值为0
此时正在处理中断A,后面排队的中断C突然闯进来,暂时搁置A,去处理C此时正在处理中断A,排队的有B、C,中断C比较紧急,处理完A后先去处理C

多个中断源汇聚到NVIC,NVIC的职责就是从多个中断源中取出优先级最高的中断,向CPU发出中断信号。处理中断时,程序可以写NVIC的寄存器,清除中断。涉及的寄存器:

img

我们暂时只需要关注:ISER(中断设置使能寄存器)、ICPR(中断清除挂起寄存器)。要注意的是,这些寄存器有很多个,比如ISER0、ISER1等等。里面的每一位对应一个中断。ISER0中的bit0对应异常向量表中的第16项(向量表从第0项开始),如下图:img

1.1.4 CPU

Cortex M3/M4处理器内部有多个用于管理中断的寄存器,包括:

1. PRIMASK
  • PRIMASK:当PRIMASK的第0位设置为1时,可以屏蔽所有可配置优先级的中断。设置方法:

    • 使用汇编指令 CPSIE I(使能中断)和 CPSID I(禁止中断)。
    • 通过 MOV R0, #1MSR PRIMASK, R0 设置PRIMASK寄存器。

img

把PRIMASK的bit0设置为1,就可以屏蔽所有优先级可配置的中断。
可以使用这些指令来设置它:

CPSIE I  ; 清除PRIMASK,使能中断
CPSID I  ; 设置PRIMASK,禁止中断或者:
MOV R0, #1
MSR  PRIMASK R0  ; 将1写入PRIMASK禁止所有中断MOV R0, #0
MSR PRIMASK, R0  ; 将0写入PRIMASK使能所有中断
2. FAULTMASK
  • FAULTMASK:类似于PRIMASK,但可以屏蔽除NMI(不可屏蔽中断)外的所有中断,包括HardFault。设置方法:

    • 使用 CPSIE F(使能中断)和 CPSID F(禁止中断)。
    • 通过指令 MOV R0, #1MSR FAULTMASK, R0 来设置FAULTMASK寄存器。

    FAULTMASK和PRIMASK很像,它更进一步,出来一般的中断外,把HardFault都禁止了。
    只有NMI可以发生。
    可以使用这些指令来设置它:

CPSIE F  ; 清除FAULTMASK
CPSID F  ; 设置FAULTMASK或者:
MOV R0, #1
MSR  FAULTMASK R0  ; 将1写入FAULTMASK禁止中断MOV R0, #0
MSR FAULTMASK, R0  ; 将0写入FAULTMASK使能中断
3. BASEPRI
  • BASEPRI:用于屏蔽优先级大于或等于BASEPRI值的中断。例如,MOVS R0, #0x60MSR BASEPRI, R0 禁止优先级在0x60到0xFF之间的中断

img

BASEPRI用来屏蔽这些中断:它们的优先级,其值大于或等于BASEPRI。
可以使用这些指令来设置它:

MOVS R0, #0x60
MSR BASEPRI, R0   ; 禁止优先级在0x60~0xFF间的中断MRS R0, BASEPRI   ; 读取BASEPRIMOVS R0, #0
MSR BASEPRI, R0    ; 取消BASEPRI屏蔽

1.3 中断执行流程

img

中断嵌套:当一个中断程序正在运行时,又有新的更高优先级的中断源申请中断,CPU再次暂停当前中断程序,转而去处理新的中断程序,处理完成后依次进行返回。但其实好像是不推荐嵌套太多的中断(在Linux当中的,对于MCU的板子我其实也不是很确定)。

中断处理函数需要调用 C 函数,这就需要用到栈。

  • 中断 A 正在处理的过程中,假设又发生了中断 B,那么在栈里要保存 A 的现场,然后处理 B。
  • 在处理 B 的过程中又发生了中断 C,那么在栈里要保存 B 的现场,然后处理C。

如果中断嵌套突然暴发,那么栈将越来越大,栈终将耗尽。

所以,为了防止这种情况发生,也是为了简单化中断的处理,而在 Linux 系统上中断无法嵌套:即当前中断 A 没处理完之前,不会响应另一个中断 B(即使它的优先级更高)。

2.旋转编码器介绍

旋转编码器:用来测量位置、速度或旋转方向的装置,当其旋转轴旋转时,其输出端可以输出与旋转速度和方向对应的方波信号,读取方波信号的频率和相位信息即可得知旋转轴的速度和方向

类型:机械触点式/霍尔传感器式/光栅式

img

第三个方框的是霍尔传感器,是直接附着在电机上的传感器。

这里使用的是第二个方框的旋转编码器,对于这种编码器,我们是不可能无时无刻的去读取它是否有数据产生,那么就可以采用外部中断的方式,stm32去干别的事情,当我们转动编码器就会产生中断,stm32接收到了就可以去读取。

下面是其硬件电路图:

img

这种编码器输出的正交波形,正向和反向是通过A、B输出的波形之间是滞后还是提前90°来判断是正向旋转还是反向旋转的。下图中上方就是B输出的波形比A滞后了90°,也就是编码器是正向旋转;而下方的则是B输出的波形比A提前了90°,也就是反向旋转。

img

3.实验

3.1 EXIT

3.1.1 结构体

stm32f10x_exit.c、.h

typedef struct
{uint32_t EXTI_Line;               /*!< Specifies the EXTI lines to be enabled or disabled.This parameter can be any combination of @ref EXTI_Lines */EXTIMode_TypeDef EXTI_Mode;       /*!< Specifies the mode for the EXTI lines.This parameter can be a value of @ref EXTIMode_TypeDef */EXTITrigger_TypeDef EXTI_Trigger; /*!< Specifies the trigger signal active edge for the EXTI lines.This parameter can be a value of @ref EXTITrigger_TypeDef */FunctionalState EXTI_LineCmd;     /*!< Specifies the new state of the selected EXTI lines.This parameter can be set either to ENABLE or DISABLE */ 
}EXTI_InitTypeDef;

uint32_t EXTI_Line,指定需要进行配置的对应的外部中断线(如PA0、PA1、…、PA15中的某一根或几根):

#define EXTI_Line0       ((uint32_t)0x00001)  /*!< External interrupt line 0 */
#define EXTI_Line1       ((uint32_t)0x00002)  /*!< External interrupt line 1 */
#define EXTI_Line2       ((uint32_t)0x00004)  /*!< External interrupt line 2 */
#define EXTI_Line3       ((uint32_t)0x00008)  /*!< External interrupt line 3 */
#define EXTI_Line4       ((uint32_t)0x00010)  /*!< External interrupt line 4 */
#define EXTI_Line5       ((uint32_t)0x00020)  /*!< External interrupt line 5 */
#define EXTI_Line6       ((uint32_t)0x00040)  /*!< External interrupt line 6 */
#define EXTI_Line7       ((uint32_t)0x00080)  /*!< External interrupt line 7 */
#define EXTI_Line8       ((uint32_t)0x00100)  /*!< External interrupt line 8 */
#define EXTI_Line9       ((uint32_t)0x00200)  /*!< External interrupt line 9 */
#define EXTI_Line10      ((uint32_t)0x00400)  /*!< External interrupt line 10 */
#define EXTI_Line11      ((uint32_t)0x00800)  /*!< External interrupt line 11 */
#define EXTI_Line12      ((uint32_t)0x01000)  /*!< External interrupt line 12 */
#define EXTI_Line13      ((uint32_t)0x02000)  /*!< External interrupt line 13 */
#define EXTI_Line14      ((uint32_t)0x04000)  /*!< External interrupt line 14 */
#define EXTI_Line15      ((uint32_t)0x08000)  /*!< External interrupt line 15 */

EXTIMode_TypeDef EXTI_Mode,触发响应方式:

typedef enum
{EXTI_Mode_Interrupt = 0x00,  //中断模式EXTI_Mode_Event = 0x04  //事件模式
}EXTIMode_TypeDef;

EXTITrigger_TypeDef EXTI_Trigger,中断的触发模式

typedef enum
{EXTI_Trigger_Rising = 0x08,  //上升沿触发EXTI_Trigger_Falling = 0x0C,   //下降沿触发EXTI_Trigger_Rising_Falling = 0x10  //双边沿触发
}EXTITrigger_TypeDef;

FunctionalState EXTI_LineCmd;:指定EXIT的状态,ENABLE or DISABLE

3.1.2 函数

/*** @brief  将 EXTI 外部中断/事件控制器的寄存器恢复到默认重置值。* @param  无* @retval 无* 该函数用于重置所有 EXTI 寄存器,将其恢复为系统默认状态。通常在初始化或* 重新配置前调用,以确保 EXTI 的状态为干净的重置状态。*/
void EXTI_DeInit(void);/*** @brief  根据指定的 EXTI_InitStruct 配置 EXTI 外设。* @param  EXTI_InitStruct: 指向 EXTI_InitTypeDef 结构体的指针,包含 EXTI 外设的配置信息。* @retval 无* 此函数用于根据 EXTI_InitStruct 提供的配置信息对 EXTI 外设进行初始化。用户可以通过配置* EXTI_InitStruct 中的参数(如触发条件、线号等)来定制 EXTI 的行为,完成中断或事件触发* 的初始化。*/
void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct);/*** @brief  将每个 EXTI_InitStruct 成员填充为其默认重置值。* @param  EXTI_InitStruct: 指向 EXTI_InitTypeDef 结构体的指针,该结构体将被初始化。* @retval 无* 此函数将 EXTI_InitStruct 结构体的每个成员变量重置为默认值,通常在创建新配置结构体时* 调用,以确保所有字段都有已知的默认状态。用户可以在此基础上修改所需的参数来完成初始化。*/
void EXTI_StructInit(EXTI_InitTypeDef* EXTI_InitStruct);/*** @brief  生成软件中断。* @param  EXTI_Line: 指定要启用或禁用的软件中断线路。该参数可以是任意组合的 EXTI_Linex,其中 x 的范围为 (0..19)。* @retval 无* 该函数生成指定线路的软件中断,模拟硬件中断触发,以用于测试或控制。例如,可在* 调试阶段用来触发中断处理程序。不同线路可以同时被触发,用于复合中断的模拟。*/
void EXTI_GenerateSWInterrupt(uint32_t EXTI_Line);/*** @brief  检查指定的 EXTI 标志状态。* @param  EXTI_Line: 指定要检查的 EXTI 标志线路。该参数可以是任意 EXTI_Linex 的组合,其中 x 的范围为 (0..19)。* @retval FlagStatus: EXTI_Line 的新状态(SET 或 RESET)。* 该函数用于获取某条 EXTI 线路的中断标志状态。返回的状态可以是 SET(中断发生)* 或 RESET(未发生)。通常在中断服务程序中使用,以确定具体的中断源。*/
FlagStatus EXTI_GetFlagStatus(uint32_t EXTI_Line);/*** @brief  清除 EXTI 的线路挂起标志。* @param  EXTI_Line: 指定要清除的 EXTI 标志线路。该参数可以是任意 EXTI_Linex 的组合,其中 x 的范围为 (0..19)。* @retval 无* 该函数清除 EXTI 线路的挂起标志,表明中断处理已完成,通常在中断服务程序末尾调用。* 一旦标志被清除,下一次相同中断触发才会重新设置标志。*/
void EXTI_ClearFlag(uint32_t EXTI_Line);/*** @brief  检查指定的 EXTI 线路是否被触发。* @param  EXTI_Line: 指定要检查的 EXTI 线路。该参数可以是 EXTI_Linex,其中 x 的范围为 (0..19)。* @retval ITStatus: EXTI_Line 的新状态(SET 或 RESET)。* 该函数用于检查指定线路的中断触发状态。如果返回值为 SET,表明中断已经触发且未处理;* 否则为 RESET。可用于确定中断触发源,以便于分辨多路中断。*/
ITStatus EXTI_GetITStatus(uint32_t EXTI_Line);/*** @brief  清除 EXTI 的线路挂起位。* @param  EXTI_Line: 指定要清除的 EXTI 挂起线路。该参数可以是任意 EXTI_Linex 的组合,其中 x 的范围为 (0..19)。* @retval 无* 此函数用于清除指定线路的挂起位,表示中断已被处理。清除后,相同线路的中断能够再次触发,* 用于控制连续的中断请求。*/
void EXTI_ClearITPendingBit(uint32_t EXTI_Line);

3.2 NVIC

在配置前是需要使用的NVIC_PriorityGroupConfig函数去选择分组配置,也就是抢占优先级和响应优先级的分配。

3.2.1 结构体

typedef struct
{uint8_t NVIC_IRQChannel;                    /*!< Specifies the IRQ channel to be enabled or disabled.This parameter can be a value of @ref IRQn_Type (For the complete STM32 Devices IRQ Channels list, pleaserefer to stm32f10x.h file) */uint8_t NVIC_IRQChannelPreemptionPriority;  /*!< Specifies the pre-emption priority for the IRQ channelspecified in NVIC_IRQChannel. This parameter can be a valuebetween 0 and 15 as described in the table @ref NVIC_Priority_Table */uint8_t NVIC_IRQChannelSubPriority;         /*!< Specifies the subpriority level for the IRQ channel specifiedin NVIC_IRQChannel. This parameter can be a valuebetween 0 and 15 as described in the table @ref NVIC_Priority_Table */FunctionalState NVIC_IRQChannelCmd;         /*!< Specifies whether the IRQ channel defined in NVIC_IRQChannelwill be enabled or disabled. This parameter can be set either to ENABLE or DISABLE */   
} NVIC_InitTypeDef;

uint8_t NVIC_IRQChannel,指定要启用或禁用的IRQ信道

img

#ifdef STM32F10X_MDADC1_2_IRQn                 = 18,     /*!< ADC1 and ADC2 global Interrupt                       */USB_HP_CAN1_TX_IRQn         = 19,     /*!< USB Device High Priority or CAN1 TX Interrupts       */USB_LP_CAN1_RX0_IRQn        = 20,     /*!< USB Device Low Priority or CAN1 RX0 Interrupts       */CAN1_RX1_IRQn               = 21,     /*!< CAN1 RX1 Interrupt                                   */CAN1_SCE_IRQn               = 22,     /*!< CAN1 SCE Interrupt                                   */EXTI9_5_IRQn                = 23,     /*!< External Line[9:5] Interrupts                        */TIM1_BRK_IRQn               = 24,     /*!< TIM1 Break Interrupt                                 */TIM1_UP_IRQn                = 25,     /*!< TIM1 Update Interrupt                                */TIM1_TRG_COM_IRQn           = 26,     /*!< TIM1 Trigger and Commutation Interrupt               */TIM1_CC_IRQn                = 27,     /*!< TIM1 Capture Compare Interrupt                       */TIM2_IRQn                   = 28,     /*!< TIM2 global Interrupt                                */TIM3_IRQn                   = 29,     /*!< TIM3 global Interrupt                                */TIM4_IRQn                   = 30,     /*!< TIM4 global Interrupt                                */I2C1_EV_IRQn                = 31,     /*!< I2C1 Event Interrupt                                 */I2C1_ER_IRQn                = 32,     /*!< I2C1 Error Interrupt                                 */I2C2_EV_IRQn                = 33,     /*!< I2C2 Event Interrupt                                 */I2C2_ER_IRQn                = 34,     /*!< I2C2 Error Interrupt                                 */SPI1_IRQn                   = 35,     /*!< SPI1 global Interrupt                                */SPI2_IRQn                   = 36,     /*!< SPI2 global Interrupt                                */USART1_IRQn                 = 37,     /*!< USART1 global Interrupt                              */USART2_IRQn                 = 38,     /*!< USART2 global Interrupt                              */USART3_IRQn                 = 39,     /*!< USART3 global Interrupt                              */EXTI15_10_IRQn              = 40,     /*!< External Line[15:10] Interrupts                      */RTCAlarm_IRQn               = 41,     /*!< RTC Alarm through EXTI Line Interrupt                */USBWakeUp_IRQn              = 42      /*!< USB Device WakeUp from suspend through EXTI Line Interrupt */  
#endif /* STM32F10X_MD */  

uint8_t NVIC_IRQChannelPreemptionPriority;,指定所选通道的抢占优先级

uint8_t NVIC_IRQChannelSubPriority;,指定所选通道的响应优先级

img

3.2.2 函数

/*** @brief  配置优先级分组:抢占优先级和子优先级。* @param  NVIC_PriorityGroup: 指定优先级分组的位数。*   参数可以是以下值之一:*     @arg NVIC_PriorityGroup_0: 0 位用于抢占优先级*                                4 位用于子优先级*     @arg NVIC_PriorityGroup_1: 1 位用于抢占优先级*                                3 位用于子优先级*     @arg NVIC_PriorityGroup_2: 2 位用于抢占优先级*                                2 位用于子优先级*     @arg NVIC_PriorityGroup_3: 3 位用于抢占优先级*                                1 位用于子优先级*     @arg NVIC_PriorityGroup_4: 4 位用于抢占优先级*                                0 位用于子优先级* @retval 无* 此函数配置优先级分组,用于设置中断的抢占优先级和子优先级的位数。在中断处理* 中,可以通过优先级分组实现不同中断的嵌套控制,确保高优先级中断的优先处理。*/
void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup);/*** @brief  根据 NVIC_InitStruct 指定的参数初始化 NVIC 外设。* @param  NVIC_InitStruct: 指向 NVIC_InitTypeDef 结构体的指针,包含指定 NVIC 外设的配置信息。* @retval 无* 此函数将 NVIC 配置为用户定义的初始化结构体,允许用户设定中断优先级、使能中断等。* 通过该初始化配置,可以灵活地控制系统的中断处理行为。*/
void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct);/*** @brief  设置中断向量表的位置和偏移量。* @param  NVIC_VectTab: 指定中断向量表的存储位置,可以选择放在 RAM 或 FLASH 中。*   参数可以是以下值之一:*     @arg NVIC_VectTab_RAM:向量表在 RAM 中*     @arg NVIC_VectTab_FLASH:向量表在 FLASH 中* @param  Offset: 向量表基址的偏移量。该值必须为 0x200 的倍数。* @retval 无* 此函数允许用户更改中断向量表的存放位置和偏移量,适用于内存布局不同的系统需求,* 如引导加载程序或自定义异常向量表等。*/
void NVIC_SetVectorTable(uint32_t NVIC_VectTab, uint32_t Offset);/*** @brief  选择系统进入低功耗模式的条件。* @param  LowPowerMode: 指定系统进入低功耗模式的新模式。*   参数可以是以下值之一:*     @arg NVIC_LP_SEVONPEND: 待决中断导致事件激活*     @arg NVIC_LP_SLEEPDEEP: 选择深度睡眠模式*     @arg NVIC_LP_SLEEPONEXIT: 在退出中断后立即进入睡眠模式* @param  NewState: LP 条件的新状态。此参数可以是:ENABLE 或 DISABLE。* @retval 无* 此函数配置系统的低功耗条件,例如选择深度睡眠模式、是否在中断结束后立即进入睡眠。* 用于实现节电控制,通常在不需要频繁处理任务的场景下使用。*/
void NVIC_SystemLPConfig(uint8_t LowPowerMode, FunctionalState NewState);/*** @brief  配置 SysTick 定时器的时钟源。* @param  SysTick_CLKSource: 指定 SysTick 的时钟源。*   参数可以是以下值之一:*     @arg SysTick_CLKSource_HCLK_Div8: AHB 时钟的 1/8 用作 SysTick 时钟源*     @arg SysTick_CLKSource_HCLK: AHB 时钟用作 SysTick 时钟源* @retval 无* 此函数设置 SysTick 定时器的时钟源选择,以控制 SysTick 定时器的计时频率。* 可以选择以较慢的分频后时钟运行以减少功耗,或直接使用系统主时钟 AHB 提高* 定时精度。*/
void SysTick_CLKSourceConfig(uint32_t SysTick_CLKSource);

3.3 混淆

img

img

	/*开启时钟*/RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);		//开启GPIOB的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);		//开启AFIO的时钟,外部中断必须开启AFIO的时钟/*GPIO初始化*/GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO_InitStructure);						//将PB0和PB1引脚初始化为上拉输入/*AFIO选择中断引脚*/GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource0);//将外部中断的0号线映射到GPIOB,即选择PB0为外部中断引脚GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource1);//将外部中断的1号线映射到GPIOB,即选择PB1为外部中断引脚/*EXTI初始化*/EXTI_InitTypeDef EXTI_InitStructure;						//定义结构体变量EXTI_InitStructure.EXTI_Line = EXTI_Line0 | EXTI_Line1;		//选择配置外部中断的0号线和1号线EXTI_InitStructure.EXTI_LineCmd = ENABLE;					//指定外部中断线使能EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;			//指定外部中断线为中断模式EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;		//指定外部中断线为下降沿触发EXTI_Init(&EXTI_InitStructure);								//将结构体变量交给EXTI_Init,配置EXTI外设/*NVIC中断分组*/NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);				//配置NVIC为分组2//即抢占优先级范围:0~3,响应优先级范围:0~3//此分组配置在整个工程中仅需调用一次//若有多个中断,可以把此代码放在main函数内,while循环之前//若调用多次配置分组的代码,则后执行的配置会覆盖先执行的配置/*NVIC配置*/NVIC_InitTypeDef NVIC_InitStructure;						//定义结构体变量NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;			//选择配置NVIC的EXTI0线NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;				//指定NVIC线路使能NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;	//指定NVIC线路的抢占优先级为1NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;			//指定NVIC线路的响应优先级为1NVIC_Init(&NVIC_InitStructure);								//将结构体变量交给NVIC_Init,配置NVIC外设NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn;			//选择配置NVIC的EXTI1线NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;				//指定NVIC线路使能NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;	//指定NVIC线路的抢占优先级为1NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;			//指定NVIC线路的响应优先级为2NVIC_Init(&NVIC_InitStructure);								//将结构体变量交给NVIC_Init,配置NVIC外设

以这个代码例子,要使用的是GPIOB的PB0、PB1引脚,同时这两个引脚都是要有中断功能的,而PB0是对应到EXIT0、PB1对应到EXIT1的。

GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource0);//将外部中断的0号线映射到GPIOB,即选择PB0为外部中断引脚
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource1);//将外部中断的1号线映射到GPIOB,即选择PB1为外部中断引脚

所以就使用了GPIO_EXTILineConfig将外部中断的0号线和1号线映射到GPIOB,这个0、1号线指的是就是PB0(EXIT0的)PB1(EXIT1的)不要认为是EXIT0的第0号线PA0和EXIT1的第1号线PB1,这个是比较容易混淆的。因为对应GPIOB模块是有PB0、…、PB15共16根引脚,对应到各自的EXITx,这里所提到的外部中断的x号线是指模块的PB0、…、PB15的PBx,然后再去根据x对应到EXITx

之后就是配置EXTI_InitStructure结构体,去配置外部中断PB0、PB1的相关信息,也就是响应方式、中断触发方式、使能。

已经配置了外部中断,就得配置一下中断的优先级,也就是去配置EXIT,而这个则是NVCI干的事情。

img

说白了就是16个EXIT,虽然每一个EXIT都有A~F的中断线,共有16*6,但是实际上呢,能够一起使用的只有16根中断线(各个EXIT抽取一根),也就是最多只能嵌套16个中断,因为一个EXIT只能取出一根来使用。GPIOA模块的PA1使用了EXIT1的PA1,GPIOB模块的PB1就不能使用EXIT1的PB1

3.4 中断函数

startup_stm32f10x_md.s启动文件中

发生中断时,会到对应的中断向量表,跳转到指定的中断函数:

__Vectors       DCD     __initial_sp               ; Top of StackDCD     Reset_Handler              ; Reset HandlerDCD     NMI_Handler                ; NMI HandlerDCD     HardFault_Handler          ; Hard Fault HandlerDCD     MemManage_Handler          ; MPU Fault HandlerDCD     BusFault_Handler           ; Bus Fault HandlerDCD     UsageFault_Handler         ; Usage Fault HandlerDCD     0                          ; ReservedDCD     0                          ; ReservedDCD     0                          ; ReservedDCD     0                          ; ReservedDCD     SVC_Handler                ; SVCall HandlerDCD     DebugMon_Handler           ; Debug Monitor HandlerDCD     0                          ; ReservedDCD     PendSV_Handler             ; PendSV HandlerDCD     SysTick_Handler            ; SysTick Handler; External InterruptsDCD     WWDG_IRQHandler            ; Window WatchdogDCD     PVD_IRQHandler             ; PVD through EXTI Line detectDCD     TAMPER_IRQHandler          ; TamperDCD     RTC_IRQHandler             ; RTCDCD     FLASH_IRQHandler           ; FlashDCD     RCC_IRQHandler             ; RCCDCD     EXTI0_IRQHandler           ; EXTI Line 0DCD     EXTI1_IRQHandler           ; EXTI Line 1DCD     EXTI2_IRQHandler           ; EXTI Line 2DCD     EXTI3_IRQHandler           ; EXTI Line 3DCD     EXTI4_IRQHandler           ; EXTI Line 4DCD     DMA1_Channel1_IRQHandler   ; DMA1 Channel 1DCD     DMA1_Channel2_IRQHandler   ; DMA1 Channel 2DCD     DMA1_Channel3_IRQHandler   ; DMA1 Channel 3DCD     DMA1_Channel4_IRQHandler   ; DMA1 Channel 4DCD     DMA1_Channel5_IRQHandler   ; DMA1 Channel 5DCD     DMA1_Channel6_IRQHandler   ; DMA1 Channel 6DCD     DMA1_Channel7_IRQHandler   ; DMA1 Channel 7DCD     ADC1_2_IRQHandler          ; ADC1_2DCD     USB_HP_CAN1_TX_IRQHandler  ; USB High Priority or CAN1 TXDCD     USB_LP_CAN1_RX0_IRQHandler ; USB Low  Priority or CAN1 RX0DCD     CAN1_RX1_IRQHandler        ; CAN1 RX1DCD     CAN1_SCE_IRQHandler        ; CAN1 SCEDCD     EXTI9_5_IRQHandler         ; EXTI Line 9..5DCD     TIM1_BRK_IRQHandler        ; TIM1 BreakDCD     TIM1_UP_IRQHandler         ; TIM1 UpdateDCD     TIM1_TRG_COM_IRQHandler    ; TIM1 Trigger and CommutationDCD     TIM1_CC_IRQHandler         ; TIM1 Capture CompareDCD     TIM2_IRQHandler            ; TIM2DCD     TIM3_IRQHandler            ; TIM3DCD     TIM4_IRQHandler            ; TIM4DCD     I2C1_EV_IRQHandler         ; I2C1 EventDCD     I2C1_ER_IRQHandler         ; I2C1 ErrorDCD     I2C2_EV_IRQHandler         ; I2C2 EventDCD     I2C2_ER_IRQHandler         ; I2C2 ErrorDCD     SPI1_IRQHandler            ; SPI1DCD     SPI2_IRQHandler            ; SPI2DCD     USART1_IRQHandler          ; USART1DCD     USART2_IRQHandler          ; USART2DCD     USART3_IRQHandler          ; USART3DCD     EXTI15_10_IRQHandler       ; EXTI Line 15..10DCD     RTCAlarm_IRQHandler        ; RTC Alarm through EXTI LineDCD     USBWakeUp_IRQHandler       ; USB Wakeup from suspend
__Vectors_End

以NVIC选择的是EXIT15_10的某一个为例,发生中断时执行的中断函数是EXTI15_10_IRQHandler,因此我们只需要是定义实现该函数就行了。但是不要认为只能执行

和linux其实不太一样的,对于一些linux开发版(可能没有EXIT),可以在设备的驱动程序中去注册自己的中断处理函数,比如在GPIO模块中连接了多个设备,这些设备发生的中断会汇聚到模块上,发送同一个中断号给GIC控制器(看作是NVIC),下级是共享同一个类型的中断的。发生中断时调用哪一个设备的中断处理函数则有上级的中断处理函数来揣测调用。(在嵌入式驱动开发专栏的interrupt子系统中有讲解过)

在stm32中,对于一个EXIT模块的16根中断线PA0、PB0、…,它只能选择其中的一根去映射到GPIO模块

3.5 对射式红外传感器

img

EXIT的配置:对于对射式红外传感器,DO输出引脚接的是GPIOB14,那么就得用AFIO去将外部中断线第14线映射到GPIOB模块,也就是EIXT14的PB14,接下来就是去配置它:使能、中断触发方式、响应模式。

img

NVIC的配置:置NVIC,也就是对应的EXIT的中断优先级

然后就是去定义中断处理函数EXTI15_10_IRQHandler

Hardware:

  • 📎OLED_Font.h
  • 📎OLED.h
  • 📎CountSensor.c
  • 📎CountSensor.h

main.c:

  • 📎main.c

📎5-1 对射式红外传感器计次.zip

3.6 旋转编码器计次

img

User:

  • 📎main.c

Hardware:

  • 📎Encoder.c
  • 📎Encoder.h
  • 📎OLED.c
  • 📎OLED.h
  • 📎OLED_Font.h

相关文章:

  • [Kaggle]:使用Kaggle服务器训练YOLOv5模型 (白嫖服务器)
  • 语音合成之七语音克隆技术突破:从VALL-E到SparkTTS,如何解决音色保真与清晰度的矛盾?
  • PyTorch数据加载与预处理
  • Redis的两种持久化方式:RDB和AOF
  • OSPF的不规则区域和特殊区域
  • WPF实现多语言切换
  • Java 实用工具类:深入讲解 CollectionUtils
  • CCF CSP 第30次(2023.05)(4_电力网络_C++)
  • C++:string 1
  • 游戏状态管理:用Pygame实现场景切换与暂停功能
  • Java 日志:掌握本地与网络日志技术
  • 6.1腾讯技术岗2025面试趋势前瞻:大模型、云原生与安全隐私新动向
  • HTML与安全性:XSS、防御与最佳实践
  • 华为OD机试真题——二维伞的雨滴效应(2025A卷:200分)Java/python/JavaScript/C/C++/GO最佳实现
  • 在WSL2+Ubuntu22.04中通过conda pack导出一个conda环境包,然后尝试导入该环境包
  • 【Linux网络】打造初级网络计算器 - 从协议设计到服务实现
  • 1.4 大模型应用产品与技术架构
  • 静态多态和动态多态的区别
  • 【Tauri】桌面程序exe开发 - Tauri+Vue开发Windows应用 - 比Electron更轻量!8MB!
  • 【高频考点精讲】实现垂直居中的多种CSS方法比较与最佳实践
  • 言短意长|政府食堂、停车场开放的示范效应
  • 新一届中国女排亮相,奥运冠军龚翔宇担任队长
  • 赛力斯拟赴港上市:去年扭亏为盈净利59亿元,三年内实现百万销量目标
  • 2025厦门体育产业采风活动圆满举行
  • 知名计算机专家、浙江大学教授张森逝世
  • 央媒谈多地景区试水“免费开放”:盲目跟风会顾此失彼