Code Along - Chapter 8

Buttons, Pull-ups & Debounce

Read a push button with INPUT_PULLUP, act on the press rather than the level, and stop contact bounce from counting one press as four.

Estimated time 75-100 minExercises passed 0 / 5

Why a pressed button reads LOW

A push button on its own leaves the pin floating - connected to nothing, reading random HIGH and LOW values. pinMode(pin, INPUT_PULLUP) switches on a resistor inside the chip that gently holds the pin HIGH.

The button then connects the pin to GND, so:

  • not pressed: HIGH
  • pressed: LOW

That feels backwards at first, and it is worth saying out loud every time you read a button.

Button with INPUT_PULLUP: open reads HIGH, pressed connects the input to GND
INPUT_PULLUP holds the pin HIGH; the button pulls it LOW.
Worked example

Reading the button

Press Run, then hold the button on the virtual board and watch the Serial Monitor change. Let go and it goes back to released.

const int BUTTON = 2;

void setup() {
  Serial.begin(9600);
  pinMode(BUTTON, INPUT_PULLUP);
}

void loop() {
  if (digitalRead(BUTTON) == LOW) {
    Serial.println("pressed");
  } else {
    Serial.println("released");
  }
  delay(300);
}

Turn on JavaScript to edit, run and test this code in the browser.

Expected output
released
released
released
Exercise 8.1

LED follows the button

Not started

Light the onboard LED while the button is held, and switch it off when the button is released. The test presses the button at 300 ms and lets go at 900 ms.

const int BUTTON = 2;

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(BUTTON, INPUT_PULLUP);
}

void loop() {
  // LED on while the button is pressed, off when it is not

}

Turn on JavaScript to edit, run and test this code in the browser.

Act on the change, not the level

loop() runs thousands of times a second, so while you hold a button the condition digitalRead(BUTTON) == LOW is true thousands of times. Counting there would count thousands of presses.

What you want is the moment it changes: it is LOW now and it was HIGH last time round. Remember the previous reading in a variable and compare:

  • if (now == LOW && last == HIGH) { ... } - a new press
  • then last = now; at the end of every pass
Exercise 8.2

Count the presses

Not started

Print Presses: 1, Presses: 2, and so on - one line per press, not per loop. The previous reading is kept in lastButton for you.

Sample output
Presses: 1
Presses: 2
Presses: 3
const int BUTTON = 2;
int presses = 0;
bool lastButton = HIGH;

void setup() {
  Serial.begin(9600);
  pinMode(BUTTON, INPUT_PULLUP);
}

void loop() {
  bool now = digitalRead(BUTTON);
  // If this is a new press: count it and print "Presses: 1"

  lastButton = now;
}

Turn on JavaScript to edit, run and test this code in the browser.

Exercise 8.3

One press, one toggle

Not started

Make the button work like a light switch: each press flips the LED on or off, and it stays that way when you let go. The test presses three times.

const int BUTTON = 2;
bool ledOn = false;
bool lastButton = HIGH;

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(BUTTON, INPUT_PULLUP);
}

void loop() {
  bool now = digitalRead(BUTTON);
  // On a new press: flip ledOn and write it to the LED

  lastButton = now;
}

Turn on JavaScript to edit, run and test this code in the browser.

Switch bounce

Button contacts are springy metal. For a few milliseconds after they touch they make and break contact several times, so one press looks like several presses to code that runs thousands of times a second.

The cure is to wait for the reading to hold still. Remember when the raw reading last changed, and only believe it once it has been steady for, say, 20 ms:

  • raw reading changed? remember the time and nothing else
  • steady for 20 ms and different from the value you trust? that is a real change

The next exercise feeds your sketch real chatter, so the version without debouncing fails.

Exercise 8.4

Debounce the button

Not started

This counter works with clean presses, but the test presses the button the way real contacts behave: a burst of chatter, then a steady press. It counts four presses instead of two.

Rewrite it so only readings that have been steady for 20 ms count. Both tests must pass - the bouncy one and the clean one.

Sample output
Presses: 1
Presses: 2
const int BUTTON = 2;
int presses = 0;
bool lastButton = HIGH;

void setup() {
  Serial.begin(9600);
  pinMode(BUTTON, INPUT_PULLUP);
}

void loop() {
  bool now = digitalRead(BUTTON);
  if (now == LOW && lastButton == HIGH) {
    presses++;
    Serial.print("Presses: ");
    Serial.println(presses);
  }
  lastButton = now;
}

Turn on JavaScript to edit, run and test this code in the browser.

Exercise 8.5

Short press or long press?

DeeperNot started

Many devices do two jobs with one button. When the button is released, print short if it was held for less than 800 ms and long if it was held for 800 ms or more.

Note when the press starts, and work out how long it lasted when it ends.

Sample output
short
long
const int BUTTON = 2;
unsigned long pressStart = 0;
bool lastButton = HIGH;

void setup() {
  Serial.begin(9600);
  pinMode(BUTTON, INPUT_PULLUP);
}

void loop() {
  bool now = digitalRead(BUTTON);
  // Press starts: remember the time

  // Press ends: work out how long it was held and print short or long

  lastButton = now;
}

Turn on JavaScript to edit, run and test this code in the browser.

Try it on a real Uno

Wire a push button from pin 2 to GND - no resistor needed, INPUT_PULLUP provides it. Upload One press, one toggle and press it twenty times: if the LED sometimes ignores a press or flips twice, you have just met contact bounce on real hardware.

Then upload your debounced version and try again.