Good evening everyone,
I am progressing with my Arduino controlled dishwahser and I am now implementing more functions, such as a service mode.
I realized that it would be very useful to have a function I can call that will tell me whether a switch is pressed or not, detecting the falling edge. Problem is, these buttons are read via a PCF8575. I can take the reading and detect the falling edge, but I can't seem to transofrm this into a function.
This is the code for reading from the PCF:
void writePCF(byte byte1, byte byte2)
{
Wire.beginTransmission(PCF_address);
Wire.write(byte1);
Wire.write(byte2);
Wire.endTransmission();
}
byte readDataFromPCF() {
writePCF(255, 255); // PCF8575 require us to set all outputs to 1 before doing a read.
Wire.beginTransmission(0x20);
Wire.requestFrom(0x20, 2); // Ask for 1 byte from slave
byte upperByte = Wire.read(); // read that one byte
byte lowerByte = Wire.read();
Wire.endTransmission();
return lowerByte;
}
What I'd like to do is have a function (eg. called isButtonPressed()) which takes the name of the button as an input (eg. isButtonPressed(PROG_SEL_BTN)) and returns true or false, but only the first time the button has been pressed. This means that if it gets held down the function will have to return true or false only once.
The way the buttons are wired now is DELAY_TIMER_BTN on PCF pin 0, PROG_SEL_BTN on PCF pin 1, START_BTN on PCF pin 2. Is it possible to do this? Many thanks in advance
I would set a flag when you determine it has detected the edge. In your code when you process that flag simply clear it. That way the reading of the switch is independent of the functions.
//https://wokwi.com/projects/344622706469110354
// limitations:
// no debounce, but this should not be a problem
// in case of very short press, risk of confusion on the buttons (maybe it will be almost never seen)
// only one button pressed at a time
#define DELAY_TIMER_BTN B00000001 // PCF pin 0 (write B00000001 or 1)
#define PROG_SEL_BTN B00000010 // PCF pin 1 (write B00000010 or 2)
#define START_BTN B00000100 // PCF pin 2 (write B00000100 or 4)
unsigned long prevmillis; // tempo
byte dataFromPCF; // PCF result
void setup() { // eventually...
Serial.begin(115200);
Serial.println(F("go ! press a button"));
}
void writePCF(byte byte1, byte byte2) // your code
{
Wire.beginTransmission(PCF_address);
Wire.write(byte1);
Wire.write(byte2);
Wire.endTransmission();
}
byte readDataFromPCF() { // your code
writePCF(255, 255); // PCF8575 require us to set all outputs to 1 before doing a read.
Wire.beginTransmission(0x20);
Wire.requestFrom(0x20, 2); // Ask for 1 byte from slave
byte upperByte = Wire.read(); // read that one byte
byte lowerByte = Wire.read();
Wire.endTransmission();
//return lowerByte & B00000111; // PCF return positive logic (1 when pressed, pulldown on pin)
return ~lowerByte & B00000111; // PCF return negative logic (0 when pressed, pullup on pin)
}
void function1(){ // here your function when DELAY_TIMER_BTN is pressed
Serial.println(F("DELAY_TIMER_BTN just pressed")); //
} //
void function2(){ // here your function when PROG_SEL_BTN is pressed
Serial.println(F("PROG_SEL_BTN just pressed")); //
} //
void function3(){ // here your function when START_BTN is pressed
Serial.println(F("START_BTN just pressed")); //
} //
void isButtonPressed() {
if (millis() - prevmillis > 50) { // wait for 50 ms period
prevmillis = millis(); // 50ms reached
dataFromPCF <<= 1; // left shift
dataFromPCF += readDataFromPCF(); // add button status
if (dataFromPCF == DELAY_TIMER_BTN) function1(); //
if (dataFromPCF == PROG_SEL_BTN) function2(); //
if (dataFromPCF == START_BTN) function3(); //
}
}
void loop() {
isButtonPressed();
//...
}
I tried your wokwi, thanks for troubling yourself to make it!
The sketch works fine.
The code you posted isn't in the wokwi exsactly, at the very least I saw
if (millis() - prevmillis > 1) { // wait for 50 ms period
TBH I have spent the last - never mind precisely how many - minutes trying to figure out how your *isButtonPressed*() function works.
I did finally get it, but only by looking at it on my hands and knees. I think it was just like nothing I had seen, or more accurately enough like something else entirely to make it hard to see in another way.
I wrote a drop-in that does the same thing. It uses logic to see which bits have transitioned; the function dispatch tests the relevant bit. It can be easily enhanced to handle multiple simultaneous button presses as well. Exercise for you with anywhere near as little life as I.
void isButtonPressed() {
if (millis() - prevmillis < 50)
return; // too soon to even look again
prevmillis = millis(); // 50ms reached
unsigned char newData = readDataFromPCF();
unsigned char actOn = newData & ~dataFromPCF; // calculate transitioning bits
dataFromPCF = newData; // bump along the history
if (actOn & DELAY_TIMER_BTN) function1();
if (actOn & PROG_SEL_BTN) function2();
if (actOn & START_BTN) function3();
}
Adding:
I finally read this at the top of the code. Usually I skip reading commentary.
// limitations:
// no debounce, but this should not be a problem
// in case of very short press, risk of confusion on the buttons (maybe it will be almost never seen)
// only one button pressed at a time
I found it quite easy to get "button confusion" using plausible short presses. Not a debouncing issue, not sure how to catch it in the act. The algorithm I supplied has debounce built in, as does @PatMax's, but mine will never jump channels.
I would like to thank everybody for helping me
Since I also needed to read multiple buttons at the same time and increase values when a button was held down for long enough, I write the following code, which works just fine for my needs
// constants won't change. They're used here to set pin numbers:
const int BUTTON_PIN = 7; // the number of the pushbutton pin
const int DEBOUNCE_TIME = 100;
const int MAX_SHORT_PRESS_TIME = 1500; // 1500 milliseconds
const int LONG_PRESS_TIME = 3000;
const int OFFSET = 7; //defines offset between position 0 of the array and the first button number (eg. here pin 7 is the first button --> offset = 7-0=7)
int lastPressedState[3] = {0, 0, 0}; // the previous state from the input pin
int currentPressedState[3]; // the current reading from the input pin
unsigned long pressedTime[3] = {0, 0, 0};
bool isPressing[3] = {0, 0, 0};
bool isLongPressed[3] = {0, 0, 0};
unsigned long releasedTime [3];
int lastHeldState[3] = {0, 0, 0}; // the previous state from the input pin
int currentHeldState[3]; // the current reading from the input pin
unsigned long pressedHeldTime[3] = {0, 0, 0};
bool isHolding[3] = {0, 0, 0};
bool isLongDetected[3] = {0, 0, 0};
void setup() {
Serial.begin(9600);
pinMode(7, INPUT_PULLUP);
pinMode(8, INPUT_PULLUP);
}
void loop() {
if (isButtonPressed(7)) {
Serial.println("SHORT PRESS 7");
}
if (isButtonPressed(8)) {
Serial.println("SHORT PRESS 8");
}
if (isButtonHeld(7)) {
Serial.println("LONG PRESS 7");
}
if (isButtonHeld(8)) {
Serial.println("LONG PRESS 8");
}
if (multipleButtonsHeld(7, 8)) {
Serial.println("LONG PRESS BOTH");
}
if (increaseWhileHolding(7)) {
Serial.println("Increasing");
}
}
bool isButtonPressed(byte pin) {
// read the state of the switch/button:
currentPressedState[pin - OFFSET] = digitalRead(pin);
if (lastPressedState[pin - OFFSET] == HIGH && currentPressedState[pin - OFFSET] == LOW) {
pressedTime[pin - OFFSET] = millis();// button is pressed
} else if (lastPressedState[pin - OFFSET] == LOW && currentPressedState[pin - OFFSET] == HIGH) { // button is released
releasedTime[pin - OFFSET] = millis();
long pressDuration[3];
pressDuration[pin - OFFSET] = releasedTime[pin - OFFSET] - pressedTime[pin - OFFSET];
if (pressDuration[pin - OFFSET] > DEBOUNCE_TIME && pressDuration[pin - OFFSET] < MAX_SHORT_PRESS_TIME) {
lastPressedState[pin - OFFSET] = currentPressedState[pin - OFFSET];
return true;
}
}
// save the the last state
lastPressedState[pin - OFFSET] = currentPressedState[pin - OFFSET];
return false;
}
bool isButtonHeld(byte pin) {
currentHeldState[pin - OFFSET] = digitalRead(pin);
if (lastHeldState[pin - OFFSET] == HIGH && currentHeldState[pin - OFFSET] == LOW) { // button is pressed
pressedHeldTime[pin - OFFSET] = millis();
isHolding[pin - OFFSET] = true;
isLongDetected[pin - OFFSET] = false;
} else if (lastHeldState[pin - OFFSET] == LOW && currentHeldState[pin - OFFSET] == HIGH) { // button is released
isHolding[pin - OFFSET] = false;
}
if (isHolding[pin - OFFSET] == true && isLongDetected[pin - OFFSET] == false) {
long pressDuration[3];
pressDuration[pin - OFFSET] = millis() - pressedHeldTime[pin - OFFSET];
if (pressDuration[pin - OFFSET] > LONG_PRESS_TIME ) {
isLongDetected[pin - OFFSET] = true;
lastHeldState[pin - OFFSET] = currentHeldState[pin - OFFSET];
return true;
}
}
// save the the last state
lastHeldState[pin - OFFSET] = currentHeldState[pin - OFFSET];
return false;
}
long buttonTimer = 0;
long buttonTime = 1000;
boolean buttonActive = false;
boolean longPressActive = false;
boolean button1Active = false;
boolean button2Active = false;
bool multipleButtonsHeld(byte button1, byte button2) {
if (digitalRead(button1) == LOW && button1Active == false) {
buttonTimer = millis();
button1Active = true;
}
if (digitalRead(button2) == LOW && button2Active == false) {
buttonTimer = millis();
button2Active = true;
}
if (button1Active == true && button2Active == true && (millis() - buttonTimer > buttonTime) && (longPressActive == false)) {
longPressActive = true;
return true;
}
if ((digitalRead(button1) == HIGH) && (digitalRead(button2) == HIGH)) {
longPressActive = false;
button1Active = false;
button2Active = false;
}
return false;
}
int lastStateIncrease = LOW; // the previous state from the input pin
int currentStateIncrease; // the current reading from the input pin
unsigned long pressedTimeIncrease = 0;
bool isPressingIncrease = false;
bool isLongDetectedIncrease = false;
int K = 0;
bool increaseWhileHolding(byte button) {
currentStateIncrease = digitalRead(button);
if (lastStateIncrease == HIGH && currentStateIncrease == LOW) { // button is pressed
pressedTimeIncrease = millis();
isPressingIncrease = true;
isLongDetectedIncrease = false;
} else if (lastStateIncrease == LOW && currentStateIncrease == HIGH) { // button is released
isPressingIncrease = false;
K = 0;
}
if (isPressingIncrease == true) {
long pressDuration = millis() - pressedTimeIncrease;
if (pressDuration > LONG_PRESS_TIME + K * 500) {
K++;
isLongDetectedIncrease = true;
lastStateIncrease = currentStateIncrease;
return true;
}
}
// save the the last state
lastStateIncrease = currentStateIncrease;
return false;
}
hi alto777
I know your passion for keyboard and button algorithms, I too have a lot of fun with it (my codes come from the time when microcontrollers had 16 RAM registers maximum, and we had to make short code; now we have all the space we want, but as these codes work well, I keep using them).
In WOKWI, the tempo delays (including millis() ) are not respected, that's why I modified the tempo value in the WOKWI code:
if (millis() - prevmillis > 50) { // for real UNO, 50 ms delay
if (millis() - prevmillis > 1) { // for WOKWI unknown delay, but must be short
My algorithm with shifting variable was an exercise in style, I made it from memory and as simple as possible, so that it is easy to read, but it has limitations.
Imma ask you to prove that. It would be a surprise as well as making wokwi almost entirely useless.
I have found the wokwi to be 99.44 percent faithful; the few problems that I have even seen we're when dealing at a low level with the CPU registers, sophisticated timer/counter stuff for example.
And those few were fixed rapidly by Urish and/or his boys over there.
A real infidelity is how LEDs look and act. This is just a limitation of the screen drawing and timing of all that.
Which is why I stuck a servo into an LED dimming demo recently - you can see the servo go back and forth, but the supposed dimming and brightening of the LED was poorly simulate.
wokwi has been a real game-changer, now that much easier to get most things mostly working before ever introducing the inevitable excitement of actual hardware and wiring.
Okay, you tell me that the timing of wokwi is accurate, I thought the opposite based on the reaction time of the leds (sometimes, on some sketch it is very slow). In my example below, a short press on Start button does not always light up the red led, while the code has no error.
I have only done 2 sketches.
I found your example above to be sturdy, at least just now I cannot make it do anything wrong. I did use very short "presses". I suggest you try
if (millis() - prevmillis < 200) return; // sluggish too soon to even look again
to really slug those buttons, so you can def miss short presses (< 200 ms). At 20 ms, it is harder I never did make a simulated button press too short (< 20 ms), but I can see the IRL it might be too short - short stabs at a button might well just ignored except to reset the "don't look" timer mechanism.
I did move one of your calculations, then wondered why actOff would't just be symmetrical ?
Yes, my example is solid, it has been implemented on thousands of devices in the last decades, if it had a defect, I would have had serious problems
You are right to increase the period from 20 to 200, but this is only valid for wokwi; I think especially IRL, with a physical processor, 20 ms is the best compromise between key reaction speed and anti-bounce. At this speed, stabbing the keys is possible, even better if you are at 10 ms.
The strongest bounces I know of are made with a bare wire rubbed on a metal part: you have to go up to 50 ms to erase the bounces, in this case.
Bravo for your remark on ActOff symmetrical of actOn! you are right, it simplifies the code, it's very clever! I will use it from now on, thanks!
Sry, I meant temporarily make it sluggish so you actually can see a missed short stab of the button.
By making a gianter window for such to occur within. Simp,y as an experiment. Way too long. I use 20-50, only my cheap arcade switches need even 20 ms to finish.
And wokwi can be slow in its tracking of the passage of real time, that would make it seem unfaithful.
Slow on a tablet. I usually see 99 or 100 percent real time performance on my desktop machine.
Many times I slow things way down and watch how they actually work even my own code will surprise me.
Edit: the code has been replaced with code that works. Better. TBC the code I originally quoted below had a few, um, flaws. The umbrella academy caught them immediately.
OK before my beach buddy - she who must never be kept waiting - gets here to, well, you know, I have been flying about @PatMax's latest sketch at a low level.
The current isButtonPressed() takes two different ideas of what pressed (or released) means.
actOn, actOff and actTog all react immediately to any change observed on the pin. This will catch glitches, although in the case of good pushbuttons, there is no such thing as a glitch: switches do not close capriciously unless on the way to being fully closed, neither do they open without being on the way to being fully opened.
So it is actually good, in that case, to react immediealt.
But intordiced latest is the idea of a stable state (I think), wherein a pin is not considered to be HIGH until it has been HIGH for two readings.
I missed the symmetry of handling only LOW if two consecutive LOW readings come in.
The new "stable" pin readings can be used in the same manner as the "raw" readings newData and prevData, only this time with the extra fussiness of needing two in a row to trigger.
Here is the whole sketch where I leave it, soon, I hope, for the beach.
I put a huge delay() in the loop() to make it easy to see the quick response on short presses, and the verified fussier repsonse that you gotta hold down a button for. Watch it on the DELAY_TIMER_BTN button.
// BUTTONS : 1 to 16
// reliable debounce
// buttons info : state, fronts down & up, toggle
// multiple buttons can be pressed at a time
#define DELAY_TIMER_BTN B00000001 // Button on A0
#define PROG_SEL_BTN B00000010 // Button on A1
#define START_BTN B00000100 // Button on A2
unsigned long prevmillis; // for debounce period
unsigned char prevData; // previous reading of buttons
unsigned char prevSta; // Button previous state
unsigned char actSta; // Button state (pressed, no pressed)
unsigned char actTog; // Toggle
unsigned char actOn; // down front
unsigned char actOff; // up front
void setup() { // pullups on pins A0,A1,A2
PORTC |= B00000111; // A3, A4, A5 as output
DDRC |= B00111000;
Serial.begin(115200); //
Serial.println(F("go ! press a button")); //
} //
void isButtonPressed() {
static unsigned int counter;
// Serial.print(counter); Serial.print(" ");
counter++;
unsigned long now = millis(); // or use global "now"
if (now - prevmillis < 25) return; // too soon to even look again
prevmillis = now; // 25 ms debounce delay reached
unsigned char newData = ~PINC & B00000111; // 0 when pressed, internal pullup on pins A0, A1, A2
// this section reacts immediatley to a new different reading
actOn = newData & ~prevData; // calculate PRESSED transitioning bits
actOff = ~newData & prevData;
actTog ^= actOn; // apply TOGGLE
if (actOn & DELAY_TIMER_BTN) {Serial.print(counter); Serial.println(F(" DELAY pressed ")); }
if (actOn & PROG_SEL_BTN) {Serial.print(counter); Serial.println(F(" PROG pressed ")); }
if (actOn & START_BTN) {Serial.print(counter); Serial.println(F(" START pressed ")); }
if (actOff & DELAY_TIMER_BTN){Serial.print(counter); Serial.println(F(" released DELAY")); }
if (actOff & PROG_SEL_BTN) {Serial.print(counter); Serial.println(F(" released PROG")); }
if (actOff & START_BTN) {Serial.print(counter); Serial.println(F(" released START")); }
digitalWrite(A5, actTog & START_BTN); // display red led toggle START/STOP
// this section below develops the idea of a stable press (same for 2 readings)
unsigned char staBits = ~(newData ^ prevData);
actSta &= ~staBits; // knock out the old stable bit readings
actSta |= newData & staBits; // and jam the stable readings in there
digitalWrite(A4, actSta & (PROG_SEL_BTN | DELAY_TIMER_BTN)); // one or both, green led ON
// the stable readings could also be used with for a fussier actOn / actOff / actTog
// by basing them on stable state readings
unsigned char fussyActOn = actSta & ~prevSta;
unsigned char fussyActOff = ~actSta & prevSta;
if (fussyActOn & DELAY_TIMER_BTN) {Serial.print(counter); Serial.println(F(" verified fussy DELAY"));}
if (fussyActOff & DELAY_TIMER_BTN) {Serial.print(counter); Serial.println(F(" released fussy DELAY"));}
// always...
prevData = newData; // bump along the history
prevSta = actSta; // bump along the history
}
void loop() { //
isButtonPressed(); //
delay(250);
} //
Veryyyyyyyyyy good idea! The beach inspires you! (or the buddy?) I continue to work on this idea (you have bluffed me, you have simplified the determination of ActOff and ActTog, I had not seen this possibility, bravo