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

STM32中断详解

STM32中断详解

  • NVIC 中断系统
    • 中断向量表
    • 相关寄存器
    • 中断优先级
    • 中断配置
  • 外部中断实验
    • EXTI框图
    • 外部中断/事件线映射
    • 中断步骤初始化
      • 代码实现
  • 定时器中断
    • 通用定时器
    • 相关功能
      • 标号1:时钟源
      • 标号 2:控制器
      • 标号 3:时基单元
    • 代码实现

NVIC 中断系统

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

中断向量表

分为系统中断和用户中断
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

相关寄存器

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 添加到工程组中。

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

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 结构体成员变量如下

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

#ifndef __MYEXTI_H__
#define __MYEXTI_H__

#include "public.h"

void My_EXTI_Init(void);


#endif


myEXTI.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

// 功能:独立按键用外部中断方式实现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

#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

#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

// 功能:使用通用定时器产生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);
	}
}


http://www.kler.cn/a/460037.html

相关文章:

  • 《深度学习梯度消失问题:原因与解决之道》
  • 图片验证码如何显示在 Apifox 的响应控制台中
  • xdoj校验和
  • vue中的设计模式
  • Dell服务器升级ubuntu 22.04失败解决
  • mac m2 安装 docker
  • RabbitMQ基础篇之数据隔离
  • 【机器学习】机器学习的基本分类-半监督学习-半监督生成对抗网络(Semi-supervised GANs)
  • Effective C++ 条款41:了解隐式接口和编译期多态
  • mysql只恢复某个库或某个表
  • 算法环境安装GPU驱动、CUDA、cuDNN、Docker及NVIDIA Container Toolkit
  • node.js文件压缩包解析,反馈解析进度,解析后的文件字节正常
  • Ungoogled Chromium127编译指南 Linux篇 - 项目要求(二)
  • 华为,新华三,思科网络设备指令
  • 异步爬虫之aiohttp的使用
  • fetch请求代码
  • 大数据_HBase的列族属性配置
  • Kotlin 协程基础知识总结四 —— Flow
  • 基于PyQt5的UI界面开发——图像与视频的加载与显示
  • Java爬虫获取速卖通(AliExpress)商品详情
  • SpringAI从入门到熟练
  • Linux day 1203
  • 41.1 预聚合提速实战项目之需求分析和架构设计
  • C++通讯录管理系统
  • 9. 大数据集群(PySpark)+Hive+MySQL+PyEcharts+Flask:信用贷款风险分析与预测
  • DotnetSpider实现网络爬虫