Hello, All. I have a sketch which uses a custom routine to check for button presses. What I want to do is be able to read a button press, record that, then do something when the button is released. This avoids a gillion loop() runs reading the same button state (active). I also want to know when the button has been held for a long period. This all seems to work just fine.
My project has an LCD screen which I turn off after a period of inactivity. A short button press should just turn it on, a long press should turn it on and do something.
My problem is this: when I short press the button...sometimes it executes the code to turn the LCD on, many times it doesn't. Long pressing the button initiates the action...I have no problems with that functionality. But the short press is making me nuts. I have some junk Serial.print statements for debugging: one prints when the button is short pressed, right before the function return true; occurs. Yet, in the calling routine, the next statements do not execute. I've condensed my main sketch to this code section below which reproduces the problem. Below that I am including the serial monitor output I get. I can find no pattern to the number of times it fails before it runs properly (pressing the button gets me the Serial.print in the button checking routine but not the follow-up action, then I get the action some number of presses later). The serial monitor output presented is from the start after uploading the code and is not consistent from run to run.
I feel insane typing that...this doesn't make sense to me. I'm still very new to the Arduino and to C++ (but not programming)...I'm hoping it's something dumb I've done.
Thank you.
--HC
//this is a playground project to dial-in the button routines
#include <Wire.h>
#include <hd44780.h>
#include <hd44780ioClass/hd44780_I2Cexp.h> // this changed from Dev Rev 2
//pin definitions from the actual CCD code are left for clarity
//IO pin definitions
#define DimmerPin 3 //pin to connect to the LCD backpack LCD power for PWM brightness control
//button input pins
//leaving 3 buttons open for future use, 9, 10, 11
#define OPENpin 7 //manual open button
#define CLOSEpin 8 //manual close button
#define CycleTime 10000 //seconds in millis to run the motor output(s)
//timeout for dimming the display
const int ScreenSaver = 10000; //starting at 10,000ms, 10 seconds
static unsigned long LastSSTime = 0; //the last millis() that any button was pressed
static byte ButtonState = 0b11111111; //8 bits to record past button state to detect rising edge (release) of a button **FOR ACTIVE LOW**
static bool ScreenOnYN; //var to know if the screen is currently on or not
hd44780_I2Cexp lcd(0x27); // declare lcd object: auto locate & auto config expander chip
// LCD geometry
const int LCD_COLS = 20;
const int LCD_ROWS = 4;
void setup()
{
Serial.begin(115200);
//be sure all IO are HIGH
digitalWrite(OPENpin, HIGH); //active LOW, all have pull-ups
digitalWrite(CLOSEpin, HIGH); //active LOW
pinMode(OPENpin, INPUT); //input to OPEN door
pinMode(CLOSEpin, INPUT); //input to CLOSE door
lcd.begin(LCD_COLS, LCD_ROWS);
}
void loop()
{
bool ButtonLongPress = false;
if (DetectButtonPress(OPENpin, ButtonLongPress) == false) //button state change not detected, let's see if it's actively being held for long-press
//what this does is to initiate the action of a long-press event without waiting for state change (button release)
{
if (ButtonLongPress == true) //button is being pressed (but not state change to release) but is long-pressed
{
Serial.println("Open button not released, but long press event detected");
BacklightOn(); //running this here so that each time the button is pressed it updates the screensaver countdown
//OpenDoor(); //this function effectively delays for the duration of the open/close event allowing the user to release the button
//DoorOpenYN = true;
}
}
//Serial.println("Calling DetectButtonPress 2");
if (DetectButtonPress(OPENpin, ButtonLongPress))
//here the button is pressed and released and if it was not long-pressed, we'll turn the backlight on
{
Serial.println("button press");
if (ButtonLongPress == false)
{
Serial.println("Open button pressed, not long pressed");
BacklightOn(); //this is here because pressing any button should turn on the backlight if it's off
}
}
//check to activate screensaver
if (millis() - LastSSTime > ScreenSaver)
{
BacklightOff();
}
}
bool DetectButtonPress(int ButtonPin, bool &LongPress)
{
static unsigned long int LastButtonPress = 0;
int buttonBit = 0;
buttonBit = ButtonPin - 7; //buttons are on pins sequentially from pin 7-11
LongPress = false; //default value
if (digitalRead(ButtonPin) == LOW && ButtonState & 1 << buttonBit) //input pins for 5 buttons: 7, 8, 9, 10, 11
//we read ButtonPin as LOW (active low/pressed) *and* ButtonState bit AND 1 = 1 (bit is HIGH/inactive)
//this is a state change from inactive to active (unpressed to pressed)
{
//the button is pressed (LOW) and the ButtonState bit is HIGH, we just detected a press
ButtonState ^= 1 << buttonBit; //toggle bit 1 to 0 so we know it's PRESSED
LastButtonPress = millis(); //set our press timer
return false; //the button was just detected as down and it is a change
}
if (digitalRead(ButtonPin) && (ButtonState & 1 << buttonBit) == 0)
//we read ButtonPin as HIGH (inactive high/released) *and* ButtonState bit = 0 (bit is LOW/active)
//this is a state change from active to inactive (pressed to unpressed)
{
ButtonState ^= 1 << buttonBit; //toggle bit 1 to 1 (HIGH/inactive), button is released
if (millis() - LastButtonPress > 1000)
{
LongPress = true; //this is returned only if the button is released but was held > line above
}
Serial.println("buttonpin high released");
return true; //button has state changed from active to inactive, released
}
if (digitalRead(ButtonPin) == LOW && (ButtonState & 1 << buttonBit) == 0)
//we read ButtonPin as LOW (active LOW/pressed) *and* ButtonState bit 1 = 0 (bit is LOW/active)
//this is a continuing button press
{
if (millis() - LastButtonPress > 1000)
{
LongPress = true;
}
return false; //returning false because I want this function to return true only on state change. however, long-pressing the button should immediately intiate the action
}
//still here means the button has not changed state and, if pressed, it's not a long press
return false;
}
void BacklightOn()
{
lcd.backlight();
analogWrite(DimmerPin, 150);
ScreenOnYN = true;
LastSSTime = millis();
}
void BacklightOff()
{
lcd.noBacklight();
ScreenOnYN = false;
}
I ran this just prior to writing this post (I've been at this for a few hours today and I have run it many times as I tried to figure it out). Below, in a code section for better readability, is the output I got. You can match the printed output to the code section to try to follow how this is (or is not) working. The two lines "button press" and "Open button pressed not long pressed" indicate the code executed as expected. Multiple lines "buttonpin high released" without those two lines indicate the code did not execute as expected.
buttonpin high released
button press
Open button pressed, not long pressed
buttonpin high released
buttonpin high released
buttonpin high released
buttonpin high released
buttonpin high released
buttonpin high released
buttonpin high released
buttonpin high released
buttonpin high released
buttonpin high released
buttonpin high released
buttonpin high released
buttonpin high released
buttonpin high released
buttonpin high released
buttonpin high released
buttonpin high released
buttonpin high released
buttonpin high released
buttonpin high released
buttonpin high released
button press
Open button pressed, not long pressed
