Maker Pro
Maker Pro

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

(*steve*)

¡sǝpodᴉʇuɐ ǝɥʇ ɹɐǝɥd
Moderator
Jan 21, 2010
25,510
Joined
Jan 21, 2010
Messages
25,510
I'll kind of answer one thing at a time. I'm not in a position to do too much right now...

As for the HOWs of these arduino's talking to each other and How the signal is sent and received and what happens to it once received I am totally in the dark... and this may explain (I Hope) my logic above


This is really simple!

You have a wire that connects ALL the grounds together for all of the arduinos. You also might accomplish this by using the same power source for all of them.

Then, the control output from one arduino is connected with a wire directly to the control input of the other arduino.

So literally, when it says XXXX on CFY_UD to YYYY on LFY_UA, you connect a wire from the output you have designated as XXXX on the CFY_UD panel arduino to the YYYY input on the LFY_UA panel arduino.

As far as your code is concerned, they're all just other inputs and outputs. The only difference is that rather than being connected to switches or LEDs, they're connected to another arduino.
 

(*steve*)

¡sǝpodᴉʇuɐ ǝɥʇ ɹɐǝɥd
Moderator
Jan 21, 2010
25,510
Joined
Jan 21, 2010
Messages
25,510
For the life of me , i cannot seem to get it to work and more frustrating i don't know why?

Wait a couple of days and I'll try to write the code for you for this simple panel.

Hopefully it will all click into place.

You really do have it almost all there. I really feel you're going to kick yourself when you see how easy it is!
 

P4 Modeller

Jan 4, 2013
120
Joined
Jan 4, 2013
Messages
120
Persistance is the key

const int S_ERM_UD_S_INIT = 0; // Initial State
const int S_ERM_UD_S_RSIL = 1; // Ready next Service State
const int S_ERM_UD_S_SDIE = 2; // Slow up Dispatch Ready State
const int S_ERM_UD_S_SAIL = 3; // Slow Down Accepted State

In Particular:

Code:
const int  S_ERM_UD_S_RSIL = 1;     // Ready next Service State

Is making "output" FSM go to that state :

Code:
 case S_ERM_UD_S_RSIL:
    {
      ledOnERM_UD_O_TRD = false;
      ledOnERM_UD_O_TA = false;
      ledOnERM_UD_O_ANS = true;

      ERM_UD_O_LFY_STRD = false;
    }
    break;

Hence the LED is lit :rolleyes:
 

P4 Modeller

Jan 4, 2013
120
Joined
Jan 4, 2013
Messages
120
This is really simple!

You have a wire that connects ALL the grounds together for all of the arduinos. You also might accomplish this by using the same power source for all of them.

Then, the control output from one arduino is connected with a wire directly to the control input of the other arduino.

So literally, when it says XXXX on CFY_UD to YYYY on LFY_UA, you connect a wire from the output you have designated as XXXX on the CFY_UD panel arduino to the YYYY input on the LFY_UA panel arduino.

As far as your code is concerned, they're all just other inputs and outputs. The only difference is that rather than being connected to switches or LEDs, they're connected to another arduino.

Kinda Guessed that was the case... though deep down i hope that it may be a case that all the Arduindos would be connected via serial link and that this would do the job... just cause i was trying to save on weight..(less wires running 60ft length of the layout) LMAO
 

(*steve*)

¡sǝpodᴉʇuɐ ǝɥʇ ɹɐǝɥd
Moderator
Jan 21, 2010
25,510
Joined
Jan 21, 2010
Messages
25,510
Kinda Guessed that was the case... though deep down i hope that it may be a case that all the Arduindos would be connected via serial link and that this would do the job... just cause i was trying to save on weight..(less wires running 60ft length of the layout) LMAO

It could be done. You could interface each arduino to an ethernet controller and use this to communicate between devices.

That would mean one cable from each to a central hub.

Perhaps that might be a viable option. The coding would get more complex, but as you've probably seen, you would just be providing a new way to either set an output or read an input.

In fact, you could even do it wirelessly with the NRF24L01 modules. In some respects that's even easier.

But let's get something basic running first!
 

(*steve*)

¡sǝpodᴉʇuɐ ǝɥʇ ɹɐǝɥd
Moderator
Jan 21, 2010
25,510
Joined
Jan 21, 2010
Messages
25,510
Sorry about the delay.

Here is my version of the arduino code for the basic ERM_UA panel.

Code:
// **** ERM_UD FSM ****

const long DBTIME = 25; // number of ms allowed to eliminate key bounce
const long FLASH_TIME = 250; // duration of on/off times for flashing LEDs

// FSM Define States for ERM_UD Panel

int iState = -1; // The main state variable (initialised to an invalid state)
long state_Time = 0; // when the state last changed.
bool bChanged = false; // Has anything changed?

#define S_ERM_UD_S_INIT  0 // Initial State  
#define S_ERM_UD_S_RSIL  1 // Ready next Service State 
#define S_ERM_UD_S_SDIE  2 // Slow  up Dispatch Ready State 
#define S_ERM_UD_S_SAIL  3 // Slow Down Accepted State

#define S_ERM_UD_S_FDIC  4 // Fast up Dispatch Ready State
#define S_ERM_UD_S_FRSE  5 // Fast Route Set State
#define S_ERM_UD_S_FAIL  6 // Fast up Accepted State

// Declare Input Controls for FSM ERM_UD Panel

const int ERM_UD_I_TRD = 6; // Train Ready Dispatch Button @ Pin 6
bool lastButton_ERM_UD_I_TRD = false;
bool currentButton_ERM_UD_I_TRD = false;
long ERM_UD_I_TRD_Time = 0;


// Declare Input Signals From Other Panels 

const int ERM_UD_I_UTA = 7; // received input from LFY_UA_O_ERM_SUTA @ pin 7
bool lastSignalIn_ERM_UD_I_UTA = false;
bool currentSignalIn_ERM_UD_I_UTA = false;
long ERM_UD_I_UTA_Time = 0;

const int ERM_UD_I_ANS = 8; // received input from LFY_UA_O_ERM_RNS @ pin 8
bool lastSignalIn_ERM_UD_I_ANS = false;
bool currentSignalIn_ERM_UD_I_ANS = false;
long ERM_UD_I_ANS_Time = 0;


// Declare Output Indications for FSM ERM_UD Panel

const int ERM_UD_O_TRD = 9;    // Train Ready Dispatch LED @ Pin 9
bool ledOn_ERM_UD_O_TRD = false;
long ERM_UD_O_TRD_Time = 0;

const int ERM_UD_O_TA = 10;     // slow Train Accepted LED @ Pin 10
bool ledOn_ERM_UD_O_TA = false;
long ERM_UD_O_TA_Time = 0;

const int ERM_UD_O_ANS = 11;    // Awaiting Next Service LED @ Pin 11
bool ledOn_ERM_UD_O_ANS = false;
long ERM_UD_O_ANS_Time = 0;


// Declare Output Signals for FSM ERM_UD Panel

const int ERM_UD_O_LFY_STRD = 12;    // Output sent to LFY_UA_I_STRD Slow train ready dispatch LED @ Pin 12
bool signalOut_ERM_UD_O_LFY_STRD = false;
long ERM_UD_O_LFY_STRD_Time = 0;

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
  
  state = debounce(state, val, time, DBTIME, lastVal, lastTime); // debounce the pin
  
  return state;                     // return state
}

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.
  }
}

// This is used to set integer states.  The most notable one is the state variable!
void setStateI(int &iState, long &lTime, int iValue) {
  if (iState != iValue) {
    iState = iValue;
    lTime = millis();
    bChanged = true;
  }
}

// This is used to set boolean states.  This is used for most outputs
void setStateB(bool &bState, long &lTime, bool bValue) {
  if (bState != bValue) {
    bState = bValue;
    lTime = millis();
    bChanged = true;
  }
}

// read the current inputs and process them
void readInputs() {
  // read the current button states
  simpleDebounce(ERM_UD_I_TRD, currentButton_ERM_UD_I_TRD, lastButton_ERM_UD_I_TRD, ERM_UD_I_TRD_Time);
  
  // and the input signal states
  simpleDebounce(ERM_UD_I_UTA, currentSignalIn_ERM_UD_I_UTA, lastSignalIn_ERM_UD_I_UTA, ERM_UD_I_UTA_Time);
  simpleDebounce(ERM_UD_I_ANS, currentSignalIn_ERM_UD_I_ANS, lastSignalIn_ERM_UD_I_ANS, ERM_UD_I_ANS_Time);
}

// Find out if we have a transition
void processTransitions ()     // Transistion From ERM_UD_State
{
  switch (iState) 
  {
  case S_ERM_UD_S_INIT:  // Initial State
    if (currentSignalIn_ERM_UD_I_ANS) 
    {
      setStateI(iState, state_Time, S_ERM_UD_S_RSIL);
    }
    break;

  case S_ERM_UD_S_RSIL:   // Ready Next Service
    if (pressEdge(currentButton_ERM_UD_I_TRD, lastButton_ERM_UD_I_TRD)) 
    {
      setStateI(iState, state_Time, S_ERM_UD_S_SDIE);
    }
    break;

  case S_ERM_UD_S_SDIE:    // Slow Train Ready Dispatch
    if (currentSignalIn_ERM_UD_I_UTA)
    {
      setStateI(iState, state_Time, S_ERM_UD_S_SAIL);
    }
    break;

  case S_ERM_UD_S_SAIL:    // Slow Train Accepted
    if (currentSignalIn_ERM_UD_I_ANS)
    {      
      setStateI(iState, state_Time, S_ERM_UD_S_RSIL);
    }
    break;

  default:
    Serial.println("Illegal State");
    setStateI(iState, state_Time, S_ERM_UD_S_INIT);
    break;
  }
}


void setState()  // Set the Output States
{
  switch(iState)
  {
  case S_ERM_UD_S_INIT:
    {
      setStateB(ledOn_ERM_UD_O_TRD, ERM_UD_O_TRD_Time, false);
      setStateB(ledOn_ERM_UD_O_TA, ERM_UD_O_TA_Time, false);
      setStateB(ledOn_ERM_UD_O_ANS, ERM_UD_O_ANS_Time, false);

      setStateB(signalOut_ERM_UD_O_LFY_STRD, ERM_UD_O_LFY_STRD_Time, false);

      Serial.println("All LEDs Are Off");
    }
    break;

  case S_ERM_UD_S_RSIL:
    {
      setStateB(ledOn_ERM_UD_O_TRD, ERM_UD_O_TRD_Time, false);
      setStateB(ledOn_ERM_UD_O_TA, ERM_UD_O_TA_Time, false);
      setStateB(ledOn_ERM_UD_O_ANS, ERM_UD_O_ANS_Time, true);

      setStateB(signalOut_ERM_UD_O_LFY_STRD, ERM_UD_O_LFY_STRD_Time, false);

      Serial.println("Awaiting Next Service LED Now ON");
    }
    break;

  case S_ERM_UD_S_SDIE:
    {
      setStateB(ledOn_ERM_UD_O_TRD, ERM_UD_O_TRD_Time, true);
      setStateB(ledOn_ERM_UD_O_TA, ERM_UD_O_TA_Time, false);
      setStateB(ledOn_ERM_UD_O_ANS, ERM_UD_O_ANS_Time, false);

      setStateB(signalOut_ERM_UD_O_LFY_STRD, ERM_UD_O_LFY_STRD_Time, true);

      Serial.println("Slow Train Ready Dispatch LED Now ON");
      Serial.println("Awaiting Next Service LED Is Now OFF");
    }
    break;

  case S_ERM_UD_S_SAIL:
    {
      setStateB(ledOn_ERM_UD_O_TRD, ERM_UD_O_TRD_Time, false);
      setStateB(ledOn_ERM_UD_O_TA, ERM_UD_O_TA_Time, true);
      setStateB(ledOn_ERM_UD_O_ANS, ERM_UD_O_ANS_Time, false);

      setStateB(signalOut_ERM_UD_O_LFY_STRD, ERM_UD_O_LFY_STRD_Time, false);

      Serial.println("Slow Train Accepted LED Now ON");
      Serial.println("Slow Train Ready Dispatch LED Is Now OFF");
    }
    break;

  default:
    {
      Serial.println("Illegal Output State");
    }
    break;
  }
}

void writeOutputs() // writes the current states to the LEDs
// These will be set On until the program has been tested 
// Then set to the correct output (i.e fade, pulse, flash)
{
  digitalWrite(ERM_UD_O_TRD, ledOn_ERM_UD_O_TRD);
  digitalWrite(ERM_UD_O_TA,  ledOn_ERM_UD_O_TA);
  digitalWrite(ERM_UD_O_ANS, ledOn_ERM_UD_O_ANS);

  digitalWrite(ERM_UD_O_LFY_STRD, signalOut_ERM_UD_O_LFY_STRD);
}


void setup()
{
  // initialize pins
  
  // Input controls
  pinMode(ERM_UD_I_TRD, INPUT);

  // Input signals
  pinMode(ERM_UD_I_UTA, INPUT); 
  pinMode(ERM_UD_I_ANS, INPUT); 
  
  // Output Indications
  pinMode(ERM_UD_O_TRD, OUTPUT);
  pinMode(ERM_UD_O_TA, OUTPUT);
  pinMode(ERM_UD_O_ANS, OUTPUT);

  // Output Signals
  pinMode(ERM_UD_O_LFY_STRD, OUTPUT);

  // initialize serial: This will only be used to verify program and then be removed
  Serial.begin(9600);
  
  int TfrmERM_UD_SetState (S_ERM_UD_S_INIT);
}

void loop()
{
  readInputs();

  processTransitions();

  setState();

  writeOutputs();
}
I'll do some changes to handle flashing and chasing if they apply -- or even if they don't -- to this panel to show you how it's done.

edit: I can't promise this code is bug free, but it does compile :D
 

(*steve*)

¡sǝpodᴉʇuɐ ǝɥʇ ɹɐǝɥd
Moderator
Jan 21, 2010
25,510
Joined
Jan 21, 2010
Messages
25,510
So here are the changes to do the flashing (I've put it all together at the bottom).

First, I've changed some definitions:

Code:
const long DBTIME = 25; // number of ms allowed to eliminate key bounce

const long FLASH_ON_TIME = 250; // duration of on times for flashing LEDs
const long FLASH_OFF_TIME = 250; // duration of off times for flashing LEDs
const long FLASH_TIME = FLASH_ON_TIME + FLASH_OFF_TIME; // total cycle time for flashing LEDs

const long CHASE_ON_TIME = 100; // duration of on times for chasing LEDs
const long CHASE_TIME = CHASE_ON_TIME*5; // total cycle time for chasing LEDs

I've split the flashing time into an on and an off duration. You might want them to be on longer than they are off (or vice versa)

I also define the time each LED is on for chasing LEDs, and how long the entire chase time is.

I decided to flash the slow train accepted LED, and no changes are required for its definition

Code:
const int ERM_UD_O_TA = 10;     // slow Train Accepted LED @ Pin 10
bool ledOn_ERM_UD_O_TA = false;
long ERM_UD_O_TA_Time = 0;

For the chasing LEDs (I chose to chase the awaiting next service LEDs -- and now there will be 5 of them.) I need to make room for the 5 outputs.

Code:
// this one will be a chase, so it uses outputs 11 to 15
const int ERM_UD_O_ANS = 11;    // Awaiting Next Service LED @ Pin 11 (to 15)
bool ledOn_ERM_UD_O_ANS = false;
long ERM_UD_O_ANS_Time = 0;


// Declare Output Signals for FSM ERM_UD Panel

const int ERM_UD_O_LFY_STRD = 16;    // Output sent to LFY_UA_I_STRD Slow train ready dispatch LED @ Pin 12
bool signalOut_ERM_UD_O_LFY_STRD = false;
long ERM_UD_O_LFY_STRD_Time = 0;

Note that the main change is really that I make the following pin 5 higher, as opposed to just using the next pin.

Next I need a routine to flash a LED

Code:
// This will flash a LED on an output pin while the output is on
void flashLED(int iPin, bool bValue, long lTime) {
  // following is a slightly complex C assignment.
  // it always returns FALSE if bValue is false
  // if bValue is TRUE it returns TRUE for the first FLASH_ON_TIME ms after
  //  the value is set (determined by lTime)
  // then it returns false for FLASH_OFF_TIME ms
  // then it repets...
  bool bOn = bValue && ((((millis() - lTime) % FLASH_TIME) - FLASH_ON_TIME) <= 0);
  
  digitalWrite(iPin, bOn);
}

This code ensures that the LED starts as ON as soon as the transition occurs, and that the flashing is independent of everything else.

Chasing is not much harder:

Code:
// This will chase 5 LEDs on 5 consecutive output pins while the output is on
void chaseLED(int iPin, bool bValue, long lTime) {
  // by default led -1 is to be on (that's no LEDs at all)
  int iOn = -1;
 
  if (bValue) {
    // if the chasing is on, determine which LED should be on now
    iOn = ((millis() - lTime) % CHASE_TIME) / CHASE_ON_TIME;
  }
  
  // go through all the LEDs, turning on only the one we want (or turning them all off)
  for (int i = 0; i < 5; i++) {
    digitalWrite(iPin+i, iOn == i);
  }
}

The chasing starts at the first LED, and runs to the last before it repeats again, and again...

To actually use this, the code to write the outputs is changed so that it now calls the appropriate routines rather than just setting the output.

Code:
void writeOutputs() // writes the current states to the LEDs
// These will be set On until the program has been tested 
// Then set to the correct output (i.e fade, pulse, flash)
{
  digitalWrite(ERM_UD_O_TRD, ledOn_ERM_UD_O_TRD);
  flashLED(ERM_UD_O_TA,  ledOn_ERM_UD_O_TA, ERM_UD_O_TA_Time); 
  chaseLED(ERM_UD_O_ANS, ledOn_ERM_UD_O_ANS, ERM_UD_O_ANS);

  digitalWrite(ERM_UD_O_LFY_STRD, signalOut_ERM_UD_O_LFY_STRD);
}

and finally the initialization:

Code:
  // Output Indications
  pinMode(ERM_UD_O_TRD, OUTPUT);
  pinMode(ERM_UD_O_TA, OUTPUT);
  pinMode(ERM_UD_O_ANS, OUTPUT); // chase outputs
  pinMode(ERM_UD_O_ANS+1, OUTPUT);
  pinMode(ERM_UD_O_ANS+2, OUTPUT);
  pinMode(ERM_UD_O_ANS+3, OUTPUT);
  pinMode(ERM_UD_O_ANS+4, OUTPUT);

I just need to ensure that I set all of the pins used to be outputs.

and now all the code together:

Code:
// **** ERM_UD FSM ****

const long DBTIME = 25; // number of ms allowed to eliminate key bounce

const long FLASH_ON_TIME = 250; // duration of on times for flashing LEDs
const long FLASH_OFF_TIME = 250; // duration of off times for flashing LEDs
const long FLASH_TIME = FLASH_ON_TIME + FLASH_OFF_TIME; // total cycle time for flashing LEDs

const long CHASE_ON_TIME = 100; // duration of on times for chasing LEDs
const long CHASE_TIME = CHASE_ON_TIME*5; // total cycle time for chasing LEDs

// FSM Define States for ERM_UD Panel

int iState = -1; // The main state variable (initialised to an invalid state)
long state_Time = 0; // when the state last changed.
bool bChanged = false; // Has anything changed?

#define S_ERM_UD_S_INIT  0 // Initial State  
#define S_ERM_UD_S_RSIL  1 // Ready next Service State 
#define S_ERM_UD_S_SDIE  2 // Slow  up Dispatch Ready State 
#define S_ERM_UD_S_SAIL  3 // Slow Down Accepted State

#define S_ERM_UD_S_FDIC  4 // Fast up Dispatch Ready State
#define S_ERM_UD_S_FRSE  5 // Fast Route Set State
#define S_ERM_UD_S_FAIL  6 // Fast up Accepted State

// Declare Input Controls for FSM ERM_UD Panel

const int ERM_UD_I_TRD = 6; // Train Ready Dispatch Button @ Pin 6
bool lastButton_ERM_UD_I_TRD = false;
bool currentButton_ERM_UD_I_TRD = false;
long ERM_UD_I_TRD_Time = 0;


// Declare Input Signals From Other Panels 

const int ERM_UD_I_UTA = 7; // received input from LFY_UA_O_ERM_SUTA @ pin 7
bool lastSignalIn_ERM_UD_I_UTA = false;
bool currentSignalIn_ERM_UD_I_UTA = false;
long ERM_UD_I_UTA_Time = 0;

const int ERM_UD_I_ANS = 8; // received input from LFY_UA_O_ERM_RNS @ pin 8
bool lastSignalIn_ERM_UD_I_ANS = false;
bool currentSignalIn_ERM_UD_I_ANS = false;
long ERM_UD_I_ANS_Time = 0;


// Declare Output Indications for FSM ERM_UD Panel

const int ERM_UD_O_TRD = 9;    // Train Ready Dispatch LED @ Pin 9
bool ledOn_ERM_UD_O_TRD = false;
long ERM_UD_O_TRD_Time = 0;

const int ERM_UD_O_TA = 10;     // slow Train Accepted LED @ Pin 10
bool ledOn_ERM_UD_O_TA = false;
long ERM_UD_O_TA_Time = 0;

// this one will be a chase, so it uses outputs 11 to 15
const int ERM_UD_O_ANS = 11;    // Awaiting Next Service LED @ Pin 11 (to 15)
bool ledOn_ERM_UD_O_ANS = false;
long ERM_UD_O_ANS_Time = 0;


// Declare Output Signals for FSM ERM_UD Panel

const int ERM_UD_O_LFY_STRD = 16;    // Output sent to LFY_UA_I_STRD Slow train ready dispatch LED @ Pin 12
bool signalOut_ERM_UD_O_LFY_STRD = false;
long ERM_UD_O_LFY_STRD_Time = 0;

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
  
  state = debounce(state, val, time, DBTIME, lastVal, lastTime); // debounce the pin
  
  return state;                     // return state
}

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.
  }
}

// This is used to set integer states.  The most notable one is the state variable!
void setStateI(int &iState, long &lTime, int iValue) {
  if (iState != iValue) {
    iState = iValue;
    lTime = millis();
    bChanged = true;
  }
}

// This is used to set boolean states.  This is used for most outputs
void setStateB(bool &bState, long &lTime, bool bValue) {
  if (bState != bValue) {
    bState = bValue;
    lTime = millis();
    bChanged = true;
  }
}

// This will flash a LED on an output pin while the output is on
void flashLED(int iPin, bool bValue, long lTime) {
  // following is a slightly complex C assignment.
  // it always returns FALSE if bValue is false
  // if bValue is TRUE it returns TRUE for the first FLASH_ON_TIME ms after
  //  the value is set (determined by lTime)
  // then it returns false for FLASH_OFF_TIME ms
  // then it repets...
  bool bOn = bValue && ((((millis() - lTime) % FLASH_TIME) - FLASH_ON_TIME) <= 0);
  
  digitalWrite(iPin, bOn);
}

// This will chase 5 LEDs on 5 consecutive output pins while the output is on
void chaseLED(int iPin, bool bValue, long lTime) {
  // by default led -1 is to be on (that's no LEDs at all)
  int iOn = -1;
 
  if (bValue) {
    // if the chasing is on, determine which LED should be on now
    iOn = ((millis() - lTime) % CHASE_TIME) / CHASE_ON_TIME;
  }
  
  // go through all the LEDs, turning on only the one we want (or turning them all off)
  for (int i = 0; i < 5; i++) {
    digitalWrite(iPin+i, iOn == i);
  }
}

// read the current inputs and process them
void readInputs() {
  // read the current button states
  simpleDebounce(ERM_UD_I_TRD, currentButton_ERM_UD_I_TRD, lastButton_ERM_UD_I_TRD, ERM_UD_I_TRD_Time);
  
  // and the input signal states
  simpleDebounce(ERM_UD_I_UTA, currentSignalIn_ERM_UD_I_UTA, lastSignalIn_ERM_UD_I_UTA, ERM_UD_I_UTA_Time);
  simpleDebounce(ERM_UD_I_ANS, currentSignalIn_ERM_UD_I_ANS, lastSignalIn_ERM_UD_I_ANS, ERM_UD_I_ANS_Time);
}

// Find out if we have a transition
void processTransitions ()     // Transistion From ERM_UD_State
{
  switch (iState) 
  {
  case S_ERM_UD_S_INIT:  // Initial State
    if (currentSignalIn_ERM_UD_I_ANS) 
    {
      setStateI(iState, state_Time, S_ERM_UD_S_RSIL);
    }
    break;

  case S_ERM_UD_S_RSIL:   // Ready Next Service
    if (pressEdge(currentButton_ERM_UD_I_TRD, lastButton_ERM_UD_I_TRD)) 
    {
      setStateI(iState, state_Time, S_ERM_UD_S_SDIE);
    }
    break;

  case S_ERM_UD_S_SDIE:    // Slow Train Ready Dispatch
    if (currentSignalIn_ERM_UD_I_UTA)
    {
      setStateI(iState, state_Time, S_ERM_UD_S_SAIL);
    }
    break;

  case S_ERM_UD_S_SAIL:    // Slow Train Accepted
    if (currentSignalIn_ERM_UD_I_ANS)
    {      
      setStateI(iState, state_Time, S_ERM_UD_S_RSIL);
    }
    break;

  default:
    Serial.println("Illegal State");
    setStateI(iState, state_Time, S_ERM_UD_S_INIT);
    break;
  }
}


void setState()  // Set the Output States
{
  switch(iState)
  {
  case S_ERM_UD_S_INIT:
    {
      setStateB(ledOn_ERM_UD_O_TRD, ERM_UD_O_TRD_Time, false);
      setStateB(ledOn_ERM_UD_O_TA, ERM_UD_O_TA_Time, false);
      setStateB(ledOn_ERM_UD_O_ANS, ERM_UD_O_ANS_Time, false);

      setStateB(signalOut_ERM_UD_O_LFY_STRD, ERM_UD_O_LFY_STRD_Time, false);

      Serial.println("All LEDs Are Off");
    }
    break;

  case S_ERM_UD_S_RSIL:
    {
      setStateB(ledOn_ERM_UD_O_TRD, ERM_UD_O_TRD_Time, false);
      setStateB(ledOn_ERM_UD_O_TA, ERM_UD_O_TA_Time, false);
      setStateB(ledOn_ERM_UD_O_ANS, ERM_UD_O_ANS_Time, true);

      setStateB(signalOut_ERM_UD_O_LFY_STRD, ERM_UD_O_LFY_STRD_Time, false);

      Serial.println("Awaiting Next Service LED Now ON");
    }
    break;

  case S_ERM_UD_S_SDIE:
    {
      setStateB(ledOn_ERM_UD_O_TRD, ERM_UD_O_TRD_Time, true);
      setStateB(ledOn_ERM_UD_O_TA, ERM_UD_O_TA_Time, false);
      setStateB(ledOn_ERM_UD_O_ANS, ERM_UD_O_ANS_Time, false);

      setStateB(signalOut_ERM_UD_O_LFY_STRD, ERM_UD_O_LFY_STRD_Time, true);

      Serial.println("Slow Train Ready Dispatch LED Now ON");
      Serial.println("Awaiting Next Service LED Is Now OFF");
    }
    break;

  case S_ERM_UD_S_SAIL:
    {
      setStateB(ledOn_ERM_UD_O_TRD, ERM_UD_O_TRD_Time, false);
      setStateB(ledOn_ERM_UD_O_TA, ERM_UD_O_TA_Time, true);
      setStateB(ledOn_ERM_UD_O_ANS, ERM_UD_O_ANS_Time, false);

      setStateB(signalOut_ERM_UD_O_LFY_STRD, ERM_UD_O_LFY_STRD_Time, false);

      Serial.println("Slow Train Accepted LED Now ON");
      Serial.println("Slow Train Ready Dispatch LED Is Now OFF");
    }
    break;

  default:
    {
      Serial.println("Illegal Output State");
    }
    break;
  }
}

void writeOutputs() // writes the current states to the LEDs
// These will be set On until the program has been tested 
// Then set to the correct output (i.e fade, pulse, flash)
{
  digitalWrite(ERM_UD_O_TRD, ledOn_ERM_UD_O_TRD);
  flashLED(ERM_UD_O_TA,  ledOn_ERM_UD_O_TA, ERM_UD_O_TA_Time); 
  chaseLED(ERM_UD_O_ANS, ledOn_ERM_UD_O_ANS, ERM_UD_O_ANS);

  digitalWrite(ERM_UD_O_LFY_STRD, signalOut_ERM_UD_O_LFY_STRD);
}


void setup()
{
  // initialize pins
  
  // Input controls
  pinMode(ERM_UD_I_TRD, INPUT);

  // Input signals
  pinMode(ERM_UD_I_UTA, INPUT); 
  pinMode(ERM_UD_I_ANS, INPUT); 
  
  // Output Indications
  pinMode(ERM_UD_O_TRD, OUTPUT);
  pinMode(ERM_UD_O_TA, OUTPUT);
  pinMode(ERM_UD_O_ANS, OUTPUT); // chase outputs
  pinMode(ERM_UD_O_ANS+1, OUTPUT);
  pinMode(ERM_UD_O_ANS+2, OUTPUT);
  pinMode(ERM_UD_O_ANS+3, OUTPUT);
  pinMode(ERM_UD_O_ANS+4, OUTPUT);

  // Output Signals
  pinMode(ERM_UD_O_LFY_STRD, OUTPUT);

  // initialize serial: This will only be used to verify program and then be removed
  Serial.begin(9600);
  
  int TfrmERM_UD_SetState (S_ERM_UD_S_INIT);
}

void loop()
{
  readInputs();

  processTransitions();

  setState();

  writeOutputs();
}

And as before, the code compiles, but I can't say I've tested it.
 

P4 Modeller

Jan 4, 2013
120
Joined
Jan 4, 2013
Messages
120
WooWWWW

Thanks Steve, Haven't tested it yet. A lot to take in a discover where i went wrong in my code.

Looks like im gonna have fun with lots of different flashing lights :D
 

(*steve*)

¡sǝpodᴉʇuɐ ǝɥʇ ɹɐǝɥd
Moderator
Jan 21, 2010
25,510
Joined
Jan 21, 2010
Messages
25,510
A lot to take in a discover where i went wrong in my code.

You did very little wrong.

This is mostly your code.

If you want to compare the code easily, I would suggest "Beyond Compare" by Scooter Software.
 

P4 Modeller

Jan 4, 2013
120
Joined
Jan 4, 2013
Messages
120
Hi Steve,

Well An Enormous Big Thanks For You Assistance

With reference to your first "basic" example
I have ammended the code so that the input signals for this panel are now buttons for the test purpose..and have to say that all works as intended.:D

I've looked and can see immediately where i went wrong and tbh i would never have come up with the answer. I bow to your wisdow and knowledge :D

I now have the building blocks to go on and ammend the first example with some flashy lights.. of which you have been kind enough to show several different concepts :)

I hope that in time i will be able to come back to this post with "the finished product" in maybe a link to the video.

I have still to decide how to connect the "ArduinoS" together but really a few extra small wires will not make a big difference and seems the easiest route to go... I had thought wireless but who knows what other signals will be floating around in an exhibition with wireless DCC Controllers readily available. So wires are probably the best choice, or serial connection., but in truth the limited amount of signals could easily be accomodated in 1 Data Cable.

I have learnt an immense amount from you in a relatively short period of time and having looked at your code here i'm sure that 95% of it can be juggled to make the "FSM" for the track routing logic and track routing diagram which will incorporate the train signalling as part of that too.

My program for the servo motors which will drive the semaphore signals is coming along nicely with some nice touches such as "Signal Bounce" (The Real Life Signals were driven by mechanical levers and bellcranks from the signal box lever and inherent with this system as the signal box lever hit the stop the Signal Arm would [ with varying amount dependant on distance from the signal box] hit its stop and reverberate "similar to electrical switch contacts"

The beauty of the microcontroller is this can be replicated in the same varying degree :D My mind comes up with more project refinements on a daily basis ....oh Noooooooo


So Steve , Thanks for your guidance and also for opening my eyes to the powerful world of microcontrollers

Mark
 
Last edited:

P4 Modeller

Jan 4, 2013
120
Joined
Jan 4, 2013
Messages
120
I have ONE big question before i sign off

Please Could you explain this in more detail

Code:
//   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
    }
  }

And more so this line

Code:
 void function setStateB(bool &bState, long &lTime, bool bValue)

and in particular the bool &bState, .... The & bit is the piece i really need to get my head round.

As i'm gonna need to write my own functions and keep these as "generic" as possible.

Warmest Regards

Mark
 

(*steve*)

¡sǝpodᴉʇuɐ ǝɥʇ ɹɐǝɥd
Moderator
Jan 21, 2010
25,510
Joined
Jan 21, 2010
Messages
25,510
This is a pretty generic routine.

the "bool &bState" is the declaration of a parameter for the function. In this case it is a boolean. The & says (in effect) I want to pass any changes I make to this value back to the calling routine. In more technical terms is it a pass by reference rather than a pass by value. In even more technical terms it asks the compiler to pass the address of the variable to the function rather than a copy of its value.

lTime is passed the same way (we want to keep track off the state and when it was set.

bValue is passed by value because we don't want to change it. It is the value we want bState to take on (if it's not already).

I think the comments describe what is going on. perhaps "If bState has changed" could read "If we want to change the value of bState".
 

(*steve*)

¡sǝpodᴉʇuɐ ǝɥʇ ɹɐǝɥd
Moderator
Jan 21, 2010
25,510
Joined
Jan 21, 2010
Messages
25,510
So Steve , Thanks for your guidance and also for opening my eyes to the powerful world of microcontrollers

It's been a pleasure.

Don't hesitate to come back with more questions at any time.
 
Top