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);
}
}