STM32 的 IO 口在第六章有详细介绍,而中断管理分组管理在前面也有详细的阐述。这里
我们将介绍 STM32 外部 IO 口的中断功能,通过中断的功能,达到第八章实验的效果,即:通
过板载的 3 个按键,控制板载的两个 LED 的亮灭以及蜂鸣器的发声。
这章的代码主要分布在固件库的 stm32f10x_exti.h 和 stm32f10x_exti.c 文件中。
1.外部中断概述
外部中断是单片机实时地处理外部事件的一种内部机制。当某种外部事件发生时,单片机的中断系统将迫使CPU暂停正在执行的程序,转而去进行中断事件的处理;中断处理完毕后.又返回被中断的程序处,继续执行下去。
STM32的每个IO都可以作为外部中断输入。
STM32的中断控制器支持19个外部中断/事件请求:
线0~15:对应外部1O口的输入中断。
线16:连接到PVD输出。
线17:连接到RTC闹钟事件
线18:连接到USB唤醒事件
每个外部中断线可以独立的配置触发方式(上升沿,下降沿或者双边沿触发),触发/屏蔽,专用的状态位。
从上面可以看出,STM32供IO使用的中断线只有16个,但是STM32F10x系列的IO口多达上百个,STM32F103ZET6(112)STM32F103RCT6(51),那么中断线怎么跟io口对应呢?
STM32 这样设计,GPIO 的管脚 GPIOx.0~GPIOx.15(x=A,B,C,D,E,F,G)分别对应中断线 0~15。这样每个中断线对应了最多 7 个 IO 口
以线 0 为例:它对应了 GPIOA.0、GPIOB.0、GPIOC.0、GPIOD.0、GPIOE.0、GPIOF.0、GPIOG.0。而中断线每次只能连接到 1 个 IO 口上,这样就需要通过配置来决定对应的中断线配置到哪个 GPIO 上了。下面我们看看 GPIO 跟中断线的映射关系图:
IO口外部中断在中断向量表中只分配了7个中断向量,也就是只能使用7个中断服务函数
从表中可以看出,外部中断线5~9分配一个中断向量,共用一个服务函数外部中断线10~15分配一个中断向量,共用一个中断服务函数。
2.外部中断常用函数库
在库函数中,配置 GPIO 与中断线的映射关系的函数 GPIO_EXTILineConfig()来实现的:
void GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource)
该函数将 GPIO 端口与中断线映射起来,使用范例是:
GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_PinSource2);
将中断线 2 与 GPIOE 映射起来,那么很显然是 GPIOE.2 与 EXTI2 中断线连接了。
设置好中断线映射之后,那么到底来自这个 IO 口的中断是通过什么方式触发的呢?接下来我们就要设置该中断线上中断的初始化参数了。
中断线上中断的初始化是通过函数 EXTI_Init()实现的。
EXTI_Init()函数的定义是:
void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct);
我们来看看结构体 EXTI_InitTypeDef 的成员变量:
typedef struct
{
uint32_t EXTI_Line; //指定要配置的中断线,取值范围为EXTI_Line0~EXTI_Line15
EXTIMode_TypeDef EXTI_Mode; //模式:事件OR中断
EXTITrigger_TypeDef EXTI_Trigger; //触发方式:上升沿/下降沿/双沿触发
FunctionalState EXTI_LineCmd; //使能OR
}EXTI_InitTypeDef;
下面我们用一个使用范例来说明这个函数的使用:
EXTI_InitTypeDef EXTI_InitStructure;
EXTI_InitStructure.EXTI_Line=EXTI_Line4;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure); //根据 EXTI_InitStruct 中指定的
//参数初始化外设 EXTI 寄存器
上面的例子设置中断线 4 上的中断为下降沿触发。
3.外部中断一般配置过程
4.硬件连接
本实验用到的硬件资源有:
1) 指示灯 DS0、DS1
2) 蜂鸣器
3) 3 个按键:KEY0、KEY1 和 KEY_UP。
DS0、DS1 以及蜂鸣器和 STM32F1 的连接在上两章都已经分别介绍了,在精英 STM32F103
上的按键 KEY0 连接在 PE4 上、KEY1 连接在 PE3 上、KEY_UP 连接在 PA0 上。如图所示:
这里需要注意的是:KEY0 和 KEY1 是低电平有效的,而 KEY_UP 是高电平有效的,并且
外部都没有上下拉电阻,所以,需要在 STM32F1 内部设置上下拉。
5.软件设计
外部中断exti.c 代码
#include "exti.h"
#include "led.h"
#include "key.h"
#include "delay.h"
#include "usart.h"
#include "beep.h"//外部中断0服务程序
void EXTIX_Init(void)
{EXTI_InitTypeDef EXTI_InitStructure;NVIC_InitTypeDef NVIC_InitStructure;KEY_Init(); // 按键端口初始化RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE); //使能复用功能时钟//GPIOE.3 中断线以及中断初始化配置 下降沿触发 //KEY1GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_PinSource3);EXTI_InitStructure.EXTI_Line=EXTI_Line3;EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;EXTI_Init(&EXTI_InitStructure); //根据EXTI_InitStruct中指定的参数初始化外设EXTI寄存器//GPIOE.4 中断线以及中断初始化配置 下降沿触发 //KEY0GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_PinSource4);EXTI_InitStructure.EXTI_Line=EXTI_Line4;EXTI_Init(&EXTI_InitStructure); //根据EXTI_InitStruct中指定的参数初始化外设EXTI寄存器//GPIOA.0 中断线以及中断初始化配置 上升沿触发 PA0 WK_UPGPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource0); EXTI_InitStructure.EXTI_Line=EXTI_Line0;EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;EXTI_Init(&EXTI_InitStructure); //根据EXTI_InitStruct中指定的参数初始化外设EXTI寄存器NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; //使能按键WK_UP所在的外部中断通道NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02; //抢占优先级2, NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x03; //子优先级3NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道NVIC_Init(&NVIC_InitStructure); NVIC_InitStructure.NVIC_IRQChannel = EXTI3_IRQn; //使能按键KEY1所在的外部中断通道NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02; //抢占优先级2 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x01; //子优先级1 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器NVIC_InitStructure.NVIC_IRQChannel = EXTI4_IRQn; //使能按键KEY0所在的外部中断通道NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02; //抢占优先级2 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x00; //子优先级0 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器}//外部中断0服务程序
void EXTI0_IRQHandler(void)
{delay_ms(10);//消抖if(WK_UP==1) //WK_UP按键{ BEEP=!BEEP; }EXTI_ClearITPendingBit(EXTI_Line0); //清除LINE0上的中断标志位
}//外部中断3服务程序
void EXTI3_IRQHandler(void)
{delay_ms(10);//消抖if(KEY1==0) //按键KEY1{ LED1=!LED1;} EXTI_ClearITPendingBit(EXTI_Line3); //清除LINE3上的中断标志位
}void EXTI4_IRQHandler(void)
{delay_ms(10);//消抖if(KEY0==0) //按键KEY0{LED0=!LED0;LED1=!LED1; } EXTI_ClearITPendingBit(EXTI_Line4); //清除LINE4上的中断标志位
}
main()函数
#include "led.h"
#include "delay.h"
#include "key.h"
#include "sys.h"
#include "usart.h"
#include "exti.h"
#include "beep.h"int main(void){ delay_init(); //延时函数初始化 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级uart_init(115200); //串口初始化为115200LED_Init(); //初始化与LED连接的硬件接口 BEEP_Init(); //初始化蜂鸣器IOEXTIX_Init(); //初始化外部中断输入 LED0=0; //先点亮红灯while(1){ printf("OK\r\n"); delay_ms(1000); }
}
代码分析
① 初始化IO口为输入
KEY_Init(); // 按键端口初始化
KEY_Init();
#include "stm32f10x.h"
#include "key.h"
#include "sys.h"
#include "delay.h"//按键初始化函数
void KEY_Init(void) //IO初始化
{ GPIO_InitTypeDef GPIO_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOE,ENABLE);//使能PORTA,PORTE时钟GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4|GPIO_Pin_3;//KEY0-KEY1GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //设置成上拉输入GPIO_Init(GPIOE, &GPIO_InitStructure);//初始化GPIOE4,3//初始化 WK_UP-->GPIOA.0 下拉输入GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //PA0设置成输入,默认下拉 GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.0}
//按键处理函数
//返回按键值
//mode:0,不支持连续按;1,支持连续按;
//0,没有任何按键按下
//1,KEY0按下
//2,KEY1按下
//3,KEY3按下 WK_UP
//注意此函数有响应优先级,KEY0>KEY1>KEY_UP!!
u8 KEY_Scan(u8 mode)
{ static u8 key_up=1;//按键按松开标志if(mode)key_up=1; //支持连按 if(key_up&&(KEY0==0||KEY1==0||WK_UP==1)){delay_ms(10);//去抖动 key_up=0;if(KEY0==0)return KEY0_PRES;else if(KEY1==0)return KEY1_PRES;else if(WK_UP==1)return WKUP_PRES;}else if(KEY0==1&&KEY1==1&&WK_UP==0)key_up=1; return 0;// 无按键按下
}
②开启IO日复用时钟AFIO
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE); //使能复用功能时钟
③设置IO口与中断线的映射关系
④初始化线上中断,设置触发条件等
按键 KEY0 连接在 PE4 上、KEY1 连接在 PE3 上、KEY_UP 连接在 PA0
KEY0 和 KEY1 是低电平有效的,所以配置为上升沿触发;
而 KEY_UP 是高电平有效的,所以配置为下降沿触发
EXTI_InitTypeDef EXTI_InitStructure;//GPIOE.3 中断线以及中断初始化配置 下降沿触发 //KEY1GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_PinSource3);EXTI_InitStructure.EXTI_Line=EXTI_Line3;EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;EXTI_Init(&EXTI_InitStructure); //根据EXTI_InitStruct中指定的参数初始化外设EXTI寄存器//GPIOE.4 中断线以及中断初始化配置 下降沿触发 //KEY0GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_PinSource4);EXTI_InitStructure.EXTI_Line=EXTI_Line4;EXTI_Init(&EXTI_InitStructure); //根据EXTI_InitStruct中指定的参数初始化外设EXTI寄存器//GPIOA.0 中断线以及中断初始化配置 上升沿触发 PA0 WK_UPGPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource0); EXTI_InitStructure.EXTI_Line=EXTI_Line0;EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;EXTI_Init(&EXTI_InitStructure); //根据EXTI_InitStruct中指定的参数初始化外设EXTI寄存器
⑤配置中断分组(NVIC),并使能中断
先在mian()函数中设置中断分组
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
NVIC_InitTypeDef NVIC_InitStructure;//EXTI0
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; //使能按键WK_UP所在的外部中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02; //抢占优先级2,
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x03; //子优先级3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道
NVIC_Init(&NVIC_InitStructure); //EXTI3
NVIC_InitStructure.NVIC_IRQChannel = EXTI3_IRQn; //使能按键KEY1所在的外部中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02; //抢占优先级2
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x01; //子优先级1
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道
NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器//EXTI4
NVIC_InitStructure.NVIC_IRQChannel = EXTI4_IRQn; //使能按键KEY0所在的外部中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02; //抢占优先级2
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x00; //子优先级0
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道
NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
⑤编写中断服务函数 EXTIx_IRQHandler()
⑥清除中断标志位
需要清除中断标志位,否则无法进行下一次中断操作,这里用到EXTI_ClearITPendingBit() 函数
EXTI_ClearITPendingBit(EXTI_Line0); //清除LINE0上的中断标志位
//外部中断0服务程序
void EXTI0_IRQHandler(void)
{delay_ms(10);//消抖if(WK_UP==1) //WK_UP按键{ BEEP=!BEEP; }EXTI_ClearITPendingBit(EXTI_Line0); //清除LINE0上的中断标志位
}//外部中断3服务程序
void EXTI3_IRQHandler(void)
{delay_ms(10);//消抖if(KEY1==0) //按键KEY1{ LED1=!LED1;} EXTI_ClearITPendingBit(EXTI_Line3); //清除LINE3上的中断标志位
}//外部中断4服务程序
void EXTI4_IRQHandler(void)
{delay_ms(10);//消抖if(KEY0==0) //按键KEY0{LED0=!LED0;LED1=!LED1; } EXTI_ClearITPendingBit(EXTI_Line4); //清除LINE4上的中断标志位
}