Nested States

Status
Not open for further replies.

mtiger

Well-known member
Hi All,

I'm currently playing with the concept of nested states (toggle states) and a) having an issue when returning to the original state and b) not sure if there is a better way of writing the code I currently have.

I have one button that toggles between state 0 & state 1. When I press and hold the same button for a period of 1000 ms, the toggle state then changes to state 2. Now I can toggle between state 2 & state 3. The goal is that when I press and hold the same button for another period of time, it returns back to the default state of toggling between 0 & 1. However currently the serial monitor is printing a toggle change state of 0 & 2. Which is not ideal, as would like it to return to state 0 and stay there, until the button is released. Then i can toggle between 0 & 1 again.

I understand it most likely has something to do with the 'timeStamp' variable storing millis() and the duration passed, however I can think of how to improve it.

Here is my code. Any help would be awesome :)

Thanks

Code:
int buttonPin = 12;
int ledPin = 13;

int buttonState;
int lastButtonState;
int mode = 0;

unsigned long timeStamp = 0;

void setup() {
  Serial.begin(9600);
  pinMode(buttonPin, INPUT);
  pinMode(ledPin, OUTPUT);
}

void loop() {
  buttonMode();
}

void buttonMode() {

  buttonState = digitalRead(buttonPin);

  //TOGGLE PRESS
  if (buttonState != lastButtonState) {
    timeStamp = millis();
    if (buttonState == HIGH) {
      if (mode == 0) {
        mode = 1;
        digitalWrite(ledPin, HIGH);
      } else {
        if (mode == 1) {
          mode = 0;
          digitalWrite(ledPin, LOW);
        }
      }
      if (mode == 2) {
        mode = 3;
      } else {
        if (mode == 3) {
          mode = 2;
        }
      }
      Serial.printf("Button Mode: %d\n", mode);
    }
  }
  //TIMED PRESS
  if (buttonState == HIGH) {
    if (mode == 0 || mode == 1) {
      if ((millis() - timeStamp) > 1000)  {
        mode = 2;
        Serial.printf("Button Mode: %d\n", mode);
      }
    }
    if (mode == 2 || mode == 3) {
      if ((millis() - timeStamp) > 2000)  {
        mode = 0;
        Serial.printf("Button Mode: %d\n", mode);
      }
    }
  }
  lastButtonState = buttonState;
}
 
Problem is long press is detected while state==HIGH.

Sets back to mode=0, while button still down - then 'lastButtonState = buttonState;' is HIGH

It repeats the loop with no state change and hits the timed loop test and indeed without a state change the old 'timeStamp = millis();' is still true and it sees a long press.

Possibly other problems - but after a long press leaving and re-entering loop will not have a new timestamp. That could be reset after each long press is detected. But if held another second it will fall back into that same long press detected state.

not sure if this will be the fix needed?
Code:
  //TIMED PRESS
  if (buttonState == HIGH) {
    if (mode == 0 || mode == 1) {
      if ((millis() - timeStamp) > 1000)  {
        mode = 2;
        Serial.printf("Button Mode: %d\n", mode);
[B]        timeStamp = millis();
[/B]      }
    }
    if (mode == 2 || mode == 3) {
      if ((millis() - timeStamp) > 2000)  {
        mode = 0;
        Serial.printf("Button Mode: %d\n", mode);
[B]        timeStamp = millis();
[/B]      }
    }
 
Like this?

Code:
#include <Bounce.h>
const int buttonPin = 12;
boolean switchStates[2]  ; // track two switch states
int stateSet = 0; // index of lower or upper switch states
int state; // output variable
elapsedMillis holdTime; // track elasped time

Bounce pushbutton = Bounce(buttonPin,10);

void setup() {
  pinMode(buttonPin, INPUT_PULLUP);
}

void loop() {
    if (pushbutton.update()) {
        if (pushbutton.fallingEdge()) {
            holdTime = 0;
        }
        if (pushbutton.risingEdge()) {
            if (holdTime<1000) {
                holdTime = 0;
                switchStates[stateSet] = !switchStates[stateSet];
            } else {
                holdTime = 0;
                stateSet++;
                if (stateSet>1){
                    stateSet=0;
                }
            }
        }
        state = stateSet*2+switchStates[stateSet]+1;
        Serial.println(state);
    }
}

...this isn't trying to fix your code... this was me trying to get the result I think you were asking for.

Are you using an external pull-up... you'll need to change my setup if you are.
 
Last edited:
Looks like I messed something up when I tried to manually edit this code last night... I've tried to fix today but I don't have a Teensy compiler to check.

This is a little like having two banks of functions and a switch to choose the function but each bank has only one switch which doubles as the bank select based on time-closed.

So you're basically looking for a two-bank system with one button in each bank as a toggle. Incrementing and zeroing stateSet gives you an index.

The fallingEdge() is used to reset the timer only... then if the risingEdge() either toggles the bank (stateSet) or toggles the Boolean associated with that bank.

You have to take the release of the button --the risingEdge()-- as the toggle signal otherwise you can't tell if your toggling the output or the active selection.

So all that's left is to map the outputs to the desired state values.

state = stateSet*2+switchStates[stateSet]+1;


The Boolean value of the active switchStates[] plus 1 (switchStates[stateSet]+1) gives us 1 for off and 2 for on.

Adding 2 when the stateSet is TRUE (stateSet*2) will give 3 and 4 for FALSE and TRUE from switchStates[1]

Since you're not using bounce or internal pull-ups I can't even tell if you're using active-LOW signalling but my example uses both.
https://www.pjrc.com/teensy/td_digital.html
https://www.pjrc.com/teensy/td_libs_Bounce.html

I would think without debouncing or at least some delay() you would get very jumpy state changes.
 
oddson - saw the solution p#4 didn't look like the original as far as state changing - but the similar thing was the [ holdTime = 0; && timeStamp = millis(); ] additions in both solutions.

I noted 'Possibly other problems ' and your direction for the solution would be more robust for tracking button change. There will be race and fall through issues if a button is never released during the timeout interval - it should require a state change HIGH>LOW before it continues and the original doesn't track that. It could with a set of intermediate change required states then directing back to indicated state - but all that could be fooled by button bounce/chatter not using the Bounce library.
 
oddson - saw the solution p#4 didn't look like the original as far as state changing ...
...OP said his state changes were wrong... so I tried to make something that outputs what I take to be the desired output based on my reading of his textual description.

I did it in a hurry and it's not very elegant but it was working the way I intended ...but then I broke it trying to make it readable with edits to the posted code that were not tested... mea cupla.

I'm not 100% on the 'race and fall' issues... if the button is never released it might mess up but I was really mostly trying to determine whether the basic behaviour is what he was looking for.

Short presses toggle between 1 and 2 OR between 3 and 4... hold for more than a second (but less than the elapsedMillis roll-over) and when you release it toggles to the other set (and here lets you know what the previous selection was in this set).

If that's not what OP is going for the I think a more precise description is required.
 
OP had issues, I didn't compile it outside the browser. Your direction is indeed a better way to detect the transitions - with good direction.
 
So is my code OK for the timing issue?

I thought you were saying mine is susceptible to the timer not resetting for the same reason as OP.
 
I didn't have time or a button setup so I just winged it reading the code.

I wasn't critiquing the Bounce solution - other than I wasn't sure the state changes were in agreement with the text at first.

I was saying we both saw the same problem and fixed it with the time set to zero after long press. I didn't run that code either - hopefully I saw the right thing - seeing you did it too suggested I did.
 
Thanks guys. Appreciate your response.

Defragster - your addition of reseting the timestamp back to millis worked, however as you stated if held down consistently, it toggles between mode 0 and mode 2 for the duration set.

Oddson - I'm trying to not use libraries to help teach myself how to write everything from scratch. I'm not using any internal pull-ups, using resistors pulled to ground.
 
mtiger - glad what I thought I saw was right. You should look at the nature of the Bounce library and keep that in mind if/when you see multiple fast mode cycles with 'one' button press. Perhaps a short delay each time mode swap between 0&1 or 2&3. Also when going from mode 2 it always goes to mode=0 - but the LED may be left high if mode==1 before going to mode==2.

Note: creative use of Teensy elapsedMillis might be helpful to avoid using delay() to debounce - also could be used for tracking the timestamp. elapsedMillis just incorporates the math around and direct use of millis().

Code:
elapsedMillis timeStamp;

setup(){
// ...
  timeStamp=0;
}

void buttonMode() {
// ...

  //TIMED PRESS
  if (buttonState == HIGH) {
    if (mode == 0 || mode == 1) {
      if (timestamp > 1000)  {
        mode = 2;
        Serial.printf("Button Mode: %d\n", mode);
        timeStamp = 0;
      }
    }
    if (mode == 2 || mode == 3) {
      if (timeStamp > 2000)  {
        mode = 0;
        Serial.printf("Button Mode: %d\n", mode);
        timeStamp = 0;
      }
    }

}

That should be the BASE usage update for timeStamp. The creative part would be ignoring a change in button state within a couple of millis after it is set to zero without causing other trouble.

Something like this ignoring State changes within '2' millis of the last change seen [using the default time of Bounce perhaps] - too high might miss a press and no rejection might see multiple state changes per press:
Code:
  //TOGGLE PRESS
  if (buttonState != lastButtonState && timestamp>2) {

That might have you chasing issues that are resolved by just using Bounce library code as suggested by oddson.
 
Bounce is very solid but...

To roll your own you can track several readings across time and recognise a state change after they settle on a new value for >=N readings.

These can be separate variables, an array, or even just a single varable where the bits stored are the sequence of HIGH/LOW readings.

In a pinch, waiting for two consequatve same readings 10 ms apart will work in most situations and still be responsive enough.

The most responsive method is to assume the change on the first changed reading but then ignore any others until some set time passes. But you need a signal with no errant readings, just contact bounce.
 
Bounce is very solid but...

The most responsive method is to assume the change on the first changed reading but then ignore any others until some set time passes. But you need a signal with no errant readings, just contact bounce.

Hopefully this is what I sketched out will do - and looking it seems Bounce defaults to 10 millis if I'm reading this right :: interval_millis(10) [link is to usage and the text is the initial default value?]

Easy test for bounce is start and print a count on transitions between modes 0 and 1, tap the switch 10 times and see how many show up, repeat.
 
Forgetting de-bouncing for a minute... I'm still not sure what the desired behaviour is.

As I understand it there are two signals... short hold and long hold (>1000ms)

If a short-hold event is sensed then the system will toggle the mode between one of two pairs (sorry about missing they were 0-1 & 2-3 and not 1-4).

If a long-hold event is sensed then the system will shift to the other pair of modes and thereafter short-holds will toggle pairs again.

What I don't understand how does the system determine which of the two modes to go into when it changes the target pair.

Should it return to the last active state of the other pair?
Should it assume the same relative position as the previous opposite number (i.e. if mode=3 and long sensed system will go to 1 as the upper of the new pair)
Should it toggle from the last active state of the other pair?
Should it toggle from the last state of the previous mode (if mode=3 and long sensed toggle to lower and value returned is 0)?

This is why I tried a code example that gave explicit output to make sure I was reading correctly in believing the first of these was most desirable in a mode selection scenario.

If so.. I can't see how you will get this behavior without a variable to hold the previous selection of the opposite pair.
 
As noted - leaving mode 2 it reverts to mode 0 when it may have entered mode 2 from mode 1 and the LED will be out of sync. Since there is no real action on mode 2 and 3 except toggle I assumed this was just a sample to see it work?

If that is real desired behavior the mode state values could be extended to cover that with added values beyond just 0,1,2,3 - perhaps a byte or digit mask to store the place to return to.

Depending on real use and # of end mode states perhaps: 0x00 would go to 0x02 or 0x03 and return to 0x00 and 0x01 would go to 0x12 or 0x13 and return to 0x01.

And that state list could be extended to prevent a hold over 2 seconds from going to mode=0 and then back to mode=2 in a single press by entering another set of state values until the next transition and in that intermediate state the long press would be ignored.
 
Thanks guys.

Yes it was just a sample to see how it works, so I can configure it further on. This was a test to see primarily how I would access multi layered toggle states, to then have a range of additional functions and states in different modes.
 
Seeing Multilayered in regards to what I noted above - might be better to have multiple state layer variables - or to compound them into a single variable with more states to keep them clear and well defined.

With mode = 0 or 1 a mode_layer could go 0 or 1 depending on if in long press. Then mode would still be 0 or 1 and long press would come and go without change and the mode_layer could decide which set of states are active. This might be easier or clearer to code than the alternative in post #16 with adding state values inside mode. Either can work - depending on your end use case and complexity.
 
Last edited:
Rather generic idea for options without knowing what the true use will be. You'd have to define a list of the states you expect to support and then find a way to support them.

A single state 'mode' variable could get rather complex with multiple substates tracked within it.
 
Status
Not open for further replies.
Back
Top