Day 2 of 5
⏱ ~60 minutes
Arduino in 5 Days — Day 2

Digital I/O

Digital I/O is the foundation of all embedded control: read a button, write to a relay, respond to events. Today you'll master digital pins and debouncing.

Digital Output Pins

Arduino digital pins can output 5V (HIGH) or 0V (LOW). pinMode(pin, OUTPUT) configures direction. digitalWrite(pin, HIGH) sets it high. Output current per pin is limited to 40mA max, 20mA recommended. For loads over 20mA (motors, relays, high-power LEDs), use a transistor or driver IC. Pins 0 and 1 are also Serial RX/TX — avoid them if using Serial.

Digital Input and Pull-Up Resistors

pinMode(pin, INPUT) reads a pin. Without a pull-up or pull-down resistor, a floating input reads random values. Use pinMode(pin, INPUT_PULLUP) to enable the internal 20–50kΩ pull-up. With INPUT_PULLUP: pin reads HIGH normally, LOW when connected to ground (inverted logic — button press = LOW). Always use INPUT_PULLUP unless you have an external pull-down.

Debouncing Buttons

Mechanical buttons bounce — the contact opens and closes 5–50 times in 10ms when pressed. This causes multiple reads of a single press. Software debounce: after detecting a state change, wait 50ms before reading again. Store the previous state. Only act when state changes after the debounce period. Millis-based debounce is more reliable than delay-based.

cpp
// Button with debouncing — correct approach
const int BTN_PIN = 2;
const int LED_PIN = 9;
const unsigned long DEBOUNCE_MS = 50;

int ledState = LOW;
int btnState;
int lastBtnState = HIGH;  // INPUT_PULLUP: normally HIGH
unsigned long lastDebounce = 0;

void setup() {
  Serial.begin(9600);
  pinMode(BTN_PIN, INPUT_PULLUP);  // internal pull-up
  pinMode(LED_PIN, OUTPUT);
  btnState = digitalRead(BTN_PIN);
}

void loop() {
  int reading = digitalRead(BTN_PIN);

  // State changed — start debounce timer
  if (reading != lastBtnState) {
    lastDebounce = millis();
  }

  // If stable for DEBOUNCE_MS, it's a real change
  if ((millis() - lastDebounce) > DEBOUNCE_MS) {
    if (reading != btnState) {
      btnState = reading;
      // Button pressed = LOW (INPUT_PULLUP inverted)
      if (btnState == LOW) {
        ledState = !ledState;
        digitalWrite(LED_PIN, ledState);
        Serial.print("Button press! LED: ");
        Serial.println(ledState ? "ON" : "OFF");
      }
    }
  }
  lastBtnState = reading;
}
💡
INPUT_PULLUP means the logic is inverted: unpressed = HIGH, pressed = LOW. This is confusing at first but it's correct — you're connecting the button between the pin and GND, and the internal resistor pulls the pin up to 5V when the button is open.
📝 Day 2 Exercise
Build a Toggle Switch with LED Feedback
  1. Wire a button to pin 2 (and GND). Wire an LED with 330Ω to pin 9. Upload the debounce sketch.
  2. Press the button rapidly 10 times. Open Serial Monitor — do you get exactly 10 'Button press!' messages? If more, reduce DEBOUNCE_MS.
  3. Add a second button on pin 3. First button toggles LED, second button sets LED to specific brightness using analogWrite (PWM).
  4. Add a counter: print the total press count to Serial each time.
  5. Implement long-press detection: if button held >1000ms, do a different action (e.g., blink the LED 3 times rapidly).

Day 2 Summary

  • OUTPUT pins: 5V/0V, max 20mA per pin — use a driver for higher-current loads
  • INPUT_PULLUP pulls the pin high; button presses pull it low — inverted logic
  • Debounce all mechanical buttons: 50ms stable reading after state change
  • Buttons without debouncing will register multiple presses per physical press
Challenge

Implement a combination lock: 4 buttons represent digits 1–4. User must press them in a specific sequence (e.g., 1,3,2,4). If the sequence is correct, turn on a green LED for 2 seconds. If wrong, flash a red LED. Use a state machine with a current_position variable and a target_sequence array.

Finished this lesson?