Object Class acting like it's not unique over different instances

Hello. I've been working on a project and created a button object class to read the button inputs I need for the project and learn how to make a object class. Everything is working the way I want it to for a single instance of the class but as soon as I add two more for the other buttons it stops working. It's like the instances are not unique but I'm not sure what I'm missing or if I'm describing this issue right.

My sketch, header, and source files are below. I've been going over and over this and am not sure what I'm missing. I've looked at lots of examples and still am not seeing it. I appreciate any help or guidance.

Sketch:

#include <Arduino.h>
#include <LiquidCrystal_I2C.h> // Library for LCD
#include <Button.h>

// Configure input button pins
const byte BTN_ENTER = 25;
const byte BTN_INC = 26;
const byte BTN_DEC = 27;

Button btnEnter(BTN_ENTER, buttonEnterDelay);
Button btnInc(BTN_INC, buttonEnterDelay);
Button btnDec(BTN_DEC, buttonEnterDelay);


void setup() {
  Serial.begin(115200);     // Initialize serial
  
  lcd.init();               // Initialize LCD
  lcd.backlight();          // Turn on LCD backlight
  lcd.clear();              // Clear LCD

 
}

void loop() {
   
  btnEnter.Pressed(); // Read enter button
  btnInc.Pressed(); // Read increase button
  btnDec.Pressed(); // Read decrease button


  updateDisplay = false;

    
 
}

Header:

#ifndef Button_h
#define Button_h
#include <Arduino.h>

/*
* @breif Mutli-function and debounce button object
*/

class Button {

    private:
        byte _pin;
        u_int16_t _LONG_PRESS_MS; // Long press delay Ms
        u_int32_t _btnReadPrevMs = 0; // Previous button read Ms
        u_int32_t _pressedPrevMs = 0; // Previous button pressed Ms
        u_int32_t _currMs;
            
    public:
        Button(byte pin, u_int16_t LONG_PRESS_MS);
        boolean isPressed = false; // Button is currently pressed
        boolean isLongPress = false; // Button is current pressed for > long time
        boolean longPress = false; // Button was released - Press was short
        boolean shortPress = false; // Button was released - Press was long
        u_int8_t btnState = 0; // Button state
        void Pressed(); // Button function
        void ResetPressed(); // Reset button press triggers
};

#endif

Source File:

#ifndef Button_h
#define Button_h
#include <Arduino.h>

/*
* @breif Mutli-function and debounce button object
*/

class Button {

    private:
        byte _pin;
        u_int16_t _LONG_PRESS_MS; // Long press delay Ms
        u_int32_t _btnReadPrevMs = 0; // Previous button read Ms
        u_int32_t _pressedPrevMs = 0; // Previous button pressed Ms
        u_int32_t _currMs;
            
    public:
        Button(byte pin, u_int16_t LONG_PRESS_MS);
        boolean isPressed = false; // Button is currently pressed
        boolean isLongPress = false; // Button is current pressed for > long time
        boolean longPress = false; // Button was released - Press was short
        boolean shortPress = false; // Button was released - Press was long
        u_int8_t btnState = 0; // Button state
        void Pressed(); // Button function
        void ResetPressed(); // Reset button press triggers
};

#endif

Where do you declare these identifiers?

Sorry about that. I grabbed the wrong file and apparently was too tired to notice when I proof read.....

CPP File

#include "Arduino.h"
#include "Button.h"

Button::Button(byte pin, u_int16_t LONG_PRESS_MS){
    _pin = pin;
    _LONG_PRESS_MS = LONG_PRESS_MS;
    pinMode(_pin, INPUT_PULLUP);
}

void Button::Pressed(){
    //static u_int32_t btnReadPrevMs = 0; // Previous button read Ms
    //static u_int32_t pressedPrevMs = 0; // Previous button pressed Ms
    //u_int32_t currMs = millis(); // Current Ms
    _currMs = millis(); // Current Ms

    static u_int8_t state = 0xFF; // Initial state of button captures
    
    if (_currMs - _btnReadPrevMs > 5) { // Run debounce routine every 5ms
        //_btnReadPrevMs = _currMs;
        state = (state<<1) | digitalRead(_pin); // Left shift state OR in current input OR mask out higher byte
        btnState = state;
        
        if (state == 0x00) { // Set pressed state if button accumulator is all 0's
            isPressed = true;
            if (_currMs - _pressedPrevMs > _LONG_PRESS_MS) { // Set long pressed if button pressed for > long time
            _pressedPrevMs = _currMs;
            isLongPress = true;
            }
        }
    }

    if (isPressed && !isLongPress && state == 0xFF) {shortPress = true;} // Set short press when button released < long time
    if (isPressed && isLongPress && state == 0xFF) {longPress = true;} // Set long press when button released > long time

     
    if (state == 0xFF) {isPressed = false; isLongPress = false; _pressedPrevMs = _currMs;} // Reset button pressed states 
                    
                                                    

}

void Button::ResetPressed() {
    shortPress = false;
    longPress = false;
    
}

I tried to not include other stuff that wasn't related to the buttons to keep the post smaller and hopefully easier to read through. I'll go on and post the whole thing. I have things commented out as part of testing and trying to figure out why this wasn't working. All of the code I have works great if I only use btnEnter. As soon as I add the btnInc and btnDec, all three button instances don't work. I was watching the .btnstate for all 3 and if I hit say the enter button, all 3 of those .btnstate variables from the class change together. I'm at a loss. If I do each button by itself it works fine.

#include <Arduino.h>
#include <LiquidCrystal_I2C.h> // Library for LCD
#include <Button.h>

// Project defines


// Project structures
struct tempCtrl{
  const String label; // Temperature control label
  float tempF; // Measured temperature (F)
  float tempF_SP; // Temperature setpoint (F)
  u_int8_t hyst; // Hysteresis for temperature control (F)
  boolean heatEnbl; // Enable heating control
  boolean coolEnbl; // Enable cooling control
  u_int8_t heatDelay; // Heating control cycle delay (min)
  u_int8_t coolDelay; // Cooling control cycle delay (min)
  u_int8_t switchDelay; // Heating/Cooling switchover delay (min)
};

LiquidCrystal_I2C lcd(0x27, 20, 4); // I2C address 0x27, 20 columns and 4 rows

// Configure input button pins
const byte BTN_ENTER = 25;
const byte BTN_INC = 26;
const byte BTN_DEC = 27;

// Menu constants and variables
const String menuLabel[4] = {"(0)Temperatures:", "(1)Temp SP Config", "(2)Temp Hyst Config", "(3)Temp Delay Config"};
const String posLabel[3] = {"Glycl", "Ferm1", "Ferm2"};
float tempValue[3] = {49.2, 66.7, 68.3};
float tempSP[3] = {50, 68, 68};
float hyst[3] = {5, 1, 1};
float delaySP[3] = {10, 1, 1};
const u_int16_t buttonEnterDelay = 2000;
const u_int16_t menuTimeOutDelay = 20000;
u_int8_t menuPos = 0; // Initialize menu position
u_int8_t menuSubPos = 0; // Initialize menu position
boolean updateDisplay = true; // Update the display

// Temperature control variables

tempCtrl glycol, ferm1, ferm2;

// Menu structure
enum menuMain {
  ROOT, // Root menu
  GLYCOL_T, // Glycol temperature menu
  GLYCOL_H, // Glycol heating menu
  GLYCOL_C, // Glycol cooling menu
  FERM1_T, // Fermenter 1 temperature menu
  FERM1_H, // Fermenter 1 heating menu
  FERM1_C, // Fermenter 1 cooling menu
  FERM2_T, // Fermenter 1 temperature menu
  FERM2_H, // Fermenter 1 heating menu
  FERM2_C // Fermenter 1 cooling menu
};
enum menuMain currPage = ROOT; // Initialize menuPos to the root menu

enum menuSub {
  POS_0, // Position 0
  POS_1, // Position 1
  POS_2, // Position 2
  POS_3 // Position 3
};


// Function forward declarations

Button btnEnter(BTN_ENTER, buttonEnterDelay);
Button btnInc(BTN_INC, buttonEnterDelay);
Button btnDec(BTN_DEC, buttonEnterDelay);

void menuNavigation();

void setup() {
  Serial.begin(115200);     // Initialize serial
  
  lcd.init();               // Initialize LCD
  lcd.backlight();          // Turn on LCD backlight
  lcd.clear();              // Clear LCD

 
}

void loop() {
   
  btnEnter.Pressed(); // Read enter button
  //btnInc.Pressed(); // Read increase button
  //btnDec.Pressed(); // Read decrease button

  menuNavigation(); // Menu navigation



  
  //if (updateDisplay) {lcd.clear();}
  lcd.setCursor(0,0);
  lcd.print(menuPos);
  lcd.setCursor(5,0);
  lcd.print(btnInc.btnState, HEX);
  lcd.setCursor(0,1);
  lcd.print(btnEnter.btnState, HEX);
  lcd.setCursor(0,2);
  lcd.print(btnDec.btnState, HEX);
  //lcd.setCursor(0,3);
  //lcd.print(btnEnter.longPress);

  updateDisplay = false;

    
 
}

void menuNavigation(){
  
  // Increase/Decrease button scrolling through sub menu positions and roll around when at ends
  if (btnInc.shortPress | btnInc.longPress) {
    if (menuSubPos == 3) {menuSubPos = 1;}
    else {menuSubPos++;}
    updateDisplay = true;
  }
  if (btnDec.shortPress | btnDec.longPress) {
    if (menuSubPos <= 1) {menuSubPos = 3;}
    else {menuSubPos--;}
    updateDisplay = true;
  }
  
  // Scroll through main menu positions and roll around when at end
  if (btnEnter.shortPress) {
    menuPos++;
    if (menuPos == 4) {menuPos = 0; menuSubPos = 0;}
    else {menuSubPos++; menuSubPos = 1;}
    updateDisplay = true;
    }

  //if (btnEnter.longPress) {menuPos++; menuSubPos = 0; updateDisplay = true;} // Long press advances menu pages
  //if (currPage != ROOT && btnEnter.shortPress) {menuSubPos++; updateDisplay = true;} // Short press advances through sub pages

  //if (menuPos == 4) {menuPos = 0;} // Roll menu back to ROOT
  //if (menuSubPos == 4) {menuSubPos = 0;} // Roll submenu back start

  // Reset button press flags
  btnEnter.ResetPressed();
  btnInc.ResetPressed();
  btnDec.ResetPressed();

  return;

}


Hello

static u_int8_t state = 0xFF;

Read about using static variable in a class

2 Likes

Well that certainly looks like an issue. The static variables belong to the class not each instance and are shared. That would do it. I’ll try that later. Thank you!

Be aware that often times we not only look at your code and try to make sense out of it, but we do run it and tweak it to figure out what the problems is and test a possible solution.

This approach does not work if compilation fails.

I think I see what you mean. I made a Begin() function that does the pinMode. Is that what you were referring to? It runs and works well now that I moved the static to a private variable and then I made this Begin() modification. My menu positions are doing what I intended now. I can start working on building them next and see what I run into next! I really appreciate the help and advice!

Button CPP

#include "Arduino.h"
#include "Button.h"

Button::Button(byte pin, u_int16_t LONG_PRESS_MS){
    _pin = pin;
    _LONG_PRESS_MS = LONG_PRESS_MS;  
}

void Button::Begin(){pinMode(_pin, INPUT_PULLUP);}

void Button::Pressed(){
    _currMs = millis(); // Current Ms
    
    if (_currMs - _btnReadPrevMs > 5) { // Run debounce routine every 5ms
        _btnReadPrevMs = _currMs;
        _state = (_state<<1) | digitalRead(_pin); // Left shift state OR in current input OR mask out higher byte
        btnState = _state;
        
        if (_state == 0x00) { // Set pressed state if button accumulator is all 0's
            isPressed = true;
            if (_currMs - _pressedPrevMs > _LONG_PRESS_MS) { // Set long pressed if button pressed for > long time
            _pressedPrevMs = _currMs;
            isLongPress = true;
            }
        }
    }

    if (isPressed && !isLongPress && _state == 0xFF) {shortPress = true;} // Set short press when button released < long time
    if (isPressed && isLongPress && _state == 0xFF) {longPress = true;} // Set long press when button released > long time

     
    if (_state == 0xFF) {isPressed = false; isLongPress = false; _pressedPrevMs = _currMs;} // Reset button pressed states                   
                                                    
}

void Button::ResetPressed() {
    shortPress = false;
    longPress = false;
    
}

Program

#include <Arduino.h>
#include <LiquidCrystal_I2C.h> // Library for LCD
#include <Button.h>

// Project defines


// Project structures
struct tempCtrl{
  const String label; // Temperature control label
  float tempF; // Measured temperature (F)
  float tempF_SP; // Temperature setpoint (F)
  u_int8_t hyst; // Hysteresis for temperature control (F)
  boolean heatEnbl; // Enable heating control
  boolean coolEnbl; // Enable cooling control
  u_int8_t heatDelay; // Heating control cycle delay (min)
  u_int8_t coolDelay; // Cooling control cycle delay (min)
  u_int8_t switchDelay; // Heating/Cooling switchover delay (min)
};

LiquidCrystal_I2C lcd(0x27, 20, 4); // I2C address 0x27, 20 columns and 4 rows

// Configure input button pins
const byte BTN_ENTER = 25;
const byte BTN_INC = 26;
const byte BTN_DEC = 27;

// Menu constants and variables
const String menuLabel[4] = {"(0)Temperatures:", "(1)Temp SP Config", "(2)Temp Hyst Config", "(3)Temp Delay Config"};
const String posLabel[3] = {"Glycl", "Ferm1", "Ferm2"};
float tempValue[3] = {49.2, 66.7, 68.3};
float tempSP[3] = {50, 68, 68};
float hyst[3] = {5, 1, 1};
float delaySP[3] = {10, 1, 1};
const u_int16_t buttonEnterDelay = 2000;
const u_int16_t menuTimeOutDelay = 20000;
u_int8_t menuPos = 0; // Initialize menu position
u_int8_t menuSubPos = 0; // Initialize menu position
boolean updateDisplay = true; // Update the display

// Temperature control variables

tempCtrl glycol, ferm1, ferm2;

// Menu structure
enum menuMain {
  ROOT, // Root menu
  GLYCOL_T, // Glycol temperature menu
  GLYCOL_H, // Glycol heating menu
  GLYCOL_C, // Glycol cooling menu
  FERM1_T, // Fermenter 1 temperature menu
  FERM1_H, // Fermenter 1 heating menu
  FERM1_C, // Fermenter 1 cooling menu
  FERM2_T, // Fermenter 1 temperature menu
  FERM2_H, // Fermenter 1 heating menu
  FERM2_C // Fermenter 1 cooling menu
};
enum menuMain currMainPage = ROOT; // Initialize menuPos to the root menu

enum menuSub {
  POS_0, // Position 0
  POS_1, // Position 1
  POS_2, // Position 2
  POS_3 // Position 3
};
enum menuSub currSubPage = POS_0;

// Function forward declarations

Button btnEnter(BTN_ENTER, buttonEnterDelay);
Button btnInc(BTN_INC, buttonEnterDelay);
Button btnDec(BTN_DEC, buttonEnterDelay);

void menuNavigation();

void setup() {
  Serial.begin(115200);     // Initialize serial

  btnEnter.Begin();
  btnInc.Begin();
  btnDec.Begin();
  
  lcd.init();               // Initialize LCD
  lcd.backlight();          // Turn on LCD backlight
  lcd.clear();              // Clear LCD

 
}

void loop() {
   
  btnEnter.Pressed(); // Read enter button
  btnInc.Pressed(); // Read increase button
  btnDec.Pressed(); // Read decrease button

  menuNavigation(); // Menu navigation



  
  //if (updateDisplay) {lcd.clear();}
  lcd.setCursor(0,0);
  lcd.print(menuPos);
  lcd.setCursor(5,0);
  lcd.print(menuSubPos);
  lcd.setCursor(0,1);
  lcd.print(btnEnter.longPress);
  lcd.setCursor(0,2);
  lcd.print(btnDec.btnState, HEX);
  //lcd.setCursor(0,3);
  //lcd.print(btnEnter.longPress);

  updateDisplay = false;

    
 
}

void menuNavigation(){
  
  // Increase/Decrease button scrolling through sub menu positions and roll around when at ends
  if (btnInc.shortPress | btnInc.longPress) {
    if (menuSubPos == 3) {menuSubPos = 1;}
    else {menuSubPos++;}
    updateDisplay = true;
  }
  if (btnDec.shortPress | btnDec.longPress) {
    if (menuSubPos <= 1) {menuSubPos = 3;}
    else {menuSubPos--;}
    updateDisplay = true;
  }
  
  // Scroll through main menu positions and roll around when at end
  if (btnEnter.shortPress) {
    menuPos++;
    if (menuPos == 4) {menuPos = 0; menuSubPos = 0;}
    else {menuSubPos++; menuSubPos = 1;}
    updateDisplay = true;
    }

  // Reset button press flags
  btnEnter.ResetPressed();
  btnInc.ResetPressed();
  btnDec.ResetPressed();

  return;

}

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.