(想要看“不用库的简单中断”的小伙伴可以直接看以下的思路二)
Note:
我们这里所说的按键是可以是广义上的按键,可以是任何一种不加处理的外部指令触发操作,所以在代码移植的时候可以根据自我的需求进行改动,而以下我们所用的是具体的按键来说明的:
1.指令信号重复产生的问题的发现:
在一开始学习使用按钮的时候我们大多数人的第一思路就是设置一个判断语句来判断按钮所连接的IO口的电频高低,理论上是没错的,不会产生我们这里所谓的信号“抖动”(只要你手速够快,以至于快过单片机主循环循环一次的时间)。
但是事实上我们在这里几乎不可能做到这样的快速操作,就算偶尔成功一次也很难复现。所以会产生这样一种现象:有关判断语句中的代码功能在一次按键操作中重复多次实现的情况,而其产生的原因就是因为我们都不是闪电侠或者快银,我们肢体执行按键动作的时间往往超出了单片机循环的周期甚至超出好几倍。
2.指令信号重复产生的问题的解决:
### 首先我们在这里声明一下这里所指的按键动作:
# 接线描述:
按键一端接IO口,一端接地,IO口通过单片机程序拉高电平;
#动作描述:
我们将按键按下产生下降沿信号(即打开IO口接地开关);按下的操作由于动作非瞬时完成而导致IO口电持续为低电平一段时间;按键动作完成,我们松手产生上升沿信号(IO口接地开关断开,IO口恢复高电平);
### 这里解决问题的两种思路:
1.通过某种方式使一次按键动作所包含的全过程电平变化处理成唯一指令信号
这种方式强调的是:
一个主循环里去检测按键所连接IO口包括“下降沿”、“上升沿”先后两个电平的有顺序的变化,以这个完整的变化作为信号触发的依据
2.通过某种方式使一次按键动作所产生的一个下降沿处理成唯一的指令信号
这种方法强调的是:
仅以按下按键瞬间的下降沿作为信号触发指令,而随后保持的电平则被隔离开致其无法单独作为信号触发的依据
3.代码示例:
思路一的代码:
这里我们不再赘述引脚设置,仅给出核心的函数:
//by_zzy
bool crackdown_button(int A)
{
if (digitalRead(A) == LOW)
{
while (1)
{
if (digitalRead(A) == HIGH)
{
break;
}
}
return 1;
}
else
{
return 0;
}
}
Note:这个函数应该比较好懂,本质上就是当读到下降沿后用进入while死循环直至读到上升沿信号才执行后续代码;所以比较适合用在setup函数里,而且可以通过判断这个函数的返回值来判断一个模式开始或结束。
思路二的代码:
这里我们不再赘述引脚设置,给出其主循环的框架:
(这里我使用的是两个按键)
//by_zzy
now = 1;//当前循环内的模式判据——初始化
beforeA = 1;//按键A在上一次循环内的模式判据——初始化
beforeB = 1;//按键B在上一次循环内的模式判据——初始化
void loop() {
switch (now)
{
case 1:
//....
if (digitalRead(A)==HIGH){beforeA=1;}
if (digitalRead(B)==HIGH){beforeB=1;}
if (digitalRead(A)==LOW&&beforeA!=2){now=2;}
if (digitalRead(B)==LOW&&beforeB!=3){now=3;}
break;
case 2:
//....
now = 1;
beforeA=2;
break;
case 3:
//....
now = 1;
beforeB=3;
break;
}
}
Note:这个框架对初学者来说可能会乍一看不太理解,本质上是:
1.用switch来判断包括:一个"隔离"模式,一个按键A模式,一个按键B模式,三种模式的变化;
2.以及用隔离模式中对beforeA,beforeB的判断来决定是否打开模式切换的通道。
PS:这种实现方式的好处是可以在不同模式中添加自己所需的功能代码且会跟随主循环而重复执行,可以理解成通过按键触发在主循环内实现了"中断",还不需要其他的库。