测试 digitalRead() 函数
Tip
|
完成接线后,在上电之前,请再次检查接线,注意是否有短路存在(如 5V 和 GND 都连接到面包板的同一条电源轨道上了)。 |
// 用 digitalRead() 函数测试 button 输入 const int buttonPin = A0; // the button is connected to A0 // 8 个控制 LED 的 pin const int ledPins[] = {2, 3, 4, 5, 6, 7, 8, 9}; uint8_t in_val; void write_leds(uint8_t data) { for(int i = 0; i < 8; i++) digitalWrite(ledPins[i], _BV(i) & data); } void setup() { //Serial.begin(9600); pinMode(buttonPin, INPUT); // 连 button 的 pin 设置成输入模式 digitalWrite(buttonPin, HIGH); // 使用内部上拉电阻 for(int i = 0; i < 8; i++) pinMode(ledPins[i], OUTPUT); // 连 LED 的 pin 设置成输出模式 } void loop() { in_val = digitalRead(buttonPin); // 读 A0 的状态,返回值: HIGH or LOW write_leds(in_val); // 输出到 LED 上 //Serial.println(in_val, HEX); // 可以把 in_val 打印到串口查看 }
上面的程序把 A0 pin 设置成输入模式,然后简单地用 digitalRead() 去读取它的状态。因为电路上加了上拉电阻,键未按下时,digitalRead(buttonPin) 读到的值始终是 HIGH, in_val 的值是 0b00000001,因此未按键时 LED 亮。
如果键被按下,A0 直接和电源地相连,in_val = 0b00000000,LED 灭。
按一下加一
// 实验: 按一下键,数字加 1 // 8 个控制 LED 的 pin const int ledPins[] = {2, 3 ,4, 5, 6, 7, 8, 9}; const int buttonPin1 = A0; // 这个实验只用一个键 const int buttonPin2 = 12; const int buttonPin3 = 11; const int buttonPin4 = 10; void setup() { Serial.begin(9600); pinMode(buttonPin1, INPUT); for(int i = 0; i < 8; i++) { pinMode(ledPins[i], OUTPUT); } } void write_leds(uint8_t data) { for(int i = 0; i < 8; i++) { digitalWrite(ledPins[i], _BV(i) & data); } } uint8_t in_val; // 用来存键值 uint8_t count = 0; void loop() { // 如果键没有被按下,则程序一直停在此处,处于等待状态,不往下走 while ( digitalRead(buttonPin1) == HIGH ) ; // <1> count++; // 有键按下了, count 自增 1 if (count > 15) count = 0; // 做越界处理,超过 15 了回到 0,因为我们只用 4 个 LED // 如果键被按下但未松开,则程序一直停在此处,不往下走 while ( digitalRead(buttonPin1) == LOW) ; write_leds(count); // 松开按键后更新显示 Serial.println(in_val, HEX); }
-
while 后的分号 (;) 是 C 语言的空语句(什么都不干),见下面的说明。
测试上面的代码。数你按键的次数,并观察 LED 上显示出的数字,观察两者是否匹配?多试几次。
Note
|
C 语言的空语句
C 语言的标准 C99 6.8.3 Expression and null statements 小节写到: "A null statement (consisting of just a semicolon) performs no operations." while 循环的格式是 while ( 判断表达式 ) 语句 while ( 表达式 ) ; 等价于下面的写法,如果“表达式”的值一直为真(true),则形成一个死循环。 while ( 表达式 ) { } |
消除抖动
从上面的实验可以发现,抖动的存在会让程序误动作。我们必须消除它。其中一个方法是用 delay() 函数跳过抖动区。见下面的代码。
用 delay() 跳过抖动区
// 用延时的方法去除抖动 const int buttonPin = A0; // 8 个控制 LED 的 pin const int ledPins[] = {2, 3 ,4, 5, 6, 7, 8, 9}; void setup() { Serial.begin(9600); pinMode(buttonPin, INPUT); for(int i = 0; i < 8; i++) { pinMode(ledPins[i], OUTPUT); } } void write_leds(uint8_t data) { for(int i = 0; i < 8; i++) { digitalWrite(ledPins[i], _BV(i) & data); } } byte in_val; byte in_val2; int buttonState; // 用来保存按键状态的变量 byte count = 0; void loop() { in_val = digitalRead(buttonPin); // 第一次读,值保存到 in_val delay(10); // 延时 10 ms, 跳过抖动区 in_val2 = digitalRead(buttonPin); // 再读一次,值保存到 in_val2 if (in_val == in_val2) // 相隔 10ms, 两次读到的值是一样的! { if (in_val != buttonState) // 按键的状态发生了变化(和上次不一样) { if (in_val == LOW) // 看一个读回的值,LOW 说明键已经被按下 { count++; // 有键按下了, count 自增 if (count > 15) count = 0; // 超过 15 了回到 0 } } buttonState = in_val2; // 把按键新的状态保存起来,下个循环要用到 } write_leds(count); // 显示 //Serial.println(in_val, HEX); // 可以打印到串口观察数值 }
一个更好的方法
上面的方法用了 delay() 函数。10ms 的 CPU 时间被浪费掉了。
Arduino 网站上的 Tutorial 区有一个利用 millis() 的更高效的方法。
这个例程也存在于 Arduino IDE 中,见 File → Examples → 02.Digital → Debounce
例程中用的是下拉电阻,做实验时,请注意代码和电路的对应。
再加一个键
// 用延时的方法去除抖动 const int button1Pin = A0; const int button2Pin = A1; // 8 个控制 LED 的 pin const int ledPins[] = {2, 3 ,4, 5, 6, 7, 8, 9}; void setup() { Serial.begin(9600); pinMode(button1Pin, INPUT); pinMode(button2Pin, INPUT); for(int i = 0; i < 8; i++) { pinMode(ledPins[i], OUTPUT); } } void write_leds(uint8_t data) { for(int i = 0; i < 8; i++) { digitalWrite(ledPins[i], _BV(i) & data); } } byte in_val; byte in_val2; int buttonState; // 用来保存按键状态的变量 byte count = 0; void loop() { in_val = digitalRead(button1Pin) + (digitalRead(button2Pin) << 1); // 2 个键的读取值组合到一个字节中 <1> delay(10); // 延时 10 ms, 跳过抖动区 in_val2 = digitalRead(button1Pin) + (digitalRead(button2Pin) << 1); if (in_val == in_val2) // 相隔 10ms, 两次读到的值是一样的! { if (in_val != buttonState) // 按键的状态发生了变化(和上次不一样) { if (in_val == 0b00000010) // 最低一位是 0, 说明 button1 被按下了 { count++; // 有键按下了, count 自增 1 if (count >= 15) count = 15; // 不超过 15 } if (in_val == 0b00000001) // 第 2 位是 0, 说明 button2 被按下了 { if (count > 0) count--; // 大于 0 才自减 1,否则会溢出 } } buttonState = in_val2; // 把按键新的状态保存起来,下个循环要用到 } write_leds(count); // 显示 //Serial.println(in_val, HEX); // 可以打印到串口观察数值 }
-
见下面的说明。
-
键 1 未被按下时,digitalRead(button1Pin) 返回的值是 0b00000001.
-
键 2 未被按下时,digitalRead(button2Pin) 返回的值也是 0b00000001.
-
digitalRead(button2Pin) << 1 的意思是把 << 符号左边的值向左边移动 1 位,于是 0b00000001 就变成了 0b00000010, 这样键 1 和键 2 两个值就错开了,加在一起,合到一个字节中,方便后续的处理。
Note
|
位操作
在给像 Arduino 一样的小型嵌入式微控制器编写代码时,会经常用到位操作(Bit manipulation). 没有经验者在实践后会慢慢熟悉。 例如 in_val = digitalRead(button1Pin) + (digitalRead(button2Pin) << 1); 还可以用按位或(Bitwise OR)写成: in_val = digitalRead(button1Pin) | (digitalRead(button2Pin) << 1); |