今天在写利用中断实现控制LED灯亮灭程序时,我遇到了一个尴尬的问题。
本来是想实现绿色标识的对应控制,即
K_UP控制LED1亮,K_DOWN控制LED2亮,
K_LEFT控制LED3亮,K_RIGHT控制LED4亮。
但结果很遗憾,出现了红色标识对应的情况,各按钮与LED灯的对应情况不符。我原始的代码如下:(仅用于记录错误代码,若无兴趣可以直接跳至中断服务函数)
#include "exti.h"
void My_EXTI_Init(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
EXTI_InitTypeDef EXTI_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //中断优先级分组 分2组
GPIO_EXTILineConfig(GPIO_PortSourceGPIOE, GPIO_PinSource2);//选择GPIO管脚用作外部中断线路
GPIO_EXTILineConfig(GPIO_PortSourceGPIOE, GPIO_PinSource3);//选择GPIO管脚用作外部中断线路
GPIO_EXTILineConfig(GPIO_PortSourceGPIOE, GPIO_PinSource4);//选择GPIO管脚用作外部中断线路
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);//选择GPIO管脚用作外部中断线路
…………//NVIC配置的代码略
EXTI_InitStructure.EXTI_Line=EXTI_Line0;
EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Rising;
EXTI_InitStructure.EXTI_LineCmd=ENABLE;
EXTI_Init(&EXTI_InitStructure);
EXTI_InitStructure.EXTI_Line=EXTI_Line2|EXTI_Line3|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);
}
void EXTI0_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line0)==1)
{
delay_ms(10);
if(K_UP==1)
{
LED1_ON;
}
}
EXTI_ClearITPendingBit(EXTI_Line0);
}
void EXTI2_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line2)==1)
{
delay_ms(10);
if(K_DOWN==1)
{
LED2_ON;
}
}
EXTI_ClearITPendingBit(EXTI_Line2);
}
void EXTI3_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line3)==1)
{
delay_ms(10);
if(K_LEFT==1)
{
LED3_ON;
}
}
EXTI_ClearITPendingBit(EXTI_Line3);
}
void EXTI4_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line4)==1)
{
delay_ms(10);
if(K_RIGHT==0)
{
LED4_ON;
}
}
EXTI_ClearITPendingBit(EXTI_Line4);
}
既然从灯值表能看出K_UP和K_RIGHT与灯的对应关系是正确的,那么问题是出在了K_DOWN和K_LEFT上。在仔细复盘查找led、key、exti的问题之后,我最终把焦点锁定在了中断服务函数上。
原来,我的代码的问题出现在了中断服务函数与中断线的对应上。什么意思呢?我做了一张表格来表明我的中断对应关系,如下:
乍一看其实没有什么问题的,也是符合中断定义规则的。但联系到笔记开头的灯值表的第一列,就能发现问题了。其中,绿色代表预想的配置关系,红色代表错误配置关系。
显然,在中断复用IO后:
K_DOWN按键(PEin(3))应该对应EXTI3,却被误设置为对应EXTI2;
K_LEFT按键(PEin(2))应该对应EXTI2,却被误设置为对应EXTI3。
这也就是为什么出现了“按这个键,那个灯却亮了”的原因。而为什么会出现明明应该是“K_DOWN== 0”和“K_LEFT== 0”(因为按照我在key.h里的宏定义,“==0”代表了低电平,而硬件设计则表示低电平代表开关打开),却在设置“K_DOWN ==1”时灯才会亮呢?
可以这么理解:由于
EXTI_InitStructure.EXTI_Line=EXTI_Line2|EXTI_Line3|EXTI_Line4;
EXTI_EXTI_Trigger=EXTI_Trigger_Falling;
故EXTI2和EXTI3都被配置成了下降沿触发(0到1时触发)。
//以EXTI2为例说明,EXTI3同
void EXTI2_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line2)==1)
{
delay_ms(10); if(K_DOWN==1) { LED2_ON; }
}
EXTI_ClearITPendingBit(EXTI_Line2);
}
当按下按键K_LEFT时,EXTI2的中断服务函数启动,此时:
若设置为
if(K_DOWN==0)
(代表K_DOWN按下)那么显然if不成立,LED2不亮;
若设置为if(K_DOWN==1)
(代表K_DOWN未按下)因为我按下的是K_LEFT按键,那么此时if条件是成立的,LED2亮。
按照此思路一一对应过去就会发现与文章最开头的灯值表是情况一致的。
那么其实要修改为正确程序,只需要将两个中断服务函数的if条件对换位置即可:
//以EXTI2为例说明,EXTI3同
void EXTI2_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line2)==1)
{
delay_ms(10); if(K_LEFT==0) { LED3_ON; }
}
EXTI_ClearITPendingBit(EXTI_Line2);
}
总结
由于对外部中断的理解不够深入,导致编程时出现了一些制杖的问题。
但从中我也学习到了:在中断里的if函数代表了先满足中断条件后,后进行判断。也就是说如果要执行中断函数里的if函数的内容,要满足两个条件:1、能中断;2、满足1的前提下同时满足if函数的条件。
中断充当了一个连接硬件层面条件和软件层面条件的桥梁,当硬件层面条件(上升沿、下降沿)被满足时,中断服务函数启动,若中断服务函数内还有条件判断语句,则软件层面条件(if语句等)开始执行判断。
另外,这也告诉我:只有当在编写软件层面程序的同时,对硬件层面也要有深刻地理解,这样才能实现精准而无bug的控制。
附言
完整程序文件见附件,我使用的开发板是普中PZ6806L型开发板,所以对应IO是以它的IO为设置的。