4、EXTI外部中断

一、中断

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

中断优先级:当有多个中断源同时申请中断时,CPU会根据中断源的轻重缓急进行裁决,优先相应更加紧急的中断源

中断嵌套:当一个中断程序正在运行时,又有新的更高优先级的中断源申请中断,CPU再次暂停当前中断程序,转而去处理新的中断程序,处理完成后依次进行返回

中断响应:申请中断,让CPU执行中断函数

事件响应:触发一个事件,则中断信号不会通向CPU,而是通向其他外设的操作,例如触发ADC转换、触发DMA等(stm32对外部中断增加的额外的功能)(也就是不会触发中断,而是触发外设操作)

img

使用中断系统可以加大的提高程序的运行效率,如果没有中断系统,为了防止外部中断被忽略或者串口数据被覆盖,主程序就只能不断地查询是否有这些事件发生。

比如如果没有定时器中断,主程序只能使用delay函数,才能实现定时的功能。

有了中断系统后,主程序就可以放心执行其他事情,有中断的时候再去处理。

对于STM32来说,想要获取的信号是外部驱动的很快的突发信号,但是又不需要一直进行检测。

不需要经常使用、不需要一直检测的模块:

  1. 按键(注意抖动问题),不推荐,可以使用定时器中断

  2. 旋转编码器

  3. 红外遥控接收器

img

二、AFIO

AFIO(Alternate Function Input Output)是用于配置微控制器引脚的外设。在配置 EXTI 时,您可以选择将特定的 GPIO 引脚连接到 EXTI 通道。这允许您灵活地选择要触发中断的引脚。

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

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

三、EXIT外部中断

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

  • 支持的触发方式:上升沿 / 下降沿 / 双边沿 / 软件触发
  • 支持的 GPIO 口:所有 GPIO 口,但相同的 Pin (例如 PA1 、 PB1 、 PC1 等 )不能 同时触发中断
  • 通道数: 16个GPIO_Pin ,外加 PVD 输出、 RTC 闹钟、 USB 唤醒、以太网 唤醒(一共 20 个)
  • 触发响应方式:中断响应 / 事件响应

1、抢占优先级和响应优先级

img

  • 抢占优先级用于确定中断的重要性,较高的抢占优先级意味着中断可以中断当前正在执行的较低优先级中断。
  • -响应优先级用于确定多个中断同时触发时的执行顺序。响应优先级高的中断会先执行,而响应优先级相同的中断会按照中断号顺序排队执行。

2、中断触发方式

  • 上升沿触发:中断在引脚电平由低到高变化时触发。
  • 下降沿触发:中断在引脚电平由高到低变化时触发。
  • 双边沿触发:中断在引脚电平由低到高或由高到低变化时触发。
  • 软件触发:通过执行一句特定的代码来触发中断,通常使用类似于 NVIC_SetPendingIRQ() 的函数。

3、触发响应方式

  • 中断响应:中断触发后,中断服务程序执行,通常用于需要立即响应的事件。
  • 事件响应:事件触发后,不会引发中断,而是触发其他外设操作,如启动ADC转换或启动DMA传输。

4、注意事项

  • 进入中断函数时,务必检查中断标志位以确定中断的原因。
  • 中断服务函数应该执行简短和高效的代码,以尽快释放中断,并允许更高优先级的中断执行。
  • 在中断服务函数退出时,通常需要清除中断标志位,以允许下一次中断触发

四、 NVIC优先级分组

NVIC的名字叫做嵌套中断向量控制器

img

NVIC用于统一管理中断和分配中断优先级,属于内核外设,是CPU的小助手,可以让CPU专注于运算。相当于医院里的叫号系统(给病人进行排号,CPU相当于医生,只负责看病)。

优先级:抢占优先级、响应优先级。

插队:响应优先级

不等上个病人看完就直接进去:抢占优先级

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

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

img

img

如果中断不多,很难导致中断冲突时,可以随意选择优先级分组,那个都行。

五、 外部中断程序设计步骤

img

涉及到的外设:RCC、GPIO、AFIO、EXTI、NVIC

1、如何设置外部中断 (EXTI)

  1. 打开RCC时钟: 在使用任何外设之前,需要确保相应的外设时钟已打开。在这种情况下,您打开了RCC (Reset and Clock Control) 时钟,它是控制各种外设时钟的关键。没有时钟,外设无法正常工作。

  2. 配置GPIO: 在使用外部中断之前,需要选择一个GPIO端口,并将其配置为输入模式。输入模式的配置可以是浮空(不连接到任何电平)、上拉(连接到高电平)或下拉(连接到低电平),具体取决于应用的要求。

  3. 配置AFIO: AFIO (Alternate Function I/O) 控制着GPIO引脚的复用功能。在这一步中,您选择了使用的GPIO并将其连接到后面的EXTI模块,以允许GPIO引脚触发外部中断。
  4. 配置EXTI: 外部中断线 (EXTI) 控制着外部中断的触发和响应方式。您需要选择外部中断触发的方式,例如上升沿、下降沿或双边沿触发。通常,中断触发方式应根据应用的需求来选择。此外,您还可以选择中断响应方式,一般选择中断响应以触发中断处理函数。
  5. 配置NVIC: NVIC (Nested Vectored Interrupt Controller) 用于配置中断优先级和中断的全局控制。在这一步中,您可以为外部中断选择一个合适的优先级,以确定它在中断处理器中的相对重要性。抢占优先级和响应优先级的选择可以确保在多个中断同时发生时,系统可以正确地处理它们。

2、GPIO中断输入输出函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/**
* @brief 锁定指定的GPIO引脚配置,防止进一步更改。
* @param GPIOx: 要锁定的GPIO端口。
* @param GPIO_Pin: 要锁定的GPIO引脚。
* @retval 无
*/
void GPIO_PinLockConfig(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);

/**
* @brief 配置外部中断的事件输出功能,通常用于触发外部事件。
* @param GPIO_PortSource: 事件输出的源GPIO端口。
* @param GPIO_PinSource: 事件输出的源GPIO引脚。
* @retval 无
*/
void GPIO_EventOutputConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource);

/**
* @brief 启用或禁用外部中断的事件输出功能。
* @param NewState: 事件输出功能的新状态(ENABLE或DISABLE)。
* @retval 无
*/
void GPIO_EventOutputCmd(FunctionalState NewState);

/**
* @brief 配置GPIO引脚重映射,允许将一个引脚映射到另一个引脚。
* @param GPIO_Remap: GPIO引脚重映射配置。
* @param NewState: 重映射功能的新状态(ENABLE或DISABLE)。
* @retval 无
*/
void GPIO_PinRemapConfig(uint32_t GPIO_Remap, FunctionalState NewState);

/**
* @brief 配置外部中断线映射,将指定的GPIO引脚映射到外部中断线上。
* @param GPIO_PortSource: 外部中断线映射的源GPIO端口。
* @param GPIO_PinSource: 外部中断线映射的源GPIO引脚。
* @retval 无
*/
void GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource);

/**
* @brief 配置以太网接口的媒体类型(例如,RMII或MII)。
* @param GPIO_ETH_MediaInterface: 以太网媒体接口类型配置。
* @retval 无
*/
void GPIO_ETH_MediaInterfaceConfig(uint32_t GPIO_ETH_MediaInterface);

3、配置AFIO外部中断引脚选择

这段代码是用于配置外部中断线映射的函数。外部中断线映射允许将特定的GPIO引脚映射到外部中断线上,从而使这些引脚能够触发外部中断。以下是对该函数的注释和解释:

调用这个函数,就可以配置AFIO的数据选择器,来选择我们想要的中断引脚

1
2
3
4
5
6
7
/**
* @brief 配置外部中断线映射,将指定的GPIO引脚映射到外部中断线上。
* @param GPIO_PortSource: 外部中断线映射的源GPIO端口。
* @param GPIO_PinSource: 外部中断线映射的源GPIO引脚。
* @retval 无
*/
void GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource);
  • GPIO_PortSource 参数用于指定外部中断线映射的源GPIO端口。这是一个数字,表示GPIO端口,例如,如果要映射的引脚属于GPIOA,则该参数为 GPIO_PortSource = GPIO_PortSourceGPIOA
  • GPIO_PinSource 参数用于指定外部中断线映射的源GPIO引脚。这是一个数字,表示要映射的GPIO引脚的位置,例如,如果要映射的引脚是GPIOA的第2号引脚,则该参数为 GPIO_PinSource = GPIO_PinSource2

通过调用此函数,您可以配置外部中断线映射,以便将特定GPIO引脚与外部中断相关联。这使得在满足外部中断触发条件时,可以触发相应的中断处理程序。在配置外部中断时,通常需要使用此函数来设置引脚到外部中断线的映射关系。

4、配置EXTI库函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/**
* @brief 将外部中断模块的配置恢复为默认值。
*/
void EXTI_DeInit(void);

/**
* @brief 初始化外部中断模块。
* @param EXTI_InitStruct: 包含外部中断配置的结构体指针。
*/
void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct);

/**
* @brief 将外部中断配置结构体初始化为默认值。
* @param EXTI_InitStruct: 要初始化的外部中断配置结构体指针。
*/
void EXTI_StructInit(EXTI_InitTypeDef* EXTI_InitStruct);

/**
* @brief 生成软件触发的外部中断。
* @param EXTI_Line: 要生成中断的外部中断线。
*/
void EXTI_GenerateSWInterrupt(uint32_t EXTI_Line);
//主程序:
/**
* @brief 检查指定外部中断线的中断标志位是否已设置。
* @param EXTI_Line: 要检查的外部中断线。
* @retval 设置标志位的状态 (SET或RESET)。
*/
FlagStatus EXTI_GetFlagStatus(uint32_t EXTI_Line);

/**
* @brief 清除指定外部中断线的中断标志位。
* @param EXTI_Line: 要清除标志位的外部中断线。
*/
void EXTI_ClearFlag(uint32_t EXTI_Line);
//中断程序:
/**
* @brief 检查指定外部中断线的中断挂起位是否已设置。
* @param EXTI_Line: 要检查的外部中断线。
* @retval 挂起位的状态 (SET或RESET)。
*/
ITStatus EXTI_GetITStatus(uint32_t EXTI_Line);

/**
* @brief 清除指定外部中断线的中断挂起位。
* @param EXTI_Line: 要清除挂起位的外部中断线。
*/
void EXTI_ClearITPendingBit(uint32_t EXTI_Line);

5、配置NVIC

以下是对这些函数的代码注释解释:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
* @brief 配置中断优先级分组。
* @param NVIC_PriorityGroup: 中断优先级分组,可以是 NVIC_PriorityGroup_x 中的一个值。
*/
void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup);

/**
* @brief 初始化中断控制器 (NVIC) 寄存器。
* @param NVIC_InitStruct: 包含中断控制器配置信息的结构体指针。
*/
void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct);

/**
* @brief 配置向量表的基地址和偏移量。
* @param NVIC_VectTab: 向量表的基地址,可以是 NVIC_VectTab_RAM 或 NVIC_VectTab_FLASH。
* @param Offset: 向量表偏移量。
*/
void NVIC_SetVectorTable(uint32_t NVIC_VectTab, uint32_t Offset);

/**
* @brief 配置系统低功耗模式。
* @param LowPowerMode: 低功耗模式,可以是 SysTick_CLKSource_HCLK_Div8 或 SysTick_CLKSource_HCLK。
* @param NewState: 新状态,可以是 ENABLE(启用)或 DISABLE(禁用)。
*/
void NVIC_SystemLPConfig(uint8_t LowPowerMode, FunctionalState NewState);

/**
* @brief 配置SysTick定时器的时钟源。
* @param SysTick_CLKSource: SysTick定时器的时钟源,可以是 SysTick_CLKSource_HCLK 或 SysTick_CLKSource_HCLK_Div8。
*/
void SysTick_CLKSourceConfig(uint32_t SysTick_CLKSource);

这些函数用于配置和管理中断控制器 (NVIC) 和系统滴答定时器 (SysTick)。以下是它们的简要解释:

  • NVIC_PriorityGroupConfig 用于配置中断优先级分组,这是为了确定中断的优先级管理方式。
  • NVIC_Init 用于初始化中断控制器的寄存器,配置中断的优先级和其他设置。
  • NVIC_SetVectorTable 用于配置向量表的基地址和偏移量,这是中断处理程序的入口。
  • NVIC_SystemLPConfig 用于配置系统低功耗模式,以降低系统功耗。
  • SysTick_CLKSourceConfig 用于配置 SysTick 定时器的时钟源,以控制其工作时钟。

这些函数通常在初始化阶段用于配置系统的中断和定时器设置,以满足应用程序的需求。确保查阅您使用的STM32系列的参考手册和文档,以获取详细的配置信息和示例代码。

六、示例程序

1、红外传感器中断计次

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
uint16_t CountSensor_Count; // 存储传感器计数值的变量

void CountSensor_Init(void)
{
// 使能GPIOB和AFIO时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);

// 配置GPIOB引脚14为上拉输入模式
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);

// 配置外部中断线,将GPIOB引脚14与外部中断线14相关联
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource14);

// 配置外部中断线14,触发方式为下降沿触发
EXTI_InitTypeDef EXTI_InitStructure;
EXTI_InitStructure.EXTI_Line = EXTI_Line14;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_Init(&EXTI_InitStructure);

// 配置中断分组为优先级组2
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

// 配置外部中断的中断处理函数和中断优先级
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn; // 对应外部中断线15-10
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; // 抢占优先级1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; // 响应优先级1
NVIC_Init(&NVIC_InitStructure);
}

uint16_t CountSensor_Get(void)
{
return CountSensor_Count; // 返回传感器计数值
}

void EXTI15_10_IRQHandler(void)
{
if (EXTI_GetITStatus(EXTI_Line14) == SET)
{
// 如果外部中断14触发(引脚电平下降)
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_14) == 0)
{
// 如果引脚电平为低(确保不是抖动),增加计数值
CountSensor_Count++;
}
EXTI_ClearITPendingBit(EXTI_Line14); // 清除中断标志位
}
}

实现逻辑

这段代码初始化了一个外部传感器计数功能。具体注释如下:

  • CountSensor_Count 是用来存储传感器计数值的变量。

  • CountSensor_Init 函数用于初始化外部传感器。它执行以下步骤:

    • 使能 GPIOBAFIO 时钟。
    • 配置 GPIOB 引脚14为上拉输入模式,这个引脚用于连接传感器。
    • 配置外部中断线,将 GPIOB 引脚14与外部中断线14关联起来。
    • 配置外部中断线14的触发方式为下降沿触发,表示当引脚电平从高变为低时触发中断。
    • 配置中断分组为优先级组2,允许设置中断的抢占优先级和响应优先级。
    • 配置外部中断的中断处理函数和中断优先级。
  • CountSensor_Get 函数用于获取传感器的计数值,并返回该值。

  • EXTI15_10_IRQHandler 是外部中断的中断处理函数。当外部中断14触发时(引脚电平下降),该函数会被调用。在函数内部,它会检查引脚的电平,如果电平为低(确保不是抖动),则增加 CountSensor_Count 计数值。最后,它会清除中断标志位,以表示已经处理了中断。

2、旋转编码器计次

这段代码是用于处理编码器的输入信号,并实时计算编码器的计数值。下面是对这段代码的详细注释:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
#include "stm32f10x.h" // 包含STM32的库文件

int16_t Encoder_Count; // 定义一个16位整数用于存储编码器计数值

// 编码器初始化函数
void Encoder_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
// 打开GPIOB时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
// 打开AFIO时钟

GPIO_InitTypeDef GPIO_InitStructure;
// 定义GPIO初始化结构体
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
// 配置GPIO引脚为上拉输入模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
// 配置GPIO引脚0和1
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
// 初始化GPIOB

GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource0);
// 配置GPIO引脚0连接到外部中断线
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource1);
// 配置GPIO引脚1连接到外部中断线

EXTI_InitTypeDef EXTI_InitStructure;
// 定义外部中断初始化结构体
EXTI_InitStructure.EXTI_Line = EXTI_Line0 | EXTI_Line1;
// 配置外部中断线0和1
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
// 使能外部中断线0和1
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
// 配置中断模式
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
// 配置下降沿触发
EXTI_Init(&EXTI_InitStructure);
// 初始化外部中断

NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
// 配置中断优先级分组

NVIC_InitTypeDef NVIC_InitStructure;
// 定义NVIC初始化结构体
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
// 配置外部中断0通道
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
// 使能外部中断0通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
// 设置抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
// 设置响应优先级
NVIC_Init(&NVIC_InitStructure);
// 初始化NVIC

NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn;
// 配置外部中断1通道
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
// 使能外部中断1通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
// 设置抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
// 设置响应优先级
NVIC_Init(&NVIC_InitStructure);
// 初始化NVIC
}

// 获取编码器计数值的函数
int16_t Encoder_Get(void)
{
int16_t Temp;
Temp = Encoder_Count;
Encoder_Count = 0;
// 清零计数值并返回
return Temp;
}

// 处理外部中断0的中断服务函数
void EXTI0_IRQHandler(void)
{
if (EXTI_GetITStatus(EXTI_Line0) == SET)
{
// 如果外部中断0被触发
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0)
{
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0)
{
Encoder_Count--; // 编码器逆时针旋转
}
}
EXTI_ClearITPendingBit(EXTI_Line0); // 清除中断标志位
}
}

// 处理外部中断1的中断服务函数
void EXTI1_IRQHandler(void)
{
if (EXTI_GetITStatus(EXTI_Line1) == SET)
{
// 如果外部中断1被触发
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0)
{
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0)
{
Encoder_Count++; // 编码器顺时针旋转
}
}
EXTI_ClearITPendingBit(EXTI_Line1); // 清除中断标志位
}
}

实现逻辑:

这段代码设置了两个外部中断线,用于处理编码器的输入信号。在外部中断触发时,会根据按钮的状态(旋转编码器的状态为双向滞后九十度的电平信号,通过判断高低电平分辨正负)递增或递减编码器的计数值。外部中断0用于逆时针旋转,而外部中断1用于顺时针旋转。每次中断触发后,计数值会被清零,以便进行下一轮计数。这个示例展示了如何使用外部中断来处理实际硬件输入,并在中断服务函数中更新相关的计数值。