本文隶属于AVR单片机教程系列。
在按键的上方有4个拨动开关。开关与按键,在原理和使用方法上都是很类似的,但有不同的用途——按键按下后松开就会弹起,而开关可以保存其状态。
<switch.h> 定义了与开关相关的函数。switch_status 对应 button_down ,switch_changed 对应 button_pressed ,使用与按键是基本相同的。
这里通过一个异常简单的例子来演示开关函数的使用,并说明开关操作与按键的细微差别:使LED与开关状态保持同步。
仿照上一篇教程,我们可以简单地把 button_pressed 替换为 switch_changed :
1 #include <ee1/led.h> 2 #include <ee1/switch.h> 3 #include <ee1/delay.h> 4 5 int main() 6 { 7 led_init(); 8 switch_init(PIN_NULL, PIN_NULL); 9 while (1) 10 { 11 if (switch_changed(SWITCH_0)) 12 led_flip(LED_GREEN); 13 delay(40); 14 } 15 }
如果把按键拨到关(即下)再复位单片机,一切安好,但是如果一开始是开的状态,LED的状态会总是与开关相反。为什么呢?因为开关可以保存状态,这是第一个差别。
所以,我们可以每次得知开关状态改变后读取开关状态,并传给LED:
1 #include <ee1/led.h> 2 #include <ee1/switch.h> 3 #include <ee1/delay.h> 4 5 int main() 6 { 7 led_init(); 8 switch_init(PIN_NULL, PIN_NULL); 9 while (1) 10 { 11 if (switch_changed(SWITCH_0)) 12 led_set(LED_GREEN, switch_status(SWITCH_0)); 13 delay(40); 14 } 15 }
这也引出了第二个差别——当 switch_changed 返回 true 时,我们不能直接知晓开关的状态,需要手动调用 switch_status ;而对于相应的按键操作,我们知道此时按键一定处于按下的状态。
如果复位时开关状态为开,除了第一次拨动按键以前,LED总是与按键同步的。然而我们的要求是保持同步,当然包括一开始的时候。为了获得正确的初始状态,我们可以在进入主循环前添加以下代码:
1 if (switch_status(SWITCH_0)) 2 led_set(LED_GREEN, true);
但事实上,这个问题根本没有那么麻烦,甚至不需要 switch_changed 函数——每次循环读取开关状态,再把LED设置成相应状态即可:
1 #include <ee1/led.h> 2 #include <ee1/switch.h> 3 #include <ee1/delay.h> 4 5 int main() 6 { 7 led_init(); 8 switch_init(PIN_NULL, PIN_NULL); 9 while (1) 10 { 11 led_set(LED_GREEN, switch_status(SWITCH_0)); 12 delay(40); 13 } 14 }
你一开始有没有想到这个简单的方案?如果有,并且第一反应就是这个,那你得开始逐渐改变思路了。这种方案在这个例子中可用,是因为对 led_set 和 switch_status 的调用的成本是很低的,每次循环都调用也没有问题。然而这并不是放之四海而皆准的,有些函数经不起这样频繁的调用。在那样的情况下,检测状态改变,再读取状态,是一种更好的方案。
思考:在判定按键动作的函数中,前一次状态通过静态变量存储,其初值为真,保证第一次调用时函数不会返回真;对于开关动作,相应静态变量的初值应该是什么?
作业:每个开关控制一个LED,一个按键控制开关是否启用;启用时要让LED与当前开关状态对应,禁用时要关闭所有LED。