技术交流QQ群【JAVA,C++,Python,.NET,BigData,AI】:170933152
51单片机好像只有2个IO口是支持中断输入的.
但是STM32的每个IO口都是支持中断输入的.
虽然每个IO口都支持中断输入,但是STM32一共只有16个中断线
但是STM32有上百个IO口,那他是怎么实现的,让所有的IO口都支持中断的呢
来看:
这里有个外部中断和IO口的对应关系,映射关系.
因为一组IO,有16个IO口,一共有7组IO口,那么
PA0IO口的中断线用EXTI0
PB0IO口的中断线用EXTI0
PC0IO口的中断线用EXTI0
PD0IO口的中断线用EXTI0
...
PG0IO口的中断线用EXTI0
这样就对应起来了,一共16个中断线,0到15,对应每组IO口的,0到15IO口
但是注意同一个时间,其实只有一个IO口占用对应的中断线,
比如,因为 PA0---PG0这7个IO口,都用EXTI0这个中断线,所以,同一个时间只有一个IO口会占用这个中断线.
是切换使用的.
这个就是为什么,这么多IO口,可以只用16个中断线的原因.
再看一下这个中断线.
这里这个上升沿触发,就是高电平的时候触发,下降沿的时候触发,就是低电平的时候触发,边沿触发是上边沿的时候触发,
还是说可以设置,上升,下降沿都触发等.
这里不是16个中断线就可以分配16个中断服务,他是这样的
IO口外部中断,在中断向量表中只分配了7个中断向量,也就是只能用7个中断服务函数.
可以看到中断线0到中断线4,这5个中断线,都有对应的自己的中断函数.
但是中断线EXTI5到9,用一个中断服务函数,也就是这几个中断线产生中断的时候都会执行这一个中断服务函数.
中断线EXTI10到15,用一个中断服务函数,也就是这几个中断线产生中断的时候都会执行这一个中断服务函数.
这样就一共7个中断服务函数.
然后中断服务函数
首先设置IO口与中断线的映射关系,就是哪个IO口用哪个中断线
然后,初始化中断线,是上沿中断,还是下沿中断,还是边沿中断,边沿中断就是上下沿都会触发中断.
然后,判断中断标志位是不是1,也就是判断有没有发生中断.
然后,用完了以后,需要手动的把中断标志位,清零.
然后,这里看EXTI_Init函数
这个函数初始化中断线的.用来对某个中断线进行一下初始化,配置
第一个是指定要配置的中断线
第二个是选择中断模式,是中断,还是事件
第三个是指定触发方式,是上升沿,还是下降沿,还是双沿触发
第四是使能这个中断线.
下面是个例子.
外部中断配置的步骤,
第一,首先要初始化一个IO口,为输入,因为要读取输入的电平
第二,然后要开启IO口复用时钟,把IO口设置为复用,即是IO口,又是中断服务触发口.
第三,要设置IO口和中断线之间的映射,给对应的IO口配置一个中断线
第四,要初始化对应的设置的中断线,比如是设置上沿触发,下沿触发,双沿触发什么的
第五,要配置中断的分组优先级,并且使能中断,前面说过.
第六,要编写中断服务函数,并且在函数中,可以判断高低电平,并且还可以判断,比如
当EXTI5--EXTI9共用一个中断线的时候,要判断具体是哪个中断线触发的中断
第7,使用完以后要清除对应的中断标志位.
看一下接下来的中断实验的硬件连接
可以看到这里的战舰版,KEY0,KEY1,KEY2当按键,按下的时候,比如是高电平,有个上拉电阻,拉成高电平
然后按下的过程中,是由高电平往下走的过程,然后走平以后,再往上沿走,往上沿走的时候其实就是按下的手松开了
这里就可以根据需要,比如按下的时候需要中断,就可以设置为这个中断线为下沿触发,如果需要松开的时候需要中断,
就设置对应的中断线为上沿触发.
接下来看一下,外部中断实验的代码
首先,新建中断头文件exti.h文件,里面有个初始化函数.
然后再去写exti.c文件
按照这个步骤来写.
首先要知道,官方提供的中断的库函数,都在上面这个文件和.h文件中.
首先这里要调用KEY_Init这个来实现初始化按键的功能
然后第二:来开启IO口复用时钟.
然后第三,设置IO口与中断线的映射.
可以看到,这里比如KEY0连接到了PE4那么,就要设置GPIOE
设置这个PE4IO口使用EXTI4这个中断线.
注意一定要按照文档中的说明来,也就是说PE4只能对应EXTI4,这个是有这个要求的,
如果PE4IO口,比如配置的中断线是EXTI5肯定是不行的.
这里这个中断线和IO口的映射配置用GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_PinSource4)
这个函数,这里的参数:
第一个是,哪个IO口,这里是GPIOE,第二个是,哪个引脚,这里是第四个引脚.
然后再去
第4步,再去初始化中断线.
这里
EXTI_InitStrue.EXTI_Line=EXTI_Line4 //设置使用中断线4
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;//模式有事件触发,还有中断触发,这里选择中断触发
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;//因为硬件连接有个接地,所以这里我们选择下降沿触发
EXTI_Init(&EXTI_InitStructure); // 根据EXTI_InitStruct中指定的参数初始化外设EXTI寄存器
KEY0接地.可以看到.
注意对应的参数都可以去定义的部分去看.
然后5.配置中断优先级.
这里的中断优先级配置:
NVIC_InitStructure.NVIC_IRQChannel = EXTI4_IRQn; //1.配置这个优先级通道,中断线4这个通道.
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; // 抢占优先级2,这里设置之前需要设置优先级分组,这个分组一般都是在main函数最上面就设置的.
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; // 子优先级2,也就是响应优先级设置为2
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // 使能这个通道,开启这个优先级通道.
NVIC_Init(&NVIC_InitStructure);
接下来第六,可以开始编写中断服务函数了.
可以在启动文件中找到.
这个外部中断服务函数,然后就开始编写中断
这里,前面说过为了防止抖动,这里首先要有10毫秒的延时,
然后根据前面的硬件连接,按下的时候,是低电平.
KEY0按下的时候是低电平,所以这里,判断
//外部中断0服务程序
void EXTI0_IRQHandler(void)
{
delay_ms(10); //消抖
if (KEY0 == 0) //
{
LED0 = !LED0;
LED1 = !LED1;
}
EXTI_ClearITPendingBit(EXTI_Line0); //清除LINE4上的中断标志位
}
按下KEY0的时候让LED0和LED1进行翻转就可以了.
然后再去看一下main函数
可以看到,前面都是一些初始化,然后调用中断线的初始化,然后
有个死循环,
int main(void)
{
delay_init(); //延时函数初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
uart_init(115200); //串口初始化为115200
LED_Init(); //初始化与LED连接的硬件接口
BEEP_Init(); //初始化蜂鸣器IO
EXTIX_Init(); //初始化外部中断输入
LED0 = 0; //先点亮红灯
while (1)
{
printf("OK\r\n");
delay_ms(1000);
}
}
这个死循环的意思是,当下载程序到开发版以后,
程序会一直执行,每隔1秒,打印OK到串口,当
产生下沿中断以后,就会去执行上面写的中断函数,就会引起
LED0,LED1的翻转.
接下来编译以后下载到开发版试试
然后下载程序后,按下按键,可以看到现象.
然后,可以把按键3也添加上.修改一下代码:
#include "exti.h"
#include "led.h"
#include "key.h"
#include "delay.h"
#include "usart.h"
#include "beep.h"
//
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//ALIENTEK精英STM32开发板
//外部中断 驱动代码
//正点原子@ALIENTEK
//技术论坛:www.openedv.com
//修改日期:2012/9/3
//版本:V1.0
//版权所有,盗版必究。
//Copyright(C) 广州市星翼电子科技有限公司 2009-2019
//All rights reserved
//
//外部中断0服务程序
void EXTIX_Init(void)
{
EXTI_InitTypeDef EXTI_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
KEY_Init(); // 按键端口初始化
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); // 使能复用功能时钟
// GPIOE.3 中断线以及中断初始化配置 下降沿触发 //KEY1
GPIO_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 中断线以及中断初始化配置 下降沿触发 //KEY0
GPIO_EXTILineConfig(GPIO_PortSourceGPIOE, GPIO_PinSource4);
EXTI_InitStructure.EXTI_Line = EXTI_Line4;
EXTI_Init(&EXTI_InitStructure); //根据EXTI_InitStruct中指定的参数初始化外设EXTI寄存器
// GPIOA.0 中断线以及中断初始化配置 上升沿触发 PA0 WK_UP
GPIO_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; // 子优先级3
NVIC_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上的中断标志位
}
这里添加上了另外的两个按键,然后添加完以后,可以再去试一试.
编译一下,去测试一下效果.