一、外部中断介绍
- EXTI简介: EXTI(External interrupt/event controller)外部中断/事件控制器
- STM32的EXTI大概有20条
- EXTI结构框图
- EXTI分两大部分的功能
- 产生中断:12345
- 产生事件:123678
二、中断线路介绍
- 输入线: EXTI控制器有20个中断/事件输入线,可通过寄存器来设.置任意一个IO,或一些外设事件,输入线一般是电平变化的信号
- 边沿检测电路: EXTI可以对触发方式进行选择(上升沿,下降沿),边沿检测电路以输入线信号作为信号的输入端,如果检测到有边沿信号的跳变,就会输出有效信号1,如果没有检测到有效信号,则输出0,输出有效信号之后,传递给下一级。上升沿,下降沿用来控制检测的类型,即电平的跳变过程,如选择的上升沿(电平升高),边沿检测电路检测到上升沿之后,就会产生一个有效信号1,如果是下降沿过来,上升沿并不会检测到,产生0,传给下一级。如果选择的是下降沿(电平下降),边沿检测电路检测到下降沿之后,就会产生一个有效信号1。也可以同时检测上升沿和下降沿。
- 3(或门电路): 当输入级只要有一个为有效信号,则输出有效。即输入端有一个为真,那输出就为真。或门电路连接边沿检测电路和软件中断事件寄存器。软件中断寄存器允许使用软件来启动中断或事件线。通常使用的多的是边沿检测电路
- 4(与门电路): 当输入端全部为真的时候输出才会有效,输入只要有一个为假或无效,则输出无效。4是3的输出和中断屏蔽寄存器提供,只有当两者都为有效信号1时,4输出有效,否则无效。这样就可以简单的通过中断屏蔽寄存器来控制中断是否输出。请求挂起寄存器用来存储3号电路的输出信号,再由请求挂起寄存器的信号传递给4号电路
- NVIC: 连接在芯片内核,由芯片来响应中断
三、事件线路介绍
- 123与前面的一样,不在过多介绍
- 6(与门电路): 两个输入端都为有效信号,才输出有效。一个连接到3号输出,另一个连接到事件屏蔽寄存器。两者均为有效电平1的时候,标号6电路才输出有效,供给下一级。
- 脉冲发生器: 输入端连接6号的输出,6号有效,脉冲发生器输出一个脉冲信号,8是产生的脉冲信号,脉冲信号的产生是事件线路的终端。信号可以供其他外设电路使用,如:定时器、ADC等。
总结:中断线路流向NVIC控制器当中,从而运行中断服务函数,实现中断相应的功能,是软件级别的。事件线路最终产生的脉冲信号,会流向其他的外设电路,是硬件级别的。外设接口时钟是PCLK2,使用的时候要使能APB2。
四、外部中断/事件线映射
- STM32的EXTI有20条中断事件线
每个IO口都可以对应作为中断,几个IO口为一组映射到一个中断线上,如GPIOx.0映射到EXTI0,GPIOx.1映射到EXTI1,…,GPIOx.15映射到EXTI15。当PA0使用时,则PB0-PG0不可以使用。同一时刻,只能有一个IO映射到中断线上。对于其他中断线是一样的。
五、EXTI配置步骤
EXTI相关库函数在stm32f10x_exti.c和stm32f10x_exti.h文件中。
- 使能IO口时钟,配置IO口模式为输入
- 开启 AFIO 时钟,设置 IO 口与中断线的映射关系
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE); // 两个参数:第一个:端口;第二个:引脚 void GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource); // 将GPIO引脚映射到EXIT引脚 GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);
- 配置中断分组(NVIC),使能中断
- 初始化EXTI,选择触发方式
void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct); typedef struct { uint32_t EXTI_Line; //中断/事件线 EXTIMode_TypeDef EXTI_Mode; //EXTI模式 EXTITrigger_TypeDef EXTI_Trigger; //EXTI触发方式 FunctionalState EXTI_LineCmd; //中断线使能或失能 }EXTI_InitTypeDef;
- 编写EXTI中断服务函数
EXTI0_IRQHandler EXTI1_IRQHandler EXTI2_IRQHandler EXTI3_IRQHandler EXTI4_IRQHandler EXTI9_5_IRQHandler // 5-9条共用一个中断服务函数 EXTI15_10_IRQHandler // 10-15条共用一个中断服务函数
六、硬件设计
开发板上一共有4个按键:
- KEY_UP:PA0,按键按下检测到有效电平是3.3V,高电平有效
- KEY0:PE4,按键按下检测到低电平有效
- KEY1:PE3,按键按下检测到低电平有效
- KEY2:PE2,按键按下检测到低电平有效
七、软件设计
- 要实现外部中断方式控制LED,程序框架如下:
- 初始化对应端口的EXTI
- 编写EXTI中断函数
- 编写主函数
- 中断是通过外部中断线(EXTI Line)来触发的,这些外部中断线通常与GPIO引脚相连。
EXTI.c
#include "EXTI.h"
#include "SysTick.h"
#include "key.h"
#include "LED.h"
#include "beep.h"
void My_EXTI_Init(void)
{
// 因为要用按键来切换中断线,开始配置按键初始化,在KEY_Init中已经配置,此处可不进行配置
// 定义NVIC结构体
NVIC_InitTypeDef NVIC_InitStrycture;
// 定义EXTI结构体
EXTI_InitTypeDef EXTI_InitStructure;
// 开启AFIO时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
// 外部中断线配置
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOE, GPIO_PinSource2);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOE, GPIO_PinSource3);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOE, GPIO_PinSource4);
NVIC_InitStrycture.NVIC_IRQChannel = EXTI0_IRQn; // 通道配置
// 抢占式优先级和响应式优先级设置的数值取决于对NVIC分组
// 如分两组,即各占两位,可选0-3
NVIC_InitStrycture.NVIC_IRQChannelPreemptionPriority = 2; // 抢占式优先级
NVIC_InitStrycture.NVIC_IRQChannelSubPriority = 3; // 响应式优先级
NVIC_InitStrycture.NVIC_IRQChannelCmd = ENABLE; // 通道使能
// 配置中断分组
NVIC_Init(&NVIC_InitStrycture); // NVIC初始化
NVIC_InitStrycture.NVIC_IRQChannel = EXTI2_IRQn;
NVIC_InitStrycture.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStrycture.NVIC_IRQChannelSubPriority = 2;
NVIC_InitStrycture.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStrycture);
NVIC_InitStrycture.NVIC_IRQChannel = EXTI3_IRQn;
NVIC_InitStrycture.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStrycture.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStrycture.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStrycture);
NVIC_InitStrycture.NVIC_IRQChannel = EXTI4_IRQn;
NVIC_InitStrycture.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStrycture.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStrycture.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStrycture);
EXTI_InitStructure.EXTI_Line = EXTI_Line0; // 中断线
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; // 模式
// KEY_UP高电平有效,按下按键有上升沿,所以选择上升沿触发
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising; // 触发方式
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
// 配置EXTI2,3,4
EXTI_InitStructure.EXTI_Line = EXTI_Line2 | EXTI_Line3 | EXTI_Line4; // 中断线
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; // 模式
// KEY2,3,4低电平有效,按下按键有上升沿,所以选择上升沿触发
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; // 触发方式
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
}
void EXTI0_IRQHandler(void)
{
// 检查是否真的触发中断,防止误触发,检查中断标志
if(EXTI_GetITStatus(EXTI_Line0) == SET) // 参数:中断线,返回值:SET(1)表示中断线产生了中断,RESET(0)
{
delay_ms(10); // 消除按键抖动
if(KEY_UP == 1)
{
LED2 = 0;
}
}
// 清除中断标志,让下一次能够响应中断
EXTI_ClearITPendingBit(EXTI_Line0);
}
void EXTI2_IRQHandler(void)
{
// 检查是否真的触发中断,防止误触发,检查中断标志
if(EXTI_GetITStatus(EXTI_Line2) == SET) // 参数:中断线,返回值:SET(1)表示中断线产生了中断,RESET(0)
{
delay_ms(10); // 消除按键抖动
if(KEY2 == 0)
{
BEEP = 1;
}
}
// 清除中断标志,让下一次能够响应中断
EXTI_ClearITPendingBit(EXTI_Line2);
}
void EXTI3_IRQHandler(void)
{
// 检查是否真的触发中断,防止误触发,检查中断标志
if(EXTI_GetITStatus(EXTI_Line3) == SET) // 参数:中断线,返回值:SET(1)表示中断线产生了中断,RESET(0)
{
delay_ms(10); // 消除按键抖动
if(KEY1 == 1)
{
LED2 = 1;
}
}
// 清除中断标志,让下一次能够响应中断
EXTI_ClearITPendingBit(EXTI_Line3);
}
void EXTI4_IRQHandler(void)
{
// 检查是否真的触发中断,防止误触发,检查中断标志
if(EXTI_GetITStatus(EXTI_Line4) == SET) // 参数:中断线,返回值:SET(1)表示中断线产生了中断,RESET(0)
{
delay_ms(10); // 消除按键抖动
if(KEY0 == 1)
{
BEEP = 0;
}
}
// 清除中断标志,让下一次能够响应中断
EXTI_ClearITPendingBit(EXTI_Line4);
}
EXTI.h
#ifndef __EXTI_H
#define __EXTI_H
#include "system.h"
void My_EXTI_Init(void);
void EXTI0_IRQHandler(void);
void EXTI2_IRQHandler(void);
void EXTI3_IRQHandler(void);
void EXTI4_IRQHandler(void);
#endif
main.c
#include "LED.h" // 包含 LED 模块的头文件
#include "system.h"
#include "SysTick.h"
#include "beep.h"
#include "key.h"
#include "EXTI.h"
// 主函数
int main()
{
u8 i = 0; //让LED1指示灯闪烁,确保程序在执行
SysTick_Init(72);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
LED_Init(); // 初始化 LED1 和 LED2
BEEP_Init();
KEY_Init();
My_EXTI_Init();
// 主循环
while(1)
{
i ++;
if(i % 20 == 0) // 每经过200ms,让LED1翻转一次
LED1 = !LED1;
delay_ms(10);
}
}