{"id":52217,"date":"2026-11-01T10:50:40","date_gmt":"2026-11-01T10:50:40","guid":{"rendered":"http:\/\/siratek.net\/index.php\/tutorials\/debouncing-inputs\/"},"modified":"2026-11-01T10:50:40","modified_gmt":"2026-11-01T10:50:40","slug":"debouncing-inputs","status":"publish","type":"siratek_tutorial","link":"https:\/\/siratek.net\/index.php\/tutorials\/debouncing-inputs\/","title":{"rendered":"Making a Button Behave"},"content":{"rendered":"<body><p><\/p>\n<h2>What this covers<\/h2>\n<p>One press, three counts. This explains why a mechanical switch does that, and how to fix it without blocking the rest of your program.<\/p>\n<h2>Before you begin<\/h2>\n<p>A push button on pin 2, wired as in <em>Read a Push Button<\/em>, and the <code>millis()<\/code> pattern from <em>Timing Without <code>delay()<\/code><\/em>.<\/p>\n<h2>Why one press counts as several<\/h2>\n<p>A switch is two pieces of metal springing together. They do not meet once and stay met. They touch, bounce apart, touch again, and settle, all within a few milliseconds.<\/p>\n<p>You feel one press. The pin sees a burst of transitions.<\/p>\n<p>An Uno reads that pin thousands of times a second, which is more than fast enough to catch every one of those bounces and count it as a separate press. The chip is not being unreliable. It is reporting exactly what happened, in more detail than you wanted.<\/p>\n<p>Bounce lasts a few milliseconds on a fresh tactile switch and longer on a worn one. It gets worse over the life of a switch, which is why a project can start behaving badly months after it was built.<\/p>\n<figure class=\"siratek-tutorial-figure\"><img decoding=\"async\" src=\"http:\/\/siratek.net\/wp-content\/uploads\/2026\/11\/debouncing-inputs-bounce.svg\" alt=\"Two timelines: the single clean edge you expect, and the several milliseconds of chatter the pin really sees\" loading=\"lazy\"><figcaption>One press, several edges. Debouncing refuses to believe a change until it has lasted.<\/figcaption><\/figure>\n<h2>See it first<\/h2>\n<p>Before fixing it, watch it happen. This counts every change with no protection at all.<\/p>\n<pre><code>\/\/ Siratek: counting raw transitions, so the bounce is visible.\n\nconst int buttonPin = 2;\nint lastState = HIGH;\nint count = 0;\n\nvoid setup() {\n  pinMode(buttonPin, INPUT_PULLUP);\n  Serial.begin(9600);\n  Serial.println(\"Siratek: press once and watch the count.\");\n}\n\nvoid loop() {\n  int state = digitalRead(buttonPin);\n\n  if (state != lastState) {\n    count++;\n    Serial.print(\"change \");\n    Serial.println(count);\n  }\n\n  lastState = state;\n}<\/code><\/pre>\n<p>Press once, slowly and deliberately. If you see several changes from one press, that is contact bounce. A clean switch may show only two, one for the press and one for the release. A worn one can show a dozen.<\/p>\n<h2>The fix<\/h2>\n<p>Ignore any further change for a short time after one is accepted. A few tens of milliseconds is longer than the bounce and far shorter than a human can press twice.<\/p>\n<p>The blocking version is <code>delay(50)<\/code> after each accepted change, and it works. It also stops the whole program for 50 ms every press, which is exactly what <em>Timing Without delay()<\/em> said to avoid.<\/p>\n<p>The non-blocking version uses the same <code>millis()<\/code> pattern.<\/p>\n<pre><code>\/\/ Siratek: debouncing a button without blocking.\n\nconst int buttonPin = 2;\nconst int ledPin = 9;\n\nconst unsigned long debounceTime = 50;   \/\/ Longer than the bounce, shorter than a press.\n\nint steadyState = HIGH;      \/\/ The state we believe the button is in.\nint lastReading = HIGH;      \/\/ What the pin said last pass.\nunsigned long lastChange = 0;\n\nint pressCount = 0;\nbool ledOn = false;\n\nvoid setup() {\n  pinMode(buttonPin, INPUT_PULLUP);\n  pinMode(ledPin, OUTPUT);\n  Serial.begin(9600);\n  Serial.println(\"Siratek: debounced button.\");\n}\n\nvoid loop() {\n  unsigned long now = millis();\n  int reading = digitalRead(buttonPin);\n\n  \/\/ Any change at all restarts the settling clock.\n  if (reading != lastReading) {\n    lastChange = now;\n  }\n  lastReading = reading;\n\n  \/\/ Once the reading has held still long enough, believe it.\n  if (now - lastChange &gt;= debounceTime &amp;&amp; reading != steadyState) {\n    steadyState = reading;\n\n    if (steadyState == LOW) {       \/\/ LOW is pressed, with INPUT_PULLUP.\n      pressCount++;\n      ledOn = !ledOn;\n      digitalWrite(ledPin, ledOn);\n\n      Serial.print(\"press \");\n      Serial.println(pressCount);\n    }\n  }\n}<\/code><\/pre>\n<p>Three variables carry the work. <code>lastReading<\/code> is the raw pin, which bounces. <code>steadyState<\/code> is what the program believes, which does not. <code>lastChange<\/code> records when the raw reading last moved, and the state is only believed once it has been still for longer than the bounce.<\/p>\n<p>Note that the toggle happens on the press, not the release, because the test is <code>steadyState == LOW<\/code>. Changing it to <code>HIGH<\/code> acts on release instead, which feels different to use and is sometimes what you want.<\/p>\n<h2>Choosing the time<\/h2>\n<ul>\n<li><strong>Too short<\/strong>, under about 20 ms, and bounces still get through on a worn switch.<\/li>\n<li><strong>Too long<\/strong>, over about 200 ms, and fast deliberate presses are swallowed.<\/li>\n<li><strong>50 ms<\/strong> suits almost everything. Raise it if a particular switch still double-counts.<\/li>\n<\/ul>\n<h2>Hardware debouncing<\/h2>\n<p>A capacitor across the switch, typically 100 nF, slows the voltage change enough to smooth the bounce away before the pin sees it.<\/p>\n<p>It works, and it costs a component per switch. For one or two buttons the software version is simpler and free. Hardware debouncing earns its place when a signal has to be clean before it reaches an interrupt, where the software pattern above does not apply.<\/p>\n<h2>Check yourself<\/h2>\n<ol>\n<li>One press gives three counts. Is the Arduino faulty?<\/li>\n<li>Why restart the clock on <em>any<\/em> change rather than only on a press?<\/li>\n<li>A project worked for a year and now double-counts. What changed?<\/li>\n<li>Why is <code>delay(50)<\/code> a poor debounce in a project that also reads a sensor?<\/li>\n<\/ol>\n<p>Answers in order: no, it is reporting real transitions. Because the settling period must cover the whole burst, in both directions. The switch has worn and bounces for longer. Because it stops the program for 50 ms each press, and the sensor is not read during that time.<\/p>\n<h2>Troubleshooting<\/h2>\n<table>\n<thead>\n<tr>\n<th>Symptom<\/th>\n<th>Likely cause<\/th>\n<th>Check or fix<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Still double-counts<\/td>\n<td>Debounce time too short<\/td>\n<td>Raise it to 80 or 100 ms<\/td>\n<\/tr>\n<tr>\n<td>Fast presses missed<\/td>\n<td>Debounce time too long<\/td>\n<td>Lower it towards 50 ms<\/td>\n<\/tr>\n<tr>\n<td>Nothing registers<\/td>\n<td><code>lastChange<\/code> updated outside the change test<\/td>\n<td>It must only move when the reading changes<\/td>\n<\/tr>\n<tr>\n<td>Reacts on release<\/td>\n<td>Test is on <code>HIGH<\/code><\/td>\n<td>Change it to <code>LOW<\/code> for press<\/td>\n<\/tr>\n<tr>\n<td>Counts once then stops<\/td>\n<td><code>steadyState<\/code> never updated<\/td>\n<td>Assign <code>reading<\/code> to it inside the accepted branch<\/td>\n<\/tr>\n<tr>\n<td>Breaks after 33 seconds<\/td>\n<td><code>lastChange<\/code> declared as <code>int<\/code><\/td>\n<td>Make it <code>unsigned long<\/code><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h2>Next steps<\/h2>\n<ul>\n<li><strong>Easier first:<\/strong> <em>Read a Push Button<\/em>.<\/li>\n<li><strong>Next:<\/strong> give the traffic light a pedestrian button that is read properly.<\/li>\n<li><strong>Later:<\/strong> interrupts, for inputs that must be caught the instant they happen.<\/li>\n<\/ul>\n<h2>Sources and attribution<\/h2>\n<p>All text and code in this tutorial are original Siratek work.<\/p>\n<ul>\n<li>Arduino core documentation for <code>digitalRead()<\/code>, <code>INPUT_PULLUP<\/code> and <code>millis()<\/code>.<\/li>\n<\/ul>\n<p>Contact bounce durations are given as typical ranges for small tactile switches and vary with the part and its wear. No third-party tutorial text, diagrams, photographs or code has been reproduced here.<\/p>\n<p><\/p>\n<\/body>","protected":false},"excerpt":{"rendered":"<p>One press, three counts. This explains why a mechanical switch does that, and how to fix it without blocking the rest of your program.<\/p>\n","protected":false},"featured_media":0,"template":"","siratek_tutorial_level":[],"class_list":["post-52217","siratek_tutorial","type-siratek_tutorial","status-publish","hentry"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v28.8 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Making a Button Behave - siratek<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"http:\/\/siratek.net\/index.php\/tutorials\/debouncing-inputs\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Making a Button Behave - siratek\" \/>\n<meta property=\"og:description\" content=\"One press, three counts. This explains why a mechanical switch does that, and how to fix it without blocking the rest of your program.\" \/>\n<meta property=\"og:url\" content=\"http:\/\/siratek.net\/index.php\/tutorials\/debouncing-inputs\/\" \/>\n<meta property=\"og:site_name\" content=\"siratek\" \/>\n<meta property=\"og:image\" content=\"http:\/\/siratek.net\/wp-content\/uploads\/2026\/11\/debouncing-inputs-bounce.svg\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data1\" content=\"5 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"http:\\\/\\\/siratek.net\\\/index.php\\\/tutorials\\\/debouncing-inputs\\\/\",\"url\":\"http:\\\/\\\/siratek.net\\\/index.php\\\/tutorials\\\/debouncing-inputs\\\/\",\"name\":\"Making a Button Behave - siratek\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/siratek.net\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"http:\\\/\\\/siratek.net\\\/index.php\\\/tutorials\\\/debouncing-inputs\\\/#primaryimage\"},\"image\":{\"@id\":\"http:\\\/\\\/siratek.net\\\/index.php\\\/tutorials\\\/debouncing-inputs\\\/#primaryimage\"},\"thumbnailUrl\":\"http:\\\/\\\/siratek.net\\\/wp-content\\\/uploads\\\/2026\\\/11\\\/debouncing-inputs-bounce.svg\",\"datePublished\":\"2026-11-01T10:50:40+00:00\",\"breadcrumb\":{\"@id\":\"http:\\\/\\\/siratek.net\\\/index.php\\\/tutorials\\\/debouncing-inputs\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"http:\\\/\\\/siratek.net\\\/index.php\\\/tutorials\\\/debouncing-inputs\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"http:\\\/\\\/siratek.net\\\/index.php\\\/tutorials\\\/debouncing-inputs\\\/#primaryimage\",\"url\":\"https:\\\/\\\/siratek.net\\\/wp-content\\\/uploads\\\/2026\\\/11\\\/debouncing-inputs-bounce.svg\",\"contentUrl\":\"https:\\\/\\\/siratek.net\\\/wp-content\\\/uploads\\\/2026\\\/11\\\/debouncing-inputs-bounce.svg\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"http:\\\/\\\/siratek.net\\\/index.php\\\/tutorials\\\/debouncing-inputs\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/siratek.net\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Tutorials\",\"item\":\"https:\\\/\\\/siratek.net\\\/index.php\\\/tutorials\\\/\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"Making a Button Behave\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/siratek.net\\\/#website\",\"url\":\"https:\\\/\\\/siratek.net\\\/\",\"name\":\"siratek\",\"description\":\"Your Best Electronic Retail Shop\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/siratek.net\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Making a Button Behave - siratek","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"http:\/\/siratek.net\/index.php\/tutorials\/debouncing-inputs\/","og_locale":"en_US","og_type":"article","og_title":"Making a Button Behave - siratek","og_description":"One press, three counts. This explains why a mechanical switch does that, and how to fix it without blocking the rest of your program.","og_url":"http:\/\/siratek.net\/index.php\/tutorials\/debouncing-inputs\/","og_site_name":"siratek","og_image":[{"url":"http:\/\/siratek.net\/wp-content\/uploads\/2026\/11\/debouncing-inputs-bounce.svg","type":"","width":"","height":""}],"twitter_card":"summary_large_image","twitter_misc":{"Est. reading time":"5 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"http:\/\/siratek.net\/index.php\/tutorials\/debouncing-inputs\/","url":"http:\/\/siratek.net\/index.php\/tutorials\/debouncing-inputs\/","name":"Making a Button Behave - siratek","isPartOf":{"@id":"https:\/\/siratek.net\/#website"},"primaryImageOfPage":{"@id":"http:\/\/siratek.net\/index.php\/tutorials\/debouncing-inputs\/#primaryimage"},"image":{"@id":"http:\/\/siratek.net\/index.php\/tutorials\/debouncing-inputs\/#primaryimage"},"thumbnailUrl":"http:\/\/siratek.net\/wp-content\/uploads\/2026\/11\/debouncing-inputs-bounce.svg","datePublished":"2026-11-01T10:50:40+00:00","breadcrumb":{"@id":"http:\/\/siratek.net\/index.php\/tutorials\/debouncing-inputs\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["http:\/\/siratek.net\/index.php\/tutorials\/debouncing-inputs\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"http:\/\/siratek.net\/index.php\/tutorials\/debouncing-inputs\/#primaryimage","url":"https:\/\/siratek.net\/wp-content\/uploads\/2026\/11\/debouncing-inputs-bounce.svg","contentUrl":"https:\/\/siratek.net\/wp-content\/uploads\/2026\/11\/debouncing-inputs-bounce.svg"},{"@type":"BreadcrumbList","@id":"http:\/\/siratek.net\/index.php\/tutorials\/debouncing-inputs\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/siratek.net\/"},{"@type":"ListItem","position":2,"name":"Tutorials","item":"https:\/\/siratek.net\/index.php\/tutorials\/"},{"@type":"ListItem","position":3,"name":"Making a Button Behave"}]},{"@type":"WebSite","@id":"https:\/\/siratek.net\/#website","url":"https:\/\/siratek.net\/","name":"siratek","description":"Your Best Electronic Retail Shop","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/siratek.net\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"}]}},"jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/siratek.net\/index.php\/wp-json\/wp\/v2\/siratek_tutorial\/52217","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/siratek.net\/index.php\/wp-json\/wp\/v2\/siratek_tutorial"}],"about":[{"href":"https:\/\/siratek.net\/index.php\/wp-json\/wp\/v2\/types\/siratek_tutorial"}],"version-history":[{"count":0,"href":"https:\/\/siratek.net\/index.php\/wp-json\/wp\/v2\/siratek_tutorial\/52217\/revisions"}],"wp:attachment":[{"href":"https:\/\/siratek.net\/index.php\/wp-json\/wp\/v2\/media?parent=52217"}],"wp:term":[{"taxonomy":"siratek_tutorial_level","embeddable":true,"href":"https:\/\/siratek.net\/index.php\/wp-json\/wp\/v2\/siratek_tutorial_level?post=52217"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}