The eventual project goal is to create an up / down counter (also known as a "totalizer") with a presettable integer start value, but before I get there I need to overcome a very basic problem: how to debounce simple pushbutton inputs.
I drew heavily from some example code, particularly
https://docs.arduino.cc/built-in-examples/digital/Debounce/
... but something is obviously wrong with my implementation of the debounce logic.
This code includes handling two different pushbutton inputs. The green pushbutton illuminates the green LED, no problem, but as you can see thousands of state changes occur because there is no debounce logic.
The red pushbutton incorporates the debounce logic... but the LED never gets illuminated.
The problem code lines are between 118 and 152.
Yes I am quite new at this, and I'm working in a vacuum with no one else to look at my code and tell me where I went astray.
Would someone kindly suggest what I'm missing?
Here is the project wokwi:
... and the code
// test file to include writing to / reading from EEPROM, blink the builtin LED without using Delay()
// output to serial
// read pushbutton inputs and illuminate red / green LEDs
#include <LiquidCrystal_I2C.h>
#define I2C_ADDR 0x27
#define LCD_COLUMNS 20
#define LCD_LINES 2
LiquidCrystal_I2C lcd(I2C_ADDR, LCD_COLUMNS, LCD_LINES);
#include <EEPROM.h>
// the following was copied from example EEPROM Read file
// initialize the starting address, a value (for each byte) and the count (unsigned long)
int memAddress = 0;
unsigned long memValue;
unsigned long count0 = 0x05; // initial value to place in EEPROM
unsigned long count1 = count0 - 1; // initial value to place in EEPROM
unsigned long count2 = count0 - 2; // initial value to place in EEPROM
unsigned long count3 = count0 - 3; // initial value to place in EEPROM
// debounce values
unsigned long lastDebounceTime = 0; // the last time the output pin was toggled
unsigned long debounceDelay = 50; // the debounce time; increase if the output flickers
// constants won't change. Used here to set a pin number:
const int ledOnboard = LED_BUILTIN; // the board number of the LED pin
const int button1Pin = 8; // the number of the pushbutton pin
const int button2Pin = 9; // the number of the pushbutton pin
const int led1Pin = 2; // the number of the red LED pin
const int led2Pin = 3; // the number of the green LED pin
const long interval = 1000; // interval at which to blink (milliseconds)
// Variables will change:
int led0State = LOW; // led0State used to set the onboard LED
int led1State = LOW; // led1State used to set the red LED
int led2State = LOW; // led2State used to set the green LED
int button1State = LOW; // variable for reading the pushbutton 1 status
int button2State = LOW; // variable for reading the pushbutton 2 status
int lastButton1State = HIGH; // previous state of button 1
int lastButton2State = HIGH; // previous state of button 2
// Generally, you should use "unsigned long" for variables that hold time
// The value will quickly become too large for an int to store
unsigned long previousMillis = 0; // will store last time LED was updated
void setup() {
// set digital pins as outputs for the LEDs:
pinMode(ledOnboard, OUTPUT);
pinMode(led1Pin, OUTPUT);
pinMode(led2Pin, OUTPUT);
// LCD Initialization
lcd.init();
lcd.backlight();
// Print something
lcd.setCursor(0, 0);
lcd.print("Line 1");
lcd.setCursor(0, 1);
lcd.print("Line 2");
Serial.begin(9600);
// initialize the external pushbutton pins as input, and choose the internal pullup resistor:
pinMode(button1Pin, INPUT_PULLUP);
pinMode(button2Pin, INPUT_PULLUP);
// populate first four (long integer i.e. 4 byte) EEPROM storage locations
// use EEPROM.put if you want to explicitly write
// use EEPROM.update if you want to write only if previous contents will be changed - prevents wear...?
EEPROM.put(memAddress + 0 , count0);
EEPROM.update(memAddress + 0 , count0);
EEPROM.put(memAddress + 4 , count1);
EEPROM.update(memAddress + 8 , count2);
EEPROM.update(memAddress + 12 , count3);
}
// define onboard LED toggle function - if onboard LED is off turn it on and vice-versa
// I think this could be written more concisely but the following code makes it obvious
void toggleLED() {
if (led0State == LOW) {
led0State = HIGH;
} else {
led0State = LOW;
}
}
void loop() {
// here is where you'd put code that needs to be running all the time.
// serial echo
if (Serial.available()) { // If anything comes in Serial (USB)
Serial.write(Serial.read()); // echo it back to Serial (USB)
}
// obtain the state of each button:
button1State = digitalRead(button1Pin);
button2State = digitalRead(button2Pin);
// check each button for state change.
// both button1Pin and button2Pin are configured to use the internal pullup
// so their default state is HIGH
// when pressed, they are connected to gnd, and the state becomes LOW
if (button1State != lastButton1State) { // check for state change, due to noise or whatever
lastDebounceTime = millis(); // reset the debounce timer
}
if ((millis() - lastDebounceTime) > debounceDelay) {
// whatever the reading is at, it's been there for longer than the debounce
// delay, so take it as the actual current state
if (button1State != lastButton1State) { // check for state change
Serial.print("State change detected! "); // THIS NEVER HAPPENS
if (button1State == LOW) { // was it pressed? LOW = pressed = turn LED on:
digitalWrite(led1Pin, HIGH);
count1++ ;
}
}
} else {
// turn LED off:
digitalWrite(led1Pin, LOW);
}
lastButton1State = button1State ;
// same for button 2.
// this code does not apply any debounce logic
if (button2State != lastButton2State) {
if (button2State == LOW) {
// turn LED on:
digitalWrite(led2Pin, HIGH);
count2++;
}
// turn LED on:
digitalWrite(led2Pin, HIGH);
count2++;
} else {
// turn LED off:
digitalWrite(led2Pin, LOW);
}
// check to see if it's time to toggle the onboard LED; that is, if the difference
// between the current time and last time you toggled the LED is larger than
// the interval at which you want to toggle the LED.
unsigned long currentMillis = millis();
unsigned long currentSecs = currentMillis / 1000;
if (currentMillis - previousMillis >= interval) {
// save the last time you toggled the LED
previousMillis = currentMillis;
toggleLED();
// seconds elapsed time serial output
Serial.print("Time ");
Serial.print(currentSecs);
Serial.print("\t");
// read a byte from the current address of the EEPROM
memValue = EEPROM.get(memAddress, memValue);
// print the EEPROM memValue at memAddress
Serial.print("Addr: ");
Serial.print(memAddress);
Serial.print(" ");
Serial.print(memValue, HEX);
Serial.print(" ");
Serial.print("Count1: ");
Serial.print(count1);
Serial.print(" ");
Serial.print("Count2: ");
Serial.print(count2);
Serial.println();
// set the onboard LED to the ledState of the variable:
digitalWrite(ledOnboard, led0State);
// Advance to the next address, when at the end restart at the beginning.
memAddress = memAddress + 4; // increment the memory address by 4 bytes
if (memAddress >= EEPROM.length()) {
memAddress = 0;
}
}
}
Ultimately, getting way ahead of myself, I would like those two pushbutton inputs to be used to increment or decrement the count one unit when pressed briefly, but "rapidly" when held down, and the longer the button is held down the more rapidly the count increases / decreases. It's like setting a digital clock, but the better kind that doesn't just advance at a fixed (slow) rate when holding a "set" button. It needs to go up or down. I would like to be able to a value anywhere between zero and about a million so I'll need to "accelerate" that rate of change, and holding the button depressed will do that.
A separate input (not yet incorporated) will be used to increment the "totalizer" count, which will be retained in EEPROM through reboots / power cycle. The value will be displayed on the LCD.