jerry_fuyi

导航

AVR单片机教程——按键动作

本文隶属于AVR单片机教程系列。

 

上一篇教程中我们学习了如何读取按键状态。而按键的动作,比如单击,至少需要两个状态才能判定,长按、双击的判定更加复杂。今天我们来学习如何使用库函数判断按键单击,以及其实现原理。

我们要实现的是:当一个按键被单击时,一个LED的状态改变(即亮变暗,暗变亮);4个按键对应4个LED。利用库提供的 button_pressed 函数,很容易就能实现这个功能。

 1 #include <ee1/button.h>
 2 #include <ee1/led.h>
 3 #include <ee1/delay.h>
 4 
 5 int main()
 6 {
 7     led_init();
 8     button_init(PIN_0, PIN_1);
 9     while (1)
10     {
11         for (uint8_t i = 0; i != BUTTON_COUNT; ++i)
12             if (button_pressed(i))
13                 led_flip(i);
14         delay(40);
15     }
16 }

在主循环中,程序对每个按键调用 button_pressed ,若返回真,则用 led_flip 将LED状态反转。

按键动作需要两个状态来判断,而函数在当下是无法从按键读取到以前的信息的,只能在本次调用时保存状态以供下次使用。自动变量保存的内容无法保留到下一次调用,可选的有全局变量与静态变量。由于此状态只被这一个函数使用,可以把它定义成静态变量。

button_pressed 函数可以判断4个按键的动作,每个按键需要一个 bool 变量,即一个bit的存储空间。为了节省空间,我使用了位操作,这里先不讲,把重心放在按键动作判断逻辑上。以下是判断一个按键动作的函数。

1 bool pressed()
2 {
3     static bool status = true;
4     bool pre = status;
5     status = button_down(BUTTON_0);
6     return !pre && status;
7 }

静态变量 status 用于保存按键在上一次调用时的状态。函数体中,先用自动变量 pre 临时保存了 status ,然后将 status 更新为当前的状态。return 语句返回的是 !pre && status ,即仅当 pre 为假且 status 为真时返回 true 。其逻辑为,如果上一次按键没有被按下而这一次被按下,则按键被单击了。

想一想,为什么 status 的初值要设置成 true ?

另外,上面的代码开头处的蓝色 bool 特别醒目,因为博客园代码着色是按照C#的规则,bool 是其中一个关键字。但是应当注意,C语言中没有 bool 这个关键字,而是 _Bool ;bool 与 true 和 false 都在 <stdbool.h> 中定义。

我们还没有解释过第一段代码中的 delay(40) 。如果你把它去掉,你会发现判定经常出错,往往在抬起的时候被多判定了一次,在按得不是很用力时很不稳定。这是按键内部的机械结构决定的,当处于连通和不连通位置的交界处时,单片机检测到的电平会迅速跳变(按键的原理,以及单片机如何检测按键状态,将在几篇后介绍),而一段延时就可以让这些跳变的电平被 button_down 函数忽略。这里的40是根据经验选取的。其实把40换成10到100之间的数,手感基本没有差别。

然而,即使有这句延时,单击判定还是有出错的时候。如果不能允许这样的错误,就需要“消抖”上场了,我们以后再讲。

 

作业:给其中一个按键加上一个功能,让它控制其余按键是否启用。

posted on 2019-09-19 02:09  jerry_fuyi  阅读(796)  评论(0编辑  收藏  举报