测试 digitalRead() 函数

sch
Figure 1. 原理图
cir-breadboard
Figure 2. 面包板上的电路
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);
}
  1. 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. 见下面的说明。

    • 键 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);