Zbotic Logo Zbotic Logo
  • Home
  • Shop
  • Sale
  • 3D Print Service
  • PCB Service
  • B2B
  • Blogs
  • Contact Us
0 0

View Wishlist Add all to cart

0 0
0 Shopping Cart
Shopping cart (0)
Subtotal: ₹0.00

View cartCheckout

  • Shop
  • About Us
  • Contact Us
  • Reseller
  • Blogs
020 69134444
1800 209 0998
[email protected]
Help Desk
Facebook Twitter Instagram Linkedin YouTube
Zbotic Logo Zbotic Logo
0 0

View Wishlist Add all to cart

0 0
0 Shopping Cart
Shopping cart (0)
Subtotal: ₹0.00

View cartCheckout

All departments
  • 3D Print Service
  • 3D Printer
  • Batteries & Chargers
  • Development Boards
  • Drone Parts
  • EBike parts
  • Sensor Modules
  • Electronic Components
  • Electronic Modules
  • IoT and Wireless
  • Mechanical Parts and Workbench Tools
  • Motors & Drivers & Pumps & Actuators
  • DIY and Robot Kits
  • Show more
  • Home
  • Shop
  • Sale
  • 3D Print Service
  • PCB Service
  • B2B
  • Blogs
  • Contact Us
Return to previous page
Home Arduino & Microcontrollers

State Machine Programming: Button Debounce on Arduino

State Machine Programming: Button Debounce on Arduino

March 11, 2026 /Posted byJayesh Jain / 0

Every Arduino beginner eventually runs into the same frustrating problem: your button press registers multiple times when it should register once. This is the button bounce problem — and solving it properly requires moving beyond simple delay() hacks into genuine software architecture. State machine programming is the professional solution used in industrial and automotive embedded systems. This tutorial teaches you the Finite State Machine (FSM) design pattern applied to reliable button debounce on Arduino, with scalable code you can reuse in any project.

Table of Contents

  • Why Buttons Bounce: The Physics
  • Why delay() Debounce Fails in Real Projects
  • Finite State Machine Theory in Plain English
  • Building the Basic 4-State Debounce FSM
  • Adding Edge Detection: Press, Release, and Hold
  • Scaling to Multiple Buttons Without Blocking
  • Advanced Patterns: Long Press and Double Click
  • Frequently Asked Questions

Why Buttons Bounce: The Physics

A mechanical push button consists of two metal contacts that physically touch when pressed. At the microscopic level, the metal surfaces are rough. When they come together, they bounce — literally vibrating apart and back together dozens of times in the first 5–50 milliseconds after a press. To a microcontroller sampling at 16 million times per second, each bounce looks like a separate button press.

With a scope, a typical button press looks like this: a series of rapid 0→1→0→1 transitions over 20–30 ms, finally settling to a stable state. The total bounce time varies by button quality — cheap tactile switches often bounce for 5–10 ms; panel-mount buttons with springs can bounce for 20–50 ms.

Hardware debounce (RC filter + Schmitt trigger) is the purest solution but adds cost and PCB real estate. Software debounce in your Arduino code is free and flexible — when done right with a state machine approach.

Recommended: Arduino Uno R3 Beginners Kit — includes breadboard, buttons, and resistors so you can build and test all the debounce circuits in this tutorial right out of the box. The perfect starting point for learning state machine programming.

Why delay() Debounce Fails in Real Projects

The standard Arduino debounce example uses delay(50) after detecting a state change — wait 50 ms, then re-read the pin. This works for trivial single-button sketches but is fundamentally flawed for anything real:

  • It blocks everything: During delay(50), your Arduino does nothing. No sensor reads, no display updates, no serial communication. A 50 ms blocking call at 20 Hz gives you a maximum system response rate of 20 updates/second — terrible for any reactive application.
  • It cannot detect press duration: You cannot detect long press vs short press if you are busy waiting.
  • It does not scale: Three buttons with independent 50 ms delays in sequence means a worst-case 150 ms latency before any button registers — the system feels unresponsive.
  • It misses other buttons during the wait: While waiting for button A to debounce, a simultaneous press on button B is missed entirely.

The state machine approach solves all of these by making the debounce logic non-blocking — the FSM checks elapsed time without ever calling delay().

Finite State Machine Theory in Plain English

A Finite State Machine is a mathematical model of computation that exists in exactly one of a finite number of states at any given time. State transitions happen when defined conditions (events) occur. Three things define an FSM:

  1. States: The possible conditions the system can be in
  2. Events: Inputs or conditions that trigger transitions
  3. Transitions: The rules for moving between states based on events

For a debounced button, the natural states are:

  • IDLE: Button is released and stable. Waiting for a press.
  • PRESSING: Pin went LOW but we are not sure yet — could be bounce. Waiting for debounce timer.
  • PRESSED: Pin has been LOW for long enough — confirmed press.
  • RELEASING: Pin went HIGH but debounce timer not expired — could be bounce.

Drawing the state transition diagram (even on paper) before writing code is the key discipline that separates robust embedded code from buggy hacks.

Building the Basic 4-State Debounce FSM

Here is a complete, well-commented implementation using millis() for non-blocking timing:

// Button Debounce using Finite State Machine
// Non-blocking: uses millis(), never delay()

const int BUTTON_PIN = 2;
const unsigned long DEBOUNCE_MS = 20;  // Adjust 15-50ms per your button

// FSM states
enum ButtonState {
  BTN_IDLE,      // Released, stable
  BTN_PRESSING,  // Went LOW, debouncing
  BTN_PRESSED,   // Confirmed pressed
  BTN_RELEASING  // Went HIGH, debouncing
};

ButtonState btnState = BTN_IDLE;
unsigned long debounceTimer = 0;
bool buttonEvent = false;   // True on confirmed press
bool releaseEvent = false;  // True on confirmed release

void updateButton() {
  int rawPin = digitalRead(BUTTON_PIN);  // LOW = pressed (pull-up)
  unsigned long now = millis();
  
  switch (btnState) {
    
    case BTN_IDLE:
      if (rawPin == LOW) {
        // Possible press detected — start debounce timer
        btnState = BTN_PRESSING;
        debounceTimer = now;
      }
      break;
    
    case BTN_PRESSING:
      if (rawPin == HIGH) {
        // Bounced back before timeout — false alarm
        btnState = BTN_IDLE;
      } else if (now - debounceTimer >= DEBOUNCE_MS) {
        // Stable LOW for full debounce period — genuine press!
        btnState = BTN_PRESSED;
        buttonEvent = true;  // Signal a press event
      }
      break;
    
    case BTN_PRESSED:
      if (rawPin == HIGH) {
        // Button released — start release debounce
        btnState = BTN_RELEASING;
        debounceTimer = now;
      }
      break;
    
    case BTN_RELEASING:
      if (rawPin == LOW) {
        // Bounced back during release — still pressed
        btnState = BTN_PRESSED;
      } else if (now - debounceTimer >= DEBOUNCE_MS) {
        // Stable HIGH for full debounce period — genuine release!
        btnState = BTN_IDLE;
        releaseEvent = true;  // Signal a release event
      }
      break;
  }
}

void setup() {
  Serial.begin(115200);
  pinMode(BUTTON_PIN, INPUT_PULLUP);  // Internal pull-up: unpressed = HIGH
}

void loop() {
  updateButton();  // Call every loop iteration
  
  // Check events (fire-and-forget flags)
  if (buttonEvent) {
    buttonEvent = false;
    Serial.println("Button PRESSED");
    // Your action here
  }
  
  if (releaseEvent) {
    releaseEvent = false;
    Serial.println("Button RELEASED");
  }
  
  // Rest of your code here — runs at full speed, NEVER blocked
}

Notice that updateButton() is called every loop iteration — hundreds of thousands of times per second — but the FSM only transitions when the time condition is met. The CPU spends almost zero time in this function, leaving full bandwidth for the rest of your application.

Recommended: Multifunction Shield for Arduino Uno / Leonardo — has 4 buttons and an LED array built in, making it ideal for testing multi-button FSM implementations without any wiring. Perfect for practicing the patterns in this tutorial.

Adding Edge Detection: Press, Release, and Hold

The boolean flags buttonEvent and releaseEvent implement edge detection — you get exactly one event per physical press, regardless of how long the button is held. This is essential for counters, toggles, and menus.

Detecting Button Hold Duration

To detect how long a button has been held (for long-press functionality), record the timestamp when the press is confirmed and check elapsed time in BTN_PRESSED state:

case BTN_PRESSED:
  // Long press detection: fire after 1 second of holding
  if (!longPressTriggered && (millis() - pressStartTime >= 1000)) {
    longPressTriggered = true;
    Serial.println("LONG PRESS detected!");
  }
  if (rawPin == HIGH) {
    btnState = BTN_RELEASING;
    debounceTimer = now;
    longPressTriggered = false;  // Reset for next press
  }
  break;

Scaling to Multiple Buttons Without Blocking

The beauty of the FSM approach is that it encapsulates all state in variables — making it trivially scalable to multiple buttons. Use a struct:

struct Button {
  int pin;
  ButtonState state;
  unsigned long timer;
  bool pressEvent;
  bool releaseEvent;
};

Button buttons[] = {
  {2, BTN_IDLE, 0, false, false},  // Button 1 on pin 2
  {3, BTN_IDLE, 0, false, false},  // Button 2 on pin 3
  {4, BTN_IDLE, 0, false, false},  // Button 3 on pin 4
};
const int NUM_BUTTONS = 3;

void updateAllButtons() {
  unsigned long now = millis();
  for (int i = 0; i < NUM_BUTTONS; i++) {
    updateSingleButton(&buttons[i], now);
  }
}

Now all three buttons are debounced simultaneously with zero blocking. Each button’s state is independent — bouncing on button 1 does not block button 2 from being detected.

Recommended: Arduino Nano Every with Headers — compact 20 MHz ATmega4809 board with 14 digital I/O pins. Perfect for projects needing multiple buttons in a small form factor, with enough flash (48KB) to run sophisticated multi-button FSM code.

Advanced Patterns: Long Press and Double Click

Double Click Detection FSM

Double click requires tracking time between successive presses — this extends the FSM with additional states:

  • WAIT_SECOND_PRESS: First press confirmed; waiting to see if a second press arrives within the double-click window (typically 300–500 ms)
  • DOUBLE_CLICKED: Second press arrived in time — fire double click event
  • SINGLE_CLICKED: Timeout expired without second press — fire single click event
// After BTN_PRESSED confirms first press:
btnState = WAIT_SECOND_PRESS;
firstPressTime = millis();

// In WAIT_SECOND_PRESS state:
if (secondPressDetected) {
  doubleClickEvent = true;
  btnState = BTN_IDLE;
} else if (millis() - firstPressTime > DOUBLE_CLICK_TIMEOUT_MS) {
  singleClickEvent = true;  // No second press came
  btnState = BTN_IDLE;
}

Why This Matters for Real Projects

State machine button handling is used in virtually every commercial embedded product — from washing machine control panels to industrial HMIs to automotive steering wheel controls. Mastering this pattern is one of the most transferable skills in embedded programming. Once you have a solid Button FSM struct, you can drop it into any future project and have reliable button handling in minutes.

Frequently Asked Questions

What debounce time should I use — 20ms, 50ms?

Most mechanical tactile switches (the small square ones common in Arduino starter kits) debounce within 5–10 ms. Using 20 ms is a safe conservative choice that handles nearly all push buttons. For high-quality panel-mount switches, you may reduce to 10 ms. For cheap microswitches or reed switches, you may need 50 ms. Test your specific button with a logic analyser or scope for the best value.

Can I use interrupts instead of polling for button FSM?

Yes, but interrupts alone do not solve debounce — they make it worse by firing on every bounce. The correct approach is to use an interrupt to wake from sleep (for power saving) and then run the polling FSM once awake. In normal active code, polling in the main loop (as shown here) is simpler and equally responsive given Arduino’s loop speed.

Why does INPUT_PULLUP reverse button logic?

With INPUT_PULLUP, the pin is pulled to 5V internally. When you press the button connecting the pin to GND, the pin reads LOW. When released (no connection to GND), the pull-up holds it HIGH. So pressed = LOW, released = HIGH — inverted from what you might expect. Always account for this in your FSM conditions.

Is there an Arduino library that does all this automatically?

Yes — the Bounce2 library by Thomas O Fredericks implements a clean debounced button abstraction with update(), fell(), rose(), and read() methods. It uses exactly the state-machine approach described here. Understanding how to write it yourself first, however, makes you a better developer and allows customisation (long press, double click) that generic libraries may not support.

My button works fine in testing but misses presses in production — why?

Several causes: (1) Long wire runs from button to Arduino pick up electrical noise — add a 100nF capacitor from the button pin to GND as hardware pre-filtering; (2) The Arduino’s loop is spending time in delay() calls elsewhere — any delay() in your loop means updateButton() is not called during that time; (3) Using attachInterrupt() and the interrupt firing during sensitive operations. The fix for (2) and (3) is to fully eliminate delay() from your sketch using the millis() pattern throughout.

Level up your embedded programming skills — explore the full range of Arduino boards and development kits at Zbotic.in, delivered across India with fast shipping.

Tags: Arduino, button debounce, digital input, embedded programming, FSM, millis, state machine
Share Post
  • Facebook
  • Linkedin
  • Whatsapp
Raspberry Pi UART Serial Commu...
blog raspberry pi uart serial communication with sensors 594889
blog arduino sd card data logger with timestamps and rtc 594892
Arduino SD Card Data Logger wi...

Related posts

Svg%3E
Read more

Arduino Batch Programming: Flash Multiple Boards Quickly

April 1, 2026 0
Table of Contents Introduction Components and Hardware Setup Wiring Diagram and Connections Complete Code with Explanation Customization and Improvements Troubleshooting... Continue reading
Svg%3E
Read more

Arduino Based Radar System with Ultrasonic Sensor

April 1, 2026 0
Table of Contents Introduction Components and Hardware Setup Wiring Diagram and Connections Complete Code with Explanation Customization and Improvements Troubleshooting... Continue reading
Svg%3E
Read more

Arduino Automatic Plant Monitor: Sunlight, Moisture, Temperature

April 1, 2026 0
Table of Contents Introduction Components and Hardware Setup Wiring Diagram and Connections Complete Code with Explanation Customization and Improvements Troubleshooting... Continue reading
Svg%3E
Read more

Arduino Lie Detector: GSR Sensor Polygraph Project

April 1, 2026 0
Table of Contents Introduction Components and Hardware Setup Wiring Diagram and Connections Complete Code with Explanation Customization and Improvements Troubleshooting... Continue reading
Svg%3E
Read more

Arduino Metal Detector: Build a Treasure Finder

April 1, 2026 0
Table of Contents Introduction Components and Hardware Setup Wiring Diagram and Connections Complete Code with Explanation Customization and Improvements Troubleshooting... Continue reading

Add comment Cancel reply

Your email address will not be published. Required fields are marked

Facebook Twitter Instagram Pinterest Linkedin Youtube

Get the latest deals and more.

Download on Google Play Download on the App Store

Call us: 020 69134444 / 1800 209 0998

Monday - Saturday 09:30 AM - 06:00 PM
For Technical Supports Email: [email protected]
For Sales / Enquiries Email: [email protected]

  • My Account

    • Cart

    • Wishlist

    • Checkout

    • My Orders

    • Track Order

    • My Account

  • Information

    • FAQs

    • Blogs

    • Career

    • About Us

    • Contact Us

    • Payment Options

  • Policies

    • Privacy Policy

    • Terms & Conditions

    • GST Input Tax Credit

    • Shipping Return Policy

    • E-Waste Collection Points

    • Our Sitemap

© Zbotic.in is registered trademark of Moxie Supply Pvt Ltd – All Rights Reserved
Login
Use Phone Number
Use Email Address
Not a member yet? Register Now
Reset Password
Use Phone Number
Use Email Address
Register
Already a member? Login Now