RESOLVED - T4.1 using Bounce2, boolean operators not working as expected.

japreja

Active member
I am in the process of updating my 1.44in Display menu with button debouncing, Code was initially posted to Arduino forums but I was quickly running out of memory so Converted to Teensy to continue working on it.

Here is my Current Code for the main file(named colors is included in the attachments to keep post less than 10,000 chars):

C++:
/* Copyright 2018-2022 Jorge Joaquin Pareja
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
documentation files (the "Software"),  to deal in the Software without restriction, including without
limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
Software, and to permit persons to whom the Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/
/* 128x128-Menu-Template.ino - Impliment a UTFT 128x128 SPI display menu template
This code is for the 1.44" TFT LCD Module using Adafruit's current libraries pulled fresh from github,
ignore the products linked video since the links included in the video, from the amazon seller, have
some older code that causes errors on this screen.
 * 1.44" TFT LCD  https://www.amazon.com/gp/product/B07B4BDWCL
 * Adafruit GFX library https://github.com/adafruit/Adafruit-GFX-Library
 * Adafruit HW Specific Library https://github.com/adafruit/Adafruit-ST7735-Library
  Pinout Display:
  These budget displays have what seem to be a misprinted silkscreen text that, at first sight, one
  will think these are I2C.  They are not!  They are in fact SPI.  Wire them as follows:
  Display | Arduino
    * 5V  to +5V
    * GND to GND
    * GND to GND
    * NC
    * NC
    * LED to 5V
    * SCL to Digital 13
    * SDA to Digital 11
    * RS  to Digital  7
    * RST to Digital  6
    * CS  to Digital  5
  Pinout Buttons (Active Low With Internal PU Resistors enabled, see setup().)
    * Left   to pin 4
    * Right  to pin 3
    * Select to pin 2
*/
#include <Adafruit_GFX.h>
#include <Adafruit_ST7735.h>
#include <SPI.h>
#include <Bounce2.h>
#include "jjp-NamedColors.h" // pulled from wikipedia list of named colors and added some conversion notes using regex to convert between RGB565 and PC
Bounce2::Button btnLeft   = Bounce2::Button();
Bounce2::Button btnRight  = Bounce2::Button();
Bounce2::Button btnSelect = Bounce2::Button();
#define BTNLEFT_PIN   4
#define BTNRIGHT_PIN  3
#define BTNSELECT_PIN 2
#define DEBOUNCE2_INTERVAL_MS  25
const byte TFT_CS    =    5;  // Silkscreen label CS
const byte TFT_RST   =    6;  // Silkscreen label RST
const byte TFT_DC    =    7;  // Silkscreen label RS
//const byte TFT_SCLK  =   13;  // Silkscreen label SCL  (Needs to be Connected even if this line is commented)
//const byte TFT_MOSI  =   11;  // Silkscreen label SDA  (Needs to be Connected even if this line is commented)
// For 1.44" TFT with ST7735 use
Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS,  TFT_DC, TFT_RST);
///// Display, Font, and Padding constants, may need to be changed to int instead of byte values for larger
// screens when the values will excede 255
// Display Properties
const byte screenWidth  = 128; // in pixels
const byte screenHeight = 128; // in pixels
// font properties
const byte fntWidth  = 6; // in pixels
const byte fntHeight = 8; // in pixels
const byte screenMaxCharsPerLine = screenWidth/fntWidth;
const byte screenMaxLines = screenHeight/fntHeight;
// Padding in pixels, incase font is slightly off screen
const byte cpadding = 0; // column padding
const byte rpadding = 0; // row/line padding
// 21 Columns on a 1.44 (128x128) display.  I use this for calculating the
// columns for text placement using setCursor, default font 6x8!
// DONE!! This can be populated in a for loop and account for alternate screen
// and font sizes, and also to possibly save memory, see setup().
int tft144Col[screenMaxCharsPerLine];
int tft144Row[screenMaxLines];
void cls(int color = c_Black, byte rotation = 1);
// Buttons
const byte bSelect = 2; // not yet implimented
const byte bLeft = 3;
const byte bRight = 4;
//pages
byte pageStartup    = 0;
byte pageDRO        = 1;
byte pageSettings_A = 2;
byte pageSettings_B = 3;
// Menu Settings and variables
byte mIndex = 5; // menu index
byte pIndex = 5; // previous page
byte cIndex = 5; // current page
const String sVersion = "V1.0";
void setup() {
  // Use this for a 1.44" TFT
  tft.initR(INITR_144GREENTAB);
  // setup easy access for text positions
  for (byte i = 0; i < screenMaxCharsPerLine; i++){
    tft144Col[i] = (cpadding+(i*fntWidth));
  }
  for (byte i = 0; i < screenMaxLines; i++){
    tft144Row[i] = (rpadding+(i*fntHeight));
  }
  // set the text color, and the background color so that new text will cover over any old text
  // without having to blank the entire screen
  setTextWhiteBlack(); // white text on black background, prevents having to redraw the entire screen
  tft.setTextWrap(false); // disable wrapping of text
  cls(c_Black, 1);
  // configure buttons
  btnLeft.attach(  BTNLEFT_PIN,   INPUT_PULLUP);
  btnRight.attach( BTNRIGHT_PIN,  INPUT_PULLUP);
  btnSelect.attach(BTNSELECT_PIN, INPUT_PULLUP);
  //Debonce intervals
  btnLeft.interval(  DEBOUNCE2_INTERVAL_MS);
  btnRight.interval( DEBOUNCE2_INTERVAL_MS);
  btnSelect.interval(DEBOUNCE2_INTERVAL_MS);
  //
  btnLeft.setPressedState(  LOW);
  btnRight.setPressedState( LOW);
  btnSelect.setPressedState(LOW);
  //
}
void loop() {
  layoutScreen();
  delay(50);
}
void layoutScreen(){
  processButtons();
  drawMenu(mIndex);
  switch(mIndex){
    case 1:
      Page1();
      break;
    case 2:
      Page2();
      break;
    case 3:
      Page3();
      break;
    case 4:
      Page4();
      break;
    case 5:
      Page5();
      break;
    default:
      Page5();
      break;
  }
}
void processButtons(){
  btnLeft.update();
  btnRight.update();
  btnSelect.update();
 
  // check if the pushbutton is pressed. If it is, the buttonState is HIGH:
  if ((btnLeft.pressed() == true) && (btnRight.pressed() == false) && (btnSelect.pressed() == false)){
    pIndex = mIndex;
    mIndex -= 1;
  }
 
  if ((btnLeft.pressed() == false) && (btnRight.pressed() == true) && (btnSelect.pressed() == false)){
    pIndex = mIndex;
    mIndex += 1;
  }
 
  if( mIndex <= 0){ // loop around to the end page
    mIndex = 5;
  }
  else if (mIndex > 5){ // loop around to the beginning page
    mIndex = 1;
  }
}
void Page1(){ // display Page 1
  // "0123456789012345678901" // max 22 chars per line referance
  setTextWhiteBlack();
  pl("Page 1", 2,4);
}
void Page2(){ // display Page 2
  // "0123456789012345678901" // max 22 chars per line referance
   pl("Page 2", 2,4);
}
void Page3(){ // display Page 3
  // "0123456789012345678901" // max 22 chars per line referance
   pl("Page 3", 2,4);
}
void Page4(){ // display Page 4
  // "0123456789012345678901" // max 22 chars per line referance
   pl("Page 4", 2,4);
}
void Page5(){// Main Screen Page
  // "0123456789012345678901" // max 22 chars per line referance
  pl("Menu Template", 2,4);
  pl(sVersion, 3, 8);
  pl("using a",6,7);
  pl("1.44\" TFT LCD", 7,4);
  pl("By", 8,10);
  pl("Jorge Pareja", 9, 5);
  tft.setTextColor(c_Red, c_Black);
  pl("Press Left Or Right", 13, 1);
  pl("buttons To Begin", 14, 3);
}

void drawMenu(byte index) {// calling this will clear the screen and draw the menu on line 0, similar to tabbed pages.  No need to modify, add code to Page#() functions instead
  String page0 = "DRO ";
  String page1 = " SA ";
  String page2 = " SB ";
  String page3 = " NA ";
  cIndex = index;
  setTextWhiteBlue();
  if(pIndex != cIndex){
    cls();
    pIndex = cIndex;
  }
  if(index == 1){
    setTextBlackRed();
    pl(page0, 0, 1);
    setTextWhiteBlue();
    pl(page1, 0, 6);
    pl(page2, 0, 11);
    pl(page3, 0, 16);
  }
  else if(index == 2){
    pl(page0, 0, 1);
    setTextBlackRed();
    pl(page1, 0, 6);
    setTextWhiteBlue();
    pl(page2, 0, 11);
    pl(page3, 0, 16);
  }
  else if(index == 3){
    pl(page0, 0, 1);
    pl(page1, 0, 6);
    setTextBlackRed();
    pl(page2, 0, 11);
    setTextWhiteBlue();
    pl(page3, 0, 16);
  }
  else if(index == 4){
    // Page 4
    pl(page0, 0, 1);
    pl(page1, 0, 6);
    pl(page2, 0, 11);
    setTextBlackRed();
    pl(page3, 0, 16);
  }
  else if(index == 5){
    // menu item not selected
    pl(page0, 0, 1);
    pl(page1, 0, 6);
    pl(page2, 0, 11);
    pl(page3, 0, 16);
  }
  setTextWhiteBlack();
}
void setTextWhiteBlue() {tft.setTextColor(c_White, c_Blue);}
void setTextWhiteBlack(){tft.setTextColor(c_White, c_Black);}
void setTextBlackRed()  {tft.setTextColor(c_Black, c_Red);}
void uprint(String txt, int fg_color, int bg_color, byte lineNumber, byte columnNumber){
  tft.setTextColor(fg_color, bg_color);
  tft.setCursor(tft144Col[columnNumber], tft144Row[lineNumber]);
  tft.print(txt);
}
void pl(String txt, byte lineNumber, byte columnNumber) { // print string based on the line and column numbers, calculated in the arrays
  tft.setCursor(tft144Col[columnNumber], tft144Row[lineNumber]);
  tft.print(txt);
}
void cls(int color = c_Black, byte rotation = 1){ // Clear the Screen w/ screen rotation
  tft.setRotation(rotation);
  tft.fillScreen(color);
}

The problem I am encountering is my use of boolean operators in the code regarding Bounce2. I've defined 3 buttons, btnLeft, btnRight, and btnSelect. When I use boolean operators to detect the button presses I am trying to ensure only one button is pressed while also leaving options to detect multiple button presses. Currently it doesn't care if another button is being held when I specifiacly am trying to detect exclusivly 1 button press in the following code.

C++:
void processButtons(){
  btnLeft.update();
  btnRight.update();
  btnSelect.update();
 
  // check if the pushbutton is pressed. If it is, the buttonState is HIGH:
  if ((btnLeft.pressed() == true) && (btnRight.pressed() == false) && (btnSelect.pressed() == false)){
    pIndex = mIndex;
    mIndex -= 1;
  }
 
  if ((btnLeft.pressed() == false) && (btnRight.pressed() == true) && (btnSelect.pressed() == false)){
    pIndex = mIndex;
    mIndex += 1;
  }
 
  if( mIndex <= 0){ // loop around to the end page
    mIndex = 5;
  }
  else if (mIndex > 5){ // loop around to the beginning page
    mIndex = 1;
  }
}

EDIT: For clarity, the "if" section of "void processButtons(){}" is still alowing multiple button presses instead of exclusivly just one at a time as I would assume the multiple boolean && operators depict. I am trying to perform an Exclusive && not somehow. Also, the code I posted to the arduino forum is at https://forum.arduino.cc/t/128x128-display-menu-template/614162 . My setup is exactly the same as pictured there except using a Teensy because the memory is greater.




END EDIT

It still works just not how I want it to, any clue as to what I am doing wrong? Thanks in advance for any help, as usual it is always appreciated!
Jorge
 

Attachments

  • 128x128-Menu-Template.ino
    9.3 KB · Views: 77
  • jjp-NamedColors.h
    93.4 KB · Views: 85
Last edited:
You're going to have to be clearer than "it doesn't work how I want it to." What do you expect to happen and what is happening instead?
 
The problem I am encountering is my use of boolean operators in the code regarding Bounce2. I've defined 3 buttons, btnLeft, btnRight, and btnSelect. When I use boolean operators to detect the button presses I am trying to ensure only one button is pressed while also leaving options to detect multiple button presses. Currently it doesn't care if another button is being held when I specifiacly am trying to detect exclusivly 1 button press in the following code.
 
The pressed() method returns true once when the button has just changed to being pressed - you can't call it twice in succession as you'd clear the state by the second call.

You could use isPressed() method, but then you have to debounce yourself.

You are overthinking this, the correct approach is:

Code:
void processButtons(){
  btnLeft.update();
  btnRight.update();
  btnSelect.update();
 
  if ((btnLeft.pressed()){
    pIndex = mIndex;
    mIndex -= 1;
  }
 
  if ((btnLeft.pressed()){
    pIndex = mIndex;
    mIndex += 1;
  }
 
  if( mIndex <= 0){ // loop around to the end page
    mIndex = 5;
  }
  else if (mIndex > 5){ // loop around to the beginning page
    mIndex = 1;
  }
}
 
@MarkT:

Did you intend for the code to be as follows (appears to have a simple cut/paste duplication):

Code:
void processButtons(){
  btnLeft.update();
  btnRight.update();
  btnSelect.update();
 
  if ((btnLeft.pressed()){
    pIndex = mIndex;
    mIndex -= 1;
  }
 
  if ((btnRight.pressed()){ // THIS LINE WAS CHANGED/UPDATED
    pIndex = mIndex;
    mIndex += 1;
  }
 
  if( mIndex <= 0){ // loop around to the end page
    mIndex = 5;
  }
  else if (mIndex > 5){ // loop around to the beginning page
    mIndex = 1;
  }
}

Mark J Culross
KD5RXT
 
I am just going to use hardware debouncing and anti ghosting. Since I have multiple projects I am trying to get back to, I am going to use an old project I had started when I was activly involved with FlightGear Space Shuttle ( https://forum.flightgear.org/viewtopic.php?f=18&t=34818 ) about 6 years ago. And my old clock project from about 11 years ago (
)

I will revert to not using Bounce2 since it will no longer be needed with hardware debouncing and anti-ghosting. I probably should have just done that in the first place. Although, it should be possible in C++ using:

C++:
if ((btnLeft.pressed() == false) && (btnRight.pressed() == true) && (btnSelect.pressed() == false)){....}

to detect exclusivly single and multiple button presses. With Bounce2 currently you cannot do that apparently.

Thanks for the help anyways.
 
You could use isPressed() method, but then you have to debounce yourself.
Actually isPressed() returns the debounced state of the button. So, replacing pressed() by isPressed() in @japreja s code should work.

Here an example showing the behaviour of isPressed()
C++:
#include "Bounce2.h"

Button b1;

void setup()
{
    pinMode(1, OUTPUT);

    b1.attach(0, INPUT_PULLUP);
    b1.setPressedState(LOW);
    b1.interval(1);  // to get a nicer LA trace, use larger value in production code
}

void loop()
{
    b1.update();
    digitalWriteFast(1, b1.isPressed());
}

Active low button on pin 0

1708066844442.png
 
Normally you'd code a state machine and use pressed() to trigger state changes - each state would test some subset of the buttons relevant to that state. Only if you need to know when a button is released would you need the extra complication of using isPressed() - you'd have to determine button state changes explicitly in that case.
 
May be a little overkill but how about something like this:

Read the pressed() value for each key (true if it's just been pressed) and the isPressed() value (true if it's currently being pressed)
By using both pressed and isPressed you only see each press once but can still check for keys being held after their pressed event has already been handled.

Read each value once and store it, this avoids the risk of the data changing half way through your logic.
Personally I prefer using bitfield flags for this sort of thing but you could use an array of bools or any other way that makes sense to you.

You can then use the pressed() values to tell you if this is a new key press and the isPressed() values to check for any other keys that were previously pressed still being held.

Part of the reason I like bit masks for this is because you can come up with very efficient ways of say checking that only the required keys are pressed and no others. But they aren't the easiest to follow at times so feel free to replace with easier to follow logic.

Code:
// flags for new presses
#define LeftPressFlag (1<<0)
#define RightPressFlag (1<<1)
#define SelectPressFlag (1<<2)

// flags for held keys
#define LeftDownFlag (1<<8)
#define RightDownFlag (1<<9)
#define SelectDownFlag (1<<10)

// All the key down flags combined
#define ButtonDownMask (SelectDownFlag | RightDownFlag | LeftDownFlag)

// true if only the allowed keys are currently down.
// could do this in one line but split up to make it slightly easier to follow.
bool NoOtherKeyPressed(uint32_t Buttons, uint32_t allowedKeys) {
  uint32_t currentlyPressed = Buttons & ButtonDownMask;
  uint32_t not_allowed = currentlyPressed & ~allowedKeys;
  return (not_allowed == 0);
}

void processButtons(){
  btnLeft.update();
  btnRight.update();
  btnSelect.update();

  uint32_t Buttons = 0;
  if (btnLeft.pressed())
      Buttons |= LeftPressFlag;
  if (btnRight.pressed())
      Buttons |= RightPressFlag;
  if (btnSelect.pressed())
      Buttons |= SelectPressFlag;

  if (btnLeft.isPressed())
      Buttons |= LeftDownFlag;
  if (btnRight.isPressed())
      Buttons |= RightDownFlag;
  if (btnSelect.isPressed())
      Buttons |= SelectDownFlag;

  if (Buttons & LeftPressFlag) {    // left button has just been pressed
      if (NoOtherKeyPressed(Buttons,LeftDownFlag)) {
        // Left just pressed and no other buttons are currently pressed.
    }
  }
 
}
 
Actually isPressed() returns the debounced state of the button. So, replacing pressed() by isPressed() in @japreja s code should work.
I will give this a try later tonight.

I think I will take @MarkT 's comments in post #4 as good advice as well. Bounce2 doesn't have examples for multiple buttons so I may attempt to make one and see if they will accept a pull request if I get it working how I would like it to. It's always nice to minimize buttons by making features like this. I should be able to get 7 functions with 3 buttons. b001, b010, b011, b100, b101, b110, and b111. After going over some old code I may also try using the MCP23017.

As I was writing this @AndyA posted so I will also try that as well. I'm not realy trying to save memory or speed right now but just get it working. This is actualy similar to how I was doing it before using Bounce2 when I posted my code in the arduino forums a few years ago ( https://forum.arduino.cc/t/128x128-display-menu-template/614162 ). I was getting too much bounce and buttons were being detected multiple times for one press.
 
Is it just the logic of checking what combination of buttons are pressed...??. This code will scan an array of touchpins (used touch so is simple to try) and give you a binary value of the result that you can use in a select/case or if statement to control what is to happen next.
In your case you have 3 buttons so there are 7 possibilities of pressing, the value will let you know what buttons are pressed.....
It was tried ona Teensy 3.2 and works......

Code:
 // This will scan 5 touch pins and give result as binary value
 // the value of which can be used for control .... 32 combinations.

int i = 0;
int tval = 0;
int pinbtn[] = {15,16,17,18,19};   //touch pins for test

void setup() {
//  Serial.begin(9600);
  pinMode(13, OUTPUT);

}

void loop() {

  delay(500);
 int press = 0;
      delay(600);
    for (i = 0; i <= 4; i++)
         {
         tval = touchRead(pinbtn[i]);
            delay(100);
    if (tval >= 1500)    // detect is pin touched
       {
        delay(100);
      bitSet(press, i);  // set the bit if pin is touched
       }
         }
          delay(100);
   
  for (int k = 0; k < press; k++)   // "press" = number of flashes = pins touched
   {
  digitalWrite(13, HIGH);         //  use press in a select/case to what each
  delay (100);                    //  combination does.
  digitalWrite(13, LOW);           //
  delay(500);
  }
}
 
Resolved by altering the code to use "rose()", still uses debouncing but the buttons also need to be released. Maybe this can also be used to detect duration of the press... Here is the altered code:

C++:
void processButtons(){
  btnLeft.update();
  btnRight.update();
  btnSelect.update();
  
  // Is left pressed?
  if ((btnLeft.rose() == true) && (btnRight.rose() == false) && (btnSelect.rose() == false)){
    // Previous Page
    pIndex = mIndex;
    mIndex -= 1;
  }
  
  // Is right pressed?
  if ((btnLeft.rose() == false) && (btnRight.rose() == true) && (btnSelect.rose() == false)){
    // Next Page
    pIndex = mIndex;
    mIndex += 1;
  }
  // Are both left and right pressed?
  if ((btnLeft.rose() == true) && (btnRight.rose() == true) && (btnSelect.rose() == false)){
    // Change to page three
    pIndex = mIndex;
    mIndex = 3;
  }
  if( mIndex <= 0){ // loop around to the end page
    mIndex = 5;
  }
  else if (mIndex > 5){ // loop around to the beginning page
    mIndex = 1;
  }
}

I went and re-read the documentation given for Bounce2 and just tried rose() and it worked although the left and right buttons need to be released at the same time. If I press both buttons and then change my mind and decide for just a single button press it just presses the button released and not both.

I am not sure if the Bounce2 library blocks until the buttons are released or not, but its working more like I would expect. The documentation is not fully clear about that.

Working Code attached.
 

Attachments

  • 128x128-Menu-Template.zip
    19.7 KB · Views: 80
Back
Top