Ever play video games? As @PerryBebbington said in post #9, switch/case is a great way to implement state machines.
I used to use if/else if/else when starting out but now use switch/case wherever possible as it is far easier for me to maintain, add to and debug.
In the IDE examples, switch/case is covered as well as the other critical concept to understand (IMO), StateChangeDetection. Pressing one button is all well and good but what happens when that one button is intended to do more than light an LED while held and turn off the same LED when released?
Spend the time understanding these concepts because the work put in now really will unlock the potential of your projects when you have a single board you want to do a few different things, reliably.
I post this video often but I post it because it really just clicks with me, and I think it's very relatable to just about anyone who has ever played a Nintendo game, which is probably almost anyone who might be on this forum.
I see a difference between state and code that state runs.
I often have one state that is waiting for a new event but once that happens I also have a cut&dry un-delay technique that ends state cases just before a delay, right after changing the state, initializing the millis-timer at the start of the function the state machine is in and anything else that case needs initialized before break then return non-block the machine.
State machines let me write functions that run as non-blocking tasks. Each one runs according to conditions, ie process state.
But consider that state could be made part or all from pin states. Port reads can catch many in 1 cycle.
I have seen mechanical and electro-mechanical machines that do similar, sort of.
I think you are tying yourself up in knots here. At first you complained that it was not clear what caused a transition from a state. I added what caused the transition and you still complain!
As to the point about transitions from state 3, I added this for completeness to show all possible transitions. This shows that when the machine is in state 3 and the state is changed 0 then it immediately it goes back to state 3.
Guilty as charged, but then even software engineers have to know about hardware in order to fully understand WTF they are doing.
Yes it does.
I think that is your problem rather than mine.
Anyway here is a new diagram taking into account some comments from @J-M-L
➜ transitions are labelled with the conditions as described in the legend (basically your state value of CK DT)
➜ states have all different descriptive names
➜ Transition labelling is "deterministic" and "complete", meaning for example if I'm in CW1 state and I read again, then this is ignored as there is no transition with the label leaving that state ➜ I'm staying there.
➜ there is no final state that is transient, it's not a state. The increment or decrement action is taken upon the final transition.
Changing state besides to react to external change…
To set up a timer to run between executions of the state machine.
To cause a set of cases to “loop” with or without a timer.
To avoid blocking code in void loop() by cutting a long series of executions into 2 or more cases. I can think that much use of floating point could hog cycles badly.
this diagram where states and stimuli are represented by 2 binary values, A and B may be easier to understand
digraph G {
AB -> A [label = "_A/cw"]
A -> __ [label = "__"]
__ -> B [label = "B_/cw"]
B -> AB [label = "BA"]
AB -> B [label = "B_"]
B -> __ [label = "__/ccw"]
__ -> A [label = "_A"]
A -> AB [label = "BA/ccw"]
}
You continue to improve your graph over and over again. Has it already reached a state from which one can create code? If so then please show that code, or else continue improving your approach until it becomes usable.
When will it have reached a state from which you can show the code created from it?
Turning the thing upside down does not in my bring anything but confusion to the table.
No it is not confusing
No way on God's Earth could anyone describe that total mess as not confusing.
I have only got examples using Python. But my next step is to show you all how it could be implemented on an Arduino. There are many, many ways the actual implementation could be done.
What you don't realise is that the state diagram is the sequence that should be followed. It is up to the skill of the user, which seems in very short supply in this discussion, to implement it. In a way they see fit.
First I must make a small mount for the rotary encoder, and at the same time attend to my domestic obligations, so give me a little time to get this done.
I’ve not turned things upwind on, at least this is not how I see it.
The main changes are removing the final state you had since it’s not a state the system is in, it’s just an action to takes whilst transitioning in between states and being more explicit for the transitions (your 0 bar notation would risk to convey that it’s any state that is not 0 and does not capture the idea that there needs to be a change in the input for the transition to trigger hence the notion of completeness in terms of event driving the transitions).
a state machine needs to handle/ignore every possibly stimuli in each state.
while an encoder typical sequences thru a number of states resulting in CW or CCW changes, it needs to handle reversals in all states.
this is a conventional and relatively simple state diagram using an online tool, graphviz, that people understanding state machine notation will not find confusing
it's helpful to describe the state machine without using logic. The programmer may be implenting a design by someone else who is not familiar with code.
the data driven approach described above doesn't require knowing how to code
here's a perhaps more compact approach, simpler state machine logic, more complicated laptop command line processing, but this is how you would test the logic in simjlation before testing on hardware
#include <stdio.h>
#include <stdlib.h>
// -----------------------------------------------------------------------------
int cnt;
void ccw () {
printf (" ccw: %3d\n", --cnt);
}
void cw () {
printf (" cw: %3d\n", ++cnt);
}
void ___ () {
}
// -----------------------------------------------------------------------------
typedef void (*Func) (void);
const char *StaStr [] = { "St0", "StA", "StBA", "StB" };
enum { __ = 0, _A = 1, BA = 2, B_ = 3 };
const int Nstate = 4;
const int Nstim = 4;
Func smFuncTbl [Nstate][Nstim] = {
// __ _A BA B_ stinuli
{ ___, ccw, ___, cw }, // ___
{ cw , ___, ccw, ___ }, // A
{ ___, cw , ___, ccw }, // BA
{ ccw, ___, cw , ___ }, // B
};
int state; // St0
void stateMach (
int stim )
{
smFuncTbl [state][stim] ();
state = stim;
printf ("stateMach: %d %s\n", state, StaStr [state]);
}
// -----------------------------------------------------------------------------
int valLst;
int main ()
{
printf ("stateMach: %d %s\n", state, StaStr [state]);
int inp = __;
printf ("> ");
while (1) {
char c = getchar ();
if (EOF == c)
break;
if ('\n' == c)
continue;
switch (c) {
case 'a':
if (_A == inp)
inp = __;
else if (BA == inp)
inp = B_;
break;
case 'b':
if (B_ == inp)
inp = __;
else if (BA == inp)
inp = _A;
break;
case 'A':
if (__ == inp)
inp = _A;
else if (B_ == inp)
inp = BA;
break;
case 'B':
if (__ == inp)
inp = B_;
else if (_A == inp)
inp = BA;
break;
case 'q':
exit (0);
break;
}
stateMach (inp);
printf ("> ");
}
return 0;
}
Reading the 2-bit Gray code from the encoder requires handling four full transitions in either direction. I don’t see how it can be done with fewer than seven states.
Don't confuse machine states and input channel "states". The encoder hardware is fixed to exactly 4 states, but the machine can have more or less states.
In the simplest case 2 machine states follow one (clock) channel and on a transition increment or decrement the position depending on the state of the other (direction) channel.