Maker Pro
Maker Pro

Arduino control of model train signalling (was: LED Power Supply for Control Panel)

Actually,

Now i have posted the above i'm not sure that it is correct :mad:

Is it a case that

Code:
void function setStateB(bool &bState, long &lTime, bool bValue) {
    if (bState != bValue) {
      bState = bValue;
      lTime = millis();
      bChanged = true;
    }
  }

bChanged registers a button change (irrespective of what button is pressed)

and that

Code:
void function stateChange() {
  switch( iState )  {     
    case 0:         
      if (some button pressed) {[COLOR="Red"]  changes to btnA1Edge[/COLOR]
      [COLOR="Black"][/COLOR]  machineState(1);
      } else if (some time expired) {
        machineState(2);
      } 
      break;     
  
    case 1:         
      if (a sensor triggered) { [COLOR="Red"]changes to btnB1Edge[/COLOR]
     [COLOR="Black"]   [/COLOR]machineState(0);
      } 
      break;     
  
    case 2:         
      if (some time expired) {
        machineState(3);
      } 
      break;     

    case 3:
      if (a button pressed) {[COLOR="Red"] changes to btnB2Edge and So forth.[/COLOR]
        [COLOR="Black"][/COLOR]machineState(0);
      } else if (some time expired) {
        machineState(2);
      } 
      break; 

    default:
      machineState(0);
      break;
  }
}

Does This Work

Code:
if (Mark) understands (all of the above)
return = its a miracle !!!!!
} else{
Mark is confused again
LMAO
 
Last edited:

(*steve*)

¡sǝpodᴉʇuɐ ǝɥʇ ɹɐǝɥd
Moderator
These are the states.... SO i would need (setStateA), (setStateB), (setStateC), (setStateD), (setStateE) and (setStateF) in the void function setState

No, you have a single machine state, and that has 6 (or more) values.

The six values would correspond to states A through F.

Also that the void function stateChange would also have:

switch(AState ), switch( BState ), switch( CState ), switch( DState ), switch( EState ) and switch( FState ) with an number of "Case" statements within each switch statement?

Not quite.

There would be a single switch statement with a case for each state (corresponding to states A through F), each one setting output variables as they need to be for that state.

The big difference with this approach is that rather than saying "if this light is on, and this sensor is set, and it's been at least 30 seconds since something, THEN if button A is pressed -- change something"

We define what is actually happening. Say that "waiting for a train" is signalled by "this light is on, and this sensor is set, and it's been at least 30 seconds since something", then rather than using the conditions to define the state, we use the state to define the conditions.

So the condition becomes "if waiting for a train THEN if button A is pressed -- change state.
 
Thanks Steve for your patience

Code:
void setup() { // code that runs once
    machineState(-1); // set an invalid state to force change
}

  void function setStateB(bool &bState, long &lTime, bool bValue) { // gives setStateB a value 0 or 1
    if (bState != bValue) {  // if bState is not equal to bValue something has changed
      bState = bValue; // set bState to equal bValue
      lTime = millis(); // set lTime to now
      bChanged = true; // bChange is true register a State Change
    }
  }

  void function setStateI(int &iState, long &lTime, int iValue) { // gives setState  a value
    if (iState != iValue) { // if iState does not equal iValue (something has changes)
      iState = iValue; // set iState to equal i value ...update iState setting
      lTime = millis(); set time to now
      bChanged = true; // register a state change
    }
  }
  
  void function stateChange() {
  switch( iState )  {     
    case 0: // State A "Train Ready Dispatch"       
      if (BtnA1Edge) {
        machineState(0);
      } else if (some time expired) {
        machineState(6);
      } 
      break;     
  
    case 1:         
      if (BtnB1Edge) { // State B "Down Train Accepted"
        machineState(1);
      } 
      break;     
  
    case 2:         
      if (BtnB2Edge) { // State C "Ready Next Service"
      machineState(2);
      } 
      break;     

    case 3:
      if (BtnA2Edge) { // State D "Fast Down Ready Next Service"
        machineState(3);
      } 
      break; 
      
    case 4:
      if (BtnB3Edge) { // State E  "Fast Down Route Set"
        machineState(4);
       } 
      break; 
      
    case 5:
      if (BtnB3Edge) { // State F   "Fast Down Accepted"
        machineState(5);
       } 
      break; 

    default:
      machineState(6);
      break;
  }
}

    
    void function machineState(int newState) { // machineState controls outputs 
  setStateI(iState, lStateTime, true);
   
  switch ( iState ) {

    case 0 : // "Train Ready Dispatch" 
      setStateB(bLedA1, lLedA1Time, true); //London End Fiddle Yard Down "Train Ready Dispatch" Solid Amber Led
      setStateB(bLedB1, lLedB1Time, true); // London End Main "Train Awaiting Acceptance" Flashing Amber led
      break;

    case 1 :// "Down Train Accepted"
      setStateB(bLedA1, lLedA1Time, false); // Case 0, false:  London End Main "Accept Down Train" Solid Green Led
      setStateB(bLedB1, lLedB1Time, false); // Case 0, false: London End Fiddle Yard " Down Train Accepted" Solid  Green led
      setStateB(bLedB2, lLedB2Time, true); // London End Main "Accept Down Train" Solid Green Led
      setStateB(bLedA2, lLedA2Time, true); // London End Fiddle Yard " Down Train Accepted" Solid  Green led
      break;

    case 2 : // "Ready Next Service"
      setStateB(bLedB2, lLedB2Time, false); // Case 1, false: London End Main "Accept Down Train" Solid Green Led
      setStateB(bLedA2, lLedA2Time, false); // Case 1, false: London End Fiddle Yard " Down Train Accepted" Solid  Green led
      setStateB(bLedB3, lLedB3Time, true); // London End Main "Ready Next Service" Solid Red Led
      setStateB(bLedA3, lLedA3Time, true); // London End Fiddle Yard "Awaiting Next Service" Flashing Red Led
      break;

    case 3 : // "Fast Down Ready Next Service"
      setStateB(bLedB3, lLed1Time, false); // Case 2, false: London End Main "Ready Next Service" Solid Red Led
      setStateB(bLedA3, lLed2Time, false); // Case 2, false: London End Fiddle Yard "Ready Next Service" Flashing Red Led
      setStateB(bLedA4, lLedA4Time, true); // London End Fiddle Yard "Fast Down Ready Dispatch" Solid Amber Led
      setStateB(bLedB4, lLedB4Time, true); // London End Main "Fast Down Ready Dispatch" Flashing Amber Led
      setStateB(bLedD4, lLedD4Time, true); // Country End Fiddle Yard "Fast Down Ready Dispatch" Fading Amber Led
      
      break;
      
     case 4 : // "Fast Down Route Set"
      setStateB(bLedB4, lLedB4Time, false); // Case 3, false: London End Main "Fast Down Ready Dispatch" Solid Amber Led **** Change of State ****
      setStateB(bLedD4, lLedD4Time, false); // Case 3, false: Country End Fiddle Yard "Fast Down Ready Dispatch" Flashing Amber Led **** Change of State ****
      setStateB(bLedA4, lLedA4Time, true); // London End Fiddle Yard "Fast Down Ready Dispatch" Solid Amber Led
      setStateB(bLedB4, lLedB4Time, true); // London End Main "Fast Down Ready Dispatch" Solid Amber Led **** Change of State ****
      setStateB(bLedD4, lLedD4Time, true); // Country End Fiddle Yard "Fast Down Ready Dispatch" Flashing Amber Led **** Change of State ****
      break;
      
     case 5 : // "Fast Down Accepted"
      setStateB(bLedA4, lLedA4Time, false); // Case 4, false: London End Fiddle Yard "Fast Down Ready Dispatch" Solid Amber Led
      setStateB(bLedB4, lLedB4Time, false); // Case 4, false: London End Main "Fast Down Ready Dispatch" Solid Amber Led **** Change of State ****
      setStateB(bLedD4, lLedD4Time, false); // Case 4, false: Country End Fiddle Yard "Fast Down Ready Dispatch" Flashing Amber Led **** Change of State ****
      setStateB(bLedA5, lLedA5Time, true); // London End Fiddle Yard "Fast Down Accepted" Chase Green Array Leds
      setStateB(bLedB5, lLedB5Time, true); // London End Main "Fast Down Accepted" Chase Green Array Leds
      setStateB(bLedD5, lLedD5Time, true); // Country End Fiddle Yard "Fast Down Accepted" Chase Green Array Leds
       break;
       
       case 6 : // Default
       //no leds
    
       break;
  }
    }
   
    
  

 
 void loop() {
  // put your main code here, to run repeatedly:

  getInput(); // what are the inputs
  stateChange(); // manage the state machine
  setOutput(); // write outputs
}

Something Like This :eek:
 
Has the penny just dropped ????

So just had a bit of a light bulb moment

Am i on the right lines that if i assign each of the buttons an independent int value

and when that button is pressed it registers the change as iValue, that will in turn initiate a state change.....

....and that using a if / else statement i can control the sequence, by using the preset button values, to ensure only the correct sequence of button presses are allowed to update iValue .

On the right lines ???
Or of the rails lmao

Mark
 
Last edited:

(*steve*)

¡sǝpodᴉʇuɐ ǝɥʇ ɹɐǝɥd
Moderator
You're getting there :)

In your first example you have this:

Code:
void function stateChange() { 
  switch( iState )  {          
    case 0: // State A "Train Ready Dispatch"              
      if (BtnA1Edge) {         
        machineState(0);       
      } else if (some time expired) {         
        machineState(6);       
      }        
      break;             

    case 1:                // State B "Down Train Accepted"         
      if (BtnB1Edge) { 
        machineState(1);       
      }        
      break;

and so on...

iState *IS* the machine state. That's what you are setting (as well as other things) in the machineState() function.

What you are doing here is NEVER changing the state.

In these case statements you should be identifying what causes, or indicates, or should produce a change in state from (say) "Train ready for dispatch" to "Down train accepted". That would be a test in the case 0: (We know that case zero is "train ready for dispatch") that if true sets the machineState to 1 (which we know is "Down train accepted"). If this think is the pressing of button A1, then the code should look like this:

Code:
void function stateChange() { 
  switch( iState )  {          // what is the current state?
    case 0: // State A "Train Ready Dispatch"              
      if (BtnA1Edge) {         // indicates down train accepted
        machineState(1);       // set the new state to be "Down Train Accepted"
      } else if (some time expired) {         
        machineState(6);       
      }        
      break;             

    case 1:                // State B "Down Train Accepted"         
      if (BtnB1Edge) { 
        machineState(1);       // should set anything BUT 1
      }        
      break;

As I have noted earlier, this code is all about how you get from one state to another. So within each case you need to define exactly how you get from that state to every other state that you can validly move directly to.
 

(*steve*)

¡sǝpodᴉʇuɐ ǝɥʇ ɹɐǝɥd
Moderator
Ignore ALL of this, I thought you meant iState. I'll rewrite it for that, but you probably don't need to read it. (And then I'll answer your real question)

How do i declare iState ?

I would use an integer for it.

The actual number doesn't mean anything. You simply designate whatever meaning you wish to each value.

You could pick -736, -500, -2, 57, 180, and 1846, but it is more typical to choose numbers like 1, 2, 3, 4, 5, 6. Machine states are represented by numbers, but they're not numbers. You will typically test for equality with another value, or assign a value but you will never test to see if one is larger than another or add two of them together. We typically use an integer because there is no datatype than has just these features and integers can do more than we need to do.

You can define constants that will help you write readable code.

Code:
int iState; // the state we are in

#define msInitial 0
#define msTrainWaiting 1
#define msTrainDeparted 2
...
Then we could write:

Code:
switch (iState) {
  case msInitial:
    ...
    break;
  case msTrainWaiting:
    if (some state) {
      machineState(msTrainDeparted); // Train has left the station
    }
    break;
  case msTrainDeparted:
    ...
    break;
}
 

(*steve*)

¡sǝpodᴉʇuɐ ǝɥʇ ɹɐǝɥd
Moderator
How do i declare machineState ?

See this post.

Code:
void function machineState(int newState) {
  setStateI(iState, lStateTime, true);
  
  switch ( iState ) {

    case 0 :
      setStateB(bLed1, lLed1Time, true);
      setStateB(bLed2, lLed2Time, true);
      break;

    case 1 :
      setStateB(bLed1, lLed1Time, true);
      setStateB(bLed2, lLed2Time, false);
      break;

    case 2 :
      setStateB(bLed1, lLed1Time, false);
      setStateB(bLed2, lLed2Time, false);
      break;

    case 3 :
      setStateB(bLed1, lLed1Time, false);
      setStateB(bLed2, lLed2Time, true);
      break;
  }
}

I set all of the variables to the correct state when the state is entered. All this code considers is what the output states will be in a given machine state.
 
Darn... Sorry Steve was gonna post

Please ignore my last post ...I was tired and frustrated that i'd spent a week trying to figure this out and my progress was slower than British Rail..

The post was totally unfair of me
 

(*steve*)

¡sǝpodᴉʇuɐ ǝɥʇ ɹɐǝɥd
Moderator
my progress was slower than British Rail.

If you're making a model railway system that's a good thing, right? :D

Once you figure this out (and you will) you'll look back at your first efforts and realize how far you've come in a very short period.

You will be armed with the skills to write very complex control logic in a way that is easy to understand and maintain.
 
A New Day a Fresh Start

Ok here Goes

In an effort to make some headway into state machines I have tried to simplify the program.. and creep up on the solution from a different angle.

I already know that the previous attempts prior to adopting the state machine concept work so that is a positive i have to draw on :)

Code:
#include <Shifter.h> //shift register library

#define SER_Pin 11 //SER_IN (Data)
#define RCLK_Pin 9 //L_CLOCK (Latch Pin)
#define SRCLK_Pin 12 //CLOCK (Clock)

#define NUM_REGISTERS 1 //how many shift registers are in the chain
#define machineState
#define setStateB
#define setStateI
//#define bChanged
#define stateChange
int iState = 2;

const long DBTIME = 25; // number of ms allowed to eliminate key bounce
const long TimeEx = 500; // time out for iState

  bool debounce(bool state, bool val, long time, long dbTime, bool& lastVal, long& lastTime) {
  // We will only register a change of state if the input value has not
  // changed for a certain time
  bool newState;

    newState = state;         // by default the new state is the old state

    if (lastVal != val) {     // if the input value has changed
      lastTime = time;        // set the last time it changed to "now"
      lastVal = val;          // and retain the current input value
    }else{                  // otherwise (input value has NOT changed) 
      if ((time - lastTime) >= dbTime) { // has it remained stable long enough
        newState = val;       // change the state
      }
    }
    return newState;          // This is the new state
}

  bool simpleDebounce(int inPin, bool& state, bool& lastVal, long& lastTime) {
  // A debounce routine with a simpler interface.
    long time = millis();             // time in milliseconds
    bool val = digitalRead(inPin);    // current pin value
    long dbTime = DBTIME;               // the debounce interval
  
    state = debounce(state, val, time, dbTime, lastVal, lastTime); // debounce the pin
  
    return state;                     // return state
}
 
  // Declare Pins
  
  // Shift Register
  //Pin connected to ST_CP of 74HC595
int latchPin = 9;
//Pin connected to SH_CP of 74HC595
int clockPin = 12;
////Pin connected to DS of 74HC595
int dataPin = 11;

  
  // Inputs

  const int buttonA1 = 8; //London End Fiddle Yard Down "Train Ready Dispatch"  
  bool currentButtonA1;
  bool lastButtonA1;
  long lastButtonA1Time;
  bool btnA1Edge;
  
  const int buttonB1 = 7; //London End Main Control Down "Train Accepted"
  bool currentButtonB1;
  bool lastButtonB1;
  long lastButtonB1Time;
  bool btnB1Edge;
  
  const int buttonB2 = 6;// London End Main control Down "Ready Next Service"
  bool currentButtonB2;
  bool lastButtonB2;
  long lastButtonB2Time;
  bool btnB2Edge;
  
  // Outputs
  const int ledA1 = 5; // Indicator "Train Ready Dispatch"
  const int ledB1 = 4; // Indicator "Train Accepted"
  const int ledB2 = 3; // Indicator "Ready Next Service"



  void setup() {
  // put your setup code here, to run once:
   machineState(-1); // set an invalid state on start-up to force change
  }

     
  void readSwitchStates() { // read the current button states
    // get the initial state of buttons
  
    currentButtonA1 = lastButtonA1 = digitalRead(buttonA1); 
    lastButtonA1Time = millis();
    
    currentButtonB1 = lastButtonB1 = digitalRead(buttonB1); 
    lastButtonB1Time = millis();
    
    currentButtonB2 = lastButtonB2 = digitalRead(buttonB2); 
    lastButtonB2Time = millis();
  
  
    simpleDebounce(buttonA1, currentButtonA1, lastButtonA1, lastButtonA1Time);
    
    simpleDebounce(buttonB1, currentButtonB1, lastButtonB1, lastButtonB1Time);
    simpleDebounce(buttonB2, currentButtonB2, lastButtonB2, lastButtonB2Time);
  }
  
    bool pressEdge(bool& state, bool lastVal) { // detects the leading edge of a button press.
  
      if ((!state) && lastVal) { // button pressed, but state not yet set
      state = true;            // set the state without waiting for debouncing
      return true;             // return a valid edge
      }else{
      return false;            // it's not a press edge.
      }  
    
    bool btnA1Edge = pressEdge(currentButtonA1, lastButtonA1);
    
    bool btnB1Edge = pressEdge(currentButtonB1, lastButtonB1);
    bool btnB2Edge = pressEdge(currentButtonB2, lastButtonB2);
    }
  
  //   void function setState - This code will allow you to set a variable to a particular value and record when the variable's value changed

  void function setStateB(bool &bState, long &lTime, bool bValue) { // setStateB -
    if (bState != bValue) { // if bState has changed
      bState = bValue; // Update bState to bValue
      lTime = millis(); // Set time to now
 //   bChanged = true; // bChange is true register a State Change
    }
  }
  
  void function setStateI(int &iState, long &lTime, int iValue) {// setStateI - 
    if (iState != iValue) { // if iState has changed
      iState = iValue; // Update iState to iValue
      lTime = millis(); // Set time to now
//      bChanged = true; // bChange is true register a State Change
    }
  }
  
 
//   void function stateChange - What causes the state to change ?
//                             - The code that is concerned with the methods of moving from one machine state to another. 
//                             - If a button is pressed this will cause a state change
//                             - This may require a certain led to be lit before the button works.
//                             - Maybe you require a certain time to have elapsed 
  void function stateChange() {
//  bChanged = false;
 
   switch( iState )  {     
      case 0:    //  Train Ready Dispatch
        if (btnB1Edge) { // if "Train Accept Button Pressed"
          machineState(0);
       } else if (TimeEx) { //** Undecided at present **
          machineState(3); //** Undecided at present **
      } 
      break;  
      
    case 1:    //  Train Accepted   
      if (btnB1Edge) { //  "Train Accept Button Pressed"
        machineState(1);
      } 
      break;     
      
    case 2:  // Ready Next Service      
      if (btnB2Edge) { // "Awaiting Next Service Button Pressed"
        machineState(2);
      } 
      break;     
  
    default:
      machineState(4);
      break;
   }
  }
   
     // void function machineState - What happens when the state is changed?
//                              The code that sets things when the state changes
//                              This particular implementation requires that all the state variables are global.

//                              All this code considers is what the output states will be in a given machine state.




  void function machineState(int newState) {
  setStateI(iState, lStateTime, true);
  
    switch ( iState ) {

      case 0 : // set "Train Ready Dispatch" Indicators
             // cancel "Ready Next Service" Indicators
        setStateB(bLedA1, lLedA1Time, true);
      
        break;

      case 1 : // set "Train Accepted" Indicators
             // cancel "Train Ready Dispatch" indicators
       setStateB(bLedB1, lLedB1Time, true);
       setStateB(bLedA1, lLedA1Time, false);
       
        break;

      case 2 : // set "Ready Next Service" Indicators
             // cancel "Train Accepted" indicators
        setStateB(bLedB2, lLedB2Time, true);
        setStateB(bLedB1, lLedB1Time, false);
        
        break;

      case 3 : // clear all indicators
        setStateB(bLedA1, lLedA1Time, false);
        setStateB(bLedB1, lLedB1Time, false);
        setStateB(bLedB2, lLedB2Time, false);
      
        break;
        
        //   if (bChanged) {
//    shifter.clear();

//    shifter.setPin(1, bLed1);
//    shifter.setPin(2, bLed2);

//    shifter.write();
    }
  
  

    

void loop() {
  // put your main code here, to run repeatedly: 
  
  getInput(); // what are the inputs
  stateChange(); // manage the state machine
  setOutput(); // write outputs
}

My intention is to get the state machine version running... nothing fancy to begin with.... just 3 buttons controlling 3 leds .

To start with i'm aiming for buttonA1 to control ledA1, buttonB1 to control ledB1 and buttonB2 to control led B2... without the shift registers hence assigning pins to the leds..... then moving them to the shift register.

Then i will move onto getting the buttons to sequence correctly and finally to adding some flashy repeater leds..

Hopefully that will teach me more but in bite size pieces and the frustrations of not getting anywhere fast will evaporate as i make some headway :)

The code above is now my starting point for this sub-project

Mark
 
Last edited:
How do i declare machineState ?

So in my frustrations i posted the above in response to the following error message

attachment.php


Whether its correct or not i managed to declare them by the following code

Code:
#define machineState
#define setStateB
#define setStateI
//#define bChanged
#define stateChange
int iState = 2;

The same applies to all shown above.
// bChanged is writtten to remind me
With reference to int iState i assigned a random number as need to get head round this process.
 

Attachments

  • State Machin Error2.jpg
    State Machin Error2.jpg
    21.6 KB · Views: 396
Code:
const long TimeEx = 500; // time out for iState

Once again this is a random time used for a timeout on the void function stateChange module.

switch( iState ) {
case 0: // Train Ready Dispatch
if (btnB1Edge) { // if "Train Accept Button Pressed"
machineState(0);
} else if (TimeEx) { //** Undecided at present **
machineState(4); //** Undecided at present **
}
break;

case 1: // Train Accepted
if (btnB1Edge) { // "Train Accept Button Pressed"
machineState(1);
}
break;

I can see the necessity to have a time expired (time-out) function. There will be some periods where the flow of trains along the model layout are very infrequent and so the buttons used to sequence this flow are not going to be pressed....

Thinking about this function i assume that the "output" of this function could just be to check that the previous state (bstate or istate) remains true?
 
Last edited:

(*steve*)

¡sǝpodᴉʇuɐ ǝɥʇ ɹɐǝɥd
Moderator
OK, first think about all the states. What are they? Is only a max of 1 LED ever lit?

If so, there will be 3 (or maybe 4) states. If the buttons toggle the LEDs then you have 8 states.

The first thing you need to do is define the states. Create a table showing all valid combinations of the LEDs, one per row and number them. It could be like this:

Code:
State | LED1 | LED2 | LED3 
------+------+------+------
  1   |  off |  off |  off
  2   |  on  |  off |  on
  3   |  on  |  on  |  off
  4   |  off |  on  |  on
  5   |  on  |  on  |  on
------+------+------+------

It is often useful to include a name for each state in the table. Because often the states will be something meaningful, line "Train arriving at the station", "Train waiting for clearance to depart station", etc.

So those are your states. And that allows you to write the machineState function.

And as a reminder, machineState is a function like this:

Code:
void machineState(int iNewState) {
...
}

Don't #define it, that is something totally different.

Within it, you set iState, and depending on what iState is, you set the outputs appropriately. In my example, if the state is 1, turn all the LEDs off. If it is 2, turn LED2 off, and LED1 & 3 on. And so on. Note that you set the state of ALL of the outputs, not just the ones you think are changing (madness lies in that direction! -- or at least many hard to find bugs).

Now you have to consider what are the valid transitions between states, and what triggers them. This will be the essence of the stateChange routine.

The "machinestate not declared in this scope" error means that you've either called it something else, or you have placed it somewhere that can't be seen. (It's hard to explain)

I tend to place all my definitions of functions before the places where I call them. Often setup() and loop() are the last two functions. This makes getting these scope issues sorted somewhat easier in my experience.

The problem will often be one too many, or one to few closing brace ("}"). For this reason, keeping the code indented correctly can also save you a lot of time.
 

(*steve*)

¡sǝpodᴉʇuɐ ǝɥʇ ɹɐǝɥd
Moderator
Code:
Thinking about this function i assume that the "output" of this function could just be to check that the previous state (bstate or istate) remains true?[/QUOTE]

Yes, you could enter a particular state when the button is pressed, and then leave it when the button is released.
 
If you're making a model railway system that's a good thing, right? :D

Once you figure this out (and you will) you'll look back at your first efforts and realize how far you've come in a very short period.

You will be armed with the skills to write very complex control logic in a way that is easy to understand and maintain.

Absolutely Steve,

The gravity of using the Arduino as a means of controlling many aspects of the model is alarming. I have already seen that in a short space of time my newly acquired skills have led me to think and plan of many more adaptations in my chosen hobby. The shear lack of electronic components other than basics resistors, diodes etc has its benefits as well. But the greatest advantage has to be how after some working out you can tweak your program to make it work better without the need to scrap anything other than a sketch :)

For us modellers this is real cutting edge stuff and i have little doubt that one day this will feature in a railway modelling magazine near you :D
 
Ok I've done the table

attachment.php


And re-done the code

Code:
  void machineState(int newState) {
  setStateI(iState, lStateTime, true);
  
    switch ( iState ) {

      case 1 : //  "Down Train Awaiting Dispatch" 
             
        setStateB(bLedA1, lLedA1Time, true);
        setStateB(bLedB1, lLedB1Time, false);
        setStateB(bLedB2, lLedB2Time, false);
        break;

      case 2 : //  "Train Accepted" 
            
        setStateB(bLedA1, lLedA1Time, false);
        setStateB(bLedB1, lLedB1Time, true);
        setStateB(bLedB2, lLedB2Time, false);
        break;

      case 3 :// "Ready Next Service" 
         
        setStateB(bLedA1, lLedA1Time, false);
        setStateB(bLedB1, lLedB1Time, false);
        setStateB(bLedB2, lLedB2Time, true);         
        break;

      case 0 : // default
      
        setStateB(bLedA1, lLedA1Time, false);
        setStateB(bLedB1, lLedB1Time, false);
        setStateB(bLedB2, lLedB2Time, false);
        break;
    }
  }
 

Attachments

  • void machineState Table1.jpg
    void machineState Table1.jpg
    38 KB · Views: 380
Last edited:
And a similar table for void stateChange function

attachment.php


Although i did say the object was just to get the button presses producing an output to begin with lol
 

Attachments

  • void stateChange Table1.jpg
    void stateChange Table1.jpg
    54 KB · Views: 419
Last edited:

(*steve*)

¡sǝpodᴉʇuɐ ǝɥʇ ɹɐǝɥd
Moderator
OK, the state change table probably needs to be explained a bit more.

All we're considering is the starting state, and the conditions which cause the state to change (and obviously to what state you change).

Let's start with your list of states and their names:

Code:
State | Designated name
======+===========================
  0   | Default (DEF)
  1   | Down Train Awaiting Dispatch (DTAD)
  2   | Down Train Accepted (DTA)
  3   | Ready Next Service (RNS)
And I've given them some abbreviations so I don't have to type out the full name :)

I've also placed them in numerical order because I like it that way.

So now we create a table that tells us when we're in each state, how do we get to another

Code:
This  | Next  |
State | State | Action
======+=======+====================
 DEF  |  DTAD | BtnA1Edge == true
------+-------+--------------------
 DTAD |  DTA  | BtnB2Edge == true
      |  RNS  | BtnB2Edge == true
------+-------+--------------------
 DTA  |  DTAD | BtnA1Edge == true
      |  RNS  | BtnB2Edge == true
------+-------+--------------------
 RNS  |  DTA  | BtnB2Edge == true
      |  DTAD | BtnA1Edge == true

So we concentrate ONLY on what takes us from one state to the next.

And that translates directly into the stateChange function which has a switch statement for the current case (switch (iState) {...}) that has a case for each of the states in the first column.

Within each of these cases (Case X: ... break;) we test for the actions in turn, and if we find we have one then we call machineState to set the value in the Next State column.
 

(*steve*)

¡sǝpodᴉʇuɐ ǝɥʇ ɹɐǝɥd
Moderator
Note that in the previous example there is no way to get back to the default state. That may be a good thing!

Also (and this is not to consider in detail right now) imagine you had two of these sets of lights and buttons.

You might think that you would have double the number of states

Code:
State | Designated name
======+===========================
  0   | Default#1 (DEF1)
  1   | Down Train Awaiting Dispatch#1 (DTAD1)
  2   | Down Train Accepted#1 (DTA1)
  3   | Ready Next Service#1 (RNS1)
  4   | Default#2 (DEF2)
  5   | Down Train Awaiting Dispatch#2 (DTAD2)
  6   | Down Train Accepted#2 (DTA2)
  7   | Ready Next Service#2 (RNS2)
And that you just progress from there.

However it's not that simple. You might want one to be in DTAD (DTAD1) and the next in DTA (DTA2). There is no single state that allows us to do that.

This is a major complication, and there are three ways to resolve it.

First solution

The first way is to set up all the combination states. that means there will be 16 states (not 4 + 4, but 4 * 4)

The States will be something like

Code:
State | Designated name
======+===========================
  0   | Default#1, Default#2 (DEF1/DEF2)
  1   | Default#1, Down Train Awaiting Dispatch#2 (DEF1/DTAD2)
  2   | Default#1, Down Train Accepted#2 (DEF1/DTA2)
  3   | Default#1, Ready Next Service#2 (DEF1/RNS2)
  4   | Down Train Awaiting Dispatch#1, Default2 (DTAD1/DEF2)
  5   | Down Train Awaiting Dispatch#1, Down Train Awaiting Dispatch#2 (DTAD1/DTAD2)
  6   | Down Train Awaiting Dispatch#1, Down Train Accepted#2 (DTAD1/DTA2)
  7   | Down Train Awaiting Dispatch#1, Ready Next Service#2 (DTAD1/RNS2)
  8   | Down Train Accepted#1, Default2 (DTA1/DEF2)
  9   | Down Train Accepted#1, Down Train Awaiting Dispatch#2 (DTA1/DTAD2)
  10  | Down Train Accepted#1, Down Train Accepted#2 (DTA1/DTA2)
  11  | Down Train Accepted#1, Ready Next Service#2 (DTA1/RNS2)
  12  | Ready Next Service#1, Default2 (RNS1/DEF2)
  13  | Ready Next Service#1, Down Train Awaiting Dispatch#2 (RNS1/DTAD2)
  14  | Ready Next Service#1, Down Train Accepted#2 (RNS1/DTA2)
  15  | Ready Next Service#1, Ready Next Service#2 (RNS1/RNS2)
Knowing what these mean, you might realise that some of these states are actually invalid. For the moment, we'll retain them, but understand that an invalid state is one in which there are no valid ways to get there.

Then you write the other table showing for each of these 16 states, what things move you to the other states.

The key thing to note is that if a state is invalid (let's say state 10 is invalid) then there will be no action which results in state 10 being the next state. Normally, for example ButtonB2 might signal a move from RNS to DTA -- so we would have perhaps Button B2-1 to change all of the states containing RNS1 (e.g. RNS1/RNS2) to the corresponding state with DTA1 (in this case DTA1/RNS2). BUT we would NOT encode the rule for RNS1/DTA2, because the "next state" would be the invalid DTA1/DTA2 state.

This can get very complex quickly. For example, having 4 of these sets of switches and buttons gives us a potential 256 states. In practice we will have fewer, because if a state is actually invalid, we can remove it completely. This will help ensure we never change to it. But we still have to consider all 256 states to decide which to encode and which not to.

However, there is an up-side. It is relatively certain that once you have decided on all of the states and transitions that everything will work. If you have 10 of these, it will take you quite some time to get all of the million states defined though.

Second Solution

The second solution is almost too simple. All we do is duplicate the state machine.

So we have one state machine for each of these things.

int iState1; // for state machine 1
int iState2; // for state machine 2

And now we simply have one set of states

Code:
State | Designated name
======+===========================
  0   | Default#1 (DEF)
  1   | Down Train Awaiting Dispatch#1 (DTAD)
  2   | Down Train Accepted#1 (DTA)
  3   | Ready Next Service#1 (RNS)
And one set of rules

Code:
This  | Next  |
State | State | Action
======+=======+====================
 DEF  |  DTAD | BtnA1Edge == true
------+-------+--------------------
 DTAD |  DTA  | BtnB2Edge == true 
      |  RNS  | BtnB2Edge == true
------+-------+--------------------
 DTA  |  DTAD | BtnA1Edge == true
      |  RNS  | BtnB2Edge == true
------+-------+--------------------
 RNS  |  DTA  | BtnB2Edge == true
      |  DTAD | BtnA1Edge == true
(Well, not exactly 1 set of rules, because we have 2 sets of buttons, but the rules are exactly the same for each state machine, we just refer to the correct buttons.)

And then we have two machineState() functions, and two changeState() functions, each of which deals with it's own iSate variable.

Effectively we have 2 state machines. and we would code things like this:

Code:
void loop() {
  getInputs(); // gets all inputs
  changeState1(); // handle the first state machine
  changeState2(); // handle the second state machine
  writeOutputs(); // write all the outputs
}
This handles both totally independently. If there are no illegal states, then 10 state machines will have a total of 40 states (better than a million)

But remember in the previous example that there were invalid states? An invalid state means there is some interaction between the state machines. And with a train setup, I expect that there will be many of these.

So the upside of this is that the number of states is reduced to a minimum. In fact, it is the number we might naively have thought we needed. But the downside is that it can't handle invalid combinations of states, so we'll crash a lot of trains. (And that's bad, right?)

Third Solution

There's got to be a middle ground, right? Well yeah.

All we do is modify the second solution slightly so that the rules take into account the other state machines.

Code:
This  | Next  |
State | State | Action
======+=======+====================
 DEF  |  DTAD | BtnA1Edge == true
------+-------+--------------------
 DTAD |  DTA  | BtnB2Edge == true AND other state machine is not DTA
      |  RNS  | BtnB2Edge == true
------+-------+--------------------
 DTA  |  DTAD | BtnA1Edge == true
      |  RNS  | BtnB2Edge == true
------+-------+--------------------
 RNS  |  DTA  | BtnB2Edge == true AND other state machine is not DTA
      |  DTAD | BtnA1Edge == true
And that's all there is to it. We just make sure that before we make a transition (in this case to DTA) that the other machine is not in the DTA state (because DTA1/DTA2 was invalid)

These rules will probably change from state machine to state machine, so the rules are likely to be different for each state machine, but those differences will be in which other state machine(s) we need to check.

Note that the states DTA for machine 1 and DTA for machine 2 still exist, and it is only the rules we have that prevents both from being set.

So the upside here is that we can have fewer states, AND not get into any invalid states. We need to be careful, but each of the tasks is relatively small.

Solution 4

Did I say 3?

Imagine that things get really big. You have 100 of these things, each with 34 buttons and 3 LEDs. That's a lot of buttons and LEDs. Whilst you can probably drive all that I/O, at some point your arduino is going to say "Too much code! I can't take any more!!!". What then?

Well, the answer then is to put some of your state machines into another arduino.

And how do they communicate?

1) They could have some communications method to send machine states.
2) you could monitor the condition of the LEDs driven by the other arduinos
3) Some state machines might be totally independent from the rest and not need communication.

Things get a little more complex because you can't ensure that the state machines are operated one at a time any more, so 2 state machines might change state simultaneously leading to an invalid state. More coding, or perhaps additional states may be needed to either prevent this or to deal with it in a sensible manner.
 
Top