structure members holding values

Here’s a tricky one. I have declared a structure as follows:

typedef struct action_button {

boolean buttonCurrent;
boolean buttonLast;
boolean buttonPressedFlag;
boolean buttonDblClickFlag;
long buttonTimePressed;
long buttonTimeReleased;
int buttonHoldTime;

boolean buttonPressed;
boolean buttonHeld;
boolean buttonReleased;
boolean buttonDblClick;

} ActionButton;

This is used to keep track of button event changes.

new buttons are declared: ActionButton button_name;

The prototype for the function that handles the button events is:
void buttonEvents(struct action_button *btn, int currentVal, boolean pressedVal);

A call to this function looks like this:
buttonEvents(&button_name, digitalRead(2), 0);

Note that I’m passing the address of the structure to the function… perhaps the problem could be with the way I’m handling the pointer.

Now the problem is that the structure doesn’t erase the members upon reset of the arduino, not even if I disconnect and reconnect power, or reprogram the device. I would think that upon reset, power on, or reprogramming arduino that the member values would reset to zero. I don’t see why they hold their last set values. I have also had this same problem before with PICs using the CCS compiler. It appears that when I create new ActionButton structures that the memory is not being allocated (the sketch size is them same whether I have created 1 or more ActionButtons).

For example:
ActionButton button1;
ActionButton button2;
ActionButton button3;

is the same code size as just creating ActionButton button1;
This wold mean that the actual memory used exceeds that reported when the sketch is compiled and for some strange reason the memory in that area is not being cleared when the device is reset or when the device is reprogrammed if the new program does not exceed the memory of the previous program.

I must be handling something incorrectly.

any ideas?

Thanks,
-jeff

The sketch size indicates program size, not RAM utilization. Technically they are related if you initialize the values, as some program space is needed for the initialization values.

Maybe the SRAM technology results in a bit that tends to default to a particular state when powered up?

I'm a bit unclear on why it matters, as you would want to initialize the values to some known state before using them.

When using a pointer to a struct (the resulting value you get in your function), IIRC you reference them like btn->buttonTimePressed .

-j

I figured out what's happening. Apparently you cannot define a structure variable from within the loop function (obviously when you're also preventing loop() from looping by including a while loop). They must be defined globally or the member values set will not be cleared when arduino is reset or reprogrammed (with the same program).

So given the code segment from my previous post above, if you have:

void loop() {

// this will compile and appear to run correctly // but the member values will remain in memory even if Arduino // is reset or reprogrammed (with the same program) // However if these structure definitions are declared outside of // loop() they will be properly initialized ActionButton button1; ActionButton button2;

while(true) { buttonEvents(&button1, digitalRead(2), 0); buttonEvents(&button2, digitalRead(3), 0); } }

Why this happens, I'm not sure. Perhaps this is just my fault for trying to make loop() act more like main() and being picky about scope.

What values do the members of the struct have? What values do you expect them to have (if initialized correctly)?

I still don't see any initialization. Is the initialization left out of the code fragment you have shown?

C does not initialize variables for you - if you want a variable to start out with 0, you'd better initialize it to 0. For automatic variables, the "default" initial value will be whatever garbage is on the stack when the function is called.

-j

Ah yes, scope. You are declaring automatic variables inside loop(). automatic variables are allocated when the function is called, and their space is freed when the function returns.

If you want a variable to keep its value between function calls, you must declare it static, e.g. static ActionButton button1; - if you don't explicitly ask for static, it's auto by default.

If your code works when the variables are declared outside the scope of any function, but fail to work with the declaration you have shown, I'm guessing this is the problem.

-j

I created a new function initButton to initialize the structure members that aren’t initialized in eventButton. That fixed it. I was hoping to avoid that step but obviously it’s necessary. Here’s the complete code. It’s a simple button event library. It’s in sketch form here for simplicity. This is all I need in terms of events for a current project, but I’d like to make the library version available to other users and maybe add a few more event types. Any feedback is welcome. Thanks.

// ActionButton Event Library
// 1/20/08
// Jeff Hoefs

#define DBL_CLICK_THRESH 100 // set double-press threshold to 100ms
#define DEBOUNCE 10 // set debounce time to 10ms
#define BUTTON_HOLD_TIME 1000 // set button hold trigger to 1000ms
#define PULL_UP 0 // using internal or external pull-up resistor
#define PULL_DOWN 1 // using external PULL_DOWN resistor

// make ActionButton a type def
typedef struct action_button {

boolean buttonCurrent;
boolean buttonLast;
boolean buttonPressedFlag;
boolean buttonDblClickFlag;
long buttonTimePressed;
long buttonTimeReleased;

int buttonHoldTime;

// Button Events
boolean buttonPressed;
boolean buttonHeld;
boolean buttonReleased;
boolean buttonDblClick;

} ActionButton;

void initButton(struct action_button *btn);
void buttonEvents(struct action_button *btn, int currentVal, boolean pressedVal);

void initButton(struct action_button *btn) {
btn->buttonLast = 1;
btn->buttonPressedFlag = 0;
btn->buttonDblClickFlag = 0;
btn->buttonTimePressed = 0;
btn->buttonTimeReleased = 0;
btn->buttonHoldTime = BUTTON_HOLD_TIME;
}

void buttonEvents(struct action_button *btn, int currentVal, boolean pressedVal) {

// clear button states
btn->buttonPressed = 0;
btn->buttonHeld = 0;
btn->buttonReleased = 0;
btn->buttonDblClick = 0;

// if button uses pull-down resistor, invert cur_val
if(pressedVal) currentVal = !currentVal;

// store current button state
btn->buttonCurrent = currentVal;

// if button is toggled
if(!btn->buttonCurrent && btn->buttonLast != btn->buttonCurrent) {
// debounce
if(millis() - btn->buttonTimePressed >= DEBOUNCE) {
// check for double click, account for millis roll over (9 hours)
if(millis() - btn->buttonTimeReleased > 0 && millis() - btn->buttonTimeReleased <= DBL_CLICK_THRESH) {
// don’t allow tripple click or any higher number of fast press events
if(!btn->buttonDblClickFlag) {
btn->buttonDblClick = 1;
btn->buttonDblClickFlag = 1;
}
}
// check for single press, always fired on initial press of double click, but never on 2nd press
// of double press event
else {
btn->buttonPressed = 1; // set state
btn->buttonDblClickFlag = 0;
}
btn->buttonLast = btn->buttonCurrent;
btn->buttonPressedFlag = 1;
btn->buttonTimePressed = millis();
}
}
// check for button released
else if(btn->buttonCurrent && btn->buttonLast != btn->buttonCurrent) {
// prevent on release from being triggered after a double click event
if(!btn->buttonDblClickFlag) {
btn->buttonReleased = 1; // set state
}
btn->buttonLast = btn->buttonCurrent;
btn->buttonPressedFlag = 0;
btn->buttonTimeReleased = millis();
}
else {
btn->buttonLast = btn->buttonCurrent;
if(btn->buttonPressedFlag) {
// button sustained press event
if(millis() - btn->buttonTimePressed >= btn->buttonHoldTime) {
btn->buttonHeld = 1; // set state
btn->buttonTimePressed = millis();
}
}
}
}

void setup() {

// test pull-down
pinMode(2, INPUT); // don’t enable internal pull-up for this pin
// test pull-up
pinMode(3, INPUT);
digitalWrite(3, HIGH); // enable internal pull-up resistor

beginSerial(19200);
}

void loop() {

// Create action buttons
ActionButton button1;
ActionButton button2;

// initialize buttons
initButton(&button1);
initButton(&button2);

// Set frequency for buttonHold events
// this means the buttonHold event will fire once every 1000ms
// while the button is held continuously
// default button hold time is 1000ms but you may override it
button1.buttonHoldTime = 2000;

while(true) {

// button, digital pin, specify if the button uses a pull-up or pull down resistor;
// note that the button must be passed as an address (&button_name)
buttonEvents(&button1, digitalRead(2), PULL_DOWN);
buttonEvents(&button2, digitalRead(3), PULL_UP);

// Test the button events
if(button1.buttonPressed) Serial.println(“Button 1 pressed”);
if(button1.buttonHeld) Serial.println(“Button 1 held”);
if(button1.buttonDblClick) Serial.println(“Button 1 double click”);
if(button1.buttonReleased) Serial.println(“Button 1 released”);

if(button2.buttonPressed) Serial.println(“Button 2 pressed”);
if(button2.buttonHeld) Serial.println(“Button 2 held”);
if(button2.buttonDblClick) Serial.println(“Button 2 double click”);
if(button2.buttonReleased) Serial.println(“Button 2 released”);

}
}

Arduino libraries are by convention C++ classes. They don’t need to be (I have recently discovered) but there are a number of very good reasons to do so.

First, the syntax is consistent with other libraries (i.e. it avoids the somewhat awkward → indirection symbol).

Also a C++ class simplifies the effort to create and initialise structures. In fact, you can think of C++ classes as a piece of magic that hides all that indirection to structure elements and throws in for free a constructor so you have a simple place to initialise stuff.

There is a tutorial on writing an Arduino library that has an example of converting c functions to a C++ class that you may want to read.

If you do want to convert your code, I have posted below some fragments showing what the header, source file and sketch would look like. Its thrown together quickly and not checked so will need some work to get going but I hope its gives you an idea of what you would do.

// ActionButton.H  - the header file 
#ifndefActionButton _h
#define ActionButton _h

 
class ActionButton
{
  public:
     ActionButton();         // Constructor 
     Events( int currentVal, boolean pressedVal); 
     boolean buttonCurrent; 
     boolean buttonLast; 
     boolean buttonPressedFlag; 
     boolean buttonDblClickFlag; 
     long buttonTimePressed; 
     long buttonTimeReleased; 
  
     int buttonHoldTime; 
   
     // Button Events 
     boolean buttonPressed; 
     boolean buttonHeld; 
     boolean buttonReleased; 
     boolean buttonDblClick; 
     
   private:      
      // move any of the above variables down to here that do not need to be exposed 
};

#endif
//ActionButon.cpp  - the cpp source file 

  ActionButton::ActionButton() {  // constructor - initialise your data here
    buttonLast = 1; 
    buttonPressedFlag = 0; 
    buttonDblClickFlag = 0; 
    buttonTimePressed = 0; 
    buttonTimeReleased = 0; 
    buttonHoldTime = BUTTON_HOLD_TIME; 
  }

void ActionButton::Events( int currentVal, boolean pressedVal) { 
   
  // clear button states 
  buttonPressed = 0; 
  buttonHeld = 0; 
  buttonReleased = 0; 
  buttonDblClick = 0; 
   
  // if button uses pull-down resistor, invert cur_val 
  if(pressedVal) currentVal = !currentVal; 
 
  // store current button state 
  buttonCurrent = currentVal; 
   
  // if button is toggled 
  if(!buttonCurrent && buttonLast != buttonCurrent) { 
    // debounce 
    if(millis() - buttonTimePressed >= DEBOUNCE) { 
      // check for double click, account for millis roll over (9 hours) 
     if(millis() - buttonTimeReleased > 0 && millis() - buttonTimeReleased <= DBL_CLICK_THRESH) { 
       // don't allow tripple click or any higher number of fast press events 
       if(!buttonDblClickFlag) { 
         buttonDblClick = 1; 
         buttonDblClickFlag = 1; 
       } 
     } 
     // check for single press, always fired on initial press of double click, but never on 2nd press 
     // of double press event   
     else { 
       buttonPressed = 1;  // set state 
       buttonDblClickFlag = 0; 
     } 
     buttonLast = btn->buttonCurrent; 
     buttonPressedFlag = 1; 
     buttonTimePressed = millis(); 
    } 
  }  
  // check for button released 
  else if(buttonCurrent && buttonLast != buttonCurrent) { 
    // prevent on release from being triggered after a double click event 
    if(!buttonDblClickFlag) { 
     buttonReleased = 1;  // set state 
    } 
    buttonLast = btn->buttonCurrent; 
    buttonPressedFlag = 0; 
    buttonTimeReleased = millis(); 
  } 
  else { 
    buttonLast = buttonCurrent; 
    if(buttonPressedFlag) { 
     // button sustained press event 
     if(millis() - btn->buttonTimePressed >= btn->buttonHoldTime) { 
       buttonHeld = 1;  // set state 
       buttonTimePressed = millis(); 
     }             
    } 
  } 
}
// ActionButton example sketch
void setup() { 
   
  // test pull-down 
  pinMode(2, INPUT); // don't enable internal pull-up for this pin 
  // test pull-up 
  pinMode(3, INPUT); 
  digitalWrite(3, HIGH); // enable internal pull-up resistor 
   
  beginSerial(19200); 
} 

  // Create action buttons 
  ActionButton button1 = ActionButton(); 
  ActionButton button2 = ActiuonButton();
  
void loop() { 
  button1.buttonHoldTime = 2000; 
 
  while(true) {   
     
    // button, digital pin, specify if the button uses a pull-up or pull down resistor; 
    // note that the button must be passed as an address (&button_name) 
    button1.Events(digitalRead(2), PULL_DOWN); 
    button2.Events(digitalRead(3), PULL_UP);  
     
    // Test the button events 
    if(button1.buttonPressed) Serial.println("Button 1 pressed"); 
    if(button1.buttonHeld) Serial.println("Button 1 held"); 
    if(button1.buttonDblClick) Serial.println("Button 1 double click"); 
    if(button1.buttonReleased) Serial.println("Button 1 released"); 
    
    if(button2.buttonPressed) Serial.println("Button 2 pressed"); 
    if(button2.buttonHeld) Serial.println("Button 2 held"); 
    if(button2.buttonDblClick) Serial.println("Button 2 double click"); 
    if(button2.buttonReleased) Serial.println("Button 2 released"); 
 }
}

Awesome. Thanks! That’s much cleaner and easier for most Arduino users (I was worried about people having to pass the address of the structure variable as a parameter… this would probably look foreign to many users). I like that C++ also allows me to declare private variable.

Prior to Arduino I’ve never used a microcontroller platform that had a C++ compiler (i used to work only with PICs) so C++ is new to me. I’ll try it out.

Thanks again.

-jeff