/*
Forum: https://forum.arduino.cc/t/need-beginner-help-with-debounce-logic/1425853
Wokwi: https://wokwi.com/projects/454699917472227329
duplicate Wokwi with diagram for the Nano is here:
https://wokwi.com/projects/455415060881775617
It's intended mostly for pinout verification.
Make no code changes there. Make changes here, then copy (if needed)
Presettable four to nine digit counter / "totalizer" with debounced inputs
and persistent onboard memory
Accel Button class and EEPROM wear leveling class by ec2021
Copied from https://wokwi.com/projects/454412444481892353 created by ec2021
Example for reading buttons with "acceleration" of returned (btn.down() == True)
Supersedes previous version https://wokwi.com/projects/454183828831326209
Generous assistance from the forum contributors
ec2021, alto777, J-M-L, dougp, many others
This 4 to 9 digit counter / "totalizer" can be preset up or down
by pressing either of two buttons. So as to rapidly preset a desired
count start value, the increment and decrement values are calculated
based on the duration of time the respective button is held down.
Count on / set switch distinguishes the active counting state
vs presetting the count value.
While in that state, the prox sw input is ignored.
Setting the count value is processor-intensive.
The totalizer will be mostly at rest when the slide switch is "on"
which will be its usual, actual counting state.
While in that state, the prox sw will be used to increment
the count value (increment only, decrement could easily be added).
While in that state, the up / down switches are ignored.
While in that state, addional time-consuming actions can be performed
because the machine can only cycle so fast
2026/2/12 implemented power on reset count option
2026/2/18 implemented EEPROM dump easter egg
2026/2/24 seriously reorganized EEPROM dump
2026/2/28 implemented EEPlus32.cpp and EEPlus32.h
2026/3/02 added board identify feature
2026/3/03 added max7219 alternate display option
2026/3/04 reorganized EEPROM dump hopefully for the last time
2026/3/06 relocated LCD in preparation for LED
2026/3/10 renamed notCounting to Setting() in preparation for up or down option
Counting() will be modified to check up or down input selection
first. If both inputs are low (impossible in hardware) they will
cancel each other out i.e. it will increment then decrement
in response to a prox sw input.
2026/3/31 implemented program selectable 2 to 9 digit option (unlikely extremes)
still need to implement complimentary changes to EEdump
Certain features such as serial USB read / write are temporary,
for debugging or personal development purposes,
and will eventually be removed.
notes / comments added / updated / corrected 2026/03/31
*/
#include <LiquidCrystal_I2C.h>
#include <DallasTemperature.h> // which includes <OneWire.h>
#include "AccelBtnClass.h" // ec2021 Accel Button Class
#include "EEPlus32.h" //
#include "Board_Identify.h"
#include "max7219.h"
// Declare the EEPlus32 class
EEPlus32 myEE;
MAX7219 max7219;
#define I2C_ADDR 0x27
#define LCD_COLUMNS 16
#define LCD_LINES 2
#define ONE_WIRE_BUS 12
#define BOARD_IDENTIFY_WARNING
// Create a new instance of the oneWire class to communicate with any OneWire device:
OneWire oneWire(ONE_WIRE_BUS);
// Pass the oneWire reference to DallasTemperature library:
DallasTemperature sensors(&oneWire);
// define LCD parameters
LiquidCrystal_I2C lcd(I2C_ADDR, LCD_COLUMNS, LCD_LINES);
const String buildDate = "260331 4";
constexpr long EEdelayTime = 10; // EEPROM write delay time in millis (use something more reasonable like 10000)
constexpr long EEdumpTime = 100; // EEPROM dump delay time in millis (use something more reasonable like 500)
int nDigits = 6; // the number of digits (range bound min 2 max 9)
long Count; // the count value, signed so that < 0 can be detected
long maxCount = 1; // it will be a function of nDigits
constexpr int buttons = sizeof(param) / sizeof(param[0]);
constexpr byte countSetIndex{ buttons - 1 }; // Addresses the parameters of the count on / set toggle sw in the array
long lastCount = -1; // effectively a flag to indicate count change (it's never negative)
long increment = 1; // separate increment / decrement values can be implemented
long decrement = 1;
long maxDelta; // max increase / decrease rate, dependent on nDigits
long lastStoredCount; // the last Count value stored in EEPROM
bool led0State = LOW; // led0State used to set the onboard LED
constexpr byte proxPin = 2; // the magnetic prox sw input input pin
constexpr byte countUpPin = 3; // the "count up preset" toggle sw input pin
constexpr byte countDownPin = 4; // the "count down preset" toggle sw input pin
constexpr byte counterOnPin = 5; // the counter "on" / "set" slide sw
constexpr byte blPin = 9; // PWM pin used to control LCD backlight brightness (connect it to I2C LED)
constexpr byte blBrightness = 64; // backlight brightness (64 = about halfway)
constexpr byte ledOnboard = LED_BUILTIN; // onboard LED pin designation (a/k/a D13... don't use it for anything else)
unsigned long previousMillis = 0; // will store last time onboard LED was updated
unsigned long eepromTimer; // An idle timer. Write updates to EEPROM after a delay.
bool updateEEPROM = false; // flag to indicate Count needs to be written to EEPROM
constexpr uint8_t heart[8] = { 0x00, 0x0a, 0x1f, 0x1f, 0x0e, 0x04, 0x00 }; // ♥
constexpr uint8_t upArrow[8] = { 0x04, 0x0E, 0x15, 0x04, 0x04, 0x04, 0x04 }; // up arrow
constexpr uint8_t dnArrow[8] = { 0x04, 0x04, 0x04, 0x04, 0x15, 0x0E, 0x04 }; // down arrow
constexpr uint8_t smiley[8] = { 0x00, 0x0A, 0x0A, 0x00, 0x11, 0x0E, 0x00 }; // smiley
/*******************************************************
// ec2021 comments
// The following declaration creates a pointer to an AccelBtnClass object
// The pointer will be set to the instance of the button[] array that
// belongs to the slide switch so that it's easier to read the code
// It's meant as an example: The same could be done for the other buttons as well ...
/*******************************************************
*/
AccelBtnClass button[buttons];
AccelBtnClass *CountSetBtn;
void setup() {
// initialize digital I/O and choose the internal pullup resistor for inputs
pinMode(proxPin, INPUT_PULLUP);
pinMode(countUpPin, INPUT_PULLUP);
pinMode(countDownPin, INPUT_PULLUP);
pinMode(counterOnPin, INPUT_PULLUP);
pinMode(ledOnboard, OUTPUT);
pinMode(blPin, OUTPUT);
Serial.begin(9600);
// LCD Initialization
lcd.init();
// create those up and down arrow emoji
// we can only use 8 custom characters
lcd.createChar(0, upArrow); // ↑
lcd.createChar(1, dnArrow); // ↓
lcd.createChar(2, smiley); // ☺
lcd.createChar(3, heart); // ♥
// turn it on slowly
lcd.backlight();
for (int n = 0; n < blBrightness; n++) {
analogWrite(blPin, n);
delay(15);
}
// init the LED
max7219.Begin();
// test it
max7219.DisplayText("-", 1); //Right justified
// init the EEPlus32 class
myEE.begin();
// set maxCount and maxDelta as a function of nDigits
if (nDigits > 9) nDigits = 9;
if (nDigits < 2) nDigits = 2;
for (int i = 0; i < nDigits; i++) {
maxCount *= 10;
}
maxCount--;
maxDelta = 1L << (nDigits * 2 - 1);
maxDelta--;
// retrieve the stored Count, set the last Count and stored Count to it
Count = myEE.read();
lastCount = Count;
lastStoredCount = Count;
// debugging stuff
Serial.print(F("Board Type: "));
Serial.println(BoardIdentify::type);
Serial.print(F("Board Make: "));
Serial.println(BoardIdentify::make);
Serial.print(F("Board Model: "));
Serial.println(BoardIdentify::model);
Serial.print(F("Board MCU: "));
Serial.println(BoardIdentify::mcu);
Serial.print(F("Build: "));
Serial.println(buildDate);
Serial.print(F("nDigits = "));
Serial.println(nDigits);
Serial.print(F("maxCount = "));
Serial.println(maxCount);
Serial.print(F("maxDelta = "));
Serial.println(maxDelta);
Serial.print(F("Count: "));
Serial.print(myEE.read(), DEC);
Serial.print(F(" at Slot: "));
myEE.getSlot();
Serial.println();
sensors.begin();
// setResolution(9) = 12 bits = about 0.5°C
// higher = more bits = takes longer, max 12
sensors.setResolution(12);
// don't wait for conversion otherwise
// count up / down becomes unacceptable
// temperature is not that important
sensors.setWaitForConversion(false);
/*
With paramType struct this initialization can be used
for (int i= 0; i<buttons;i++){
button[i].init(param[i].pinNo,
param[i].minIntv,
param[i].maxIntv,
param[i].decr
);
}
or as an alternative one can call the init function that
directly reads the struct as its parameter.
CountSetBtn is set to the corresponding array entry.
These calls can be used alternatively:
button[3].getDebouncedState()
button[countSetIndex].getDebouncedState()
CountSetBtn->getDebouncedState()
*/
for (int i = 0; i < buttons; i++) {
button[i].init(param[i]);
}
CountSetBtn = &button[countSetIndex];
// Detect an unlikely count up / down button input state on boot
//
// The count up / down switch is a spring return to center toggle switch,
// so if is determined to be pressed on boot it was intentional.
// It cannot be left in that position on its own. It has to be held there.
// The count "up" condition in which button[0] is determined to have been
// held down will be explained first.
//
// This condition will offer the opportunity to show all saved count values
// in sequence. The sequence will repeat forever until the user presses
// the "down" button[1], which cancels. The program loop() then continues.
//
// To implement "the user changes his mind" all he has to do is momentarily press
// the count "down" switch. Nothing will change and Count will remain unaffected.
// A brief confirmation message will be provided. If the switch is kept down
// for over one second instead of just being momentarily depressed, the next
// input state detection routine will be the option to reset the counter.
// Since that routine interprets the "down" switch as "cancel", it's harmless.
//
// Another way of backing out would simply to reboot without holding any buttons.
//
if (button[0].down()) { // That's the "up" button / toggle sw
Serial.println(F("Display EEPROM contents?")); //
Serial.println(F("To confirm, press the UP switch again. ")); //
Serial.println(F("To cancel, press the DOWN switch. ")); //
Serial.println(F("Waiting for you to decide."));
lcd.clear();
lcd.print(F("Build "));
lcd.print(buildDate);
delay(1000);
lcd.clear();
lcd.print(F("Display EEPROM?"));
lcd.setCursor(2, 1); // write the text, up arrow, down arrow, etc to LCD
lcd.print(F("Yes"));
lcd.setCursor(10, 1);
lcd.print(F("No"));
lcd.setCursor(0, 1);
lcd.write(byte(0)); // ↑
lcd.setCursor(8, 1);
lcd.write(byte(1)); // ↓
while (true) { // stop here and wait patiently for user to contemplate his life choices
if (button[0].pressed()) { // That's the "increment" button. User intent to reset counter confirmed.
Serial.println(F("Printing all saved counter values"));
EEdump();
break; // bang out of setup()
}
if (button[1].pressed()) { // That's the "decrement" button. User cancels dump
Serial.println(F("Storage dump cancelled"));
lcd.setCursor(0, 1); // write to LCD so user can see some kind of confirmation...
lcd.print(F("Cancelled "));
lcd.setCursor(10, 1);
lcd.write(byte(3)); // ♥
delay(1000); // wait a moment for the user to read the message.
break; // bang out of setup()
}
}
}
// If we get here the first Easter egg was either bypassed or cancelled.
// In either case, the previous Count value will have been restored.
startLCD(); // show the static Count: text and present Count value
// The count "down" condition in which button[0] is determined to have been
// held down will be explained next.
//
// If that condition is detected, the user is presented with two choices:
// reset the count value to zero, cancel, or (thrid of two choices) reboot.
//
// To confirm the user really wanted to reset the counter, all he has to do is
// release and then momentarily press the count "up" switch again. Count will be reset.
// A brief confirmation message will be provided, and loop() runs as it always has.
//
// To implement "the user changes his mind" all he has to do is momentarily press
// the count "down" switch. Nothing will change and Count will remain unaffected.
// A brief confirmation message will be provided, and loop() runs as it always has.
//
// In either case we will wait for one of those two actions before proceeding to loop().
//
// Another way of backing out would simply to reboot without holding any buttons.
//
if (button[1].down()) { // That's the "down" button / toggle sw
Serial.println(F("Reset counter?")); //
Serial.println(F("To confirm, press the UP switch again. ")); //
Serial.println(F("To cancel, press the DOWN switch. ")); //
Serial.println(F("Waiting for you to decide."));
lcd.setCursor(2, 1); // write the text, up arrow, down arrow, etc to LCD
lcd.print(F("Reset"));
lcd.setCursor(10, 1);
lcd.print(F("Cancel"));
lcd.setCursor(0, 1);
lcd.write(byte(0));
lcd.setCursor(8, 1);
lcd.write(byte(1));
while (true) { // stop here and wait patiently for user to contemplate his life choices
if (button[0].pressed()) { // That's the "increment" button. User intent to reset counter confirmed.
Serial.println(F("Reset confirmed"));
resetCount(); // erase counter but wait another moment for user to release the up / down button.
lcd.setCursor(0, 1); // write to LCD so user can see some kind of confirmation...
lcd.print(F("Counter reset! ")); //
delay(1000); // wait a moment for the user to read the message.
return; // bang out of setup()
}
if (button[1].pressed()) { // That's the "decrement" button. User changes his mind.
Serial.println(F("Reset counter cancelled"));
lcd.setCursor(0, 1); // write to LCD so user can see some kind of confirmation...
lcd.print(F("Cancelled "));
lcd.setCursor(10, 1);
lcd.write(byte(2)); // :-)
delay(1000);
return; // bang out of setup()
}
}
}
} // ***** end of setup() *****
void loop() {
/*
The function getDebouncedState() returns .... the debounced state of course ... ;-)
It can be used here alternatively as
if (button[3].getDebouncedState()){}
if (button[countSetIndex].getDebouncedState()){} or
if (CountSetBtn->getDebouncedState()) {}
jg1: I reverted to the below for debugging purposes
Besides, I'm not too concerned about that switch bouncing.
Nothing bad will happen if it does.
Extraneous counts, etc can't happen unless an up / down button is pressed
at the same time the switch moves, and that's unlikely given the hardware.
Tested in hardware... if you hold an up / down sw at the same time you move
the count on / off switch, the existing delta value remains the same,
which is pretty cool. I don't want to change that.
If you hold the prox switch down at the same time you move the count
on / off switch, exactly nothing happens. That's good.
jg1: 2/18/2026: I think this is causing unintended consequences so I reverted.
It only manifests when invoking the "easter egg" routine to reset the counter, and
the "cancel" button is held for more than a second. That causes the count to spuriously
decrement (or increment, if the "cancel" / down option is immediately followed by
pressing the "up" button.) The EEPROM is not affected, only the displayed Count value.
As edge cases go, this is really, really insignificant, but reverting to the above
direct counterOnPin read completely obliviates the problem. Besides, that "button"
(toggle sw IRL) cannot not cause undue harm, even if it is bouncy, because nothing
can possibly happen unless other inputs change state commensurate with the bouncing.
Suspect the getDebouncedState() takes longer than it takes to get to the code that
detects an increment or decrement. Unsure. So I'm reading it directly for now.
if (CountSetBtn->getDebouncedState()) {
Counting();
} else {
Setting();
}
*/
if (digitalRead(counterOnPin)) { // get count on / set sw state
Counting();
} else {
Setting();
}
indicateProxState();
EEPROMwriteAfterDelay(); // write to EEPROM but only after some idle time
serialEcho();
backgroundTasks();
} // ***** end of loop() *****
void startLCD() { // clear display and write static text
lcd.clear(); // clear display and set cursor to upper left
lcd.print(F("Count: "));
displayCount();
}
void displayCount() { // show Count value on LCD correctly formatted for nDigits
char countTxt[16]; // specific for this particular 2 line x 16 char LCD
char formStr[] = "%08.Xld"; // "X" is the placeholder at position 4 (counting from 0 as usual in C/C++)
if (nDigits > 1 && nDigits < 10) { // If nDigits is not in the range 2..9 nothing happens
formStr[4] = '0' + nDigits; // is equal to = 48 + nDigits; but easier to read/understand
snprintf(countTxt, sizeof countTxt, formStr, Count);
lcd.setCursor(8, 0); // NORMAL CASE in which field starts at position 8
if (nDigits == 9) lcd.setCursor(7, 0); // SPECIAL CASE needs the ↑ ↓ character position 7
lcd.print(countTxt);
// Serial.print(countTxt);
// Serial.print('\t');
// Serial.println(formStr); // Just to show the effect of changing the char at position 4 in formStr
}
}
void resetCount() {
Count = 0;
myEE.write(Count);
lastStoredCount = Count;
displayCount();
}
void backgroundTasks() {
// do something every other second
// heartbeat routine runs all the time
// this is the most basic Arduino newbie code
// 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.
const long blinkinterval = 1000; // interval at which to blink the onboard LED (milliseconds)
unsigned long currentMillis = millis();
unsigned long currentSecs = currentMillis / 1000;
if (currentMillis - previousMillis >= blinkinterval) {
previousMillis = currentMillis;
toggleLED();
digitalWrite(ledOnboard, led0State);
if (led0State) { // do something like blinkenlights
getTemperature();
Serial.print(F("Time "));
Serial.print(currentSecs);
Serial.print(F("\t\t"));
Serial.println();
} else { // do something other than blinkenlights
printTemperature();
doNothing();
}
}
}
void serialEcho() {
if (Serial.available()) { // If anything comes in Serial (USB)
Serial.write(Serial.read()); // echo it back to Serial (USB)
}
}
void toggleLED() { // this is kinda dumb but why not
led0State = !led0State;
}
void doNothing() { // do nothing placeholder
}
void getTemperature() { // Send temperature conversion request
// ****** very important to use sensors.setWaitForConversion(false); in setup() above ******
// ****** otherwise this request will hold things up for many millis, causing jerkiness
sensors.requestTemperatures();
}
void printTemperature() {
char lcdTemp[17]; // specific for this particular 2 line x 16 char LCD
float tempF = sensors.getTempFByIndex(0);
// Check if sensor is taking a smoke break. If so blank the entire LCD line and bail out
// See the next section (commented out) for another way.
if (tempF == DEVICE_DISCONNECTED_F) {
memset(lcdTemp, ' ', 16);
lcdTemp[16] = '\0'; // null terminator
lcd.setCursor(0, 1);
lcd.print(lcdTemp); // print a blank line... and
return; // pull the ripcord
}
memcpy(lcdTemp, "Motor: ", 7); // Copy prefix
// Note: Arduino UNO snprinf() does not work with float
dtostrf(tempF, 7, 1, lcdTemp + 7); // Format temperature into buffer
lcdTemp[14] = '\xDF'; // Append degree symbol
lcdTemp[15] = 'F'; // and 'F'
lcdTemp[16] = '\0'; // null terminator
lcd.setCursor(0, 1);
lcd.print(lcdTemp);
}
/*******************************************************
Another method. It displays dashes for temperature
instead of just a blank line,
lest the user think something is wrong with the display.
Choose one or the other.
void printTemperature() {
char lcdTemp[17]; // specific for this particular 2 line x 16 char LCD
Float tempF = sensors.getTempFByIndex(0);
memcpy(lcdTemp, "Motor: ", 7);
if (tempF == DEVICE_DISCONNECTED_F) {
// Right-align placeholder in 7-character space
// Fill first 3 chars with spaces, then '--.-'
memcpy(lcdTemp + 7, " --.-", 7);
} else {
// Format temperature into the main buffer
dtostrf(tempF, 7, 1, lcdTemp + 7);
}
lcdTemp[14] = '\xDF'; // Append degree symbol
lcdTemp[15] = 'F'; // and 'F'
lcdTemp[16] = '\0'; // null terminator
lcd.setCursor(0, 1);
lcd.print(lcdTemp);
}
********************************************************/
void EEPROMwriteAfterDelay() {
//
// If updateEEPROM flag has been set, wait a while (ten seconds) first,
// then do it, then indicate it's been done.
// Actions that change the Count value are responsible for restarting
// the idle timer: eepromTimer = millis() accomplishes that.
// alto777 wrote this part
//
if (!updateEEPROM) return; // no need to, we outta here
if (Count == lastStoredCount) return; // don't write if it hasn't changed (eeclass checks that anyway)
if (millis() - eepromTimer < EEdelayTime) return; // value needs to be written, but not yet!
myEE.write(Count); // EEPROM write using the new wear leveler
lastStoredCount = Count;
Serial.print(F("Wrote to EEPROM slot ")); // everything will eventually be displayed on the LCD
myEE.getSlot();
Serial.print(F(", count ")); // so we can get rid of this later
Serial.print(Count);
Serial.println();
updateEEPROM = false; // ok ok, get off my back
}
void indicateProxState() {
//
// prox sw state indicator
// this might be an LED or some other indication of prox state
//
lcd.setCursor(7, 0);
if (nDigits == 9) lcd.setCursor(6, 0); // special case... national debt counter
if (button[2].down()) {
lcd.write(byte(0)); // indicate prox sw closed
}
if (button[2].isReleased()) {
lcd.write(byte(1)); // indicate prox sw open
}
}
// This was a bitch. Decided to separate everything into nine columns.
// Each line is printed as a record and intended to be human-readable.
// This ought to work with any size EEPROM, 512 bytes through 8k.
void EEdump() {
const int eepromSize = EEPROM.length();
const int recordLength = 6;
const int maxSlots = eepromSize / recordLength; // = 170 for 1024 bytes
uint16_t slot = 0;
uint16_t printedSlot;
uint32_t retrievedCount;
char slotBuffer[16];
char countBuffer[16];
Serial.print(F("EEPROM length is: "));
Serial.println(EEPROM.length());
Serial.print(F("EE write delay time is: "));
Serial.print(EEdelayTime, DEC);
Serial.println(F(" millis"));
Serial.print(F("Current Slot: "));
myEE.getSlot();
Serial.print(F(" Current Count: "));
Serial.println(Count, DEC);
lcd.clear();
lcd.print(F("Index: "));
lcd.setCursor(0, 1);
lcd.print(F("Count: "));
while (1) {
for (int idx = 0; idx < eepromSize; idx += recordLength) { // Column 0: EEPROM address
Serial.print(idx);
Serial.print('\t'); // Columns 1 - 6: six bytes
for (int i = 0; i < recordLength; i++) {
int addr = idx + i;
if (addr > eepromSize) {
Serial.print(F("-- "));
} else {
byte b = EEPROM.read(addr);
if (b < 0x10) Serial.print('0');
if (addr <= eepromSize) Serial.print(b, HEX);
Serial.print(F(" "));
}
}
printedSlot = slot - 1; // Column 7: record number (3-digit, leading zeros)
if (printedSlot > maxSlots) {
Serial.print(F(" - "));
lcd.setCursor(7, 0);
lcd.print(F("Reserved "));
} else {
sprintf(slotBuffer, "%03u", printedSlot);
Serial.print(slotBuffer);
lcd.setCursor(7, 0);
lcd.print(slotBuffer);
lcd.print(F(" "));
}
Serial.print(' ');
if (slot == 0 || idx + 2 >= eepromSize) { // Column 8: cross-record 32-bit value (bitch)
Serial.print(F("Not Valid"));
} else { // get count at the slot
EEPROM.get(slot * 6 - 1, retrievedCount);
if (retrievedCount > maxCount) {
Serial.print(F("Not Valid"));
lcd.setCursor(7, 1);
lcd.print(F("Not Valid "));
} else {
if (nDigits == 2) {
sprintf(countBuffer, "%02lu", retrievedCount); // Column 8: the displayed Count, pretty
}
if (nDigits == 3) {
sprintf(countBuffer, "%03lu", retrievedCount); // Column 8: the displayed Count, pretty
}
if (nDigits == 4) {
sprintf(countBuffer, "%04lu", retrievedCount); // Column 8: the displayed Count, pretty
}
if (nDigits == 5) {
sprintf(countBuffer, "%05lu", retrievedCount); // Column 8: the displayed Count, pretty
}
if (nDigits == 6) {
sprintf(countBuffer, "%06lu", retrievedCount); // Column 8: the displayed Count, pretty
}
if (nDigits == 7) {
sprintf(countBuffer, "%07lu", retrievedCount); // Column 8: the displayed Count, pretty
}
if (nDigits == 8) {
sprintf(countBuffer, "%08lu", retrievedCount); // Column 8: the displayed Count, pretty
}
if (nDigits == 9) {
sprintf(countBuffer, "%09lu", retrievedCount); // Column 8: the displayed Count, pretty
}
Serial.print(countBuffer);
lcd.setCursor(7, 1);
lcd.print(countBuffer);
lcd.print(F(" "));
}
}
Serial.println();
slot++;
if (slot > maxSlots) slot = 0;
delay(EEdumpTime); // strictly so user can read - make it 500 millis for production
if (button[1].pressed()) { // had enough of this and want to bail out
Serial.println(F("\nEEPROM dump cancelled"));
lcd.setCursor(0, 1); // write to LCD so user can see some kind of confirmation...
lcd.print(F("Cancelled "));
lcd.setCursor(10, 1);
lcd.write(byte(3)); // :-)
delay(1000);
return;
}
}
}
}
void Counting() { // meat of the program lives here.
// it is what counts the actual machine cycles
if (button[2].pressed()) { // magnetic prox switch sensed
Count++;
if (Count > maxCount) { // wrap through zero
Count = 0;
}
displayCount();
if (lastCount != Count) {
updateEEPROM = true; // update needs doing
eepromTimer = millis(); // ... after some quiet time
}
lastCount = Count;
Serial.print(F("Prox sw detected, count offered to be written: "));
Serial.println(Count);
//
// time-consuming things while we wait
// for something interesting to happen
// can be added here.
//
doNothing(); // it's what I do best
}
}
void Setting() {
// This routine will be called upon only when count on / off sw is in the "set" position.
// It means the machine is not running production, and the operator either wants to turn
// the counter off for maintenance - or - to preset a new count value prior to resuming
// production on the machine.
// It checks the up / down toggle switch action,
// and increments / decrements the count value when the corresponding button is depressed.
// The resulting count value should be written to EEPROM only when
// the up / down toggle switch is released, and a few seconds have transpired
// after releasing the up / down toggle switch (green / red buttons on the Wokwi)
if (Count != lastCount) { // LCD display the count value only if it has changed.
displayCount();
}
if (button[0].isReleased() && button[1].isReleased()) {
if (Count != lastCount) { // If the Count has changed,
updateEEPROM = true; // update needs doing
eepromTimer = millis(); // ... after some quiet time
lastCount = Count;
Serial.print(F("Counter adjustment made, count offered to be written: "));
Serial.println(Count);
//
// time-consuming things while we wait
// for something interesting to happen
// can be added here.
//
doNothing(); // it's what I do best
}
}
if (button[0].down()) { // increment preset
Count += increment;
if (Count > maxCount) { // wrap through zero
Count = 0;
}
// The following changes the increment (delta) depending
// on the duration the button is held down
increment = min(pow(2, button[0].getDownInterval() / 1000), maxDelta);
// The following resets the idle time to address the possibility
// a count up / down button is pressed and released then
// pressed again before the EEPROM write time expires.
// Otherwise the count would be written to EEPROM while still
// actively setting the counter, which is kind of needless.
eepromTimer = millis();
}
if (button[1].down()) { // decrement preset
Count -= decrement;
if (Count < 0) { // wrap through zero
Count = maxCount;
}
// The following changes the decrement (delta) depending
// on the duration the button is held down
decrement = min(pow(2, button[1].getDownInterval() / 1000), maxDelta);
// The following resets the idle time to address the possibility
// a count up / down button is pressed and released then
// pressed again before the EEPROM write time expires.
// Otherwise the count would be written to EEPROM while still
// actively setting the counter, which is kind of needless.
eepromTimer = millis();
}
// The following lines reset the increment (delta) to 1
// when button[0] is not down
if (button[0].isReleased()) {
increment = 1;
}
// The following lines reset the decrement (delta) to 1
// when button[1] is not down
if (button[1].isReleased()) {
decrement = 1;
}
}
Up toggle sw
down toggle sw
up or down
prox sw
up
dn
to LCD backlight
to 7 segment LED module
DIN
CS
CLK
count or set
c
s
This will be one on-off-on toggle sw
Center position set (both inputs high)
Up counts up / Down counts down