Idiot programming error, boolean event inversion

ok, I've held off posting for 2 days while I try to track this bug down. Im sure its going to be something really stupid and obvious....and I've isolated it to this one routine:

void buttondouble() {
  LastButton = millis(); // record button time for menu timeout
  DebugVar=!DebugVar; // toggle debug state
  if (!DebugVar) { 
    tft.fillRect(0, DispWidth-30, DispHeight, DispWidth, ST7735_BLACK); // Clear debug text,  within the routine so its only triggered once per change
  } // end IF
} // end buttondouble()

The routine is triggered from a button click event, using a button library. I know the events are occuring, since I added some debug functions to prove it.

What should happen is that each time the routine runs, it inverts the state of DebugVar, then immediately checks it and acts upon it. The boolean is initialised to an off state at the start with :

boolean DebugVar=false; // generate debug info

In operation, Im printing the state of DebugVar in the main loop so i can see it. It starts as false, then on a double click event it'll invert to true (so far, so good). BUT... any subsequent double clicks will NOT invert it back to false.

I've checked the whole of the rest of the code and this is the ONLY place where the variable is manipulated.

ok guys, over to you..... and Im hoping its going to be something obvious and stupid :)

You need to post all your code. That snippet doesn’t tell us much. The fact that it’s the part you’ve been staring at for the last, god knows how long, is probably an indication that the problem is elsewhere in your code.

KenF is right…we need to see all of the code. My guess is that DebugVar has local scope and is defined in a function where it doesn’t remember its last state. Either make it a global or (better yet) make it a static:

static boolean DebugVar = false;

Fair enough. Heres a cutdown version of the full code, which still exhibits the same problem

/*

 Functions:
 
 - Button interface
 - Menu activation/timeout
 - Sleep mode on timer
 
 ______________________________
 Hardware:
 
 Nano v3.0 
 Sainsmart 1.8" TFT/LCD
 
 ______________________________
 Physical pin wiring:
 
 5: Switch (which has an RC debounce using 10kOhm and 10nF, pulled down)
 11: to TFT Reset, pin 6
 12: to TFT DC, pin 7
 13: to TFT CS, pin 5 
 14: SPI MOSI to TFT SDA pin 8
 16: SPI SCK to Display pin 9
 ______________________________
 */


#include <Adafruit_GFX.h>    // Core graphics library
#include <Adafruit_ST7735.h> // Hardware-specific library
#include <SPI.h>  // Interface to the TFT display uses SPI
#include <OneButton.h> // Mathertel button lib for click, doubleclick and press
#include <avr/sleep.h> // avr sleep modes library
#include <EEPROM.h> // used for persistent storage of user config data & cal factors etc

// button related shizzle
const byte UIbutton=2;
OneButton button(UIbutton, false); //boolean part: false=high.

// Config for HW SPI
#define cs   10
#define dc   9
#define rst  8  // you can also connect this to the Arduino reset
Adafruit_ST7735 tft = Adafruit_ST7735(cs, dc, rst); 

volatile long LastButton=millis(); // used in timer bodge, volatile declaration makes it accessible in ISR
long LoopTime=0; // main loop timer
boolean DebugVar=false; // generate debug info

// Display variables
boolean DisplayRotate=false; // will be user configurable & read from defaults in eeprom on startup 
const byte DispWidth=tft.width();
const byte DispHeight=tft.height(); 

void setup(void) 
{
  analogReference(EXTERNAL); // Set to external ref on Aref pin (connected to 3v3 in wiring)

  // button related
  pinMode(2,INPUT);
  button.attachClick(buttonclick);
  button.attachDoubleClick(buttondouble);
  button.attachPress(buttonpress);
  button.setClickTicks(350); // ms duration for click trigger
  button.setPressTicks(700); // ms duration for press trigger

  // Display setup
  displayInit();

  Serial.begin(115200); // used just for diagnostics mode. Check that in final barebones version a connection doesnt cause a reset.

} // end setup()


// ------- Subroutines ----------
// Display Related -------

void displayInit() {
  digitalWrite(6, HIGH); // physical 9 high: Feeds FET for TFT Vcc
  tft.initR(INITR_BLACKTAB);   // initialize a ST7735S chip, black tab
  if (DisplayRotate){
    tft.setRotation(3);
  }
  else {
    tft.setRotation(1);
  }
  tft.fillScreen(ST7735_BLACK); 
} 

void buttonclick() {
  LastButton = millis();
  // next stage adds a check on system state, then selects appropriate peripheral power. eg full off > full on. display off > full on.  
} // end buttonclick()

void buttondouble() {
  LastButton = millis(); // record button time for menu timeout
  DebugVar=!DebugVar; // toggle debug state
  if (!DebugVar) { 
    tft.fillRect(0, DispWidth-30, DispHeight, DispWidth, ST7735_BLACK); // Clear debug text,  within the routine so its only triggered once per change
  } // end IF
} // end buttondouble()

void buttonpress() {
  LastButton = millis();
} // End buttonpress()


void debug() { // various print statements for debugging purposes
  debugTFT(); 
} // End debug()

void debugTFT() {
  tft.setTextColor(ST7735_WHITE, ST7735_BLACK);
  tft.setCursor((DispHeight/2),DispWidth-10);
  tft.print(F("LET "));
  tft.print(millis()-LoopTime);
  tft.print(F(" "));
  tft.setCursor((DispHeight/2),DispWidth-20);
  tft.print(F("UI: "));
  tft.print(LastButton);
  tft.setCursor(5,DispWidth-20);
  tft.print(F("mS: "));
  tft.print(millis());
} // end debugTFT()



// Main loop -------------------------------------------------------------------------------

void loop() 
{
  LoopTime=millis();
  button.tick(); // watch the button activity

  if(DebugVar) {
    debug();
  } // end IF
  tft.setTextSize(1);
  tft.setTextColor(ST7735_WHITE, ST7735_GREEN);
  tft.setCursor(DispHeight-10, 0);
  tft.println(DebugVar);
  // end debug helpers

} // end Loop()

What happens if you put a Serial.println(DebugVar) as the first line in buttondouble()?

Is buttonDouble being called by an interrupt hander? If so, is it safe to call the TFT functions from an interrupt context (almost certainly its NOT....).

Regards, Ray L.

Did you think to check if buttondouble is being called more than once?

I've put debug prints into buttondouble, its being entered as expected...and its not being double triggered.

TFTprint/interrupt handler would be valid, but Im using the same method elsewhere and it IS working.

void buttonpress() {
  LastButton = millis();
  UIDisplay=!UIDisplay; // toggle menu
  if (!UIDisplay) {
    menuoff();
  }
  else {
    UIUpdate=true;
    MenuState=true;
  } // end if
} // End buttonpress()

I've not posted everything because Im trying to isolate it, so limiting the code list to just the broken bit seems a quicker way to diagnose. The menuoff routine is also tft.print commands....

If buttonDouble is called by an interrupt, you also cant use Serial.print in there....

Regards, Ray L.

Ray, got that bit. As part of the diagnostics i setup some variables and put if statements in the main loop to do prints, to remove the question of the serial activities… the issue seems to be limited to that debugvar and thats what i cant fathom.

As I see it, its declared as a global variable (I’ve even tried declaring it volatile just in case theres something Im not seeing). I included the following in the main loop (so avoiding any ISR issues) while i was diagnosing;

 // begin debug helpers.
  tft.setTextSize(1);
  tft.setTextColor(ST7735_WHITE, ST7735_GREEN);
  tft.setCursor(DispHeight-10, 0);
  tft.println(DebugVar);
  // end debug helpers

and from that you can clearly see that the state of debugvar WILL change to “1”, but it will not change back to “0”. Im slowly turning into a gibbering wreck…with the suspicion that Im going to find a missing “;” after days of searching :slight_smile:

Changing the buttondouble routine to the following, still exhibits the lack of inversion BACK to a zero

void buttondouble() {
  LastButton = millis(); // record button time for menu timeout
  DebugVar=!DebugVar; // toggle debug state
}

There is nice simple code for button clicks here

...R

Thanks for the link Robin, but with respect thats not helping my understanding/learning process.

I might come across a similar problem in the future and it’d be good to know why its occurring.

I modified the routine to count button clicks in an array and then print them in the main loop:

void buttondouble() {
  btnevent[1]++; 
  LastButton = millis(); // record button time for menu timeout
  DebugVar=!DebugVar; // toggle debug state
}
void loop() 
{
  LoopTime=millis();
  button.tick(); // watch the button activity
  // couple of lines to debug buttons
  tft.setTextSize(1);
  tft.setTextColor(ST7735_BLACK, ST7735_GREEN);
  for (int i = 0; i < 3; i++) {
    tft.setCursor((20+(i*20)), 1);
    tft.print(btnevent[i]);
  }
  tft.setCursor(50,50);
  tft.print(DebugVar);
}

No other changes made. Now i can see each different button event count incrementing (click/double/press), proving each routine is being entered and no double events are occurring. DebugVar still does its funky trick of only switching to ‘true’ and never toggling back to false.

Thanks for the link Robin, but with respect thats not helping my understanding/learning process. I might well have to try it later, but I might come across a similar problem in the future and it’d be good to know why its occurring.

So, I modified the routine to count button clicks in an array and then print them in the main loop:

void buttondouble() {
  btnevent[1]++; 
  LastButton = millis(); // record button time for menu timeout
  DebugVar=!DebugVar; // toggle debug state
}
void loop() 
{
  LoopTime=millis();
  button.tick(); // watch the button activity
  // couple of lines to debug buttons
  tft.setTextSize(1);
  tft.setTextColor(ST7735_BLACK, ST7735_GREEN);
  for (int i = 0; i < 3; i++) {
    tft.setCursor((20+(i*20)), 1);
    tft.print(btnevent[i]);
  }
  tft.setCursor(50,50);
  tft.print(DebugVar);
}

No other changes made. Now i can see each different button event count incrementing (click/double/press), proving each routine is being entered and no double events are occurring. DebugVar still does its funky trick of only switching to ‘true’ and never toggling back to false.

scrumfled: Thanks for the link Robin, but with respect thats not helping my understanding/learning process.

No problem. I just like simple stuff I can understand.

...R