state machine in spin?
sssidney
Posts: 64
I’ve been trying to implement a table driven state machine in spin. When I do this in C I use function pointers for the transition routines.
Here’s the C struct I typically use to define the state table
So my state table is just an array of these structures.
I can get by the lack of structs in spin by using an array and offsets to make the state table. My problem is the lack of a function pointer. I could substitute another constant for the function pointer and then have a huge case statement to execute the proper transition routine but that’s pretty ugly. Anyone have a better idea? Maybe I need to trash the C way of thinking about this and start fresh with a spin POV?
Post Edited (sssidney) : 6/8/2010 1:29:54 PM GMT
Here’s the C struct I typically use to define the state table
typedef struct { StateType curState; EventType event; StateType nextState; TransFnctPtr fnctPtr; /* the function pointer */ }StateMachine_t;
So my state table is just an array of these structures.
StateMachine_t exStateMachine[noparse][[/noparse]] = { { INITIAL_STATE, HELLO_EVENT, STARTUP_STATE, startFnct }, { STARTUP_STATE, DEV_OK_EVENT, IN_USE_STATE, waitForDataFnct }, /* etc... */ };
I can get by the lack of structs in spin by using an array and offsets to make the state table. My problem is the lack of a function pointer. I could substitute another constant for the function pointer and then have a huge case statement to execute the proper transition routine but that’s pretty ugly. Anyone have a better idea? Maybe I need to trash the C way of thinking about this and start fresh with a spin POV?
Post Edited (sssidney) : 6/8/2010 1:29:54 PM GMT
Comments
I'm only familiar with delphi as a PC programming language. Your C-construct seems to be pretty complicated for me
A microcontroller is much smaller than a PC. You don't have a n-layer model and you don't use eventdriven programming like in windows where each application has dozens of threads.
What's so "huge" about a case-statement like this?
best regards
Stefan
Do what? This is the Prop we are talking about. We have at least a max of 8 event driven threads to play with. There are already micro-controllers around with far more.
However I agree with you, for the scale of program you are likely to be creating on the Prop that case based state machine is just fine. Not ugly at all.
On might argue the performance is not so hot unless the Spin compiler is smart enough to build a jump table for the cases rather than a string of "if casex.. if casey.. ifcasez...".
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
For me, the past is not over yet.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
Timothy D. Swieter, P.E.
www.brilldea.com - Prop Blade, LED Painter, RGB LEDs, 3.0" 16:9 LCD Composite video display, eProto for SunSPOT, PropNET, PolkaDOT-51
www.tdswieter.com
granted it is not a true state machine but the similarities are unavoidable.
heres a link
http://obex.parallax.com/objects/240/
Attached below is a possible stripped state demo in spin
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
Never force anything - Always get a bigger hammer.
Post Edited (Ray0665) : 6/9/2010 12:17:33 AM GMT
I was just wondering if someone had discovered a cool method of doing a state machine other than using a case statement.
Any transition can be decided within the method that handles the current state. Just like one might do it in C.
So for only 20 states or so Stefan's case statement method is quite neat no matter how many ways there are to get from state to state.
There is a limit to the number of "arms" a Spin case statement can handle though. I don't remember what it is off hand.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
For me, the past is not over yet.
If you have a ridiculously large number of cases you can use a mothership CASE statement to break the condition down into ranges, and range CASE statements to refine the search.
There was a top level manager/variable that kept track of the current state. This top level manager would select a object/method/code-chunk and capture the 'exit signal' from that execution.
Each state node would eject an "exit signal" when it finished running.
There was a collection of transition definitions that mapped the tuple formed by the current state + exit signal to a new state. This new state would be assigned to the top level manager/variable.
With this approach, each state handler knew nothing about the machine it was node of. The real mainteance point in the implementation was the implementation of the "edge mapping table" that would convert the input-state + exit code to a new state value.
I hope this explanation is not too obtuse and/or is helpful to somebody.
For overall discussion I will paste the code formats that I have been using. I'm interested to see how it could be improved. In general the state machines that I have coded have been synchronous loops. That is a loop that goes through the state machine, one pass at a state, every x milliseconds - usually 20 or 30ms.
First - a couple definitions in the CON section:
Next, a few variables:
Next is the main routine:
Then after this are the various PUB and PRI subroutines such as:
Now - you can advance from one state by an event that happens in each state. You have to write IF structures. I have a somewhat fancy button/UI code which is inaddition to the above items. There is a comment where I normally place the calls to button processing code. I'll save that bit of code for later.
Tell me what you think of the above.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
Timothy D. Swieter, P.E.
www.brilldea.com - Prop Blade, LED Painter, RGB LEDs, 3.0" 16:9 LCD Composite video display, eProto for SunSPOT, PropNET, PolkaDOT-51
www.tdswieter.com
Why should be "each individual transition routine manage the state is IMO a potential nightmare" ????
To me this fact that each transistion sets the next state is one of the substancial things about stae-machines.
How does a statemachine work where NOT EVERY transistion-routine changes the state?
To be provocant: To me this sounds like spaghetti-code by avoiding gotos ! ; ; - )
How do you want to keep oversight of who is doing what in your code when at the end of each state the new state is NOT set????
maybe you have found a brilliant way how to code huge statemachines but I can't imagine a tiny bit of how this should work
so please give an example
best regards
Stefan
So in the same vein as what TinkersALot said in their post in the state machines I write the currentState is only changed in one place in the main event loop and is selected using the combination of currentState value and the return value of the transition routine based on the state table data. currentState becomes the value of nextState from the selected transition struct.
Some other design rules for state machines I try to live by:
I like to implement my state table as data and keep the state machine infrastructure/code static i.e. when I need to add new states I just add the needed data to define the transitions and then write the transition routines. I can’t figure out how to do this in spin without function pointer capability so the case statement is necessary.
Don’t allow your transition routines to modify the next state variable. This becomes a nightmare looking for how you transitioned from state to state. Build it into the state transition data structure (or arrays in spin).
Unless the state is a “steady state” always have a timeout associated with it. You never want to get stuck in a transitory state. Have a timeout on each of these transitions and have an error handler routine to call if the timeout occurs.
Keep debug counters for state transition success and failures. This is good postmortem data.
While in debug mode have the routine that finds the appropriate next transition validate your state table.
So adding more detail to my state transition data structure:
Post Edited (sssidney) : 6/10/2010 2:29:33 PM GMT
That is to say it is not necessary for each state handler to know how it is connected in the state graph and it is not necessary to have a tangled mess of "if bla bla" in each state handler to determine the next state
This can be done by placing the state graph into some data structure, probably parallel arrays in Spin as it has no structures as such.
Each state handler function returns some indication of what has happened within that state. The state machine loop then uses that indication to determine the next state based on the current state, the indication returned from the handler and data in the state graph structure.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
For me, the past is not over yet.
or
wouldn't be in the state code area where it is? In its place would be some sort of data manipulation into an array?
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
Timothy D. Swieter, P.E.
www.brilldea.com - Prop Blade, LED Painter, RGB LEDs, 3.0" 16:9 LCD Composite video display, eProto for SunSPOT, PropNET, PolkaDOT-51
www.tdswieter.com
As a simple example. Let's say you have a state that checks for a charcter, or not, from a serial line. Using FullDuplexSerial One might write:
Now this can be a mess as this state handler has to know where it is in the state transition diagram. That is it has to know about "NoCharState" and "GotCharState" and it has to know and decide which one of those states to go to next. The state "graph" is hard coded into the handlers.
How can we make it a bit more flexible? What about removing the info about next state from it and having it just return some indication of the event it has detected.
Now we can put that state handler in any position in the state machine. We can fix the logic of the transitions without touching the method code. We can possibly use it in more than one state.
BUT where is the state transition map now?
Well lets invent a method "selctNextState", say. It will use the current state, the event and some data tables to determine the next state.
nextState := selectNextState(currentState, event)
We call this every time around the event loop somewhere.
So now the connectivity of our state machine is in some data structure used by selectNextState rather than hardwired into our code.
The nature of that data structure is an exercise for the reader[noparse]:)[/noparse]
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
For me, the past is not over yet.
This code modifies the object's method table so that a dummy method points at the desired function based on an index.· The method table consist of a long for each public method followed by a long for each private method in an object.· The long contains the method's address offset and the space used for the local stack variables.
This code is quite straight-forward as long as all the methods are in the same object.· It becomes a bit more complex if they are in different objects.· You would have to worry about the VAR offset, and you would have to modify Spin byte codes instead of just changing the method table.
Dave
·
I've look at a few ways of doing this, but always bump up against the same issue. None of this stuff is thread safe. As long as your code will never execute in more than one cog it's ok though.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
"I mean, if I went around sayin' I was an emperor just because some moistened bint had lobbed a scimitar at me they'd put me away!"
The two techniques could be combined having 8 extra CallMethod's as well.· CallMethod would set a lock, and then configure the table entry for DummyMethod for CallMethod0, CallMethod1, etc.· It would then call CallMethod, whcih would indirectly call the correct version of CallMathodX.· This would clear the lock and then call the target method.· It's messy, but it would work.
I have an idea for a jump table in SPIN, but I need some more time to get into the internals of the interpreter to see if its feasible first.
There's no way to do a jmp in spin that's not hard coded, but you can push an address on the stack (along with some other cruft) and use one of the portions of case or lookup/dn to jump to it (a bit like a buffer overflow exploit). If I can make the compiler generate constant 2 byte addresses (it uses 1 or 2 depending on the offset size), I *think* I could make an instruction along the lines of..
gosubtbl(index,method1,method2,method3,method4...)
If you overflowed the table, then you're in the hands of the big sky bully though.
I've also not really figured the parameter passing yet, but all the referenced methods would require the same numbers of parameters.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
"I mean, if I went around sayin' I was an emperor just because some moistened bint had lobbed a scimitar at me they'd put me away!"