STM32中断详解

STM32中断详解

NVIC 中断系统

STM32F10x 芯片有 84 个中断通道,包括 16 个内核中断和 68 个可屏蔽中断, 对于STM32F103系列芯片只有60个可屏蔽中断,在 STM32F107 系列才有 68 个。

中断向量表

分为系统中断和用户中断


相关寄存器

c 复制代码
typedef struct
{
    __IO uint32_t ISER[8]; //中断使能寄存器
    uint32_t RESERVED0[24];
    __IO uint32_t ICER[8]; //中断清除寄存器
    uint32_t RSERVED1[24];
    __IO uint32_t ISPR[8]; //中断使能悬起寄存器
    uint32_t RESERVED2[24];
    __IO uint32_t ICPR[8]; //中断清除悬起寄存器
    uint32_t RESERVED3[24];
    __IO uint32_t IABR[8]; //中断有效位寄存器
    uint32_t RESERVED4[56];
    __IO uint8_t IP[240]; //中断优先级寄存器
    uint32_t RESERVED5[644];
    __O uint32_t STIR; //软件触发中断寄存器
} NVIC_Type;

在配置中断时,我们通常使用的只有 ISER、 ICER 和 IP 这三个寄存器:ISER 是中断使能寄存器,ICER 是中断清除寄存器,IP 是中断优先级寄存器。

中断优先级

但是 STM32F103 中只使用 4 位,高 4 位有效), 用于表达优先级的高 4 位又被分组成抢占式优先级和响应优先级,通常也把响应优先级称为"亚优先级"或"副优先级",每个中断源都需要被指定这两种优先级。

高抢占式优先级的中断事件会打断当前的主程序或者中断程序运行,俗称中断嵌套。

第 0 组:所有 4 位用于指定响应优先级

第 1 组:最高 1 位用于指定抢占式优先级,最低 3 位用于指定响应优先级

第 2 组:最高 2 位用于指定抢占式优先级,最低 2 位用于指定响应优先级

第 3 组:最高 3 位用于指定抢占式优先级,最低 1 位用于指定响应优先级

第 4 组:所有 4 位用于指定抢占式优先级 设置优先级分组可调用库函数

NVIC_SetPriorityGrouping()实现,有关 NVIC 中断相关的库函数都在库文件misc.c misc.h 中,所以当使用到中断时,一定要记得把 misc.c 和 misc.h 添加到工程组中。

c 复制代码
void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup)
{
/* Check the parameters */
    assert_param(IS_NVIC_PRIORITY_GROUP(NVIC_PriorityGroup));
/* Set the PRIGROUP[10:8] bits according to NVIC_PriorityGroup value */
    SCB->AIRCR = AIRCR_VECTKEY_MASK | NVIC_PriorityGroup;
}

NVIC_PriorityGroupConfig 函数带一个形参用于中断优先级分组,该值范围 可以是 NVIC_PriorityGroup_0-NVIC_PriorityGroup_4,

中断配置

(1)使能外设中断,这个具体是由外设相关中断使能位来控制,比如定时器有溢出中断,这个可由定时器的控制寄存器中相应中断使能位来控制。

(2)设置中断优先级分组,初始化 NVIC_InitTypeDef 结构体,设置抢占优先级和响应优先级,使能中断请求。 NVIC_InitTypeDef

c 复制代码
typedef struct
{
    uint8_t NVIC_IRQChannel; //中断源
    uint8_t NVIC_IRQChannelPreemptionPriority; //抢占优先级
    uint8_t NVIC_IRQChannelSubPriority; //响应优先级
    FunctionalState NVIC_IRQChannelCmd; //中断使能或失能
} NVIC_InitTypeDef;
  • 中断源放在 stm32f10x.h 文件的 IRQn_Type 结构体内,自己去查看

  • NVIC_IRQChannelPreemptionPriority:抢占优先级,具体的值要根据优先级分组来确定,可以参考前面中断优先级分组内容。

  • NVIC_IRQChannelSubPriority:响应优先级,具体的值要根据优先级分组来确定,可以参考前面中断优先级分组内容。

  • NVIC_IRQChannelCmd:中断使能/失能设置,使能配置为 ENABLE,失能配置为 DISABLE。

外部中断实验

EXTI框图

外部中断/事件线映射

中断步骤初始化

  • 使能 IO 口时钟,配置 IO 口模式为输入

    由于本章使用开发板上 4 个按键 IO 口作为外部中断输入线,因此需要使能对应的 IO 口时钟及配置 IO 口模式。

  • 开启 AFIO 时钟,设置 IO 口与中断线的映射关系

    接下来我们需要将 GPIO 映射到对应的中断线上,只要使用到外部中断,就 必须先使能 AFIO 时钟,前面已经说了它是挂接在 APB2 总线上的,所以使能 AFIO 时钟库函数为:
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);

    然后,我们就可以把 GPIO 映射到对应的中断线上,配置 GPIO 与中断线映射 的库函数如下:
    void GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource);

    比如我们将中断线 15 映射到 GPIOA 端口,那么就需要如下配置:
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource15);

  • 配置中断分组(NVIC),使能中断
    NVIC_InitTypeDef NVIC_InitStructure; NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;//EXTI15 中断通道 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;//抢占优先级 NVIC_InitStructure.NVIC_IRQChannelSubPriority =3; //子优先级 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ 通道使能 NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化 VIC 寄存器

  • 初始化 EXTI,选择触发方式 配置好 NVIC 后,我们还需要对中断线上的中断初始化,EXTI 初始化库函数 如下:
    void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct);

    函数形参是有一个结构体 EXTI_InitTypeDef 类型的指针变量, EXTI_InitTypeDef 结构体成员变量如下

c 复制代码
typedef struct 
{ 
uint32_t EXTI_Line; //中断/事件线 
EXTIMode_TypeDef EXTI_Mode; //EXTI 模式 
EXTITrigger_TypeDef EXTI_Trigger; //EXTI 触发方式 
FunctionalState EXTI_LineCmd; //中断线使能或失能 
}EXTI_InitTypeDef; 
  • -- EXTI_Line:EXTI 中断/事件线选择,可配置参数为 EXTI0-EXTI20,可参考上表。
  • -- EXTI_Mode:EXTI 模式选择,可以配置为中断模式 EXTI_Mode_Interrupt 和 事件模式 EXTI_Mode_Event。
  • -- EXTI_Trigger:触发方式选择,可以配置为上升沿触发 EXTI_Trigger_Rising、下降沿触发 EXTI_Trigger_Falling、上升沿和下降沿触 发 EXTI_Trigger_Rising_Falling。
  • -- EXTI_LineCmd:中断线使能或者失能,配置 ENABLE 为使能,DISABLE 为失 能,我们这里要使用外部中断,所以需使能。
  • 编写 EXTI 中断服务函数
  • 所有中断函数都在 STM32F1 启动文件中,不知道中断函数名的可以打开启动 文件查找。这里我们使用到的是外部中断,其函数名如下:
  • EXTI0_IRQHandler

    EXTI1_IRQHandler

    EXTI2_IRQHandler

    EXTI3_IRQHandler

    EXTI4_IRQHandler

    EXTI9_5_IRQHandler

    EXTI15_10_IRQHandler

获取中断标志EXTI_ClearITPendingBit(EXTI_Line12);

最后需要清除标志位EXTI_ClearITPendingBit(EXTI_Line12);

代码实现

功能:独立通过外部中断方式实现KEY1改变LED1的状态,key2改变led2...

myEXTI.h

c 复制代码
#ifndef __MYEXTI_H__
#define __MYEXTI_H__

#include "public.h"

void My_EXTI_Init(void);


#endif

myEXTI.c

c 复制代码
#include "myEXTI.h"

void My_EXTI_Init(void)
{
	// 开启外部中断时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);

	// 将GPIOA上的12到15映射到外部中断线上
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource15);
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource14);
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource13);
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource12);

	// EXTI模块初始化
	EXTI_InitTypeDef EXTI_InitStructure;

	EXTI_InitStructure.EXTI_Line = EXTI_Line12 | EXTI_Line13 | EXTI_Line14 | EXTI_Line15; // 外部中断线
	EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;									  // 中断还是事件
	EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;								  // 触发方式上升还是下降沿 这里是下降沿触发
	EXTI_InitStructure.EXTI_LineCmd = ENABLE;											  // 使能外部中断线
	EXTI_Init(&EXTI_InitStructure);														  // 根据指定的参数初始化 EXTI 寄存器

	// NVIC模块初始化
	NVIC_InitTypeDef NVIC_InitStructure;

	NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;	  // EXTI 中断通道
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; // 抢占优先级 2个
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;		  // 响应优先级 3个
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			  // IRQ 通道使能
	NVIC_Init(&NVIC_InitStructure);							  // 根据指定的参数初始化 VIC 寄存器
}

main.c

c 复制代码
// 功能:独立按键用外部中断方式实现KEY1改变LED1的状态,K2,K3,K4类似

#include "myEXTI.h"
#include "key.h"
#include "led.h"

int main()
{
	SysTick_Init(72);
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 中断优先级分组 分 2 组

	LED_Init();
	Key_Init();
	My_EXTI_Init();

	while (1) // 保持应用程序不退出
	{
	}
}

void EXTI15_10_IRQHandler(void) //中断事件函数
{
	if (EXTI_GetITStatus(EXTI_Line12) == SET) // K4 引脚对应的中断函数状态 RESET代表0  SET代表1  代表是这个中断函数触发了
	{
		LED4 = !LED4;
	}
	if (EXTI_GetITStatus(EXTI_Line13) == SET) // K3
	{
		LED3 = !LED3;
	}
	if (EXTI_GetITStatus(EXTI_Line14) == SET) // K2
	{
		LED2 = !LED2;
	}
	if (EXTI_GetITStatus(EXTI_Line15) == SET) // K1
	{
		LED1 = !LED1;
	}
	EXTI_ClearITPendingBit(EXTI_Line12 | EXTI_Line13 | EXTI_Line14 | EXTI_Line15); // 清除外部中断线上的中断标志
}

定时器中断

STM32F1 的通用定时器包含一个 16 位自动重载计数器(CNT),该计数器由可编程预分频器(PSC)驱动。STM32F1 的通用定时器可用于多种用途,包括测 量输入信号的脉冲宽度(输入捕获)或者生成输出波形(输出比较和 PWM)等。

使用定时器预分频器和 RCC 时钟控制器预分频器,脉冲长度和波形周期可以在几个微秒到几个毫秒间调整。STM32F1 的每个通用定时器都是完全独立的,没有互相共享的任何资源。

基本定时器 的功能最为简单,类似于 51 单片机内定时器。
通用定时器 是在基本定时器的基础上扩展而来,增加了输入捕获与输出比较等功能。
高级定时器又是在通用定时器基础上扩展而来,增加了可编程死区互补输出、重复计数器、带刹车(断路)功能,这些功能主要针对工业电机控制方面。

通用定时器

  • (1)16 位向上、向下、向上/向下自动装载计数器(TIMx_CNT)。
  • (2)16 位可编程(可以实时修改)预分频器(TIMx_PSC),计数器时钟频率的 分频系数为 1~65535 之间的任意数值。
  • (3)4 个独立通道(TIMx_CH1-4),这些通道可以用来作为:

A.输入捕获

B.输出比较

C. PWM 生成(边缘或中间对齐模式)

D.单脉冲模式输出

  • (4)可使用外部信号(TIMx_ETR)控制定时器,且可实现多个定时器互连 (可以用 1 个定时器控制另外一个定时器)的同步电路。
  • (5)发生如下事件时产生中断/DMA 请求:

A.更新:计数器向上溢出/向下溢出,计数器初始化(通过软件或者内部/ 外部触发)

B.触发事件(计数器启动、停止、初始化或者由内部/外部触发计数)

C.输入捕获

D.输出比较

  • (6)支持针对定位的增量(正交)编码器和霍尔传感器电路
  • (7)触发输入作为外部时钟或者按周期的电流管理

相关功能

标号1:时钟源

①内部时钟 (CK_INT)
②外部时钟模式 1:外部输入引脚 TIx(x=1,2,3,4)
③外部时钟模式 2:外部触发输入 ETR
④内部触发输入(ITRx(x=0,1,2,3))

通常我们都是将内部时钟(CK_INT)作为通用定时器的时钟来源,而且通用 定时器的时钟是 APB1 时钟的 2 倍,即 APB1 的时钟分频数不为 1。所以通用定时器的时钟频率是 72MHz

标号 2:控制器

通用定时器控制器部分包括触发控制器、从模式控制器以及编码器接口。触 发控制器用来针对片内外设输出触发信号,比如为其它定时器提供时钟和触发 DAC/ADC 转换。

从模式控制器可以控制计数器复位、启动、递增/递减、计数。 编码器接口专门针对编码器计数而设计。

标号 3:时基单元

通用定时器时基单元包括 3 个寄存器,分别是计数器寄存器(TIMx_CNT)、预分频器寄存器(TIMx_PSC)、自动重载寄存器(TIMx_ARR)。
通用定时器这三个寄存器都是 16 位有效。而高级定时器的 TIMx_RCR 寄存器是 8 位有效。 在这个时基单元中,有个预分频器寄存器(TIMx_PSC),用于对计数器时钟频 率进行分频,通过寄存器内的相应位设置,分频系数值可在 1 到 65536 之间
通用定时器计数方式有向上 计数、向下计数、向上向下计数(中心对齐计数)

代码实现

功能:使用通用定时器产生500ms的中断来对LED1进行闪烁

MYTIME.h

c 复制代码
#ifndef __MYTIME_H
#define __MYTIME_H

#include "public.h"

// TIMx只能是TIM2,TIM3,TIM4, u16psc 表示时基分频系数,u16per表示时基周期
void MY_TIME_Init(TIM_TypeDef *TIMx, u16 u16psc, u16 u16per);

#endif /* __MYTIME_H */

MYTIME.C

c 复制代码
#include "myTIME.h"
// TIMx只能是TIM2,TIM3,TIM4, u16psc 表示时基分频系数,u16per表示时基周期

void MY_TIME_Init(TIM_TypeDef *TIMx, u16 u16psc, u16 u16per)
{
    NVIC_InitTypeDef NVIC_InitStructure;
    // 时钟使能
    if (TIMx == TIM2)
    {
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);  // 时钟使能
        NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; // 选择中断向量
    }
    if (TIMx == TIM3)
    {
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);  // 时钟使能
        NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; // 选择中断向量
    }
    if (TIMx == TIM4)
    {
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);  // 时钟使能
        NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn; // 选择中断向量
    }
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;              // 定义定时器结构体
    TIM_TimeBaseStructure.TIM_Prescaler = u16psc;               // 时基分频系数
    TIM_TimeBaseStructure.TIM_Period = u16per;                  // 时基周期
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;     // 时钟分频系数
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; // 向上计数模式
    TIM_TimeBaseInit(TIMx, &TIM_TimeBaseStructure);             // 初始化定时器

    TIM_ITConfig(TIMx, TIM_IT_Update, ENABLE); // 使能更新中断

    // NVIC模块初始化
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
    TIM_Cmd(TIMx, ENABLE); // 使能定时器
}

main.c

c 复制代码
// 功能:使用通用定时器产生500ms的中断来对LED1进行闪烁

#include "myTIME.h"
#include "led.h"

int main()
{
	SysTick_Init(72);
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 中断优先级分组 分 2 组

	LED_Init();
	MY_TIME_Init(TIM2, 36000 - 1, 1000); // 500ms

	while (1) // 保持应用程序不退出
	{
	}
}

void TIM2_IRQHandler(void)
{
	if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
	{
		LED2 = !LED2;
		TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
	}
}

void TIM3_IRQHandler(void)
{
	if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
	{
		LED3 = !LED3;
		TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
	}
}

void TIM4_IRQHandler(void)
{
	if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
	{
		LED4 = !LED4;
		TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
	}
}
相关推荐
Miuney_MAX11 小时前
【单片机】之HC32F460中断向量选择
单片机·嵌入式硬件
XINVRY-FPGA14 小时前
XC3S1000-4FGG320I Xilinx AMD Spartan-3 SRAM-based FPGA
嵌入式硬件·机器学习·计算机视觉·fpga开发·硬件工程·dsp开发·fpga
猫猫的小茶馆16 小时前
【ARM】ARM的介绍
c语言·开发语言·arm开发·stm32·单片机·嵌入式硬件·物联网
猫猫的小茶馆16 小时前
【PCB工艺】数模电及射频电路基础
驱动开发·stm32·单片机·嵌入式硬件·mcu·物联网·pcb工艺
点灯小铭16 小时前
基于单片机的智能药物盒设计与实现
数据库·单片机·嵌入式硬件·毕业设计·课程设计·期末大作业
梓德原17 小时前
【基础】详细分析带隙型稳压电路的工作原理
单片机·嵌入式硬件·物联网
国科安芯18 小时前
航天医疗领域AS32S601芯片的性能分析与适配性探讨
大数据·网络·人工智能·单片机·嵌入式硬件·fpga开发·性能优化
小李做物联网18 小时前
【物联网毕业设计】60.1基于单片机物联网嵌入式项目程序开发之图像厨房监测系统
stm32·单片机·嵌入式硬件·物联网
贝塔实验室19 小时前
新手如何使用Altium Designer创建第一张原理图(三)
arm开发·单片机·嵌入式硬件·fpga开发·射频工程·基带工程·嵌入式实时数据库
@good_good_study19 小时前
STM32 ADC多通道采样实验
stm32·单片机·嵌入式硬件