Maker Pro
Maker Pro

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

(*steve*)

¡sǝpodᴉʇuɐ ǝɥʇ ɹɐǝɥd
Moderator
After careful consideration I have decided to spend some time trying to write the sketch for the Arduino to suite my needs rather than find someone to do it for me. I think that if it was written for me I would still be in the dark as to what was happening, even though i'm sure it would work fine, if it didn't or needed tweaking then i wouldn't know how to achieve this.. understanding how and why it is written in the first place is key to upgrading or adding functions.

Spoken like a true hacker!

Winter is nearly upon us, the nights a drawing in and i will have more time to devote to this part of the project... so i'm sorry your stuck with me for a while longer. lmao
That's fine. I will try to provide you with assistance, but as the project gets more and more complex you need to understand how all the pieces work. I'm not going to be able to keep it in my head and you need to become the expert.

Having reflected upon my requirements stated in previous posts

1. Control the points (turnouts) to select the desired route
2. LED route to light the route the track is selected to.
3. Control the servo operated signals/ interfaced with LED Routing Display
4. Operator Sequence Control Panels

I am going to focus my efforts on Number 4 "Operator Sequence Control Panels" as this does not need to be linked to any of the other requirements. This will hopefully give me a chance to learn some of what i need to understand for future parts of the project.
That sounds like a plan!

As part of that i am going to continue with the Arduino Uno as delivered and when i run out of pins then i will look at ways around that issue....unless ofcourse i am advised otherwise .
OK, now this means you need to very carefully follow the model of:

Inputs --> States --> Outputs

You need to read the inputs and use these to control the internal states.

And then maybe do processing on these states (an example would be deciding if a flashing LED is currently ON or OFF) to change secondary states (e.g. a primary state might be LightXX is on, off, or flashing. The processing would generate a secondary state saying whether LightXX is currently on or off, presumably by looking at the primary state, and if it's flashing, at current "time" and the time it was set to flashing, and the desired flash rate.)

Then you need to take the states and drive the outputs.

You *MUST* keep these separate or you will be in a big hole if you ever decide to change from direct I/O to devices on pins to some mechanism where you control a chip that drives many outputs.

At present i think i understand 80% of the code already written, the other 20% I'm working on.
That's good.

Time for my first question:...Any Recommendations reading material on Arduino programming ?
That's a hard one. I've tended to use google, but I have a pretty good hardware and software background, so specs, examples, and reference guides are all I really need.

Most books I've seen tend to concentrate on basic hardware stuff and "projects" and really a lot less on software design principles. Your project is complex enough that the software design is really going to be what makes it useable and maintainable an flexible.

Maybe someone else can better comment on this.

What i need to happen is that:

If Button A is ON then LedA will Illuminate and new Led [ LedA1] will flash
This will be the same for all 3 Leds mentioned in this sketch
OK, so let's go back to basics.

One of the basic things we want is the pressing of the buttons. We don't care if they're held down, or when they're released, all we want to know is the instant they're pressed.

And I believe we wrote code that would detect that.

And they can be our three primary states.

Button_1_pressed
Button_2_pressed
Button_3_pressed

And using the code we have, these will be true ONLY the first time the press is detected.

And that's ALL we need on the input side!

Now we need to look at secondary states and state processing. Let's just do that for 1 button, the rest will be the same.

Code:
if Button_1_pressed is TRUE
then
  set LEDA to ON
  set LEDA1_flash to TRUE
  set LEDA1_time to NOW
endif
That says, when we see Button 1 pressed, set the state for LEDA to ON (simple) and some secondary state LEDA1_flash to TRUE (to start it flashing) and also set a time value so we know when to turn it on and off.

That looks fine. But is it? You might consider what will happen if the button is pressed again. Should anything happen? This code has a side-effect you might not want. Can you figure out what it is (it may be come apparent in a minute)

So far we know that LED1A is flashing, but not if it's on or off, so there's more processing of states to do.

Code:
if set LEDA1_flash is TRUE
then
  if LED1A supposed to be ON
  then
    set LEDA1 to ON
  else
    set LEDA1 to OFF
  endif
else
  set LEDA1 to OFF
endif
Well that looks simple. But what's this "if LED1A supposed to be ON"?

That might be a function which takes the current "time" and the time the LED was set to flash, and the flash duration, and returns whether the LED should be on or off right now.

The next thing to do is to handle the outputs. This is as simple as reading the LED states (most of them secondary states that we have assigned above) and setting the appropriate outputs.

If you're using charlieplexing, or using an external chip to drive multiple LEDs, then this code will change, but the rest will stay the same.

Can you see what happens to the flashing LED if you press the button a second time? Can you see how to fix that?

I can see that this could be achieved by simply assigning 3 more OUTPUTS and linking them to the relevant existing Push buttons

However i don't want to go out of my way to use the Arduino Pins up.
So i have read up on Charlieplexing and was wondering if i could use this function to gain the flashing led ?
Charlieplexing is a way of driving multiple LEDs form a fewer number of outputs. It is best used when you only want a single LED on as it is not possible to have an arbitrary combination of LEDs turned on. If you want several LEDs to be turned on, you may need to swap quickly between states where one o the other is lit. Because of the way charlieplexing works, it can be difficult to do this and maintain a constant brightness.

From My Understanding albeit very limited
The easiest way to think of charliplexing is that you have a large number of LEDs connected to a number of pins. There is a particular setting of those pins (which are inputs, which are outputs, and for those that are outputs, are they high or low) which will light each LED.

One way of handling this is to have a lookup table in your program that says, if I want LED1 lit, I take these two values from the first position of an array and use them to configure the pins.

If you want an arbitrary number of LEDs lit, I wouldn't recommend it.
 
Hi Steve,

Thanks for the reply, as i lay my head on the pillow that night, i realized that the concept of using charlieplexing would not work... something must have sunk into the dark grey matter

:rolleyes:
 
I have finally got round to making a mock-up of 2 of the Control Sequence Panels, mainly to aid in making sure the buttons and leds do what i want them to. (attached)

So far everything is working as intended with the buttons cancelling the previous led.....
however :eek:

Code:
void buttonLogic() {
// 1) Pressing a button when its LED is off causes the LED to turn on
// 2) pressing a button while its LED is on turns it off.
// 3) Cancel LED A when LED B is illuminated
// 4) Cancel LED B when LED C is illuminated
// 5) Cancel LED C when LED A is illuminated
    if (pressEdge(currentButtonA, lastButtonA)) { // rules 1 & 2 for LED A
      ledOnA = !ledOnA;
      
      if (ledOnA) { // rule 5
        ledOnC = false;
      }
    }
   
    if (pressEdge(currentButtonB, lastButtonB)) { // rules 1 & 2 for LED B
      ledOnB = !ledOnB;
      
      if (ledOnB) { // rule 3
        ledOnA = false;
      }
    }
   
    if (pressEdge(currentButtonA, lastButtonA)) { // rules 1 & 2 for LED C
      ledOnB = !ledOnB;
      
      if (ledOnC) { // rule 4
        ledOnB = false;
      }
    }
}

Old Rules
Rule 1 is correct
Rule 2 no longer applies
Rule 3 to 5 are correct

So this is what i'm working on now

New Rules for Button Logic
Pressing a button when its LED is off causes the LED to turn on
// 1) Pressing Button A when its LED is off causes the LED to turn on
// 2) Pressing Button B when its LED is off causes the LED to turn on
// 3) Pressing Button C when its LED is off causes the LED to turn on
// 4) Button B cancels Button A
// 5) Button C cancels Button B
// 6) Button A cancels Button C
// 7) Button A must be the first button press in the sequence
// 8) For Button B to work Rule 4 must be true (Button A =HIGH && Button C = LOW)
// 9) For Button C to work (Button B = HIGH && Button A = LOW)
// 10) For Button A to work (Button C = HIGH && Button B = LOW)

// 11) Cancel LED A when LED B is illuminated
// 12) Cancel LED B when LED C is illuminated
// 13) Cancel LED C when LED A is illuminated

If i've worked this out correctly Rules 11 to 13 are already catered for in rules 4 to 6

Keep you posted, but don't hold your breath lol

Once this is achieved i can move onto adding the final 3 functions of these panels which in truth are duplicates of what has already been achieved with the addition of a 5 led chase sequence :cool:
 

Attachments

  • 2013-09-21 20.53.40.jpg
    2013-09-21 20.53.40.jpg
    139.8 KB · Views: 121
OK Well Progress

The only part of the code that has been altered.

Code:
void buttonLogic() {
// 1) Pressing a button when its LED is off causes the LED to turn on
// 2) Cancel LED A1 when LED B is illuminated
// 3) Cancel LED B when LED C is illuminated
// 4) Cancel LED C when LED A is illuminated
// 5) Sequence Must Start with ButtonA
// 6) Sequence Must Be Button A, ButtonB, then ButtonC. Ensures Correct Sequence is followed (prevents inadvertant button push illuminating led out of sequence)

    if (pressEdge(currentButtonA, lastButtonA) && (!ledOnB)) { // Rule 1, Rule 7 for LED A
      ledOnA1 = true;
        if (ledOnA1){
          ledOnC = false; // Rule 4
        }              
      }
   
    if (pressEdge(currentButtonB, lastButtonB) && (!ledOnC)) { // Rule 1, Rule 7 for LED B
      ledOnB = true;
        if (ledOnB){
          ledOnA1 = false; // Rule 2
        }
       }
   
    if (pressEdge(currentButtonC, lastButtonC) && (!ledOnA1)) { // Rule 1, Rule 7 for LED C
      ledOnC = true;
      
      if (ledOnC) { // Rule 3
        ledOnB = false;
        
      }
    }
}

Wether or not it is the correct way to go I'm unsure but one thing is for certain it works and its my first success in programming :)

For those wondering as to why i have changed the name of led A to led A1 the logic behind it is that the 1 inticates it is a repeater led and as such will flash to make the operator aware "user invention" needed.

Only Rule 5 to achieve :rolleyes:
 

(*steve*)

¡sǝpodᴉʇuɐ ǝɥʇ ɹɐǝɥd
Moderator
OK Well Progress

Excellent!

I'd just make a small change.

Code:
    // gather data
    btnAEdge = pressEdge(currentButtonA, lastButtonA);
    btnBEdge = pressEdge(currentButtonB, lastButtonB);
    btnCEdge = pressEdge(currentButtonC, lastButtonC);
If you get these values and store them, you can refer to them again if you need to.
Code:
    // apply the rules
    if (btnAEdge) && (!ledOnB)) { // Rule 1, Rule 7 for LED A
      ledOnA1 = true;
      ledOnC = false; // Rule 4
      }
   
    if (btnBEdge) && (!ledOnC)) { // Rule 1, Rule 7 for LED B
      ledOnB = true;
      ledOnA1 = false; // Rule 2
       }
   
    if (btnCEdge) && (!ledOnA1)) { // Rule 1, Rule 7 for LED C
      ledOnC = true;
      ledOnB = false;
    }
}
The extra embedded IF statements are not required because the value is always true.

Wether or not it is the correct way to go I'm unsure but one thing is for certain it works and its my first success in programming :)
It's a good achievement. Programming depends a lot on figuring out how to do something and remembering that so you can do it again later. Another part is realising that if you're doing it often enough then you can develop a routine to do that and to hide the complexity (you will note that your code doesn't even seem to worry itself about keybounce any more -- that complexity is hidden).

You've already seen the development and use of routines such as "pressEdge", and you're using many more that you don't even realise.

Only Rule 5 to achieve :rolleyes:
Isn't this achieved by setting only LED B on? Or do you mean it also is to happen when *no* LED is on? (edit: No, I misread the code... the solution below is better)

It seems the solution might be to change your IF statements so that the test for buttonAEdge requires LED B to be OFF, but that the other two require that some combination of LEDs must also be on.

So
Code:
 if (btnBEdge) && (!ledOnC)) {
might change to
Code:
 if (btnBEdge) && (!ledOnC) && (ledOnA1  || ledOnB)) {
This means that button B has the same action, but as a precondition also requires LED A or B to be on. If all LEDs are off, then the button can have no effect.
 
Excellent!

I'd just make a small change.

Code:
    // gather data
    btnAEdge = pressEdge(currentButtonA, lastButtonA);
    btnBEdge = pressEdge(currentButtonB, lastButtonB);
    btnCEdge = pressEdge(currentButtonC, lastButtonC);
If you get these values and store them, you can refer to them again if you need to.

I had thought about doing this as i am fairly certain they will be needed later on

Code:
    // apply the rules
    if (btnAEdge) && (!ledOnB)) { // Rule 1, Rule 7 for LED A
      ledOnA1 = true;
      ledOnC = false; // Rule 4
      }
   
    if (btnBEdge) && (!ledOnC)) { // Rule 1, Rule 7 for LED B
      ledOnB = true;
      ledOnA1 = false; // Rule 2
       }
   
    if (btnCEdge) && (!ledOnA1)) { // Rule 1, Rule 7 for LED C
      ledOnC = true;
      ledOnB = false;
    }
}

Sorry i dont seem to even be apply to verify this revision to the code, no matter how hard i try :eek:
Cant figure it out either
Error message state btnEdgeA was not declared :eek:
 

(*steve*)

¡sǝpodᴉʇuɐ ǝɥʇ ɹɐǝɥd
Moderator
There's another trick. You didn't show your declarations, so neither did I. I'm expecting you to spot new variables, figure out what type they are and declare them.

I originally gave the answer in a paragraph I've removed, but I think it's good practice for you to figure it out.

If you can't figure it out, I'll tell you, but I have confidence in you.
 
There's another trick. You didn't show your declarations
,

Ahhhh But I did ;) Just not in the right place and still having issues with placement of "{" and "}", causing error messages galore :mad:

I have confidence in you

Glad someone does and I'm starting to enjoy this immensely :D Only Cause it works !!!!

Code:
void buttonLogic() {
// 1) Pressing a button when its LED is off causes the LED to turn on
// 2) Cancel LED A1 when LED B is illuminated
// 3) Cancel LED B when LED C is illuminated
// 4) Cancel LED C when LED A is illuminated
// 5) Sequence Must Start with ButtonA
// 6) Sequence Must Be Button A, ButtonB, then ButtonC. Ensures Correct Sequence is followed (prevents inadvertant button push illuminating led out of sequence)

 // Gather Button Data for ButtonA Logic
  
  bool btnAEdge = pressEdge(currentButtonA, lastButtonA);
  bool btnBEdge = pressEdge(currentButtonB, lastButtonB);
  bool btnCEdge = pressEdge(currentButtonC, lastButtonC); {
    
// apply the sequence rules

    if ((btnAEdge) && !ledOnB) { // Rule 1, Rule 6 for LED A
      ledOnA1 = true;
      ledOnC = false; // Rule 4
      }
   
    if ((btnBEdge) && !ledOnC && ledOnA1) { // Rule 1,Rule 5 Rule 6 for LED B
      ledOnB = true;
      ledOnA1 = false; // Rule 2
       }
   
    if ((btnCEdge) && !ledOnA1 && ledOnB) { // Rule 1, Rule 5 Rule 6 for LED C
      ledOnC = true;
      ledOnB = false; 
    
    }
  }
}
       

void loop() {

Plus i figured out Rule 5 which was only inserted as on start-up any button could be used as the first button press... Now its limited to BtnAEdge which is the starting point i desired ..

The Good News Is that The Code for the 1st 3 Buttons and 3 LEDs is complete and functioning as i wanted. :D

Now its time to turn my attention to the last 3 Buttons on this Sequence Control Panel these relate to "Fast 'non-stopping' movements" and are in many respects the same as what has already been achieved. The only difference is a 5 led chase indication on one button function instead of a single flashing or solid LED.

"But its still an LED that's either On or Off" I here You Cry LMAO

Seriously Steve
Thank-You for your valuable assistance and guidance,without which i most certainly wouldn't have achieved the above. I really appreciate your time spent with me. ;)

I hope you can follow my progress as i will be looming into unchartered waters soon... The Shift Registers are needed and I have many questions that need answers.

(Bear with me,. My intention is to complete this sequence panel and when it is functioning as intended I am going to re-write the sketch so that the Layout / Structure / Pins / LED and Button Names make more sense than at present ( seem to be a little confusing even to me at times)
 
Last edited:
HeeeellllllP Its All Gone Horibbly Wrong

:mad:

Just spent the entire day trying to figure out why the code posted previously works on 1 control panel but when 2 control panels are linked together it doesn't :confused:

If you have a chance please can you look and advise.

All 3 statements in void buttonLogic() work on a single panel
2 outta 3 statements work when 2 panels are connected together.
When In this configuration:

ledOnA1 and LedOnB1 light when ButtonA1 pressed :cool:
ledOnA1 and LedOnB1 extinguish when ButtonB1 pressed :cool:
ledOnA2 and LedOnB2 light when ButtonB1pressed :cool:
ledOnA2 and LedOnB2 stay lit when ButtonB2 pressed :(
ledOnA3 lights ledOnB3 doesnt light when ButtonB2 is pressed :(
Locks out rest of sequence :(

Was wondering as i can't get 2 x Red led lit from 2 seperate I/O Pins whether this was the cause, single one is fine. 2 Orange leds fine, 2 green fine


void buttonLogic() {

/*
**** RULES FOR LONDON END SEQUENCE CONTROL PANELS *****

1) Pressing a button when its LED is off causes the LED to turn on
2) Cancel LED A1 when LED B is illuminated
3) Cancel LED B when LED C is illuminated
4) Cancel LED C when LED A is illuminated
5) Sequence Must Start with ButtonA
6) Sequence Must Be Button A, ButtonB, then ButtonC. Ensures Correct Sequence is followed (prevents inadvertant button push illuminating led out of sequence)
*/

// Gather Button Data for London End Sequence

bool btnA1Edge = pressEdge(currentButtonA1, lastButtonA1);
bool btnA2Edge = pressEdge(currentButtonA2, lastButtonA2);

bool btnB1Edge = pressEdge(currentButtonB1, lastButtonB1);
bool btnB2Edge = pressEdge(currentButtonB2, lastButtonB2);
bool btnB3Edge = pressEdge(currentButtonB3, lastButtonB3);

bool btnC1Edge = pressEdge(currentButtonC1, lastButtonC1);

bool btnD1Edge = pressEdge(currentButtonD1, lastButtonD1);
bool btnD2Edge = pressEdge(currentButtonD2, lastButtonD2);
bool btnD3Edge = pressEdge(currentButtonD3, lastButtonD3);
bool btnD4Edge = pressEdge(currentButtonD4, lastButtonD4);

// ** LONDON END FIDDLE YARD SEQUENCE CONTROL PANEL RULES **

// ** LONDON END MAIN SEQUENCE CONTROL PANEL RULES **

// ** COUNTRY END MAIN SEQUENCE CONTROL PANEL RULES **

// ** COUNTRY END FIDDLE YARD SEQUENCE CONTROL PANEL RULES **


if ((btnA1Edge) && !ledOnB2) { // Rule 1, Rule 6 for LED A
ledOnA1 = true;
ledOnB1 = true;
ledOnA2 = false; // Rule 4
ledOnB2 - false;
}

if ((btnB1Edge) && !ledOnB2 && ledOnA1) { // Rule 1,Rule 5 Rule 6 for LED B
ledOnA2 = true;
ledOnB2 = true;
ledOnA1 = false; // Rule 2
ledOnB1 = false;
}

if ((btnB2Edge) && !ledOnA1 && ledOnB2) { // Rule 1, Rule 5 Rule 6 for LED C
ledOnA3 = true;
ledOnB3 = true;
ledOnA2 = false;
ledOnB2 = false;

/* FOR INFO: ledA1 and ledB1 are selected On by btnA1Edge
ledA1 and ledB1 are selected Off by btnB1Edge
ledA2 and ledB2 are selected On by btnB1Edge
ledA2 and ledB2 are selected Off by btnB2Edge
ledA3 and LedB3 are selected On by btnB2Edge
ledA3 and LedB3 are selected Off by btnA1Edge
*/

}
}


FULL CODE
/*
Date Last Test undertaken : 27/09/2013

Sketch Modification Undertaken: Major Upgrade to include London End Fiddle Yard Sequence

*****************************************************************************************
CURRENTLY WORKING ON DIVIDING RULES INTO EACH PANEL
FINAL ACTION MUST BE TO RE-ASSIGN I/O PINS TO SEQUENTIAL ORDER
*****************************************************************************************


Test Results: LONDON END MAIN DOWN PANEL TESTED ON SLOW TRAINS ONLY
RULES 1 TO 6 CORRECT ON LONDON END MAIN DOWN PANEL

Next Action: Major Upgrade to include London End Fiddle Yard Sequence

Following Action : Modify input/output pins to align sequential



Eridge Operator Sequence Panel - This Sketch is used to confirm next step and test prior to saving to Final Sketch
- This Sketch will deal only with DOWN LINE Operations ONLY

The aim is to use MO Pushbutton Switches and LEDs to communicate between each operator the current sequence of events during a running session



***** DOWN MAIN SEQUENCE CONTROL I/O FUNCTIONS *****


** LONDON END FIDDLE YARD SEQUENCE CONTROL PANEL **

buttonA1 - London End Fiddle Yard Down "Train Ready Dispatch" (Button A)

buttonA2 - London End Fiddle Yard Fast Down "Fast Service Ready Dispatch" (Button D)

ledA1 - Amber Solid - "Train Ready Dispatch" (LED A)
ledA2 - Green Solid - "Train Accepted" (LED B1)
ledA3 - Red Flashing - "Ready Next Service" (LED C1)

ledA4 - Amber Solid - "Fast Service Ready Dispatch" (LED D)
ledA5 - Green Solid - "Route Set" (LED E1)
ledA6 - Green Array - "Down Fast Accepted" (LED F2)

** LONDON END MAIN SEQUENCE CONTROL PANEL **

buttonB1 - London End Main Down "Train Accepted" (Button B)
buttonB2 - London End Main Down "Ready Next Service" (Button C)

buttonB3 - London End Main Fast Down "Fast Route Set" (Button E)

ledB1 - Amber Flashing - "Train Awaiting Acceptance" (LED A1)
ledB2 - Green Solid - "Accept Down Train" (LED B)
ledB3 - Red Solid - "Ready Next Service" (LED C)

ledB4 - Amber Flashing - "Fast Service Ready Dispatch" (LED D1)
ledB5 - Green Solid - "Route Set" (LED E)
ledB6 - Green Array - "Down Fast Accepted" (LED F1)

** COUNTRY END MAIN SEQUENCE CONTROL PANEL **

buttonC1 - Country End Main Control Down " Train Ready Dispatch"

ledC1 - Amber Solid - "Train Ready Dispatch"
ledC2 - Green Solid - "Down Train Accepted"
ledC3 - Red Flashing - "Ready Next Service"

ledC4 - Green Array - "Next Train Fast Service"


** COUNTRY END FIDDLE YARD SEQUENCE CONTROL PANEL **

buttonD1 - Country End Fiddle Yard Down - "Train Awaiting Acceptance"
buttonD2 - Country End Fiddle Yard Down - "Train Accepted"
buttonD3 - Country End Fiddle Yard Down - "Ready Next Service"

buttonD4 - Country End Fiddle Yard Down " Accept Down Fast" (Button F)

ledD1 - Amber Flashing - "Train Awaiting Acceptance"
ledD2 - Green Solid - "Train Accepted"
ledD3 - Red Solid - "Ready Next Service"

ledD4 - Amber Flashing - "Accept Down Fast" (LED E2)
ledD5 - Green Array - "Down Fast Accepted" (LED F)

*/


// **** CODE STARTS HERE ****

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

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
}


// **** INPUTS ON DOWN LINE SEQUENCE CONTROL PANELS ****


// ** LONDON END FIDDLE YARD DOWN SEQUENCE PANEL **


// buttonA1 - London End Fiddle Yard Down "Train Ready Dispatch" (ButtonA)
const int buttonA1 = 7;
bool lastButtonA1;
bool currentButtonA1;
long lastButtonA1Time;


// buttonA2 - London End Fiddle Yard "Fast Train Ready Dispatch" (ButtonD)
const int buttonA2 = 0;
bool lastButtonA2;
bool currentButtonA2;
long lastButtonA2Time;



// ** LONDON END MAIN DOWN SEQUENCE PANEL **


// buttonB1 - London End Main Control Down "Train Accepted" (ButtonB)
const int buttonB1 = 8;
bool lastButtonB1;
bool currentButtonB1;
long lastButtonB1Time;

// buttonB2 - London End Main Control Down "Ready Next Service" (ButtonC)
const int buttonB2 = 9;
bool lastButtonB2;
bool currentButtonB2;
long lastButtonB2Time;

// buttonB3 - London End Main control Down "Route Set" (ButtonE)
const int buttonB3 = 0;
bool lastButtonB3;
bool currentButtonB3;
long lastButtonB3Time;




// ** COUNTRY END MAIN DOWN SEQUENCE PANEL **

// buttonC1 - Country End Main Control Down " Train Ready Dispatch"
const int buttonC1 = 0;
bool lastButtonC1;
bool currentButtonC1;
long lastButtonC1Time;



// ** COUNTRY END FIDDLE YARD DOWN SEQUENCE CONTROL PANEL **

//buttonD1 - Country End Fiddle Yard Down - "Train Awaiting Acceptance"
const int buttonD1 = 0;
bool lastButtonD1;
bool currentButtonD1;
long lastButtonD1Time;

//buttonD2 - Country End Fiddle Yard Down - "Train Accepted"
const int buttonD2 = 0;
bool lastButtonD2;
bool currentButtonD2;
long lastButtonD2Time;

//buttonD3 - Country End Fiddle Yard Down - "Ready Next Service"
const int buttonD3 = 0;
bool lastButtonD3;
bool currentButtonD3;
long lastButtonD3Time;

//buttonD4 - Country End Fiddle Yard Down " Accept Down Fast"
const int buttonD4 = 0;
bool lastButtonD4;
bool currentButtonD4;
long lastButtonD4Time;



// **** OUTPUTS ON DOWN LINE SEQUENCE CONTROL PANEL****


// ** LONDON END FIDDLE YARD SEQUENCE CONTROL PANEL **

//ledA1 - Amber Solid - "Train Ready Dispatch"
const int ledA1 = 2;
bool ledOnA1 = false;

//ledA2 - Green Solid - "Train Accepted"
const int ledA2 = 3;
bool ledOnA2 = false;

//ledA3 - Red Flashing - "Ready Next Service"
const int ledA3 = 4;
bool ledOnA3 = false;
bool ledA3FlashState = false;
long ledA3LastFlashTime = -(FLASH_TIME);


//ledA4 - Amber Solid - "Fast Service Ready Dispatch"
const int ledA4 = 0;
bool ledOnA4 = false;

//ledA5 - Green Solid - "Route Set"
const int ledA5 = 0;
bool ledOnA5 = false;

//ledA6 - Green Array - "Down Fast Accepted"
const int ledA6 = 0;
bool ledOnA6 = false;
bool ledA6FlashState = false;
long ledA6LastFlashTime = -(FLASH_TIME);


// **LONDON END MAIN SEQUENCE CONTROL PANEL **


//ledB1 - Amber Flashing - "Train Awaiting Acceptance"
const int ledB1 = 10;
bool ledOnB1 = false;
bool ledB1FlashState = false;
long ledB1LastFlashTime = -(FLASH_TIME);

//ledB2 - Green Solid - "Accept Down Train"
const int ledB2 = 11;
bool ledOnB2 = false;

//ledB3 - Red Solid - "Ready Next Service"
const int ledB3 = 12;
bool ledOnB3 = false;

//ledB4 - Amber Flashing - "Fast Service Ready Dispatch"
const int ledB4 =1;
bool ledOnB4 = false;
bool ledB4FlashState = false;
long ledB4LastFlashTime = -(FLASH_TIME);

//ledB5 - Green Solid - "Route Set"
const int ledB5 =1;
bool ledOnB5 = false;

//ledB6 - Green Array - "Down Fast Accepted"
const int ledB6 =1;
bool ledOnB6 = false;
bool ledB6FlashState = false;
long ledB6LastFlashTime = -(FLASH_TIME);


// ** COUNTRY END MAIN SEQUENCE CONTROL PANEL **


//ledC1 - Amber Solid - "Train Ready Dispatch"
const int ledC1 = 1;
bool ledOnC1 = false;

//ledC2 - Green Solid - "Down Train Accepted"
const int ledC2 = 1;
bool ledOnC2 = false;

//ledC3 - Red Flashing - "Ready Next Service"
const int ledC3 = 1;
bool ledOnC3 = false;
bool ledC3FlashState = false;
long ledC3LastFlashTime = -(FLASH_TIME);

//ledC4 - Green Array - "Next Train Fast Service"
const int ledC4 = 1;
bool ledOnC4 = false;
bool ledC4FlashState = false;
long ledC4LastFlashTime = -(FLASH_TIME);



// ** COUNTRY END FIDDLE YARD SEQUENCE CONTROL PANEL **


//ledD1 - Amber Flashing - "Train Awaiting Acceptance"
const int ledD1 = 1;
bool ledOnD1 = false;
bool ledD1FlashState = false;
long ledD1LastFlashTime = -(FLASH_TIME);


//ledD2 - Green Solid - "Train Accepted"
const int ledD2 = 1;
bool ledOnD2 = false;

//ledD3 - Red Solid - "Ready Next Service"
const int ledD3 = 1;
bool ledOnD3 = false;

//ledD4 - Green Flashing - "Route Set"
const int ledD4 = 1;
bool ledOnD4 = false;
bool ledD4FlashState = false;
long ledD4LastFlashTime = -(FLASH_TIME);


//ledD5 - Green Array - "Down Fast Accepted"
const int ledD5 = 1;
bool ledOnD5 = false;
bool ledD5FlashState = false;
long ledD5LastFlashTime = -(FLASH_TIME);



void writeLEDStates() { // WRITES THE CURRENT STATES TO THE LEDs


// ** LONDON END FIDDLE YARD SEQUENCE CONTROL PANEL **

// ** Flashing ledA3 **
long time = millis(); // get the time

if (ledOnA3) { // are we flashing?
if (time >= (ledA3LastFlashTime + FLASH_TIME)) { // time to chang LED state?
ledA3FlashState = !ledA3FlashState; // change LED state
ledA3LastFlashTime = time; // and set the time
digitalWrite(ledA3, ledA3FlashState); // Change LED state
}
} else { // not flashing?
digitalWrite(ledA3, false); // turn LED off

// ** Flashing ledA6 ** (LED ARRAY Needs Writing)

if (ledOnA6) {
if (time >= (ledA6LastFlashTime + FLASH_TIME)) {
ledA6FlashState = !ledA6FlashState;
ledA6LastFlashTime = time;
digitalWrite(ledA6, ledA6FlashState);
}
} else {
digitalWrite(ledA6, false);
}


// **SOLID**
digitalWrite(ledA1, ledOnA1);
digitalWrite(ledA2, ledOnA2);
digitalWrite(ledA4, ledOnA4);
digitalWrite(ledA5, ledOnA4);


// ** LONDON END MAIN SEQUENCE CONTROL PANEL **


// ** Flashing ledB1 **

if (ledOnB1) { // are we flashing?
if (time >= (ledB1LastFlashTime + FLASH_TIME)) { // time to chang LED state?
ledB1FlashState = !ledB1FlashState; // change LED state
ledB1LastFlashTime = time; // and set the time
digitalWrite(ledB1, ledB1FlashState); // Change LED state
}
} else { // not flashing?
digitalWrite(ledB1, false); // turn LED off
}

// **special code for flashing ledB4 **
if (ledOnB4) {
if (time >= (ledB4LastFlashTime + FLASH_TIME)) {
ledB4FlashState = !ledB4FlashState;
ledB4LastFlashTime = time;
digitalWrite(ledB4, ledB4FlashState);
}
} else {
digitalWrite(ledB4, false);
}
// ** Flashing ledB6 ** (LED ARRAY Needs Writing)

if (ledOnB6) {
if (time >= (ledB6LastFlashTime + FLASH_TIME)) {
ledB6FlashState = !ledB6FlashState;
ledB6LastFlashTime = time;
digitalWrite(ledB6, ledB6FlashState);
}
} else {
digitalWrite(ledB6, false);
}

// **SOLID**
digitalWrite(ledB2, ledOnB2);
digitalWrite(ledB3, ledOnB3);
digitalWrite(ledB5, ledOnB5);
}



// ** COUNTRY END MAIN SEQUENCE CONTROL PANEL **

// ** Flashing ledC3**

if (ledOnC3) { // are we flashing?
if (time >= (ledC3LastFlashTime + FLASH_TIME)) { // time to chang LED state?
ledC3FlashState = !ledC3FlashState; // change LED state
ledC3LastFlashTime = time; // and set the time
digitalWrite(ledC3, ledC3FlashState); // Change LED state
}
} else { // not flashing?
digitalWrite(ledC3, false); // turn LED off
}

// **SOLID**
digitalWrite(ledC1, ledOnC1);
digitalWrite(ledC2, ledOnC2);


// ** COUNTRY END FIDDLE YARD SEQUENCE CONTROL PANEL **

// ** Flashing ledD1 **

if (ledOnD1) { // are we flashing?
if (time >= (ledD1LastFlashTime + FLASH_TIME)) { // time to chang LED state?
ledD1FlashState = !ledD1FlashState; // change LED state
ledD1LastFlashTime = time; // and set the time
digitalWrite(ledD1, ledD1FlashState); // Change LED state
}
} else { // not flashing?
digitalWrite(ledD1, false); // turn LED off
}

// **special code for flashing ledD4 **
if (ledOnD4) {
if (time >= (ledD4LastFlashTime + FLASH_TIME)) {
ledD4FlashState = !ledD4FlashState;
ledD4LastFlashTime = time;
digitalWrite(ledD4, ledD4FlashState);
}
} else {
digitalWrite(ledD4, false);
}

// ** Flashing ledD5 ** (LED ARRAY Needs Writing)

if (ledOnD5) {
if (time >= (ledD5LastFlashTime + FLASH_TIME)) {
ledD5FlashState = !ledD5FlashState;
ledD5LastFlashTime = time;
digitalWrite(ledD5, ledD5FlashState);
}
} else {
digitalWrite(ledD5, false);
}


// **SOLID**
digitalWrite(ledD2, ledOnD2);
digitalWrite(ledD3, ledOnD3);
}

void setup()
{

// initialize pins
pinMode(buttonA1, INPUT);
pinMode(buttonA2, INPUT);

pinMode(buttonB1, INPUT);
pinMode(buttonB2, INPUT);
pinMode(buttonB3, INPUT);

pinMode(buttonC1, INPUT);

pinMode(buttonD1, INPUT);
pinMode(buttonD2, INPUT);
pinMode(buttonD3, INPUT);
pinMode(buttonD4, INPUT);


pinMode(ledA1, OUTPUT);
pinMode(ledA2, OUTPUT);
pinMode(ledA3, OUTPUT);
pinMode(ledA4, OUTPUT);
pinMode(ledA5, OUTPUT);
pinMode(ledA6, OUTPUT);

pinMode(ledB1, OUTPUT);
pinMode(ledB2, OUTPUT);
pinMode(ledB3, OUTPUT);
pinMode(ledB4, OUTPUT);
pinMode(ledB5, OUTPUT);
pinMode(ledB6, OUTPUT);

pinMode(ledC1, OUTPUT);
pinMode(ledC2, OUTPUT);
pinMode(ledC3, OUTPUT);
pinMode(ledC4, OUTPUT);

pinMode(ledD1, OUTPUT);
pinMode(ledD2, OUTPUT);
pinMode(ledD3, OUTPUT);
pinMode(ledD4, OUTPUT);
pinMode(ledD5, OUTPUT);

// set output states
writeLEDStates();

// get the initial state of buttons

currentButtonA1 = lastButtonA1 = digitalRead(buttonA1);
lastButtonA1Time = millis();

currentButtonA2 = lastButtonA2 = digitalRead(buttonA2);
lastButtonA2Time = millis();

currentButtonB1 = lastButtonB1 = digitalRead(buttonB1);
lastButtonB1Time = millis();

currentButtonB2 = lastButtonB2 = digitalRead(buttonB2);
lastButtonB2Time = millis();

currentButtonB3 = lastButtonB3 = digitalRead(buttonB3);
lastButtonB3Time = millis();

currentButtonC1 = lastButtonC1 = digitalRead(buttonC1);
lastButtonC1Time = millis();

currentButtonD1 = lastButtonD1 = digitalRead(buttonD1);
lastButtonD1Time = millis();

currentButtonD2 = lastButtonD2 = digitalRead(buttonD2);
lastButtonD2Time = millis();

currentButtonD3 = lastButtonD3 = digitalRead(buttonD3);
lastButtonD3Time = millis();

currentButtonD4 = lastButtonD4 = digitalRead(buttonD4);
lastButtonD4Time = millis();

}

void readSwitchStates() {
// read the current button states
simpleDebounce(buttonA1, currentButtonA1, lastButtonA1, lastButtonA1Time);
simpleDebounce(buttonA2, currentButtonA2, lastButtonA2, lastButtonA2Time);

simpleDebounce(buttonB1, currentButtonB1, lastButtonB1, lastButtonB1Time);
simpleDebounce(buttonB2, currentButtonB2, lastButtonB2, lastButtonB2Time);
simpleDebounce(buttonB3, currentButtonB3, lastButtonB3, lastButtonB3Time);

simpleDebounce(buttonC1, currentButtonC1, lastButtonC1, lastButtonC1Time);

simpleDebounce(buttonD1, currentButtonD1, lastButtonD1, lastButtonD1Time);
simpleDebounce(buttonD2, currentButtonD2, lastButtonD2, lastButtonD2Time);
simpleDebounce(buttonD3, currentButtonD3, lastButtonD3, lastButtonD3Time);
simpleDebounce(buttonD4, currentButtonD4, lastButtonD4, lastButtonD4Time);

}

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

}




void buttonLogic() {

/*
**** RULES FOR LONDON END SEQUENCE CONTROL PANELS *****

1) Pressing a button when its LED is off causes the LED to turn on
2) Cancel LED A1 when LED B is illuminated
3) Cancel LED B when LED C is illuminated
4) Cancel LED C when LED A is illuminated
5) Sequence Must Start with ButtonA
6) Sequence Must Be Button A, ButtonB, then ButtonC. Ensures Correct Sequence is followed (prevents inadvertant button push illuminating led out of sequence)
*/

// Gather Button Data for London End Sequence

bool btnA1Edge = pressEdge(currentButtonA1, lastButtonA1);
bool btnA2Edge = pressEdge(currentButtonA2, lastButtonA2);

bool btnB1Edge = pressEdge(currentButtonB1, lastButtonB1);
bool btnB2Edge = pressEdge(currentButtonB2, lastButtonB2);
bool btnB3Edge = pressEdge(currentButtonB3, lastButtonB3);

bool btnC1Edge = pressEdge(currentButtonC1, lastButtonC1);

bool btnD1Edge = pressEdge(currentButtonD1, lastButtonD1);
bool btnD2Edge = pressEdge(currentButtonD2, lastButtonD2);
bool btnD3Edge = pressEdge(currentButtonD3, lastButtonD3);
bool btnD4Edge = pressEdge(currentButtonD4, lastButtonD4);

// ** LONDON END FIDDLE YARD SEQUENCE CONTROL PANEL RULES **

// ** LONDON END MAIN SEQUENCE CONTROL PANEL RULES **

// ** COUNTRY END MAIN SEQUENCE CONTROL PANEL RULES **

// ** COUNTRY END FIDDLE YARD SEQUENCE CONTROL PANEL RULES **


if ((btnA1Edge) && !ledOnB2) { // Rule 1, Rule 6 for LED A
ledOnA1 = true;
ledOnB1 = true;
ledOnA2 = false; // Rule 4
ledOnB2 - false;
}

if ((btnB1Edge) && !ledOnB2 && ledOnA1) { // Rule 1,Rule 5 Rule 6 for LED B
ledOnA2 = true;
ledOnB2 = true;
ledOnA1 = false; // Rule 2
ledOnB1 = false;
}

if ((btnB2Edge) && !ledOnA1 && ledOnB2) { // Rule 1, Rule 5 Rule 6 for LED C
ledOnA3 = true;
ledOnB3 = true;
ledOnA2 = false;
ledOnB2 = false;

/* FOR INFO: ledA1 and ledB1 are selected On by btnA1Edge
ledA1 and ledB1 are selected Off by btnB1Edge
ledA2 and ledB2 are selected On by btnB1Edge
ledA2 and ledB2 are selected Off by btnB2Edge
ledA3 and LedB3 are selected On by btnB2Edge
ledA3 and LedB3 are selected Off by btnA1Edge
*/

}
}




void loop() {

while (true) {
// get input states
readSwitchStates();

// do the button logic
buttonLogic();

// set output states
writeLEDStates();
}
}

Thanks for looking . Any Advice Welcome :D
 
Fresh New Day Brings New Thoughts !!

OK I have solved the issue relating to the sequence working with 2 panels.

Code:
 bool btnA1Edge = pressEdge(currentButtonA1, lastButtonA1);
  bool btnA2Edge = pressEdge(currentButtonA2, lastButtonA2);
  
  bool btnB1Edge = pressEdge(currentButtonB1, lastButtonB1);
  bool btnB2Edge = pressEdge(currentButtonB2, lastButtonB2);
  bool btnB3Edge = pressEdge(currentButtonB3, lastButtonB3);
  
  bool btnC1Edge = pressEdge(currentButtonC1, lastButtonC1);
  
  bool btnD1Edge = pressEdge(currentButtonD1, lastButtonD1);
  bool btnD2Edge = pressEdge(currentButtonD2, lastButtonD2);
  bool btnD3Edge = pressEdge(currentButtonD3, lastButtonD3);
  bool btnD4Edge = pressEdge(currentButtonD4, lastButtonD4); 
 {

   if (btnA1Edge && !ledOnA1 && !ledOnA2 && !ledOnA2){
   ledOnA1 = true;
   ledOnA2 = false;
 }
     else if (btnA1Edge && ledOnA3){ // Rule 1, Rule 6 for LED A
     ledOnA1 = true;
     ledOnA2 = false;
     ledOnA3 = false;
     ledOnB1 = true;
     ledOnB2 = false;
     ledOnB3 = false;
     }
     else if (btnB1Edge && ledOnA1){ // Rule 1,Rule 5 Rule 6 for LED B
     ledOnA1 = false;
     ledOnA2 = true;
     ledOnA3 = false;
     ledOnB1 = false;
     ledOnB2 = true;
     ledOnB3 = false;
          }
     else if (btnB2Edge && ledOnA2) { // Rule 1, Rule 5 Rule 6 for LED C
     digitalWrite (ledA2, false);
     digitalWrite (ledB2, false);
     ledOnA1 = false;
     ledOnA2 = false;
     ledOnA3 = true; 
     ledOnB1 = false;
     ledOnB2 = false;
     ledOnB3 = true;
     }
 }
}

Slightly Mistifyed why need to write following statement in program

digitalWrite (ledA2, false);
digitalWrite (ledB2, false);
to get these two leds to switch off when the rest work as per code :confused:
Maybe Someone can enlighten me

Only thing now wrong is that only 1 led lights on ( ledOnA3, ledOnB3) statement but i'm convinced this is down to power not program.
I could be proved otherwise, NPN transistor enroute :D
 

(*steve*)

¡sǝpodᴉʇuɐ ǝɥʇ ɹɐǝɥd
Moderator
I posted a large reply, but loss of internet at an inopportune moment caused it to be lost :-(

The big hint with your fix above is that it means that your code is never turning those LEDs off. Check it again very carefully.

I posted a long tome on state machines. This is a technique which makes coding complex things like this a lot easier, but I don't have time to re-think it and re-post it now, so it will have to wait until I get home.

Sorry :-(
 
The big hint with your fix above is that it means that your code is never turning those LEDs off. Check it again very carefully.
:D Yup guessed that was the case, the biggest clue was the Green glow from the LEDs lol

Bummer bout loss of post
 
Last edited by a moderator:

(*steve*)

¡sǝpodᴉʇuɐ ǝɥʇ ɹɐǝɥd
Moderator
I might do this is parts (not the least reason being that I don't have a lot of time -- still)

I think you've reached the point in your coding that you need to abstract the problem one more level.

At the moment you get all the inputs, then decide from these inputs and the current state of the outputs, what the new outputs should be. Doing this is pretty simple at first, but rapidly gets more complex.

What you need is a way to reduce that complexity. One good way is with a state machine.

A state machine is a concept where your "device" has a set number of states. Each state has a fixed configuration of outputs, and a set of conditions that will lead to another state. This is easier because you have the knowledge of not only the inputs and outputs, but a higher level understanding of the meaning of these.

In addition to these things, the only other thing you need to know is, what state does the machine start up in.

As a safety feature, you normally encode one more thing -- a rule that places you in some defined state (most often the startup-state) if ever it detects that you are in an invalid state.

When you're coding this, you change the convoluted logic to something quite simple. First is a CASE (or a long compound IF) statement that tests for each state, then within each state tests for the conditions that would cause you to change state. Then you have a routine that sets the state. This will, in addition to setting the main state variable also set other outputs which must change as that state is entered.

There is some good information here. Note especially the diagram in the turnstile example. This can be simplified by removing all the transitions which do not change state ("push" in the "locked" state, and "coin" in the "unlocked" state) since we're frequently not concerned with things that have no effect.

In real life, the states are typically given a number. The locked state could be state 1, and the unlocked state could be state 2. The startup initialization would be to set the state to 1, and as a further belt and braces, code would change the state to 1 if it were ever other than 1 or 2 (if it fell off the end of a compound IF or CASE).

I find that it is often useful to also record the time of the state transition. This allows a very simple test for "have I been in this state for more than x time". Whilst you could use this to handle flashing (you'll probably note that a flashing LED controlled the way I suggested quite some time ago is very much like a state machine) is is more often used for things like timeouts, e.g. "if no button is pressed for 1 minute, reset".

I'll try to give you an example of this more relevant to your requirements soon(ish).
 
Hi Steve,

Yes your last post does seem to be the most logical step forward.

I stated previously that when the time came and no more pins were available for either inputs or outputs i would look at shift registers.

That time has come ... so i have spent a couple of days getting my head round shift registers and more importantly how to control individual pins within them.

When you're coding this, you change the convoluted logic to something quite simple. First is a CASE (or a long compound IF) statement that tests for each state, then within each state tests for the conditions that would cause you to change state. Then you have a routine that sets the state. This will, in addition to setting the main state variable also set other outputs which must change as that state is entered.

Actually the move to a state machine has come at the right time. The output states (LEDs) have now become their own function and now need tying back to the buttons within the state machine scenario.

This is my wee test of controlling the 74HC595 chip ( i have 2 connected together but for this purpose only used one shift register and it was merely as a test to see the what could be achieved.

Code:
#include <Shifter.h>

#define SER_Pin 11 //SER_IN
#define RCLK_Pin 9 //L_CLOCK
#define SRCLK_Pin 12 //CLOCK

#define NUM_REGISTERS 1 //how many registers are in the chain


//initaize shifter using the Shifter library
Shifter shifter(SER_Pin, RCLK_Pin, SRCLK_Pin, NUM_REGISTERS); 

void setup(){

}

void loop(){
  shifter.clear(); //set all pins on the shift register chain to LOW
  shifter.write(); //send changes to the chain and display them
    
  delay(1000);
  
  shifter.setPin(1, HIGH); //set pin 1 in the chain(second pin) HIGH
  
  
  shifter.write(); //send changes to the chain and display them
  //notice how you only call write after you make all the changes you want to make
  
  delay(1000);
  shifter.clear();
  shifter. setPin(2, HIGH); // sets led2 on
  
  shifter.write(); //send changes to the chain and display them
  
  delay(1000);
  shifter.clear(); // clears all (led2 now off)
  shifter. setPin(3, HIGH); // sets led 3 on
  
  shifter.write(); //send changes to the chain and display them
  
  delay(1000);
  
  shifter.setPin(4, HIGH); // sets led 4 on (leaves led 3 on from previous)
  
  
  shifter.write(); //send changes to the chain and display them
  
  delay(1000);
  shifter.clear();  // clears all
  shifter.setPin(5, HIGH); // sets led 5 on
  shifter.setPin(6, LOW); // sets led 6 low 
  delay(400);
  
  shifter.write(); //send changes to the chain and display them
  
  shifter.setPin(5, HIGH); // keeps led 5 on
  shifter.setPin(6, HIGH); // rest of sequence blinks led 6
  delay(400);
  
  shifter.write(); //send changes to the chain and display them
  
  shifter.setPin(5, HIGH);
  shifter.setPin(6, LOW);
  delay(400);
  
  shifter.write(); //send changes to the chain and display them
  
  shifter.setPin(5, HIGH);
  shifter.setPin(6, LOW);
  delay(400);
   
  shifter.write(); //send changes to the chain and display them
  
  shifter.setPin(5, HIGH);
  shifter.setPin(6, HIGH);
  delay(400);
  
  shifter.write(); //send changes to the chain and display them
  
  shifter.setPin(5, HIGH);
  shifter.setPin(6, LOW);
  delay(400);
  
  shifter.write(); //send changes to the chain and display them
  
  shifter.setPin(5, HIGH);
  shifter.setPin(6, HIGH);
  delay(400);
  
  shifter.write(); //send changes to the chain and display them
  
  shifter.setPin(5, HIGH);
  shifter.setPin(6, LOW);
  delay(400);
  
  shifter.setPin(5, HIGH);
  shifter.setPin(6, HIGH);
  delay(400);
  
  shifter.write(); //send changes to the chain and display them
  
  shifter.setPin(5, HIGH);
  shifter.setPin(6, LOW);
  delay(400);
  
  shifter.write(); //send changes to the chain and display them
  
  shifter.setPin(5, HIGH);
  shifter.setPin(6, HIGH);
  delay(400);
  
  shifter.write(); //send changes to the chain and display them
  
  shifter.setPin(5, HIGH);
  shifter.setPin(6, LOW);
  delay(400);
  
  shifter.setPin(5, HIGH);
  shifter.setPin(6, HIGH);
  delay(400);
  
  shifter.write(); //send changes to the chain and display them
  
  shifter.setPin(5, HIGH);
  shifter.setPin(6, LOW);
  delay(400);
  
  shifter.write(); //send changes to the chain and display them
  
  shifter.setPin(5, HIGH);
  shifter.setPin(6, HIGH);
  delay(400);
  
  shifter.write(); //send changes to the chain and display them
  
  shifter.setPin(5, HIGH);
  shifter.setPin(6, LOW);
  delay(400);
  
  shifter.setPin(5, HIGH);
  shifter.setPin(6, HIGH);
  delay(400);
  
  shifter.write(); //send changes to the chain and display them
  
  shifter.setPin(5, HIGH);
  shifter.setPin(6, LOW);
  delay(400);
  
  shifter.write(); //send changes to the chain and display them
  
  shifter.setPin(5, HIGH);
  shifter.setPin(6, HIGH);
  delay(400);
  
  shifter.write(); //send changes to the chain and display them
  
  shifter.setPin(5, HIGH);
  shifter.setPin(6, LOW);
  delay(400);
  
   shifter.write(); //send changes to the chain and display them
}

What i have noticed is that the LEDs that do not need a seperate function (flashing) can be attached to the same pin on the shift register as the primary LEDs... thus saving more pins

Getting Back To The State Machine


When you're coding this, you change the convoluted logic to something quite simple. First is a CASE (or a long compound IF) statement that tests for each state, then within each state tests for the conditions that would cause you to change state..

So I have 6 pushbuttons on these 2 panels. They are:

1. Train Ready for Dispatch
2. Train Accepted
3. Awaiting Next Service
4. Fast Down Service Ready Dispatch
5. Route Set
6. Fast Down Service Accepted

These Change the States

State Machin Flow1.jpg

BEST GET TO WORK :)


.
 

(*steve*)

¡sǝpodᴉʇuɐ ǝɥʇ ɹɐǝɥd
Moderator
Looks like you're doing the right thing as regards the 74HC595.

With regards to the state machine, I would recommend all the "shifter" stuff is in the code whivh handles the outputs, not in the state machine itself. The reason is that the state machine should be kept independent of the implementation

Also, I'd advise a routine something like this (forgive me for C coding errors, I'm doing this without the benefit of my compiler or code...):

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

  void function setStateI(int &iState, long &lTime, int iValue) {
    if (iState != iValue) {
      iState = iValue;
      lTime = millis();
    }
  }

This code will allow you to set a variable to a particular value and record when the variable's value changed (this is handy when you are interested in how long a variable has been in a particular state -- for example for a flashing LED -- but where the variable might be "set" to the same value frequently).

Then your state machine might look like this:

Code:
void function stateChange() {
  switch( iState )  {     
    case 0:         
      if (some button pressed) {
        machineState(1);
      } else if (some time expired) {
        machineState(2);
      } 
      break;     
  
    case 1:         
      if (a sensor triggered) {
        machineState(0);
      } 
      break;     
  
    case 2:         
      if (some time expired) {
        machineState(3);
      } 
      break;     

    case 3:
      if (a button pressed) {
        machineState(0);
      } else if (some time expired) {
        machineState(2);
      } 
      break; 

    default:
      machineState(0);
      break;
  }
}

All this code is concerned with are the methods of moving from one machine state to another. (The tests need to be entered. They could refer to almost anything, including output states. For example, we might only take notice of a press of button A when the LED2 is flashing and is currently ON).

And what is this machineState? It is the piece of code that sets things when the state changes. This particular implementation requires that all the state variables are global.

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.

Once you code something up like this, you have several modules in your code.

1) A module to get all your inputs. This only changes (and is all that changes) if your inputs change. e.g. if button A is now on a different pin, or if it is multiplexed with several other things using the input equivalent of the 74HC595.

2) A module to determine if the current state means we need to change from one machine state to another. This only changes if the rules governing how we change from state to state change.

3) A module setting the states. This defines what output states are set at each machine state. This is only changed when the outputs for a stare are redefined.

4) A module to write the outputs. This only changes when the method of setting an output changes (for example you change from using a pin to directly control a LED to using a 74HC595 to control a number of LEDs.

Added to this, there is one more module:

0) A module to set the initial conditions. It is possibly as simple as call to machineState().

In most cases this will be sufficient, however you might like to change module 4 so that it only writes outputs if they have changed. This might be as simple as seeing if the state has changed, or it might be more complex (it will if LEDs are flashing)

If you make use of the setStatex functions whenever a state is set, you could modify them like this:

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

  void function setStateI(int &iState, long &lTime, int iValue) {
    if (iState != iValue) {
      iState = iValue;
      lTime = millis();
      bChanged = true;
    }
  }

With this, you can set all of your states (and then all of your sub-states in the output routine) and check for bChanged being true. If it is, write the outputs, if not don't bother. Finally, clear bChanged (or do it at the top of the state setting module

Code:
void function stateChange() {
  bChanged = false;

  switch( iState )  {     
    case 0:         
      if (some button pressed) {
        machineState(1);
      } else if (some time expired) {
        machineState(2);
      } 
      break;     
...

The problem with this is that the first time through, the outputs will probably not be set because bChanged is false. This may be a bad bug, but we can turn it to our advantage.

Back to something I said earlier. I said the initialization could be as simple as setting a state.

If you initialize the state to a particular initialization state which the state machine will *always* change immediately to another state (the most obvious one is an invalid state) then you will force the first time through to generate a change, and place the system into the correct state.

You might initialize with

machineState(-1);

So, your main loop might look like this

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

void Loop() {
  getInput(); // what are the inputs
  stateChange(); // manage the state machine
  setOutput(); // write outputs
}
 
Code:
void function setStateB(bool &bState, long &lTime, bool bValue) {
    if (bState != bValue) {
      bState = bValue;
      lTime = millis();
    }
  }

  void function setStateI(int &iState, long &lTime, int iValue) {
    if (iState != iValue) {
      iState = iValue;
      lTime = millis();
    }
  }

Not surprisingly given my very limited knowledge (nearly able to fill the back of a postage stamp) i didn't understand this coding when it was first posted and tbh i still don't.


This code will allow you to set a variable to a particular value and record when the variable's value changed (this is handy when you are interested in how long a variable has been in a particular state -- for example for a flashing LED -- but where the variable might be "set" to the same value frequently).

Set a variable to a particular value and record when the value has changed.... understand the concept but what value and why that value and how do you see it has changed :eek:
.
 

(*steve*)

¡sǝpodᴉʇuɐ ǝɥʇ ɹɐǝɥd
Moderator
Remember that we had states and times for things (LED1, for example).

So bLed1State was a boolean indicating whether the LED was on or off, and lLED1Time was the time that the LED changed state. The time is required for things like flashing LEDs so we can tell if it is currently on or off. It's also useful for us to be able to test how long it has been activates (to allow you to have rules about that).

These functions allow you to turn on or off a boolean variable, and set the time, only if the state changes. This if you set the LED1 state 10 times to true, the time reflects the first time you set it to true, so it just keeps flashing.

The other function does the same for a state which is a number. Same thing, but only sets the time if the numerical value changes.

In either case, if you don't need the timestamp value, you can just pass a dummy variable (i.e. you define a variable -- say lDummyTimestamp, and use that. You then never refer to it elsewhere). This might seem strange, but later I suggest you add a "changed" status to this function. Because you use what appears to be a superfluous function to set the state, you can now take advantage of that to add functionality to allow the code to be more efficient.

I'm probably going a little fast for you...
 

(*steve*)

¡sǝpodᴉʇuɐ ǝɥʇ ɹɐǝɥd
Moderator
Code:
Set a variable to a particular value and record when the value has changed.... understand the concept but what value and why that value and how do you see it has changed :o[/QUOTE]

In the second version of that code I added a bChanged = true into the function when the value was set.

So imagine you're in state 1, and in the stateChange function you have:

[code]
    case 1:                
      if (a sensor triggered) {
         machineState(0);
       }
        break;
And let's assume "a sensor triggered" is something which is true -- it might be "bSwitch1Pressed == true".

So that means we need to change the state machine to state 0 from state 1.

And recall in this particular example, the machineState function looked like this:

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

Let's assume that LED1 was already ON (true) and LED2 was OFF (false).

After these 2 calls, both would be true, but only lLed2Time would be the "current" time, and because LED2 changed, (using the latter definition) bChanged is not true.

So in the function which handles the output we can do this:

Code:
  if (bChanged) {
    shifter.clear();

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

    shifter.write();
  }

And the code only does the hard work of setting the states if something has changed.

There are a number of reasons why you'd want to do this.

1) it reduces the amount of unnecessary switching of signals. These can generate electrical noise.

2) If your code to set the states takes quite a while, it is more efficient only to do it when you actually need to.

If one of your LEDs is flashing (say LED1), you might have another state bLED1FlashState which is true when the LED is on, and false if it is off. (Remember previously we had discussed how this would look to see if the LED was flashing (bLED1 == true) and the time since it started (millis() - lLed1Time) to determine if it was on or off).

I have referred to this as a secondary status previously. This would be tested and set in your output routine, and might also set the bChanged variable.

Of necessity, this setting of the secondary state needs to precede the test for bChanged == true.
 
Hi Steve,

Thanks for the explanation

Still trying to get my head round all this :) Previous to this we have used code to control some "physical" function ( a button or a led). Since "shifting out" the leds can no longer be used as a pre-requiste for a button press to be "true". However a dummy code can still be used... erm i think

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

  void function setStateI(int &iState, long &lTime, int iValue) {
    if (iState != iValue) {
      iState = iValue;
      lTime = millis();
      bChanged = true;
    }
  }
  }

I now understand this a little more..... So am i correct in saying that these variables control whether or not the state qualifies to be changed.. if they return true it allows a state change and if false no state change can take place ?

Also that as i have
So I have 6 pushbuttons on these 2 panels. They are:

1. Train Ready for Dispatch (setStateA)
2. Train Accepted (setStateB)
3. Awaiting Next Service (setStateC)
4. Fast Down Service Ready Dispatch (setStateD)
5. Route Set (setStateE)
6. Fast Down Service Accepted (setStateF)

ADDED TEXT FOR CLARITY IN ABOVE QUOTE

These are the states.... SO i would need (setStateA), (setStateB), (setStateC), (setStateD), (setStateE) and (setStateF) in the void function setState

Code:
void function stateChange() {
  switch( iState )  {     
    case 0:         
      if (some button pressed) {
        machineState(1);
      } else if (some time expired) {
        machineState(2);
      } 
      break;     
  
    case 1:         
      if (a sensor triggered) {
        machineState(0);
      } 
      break;     
  
    case 2:         
      if (some time expired) {
        machineState(3);
      } 
      break;     

    case 3:
      if (a button pressed) {
        machineState(0);
      } else if (some time expired) {
        machineState(2);
      } 
      break; 

    default:
      machineState(0);
      break;
  }
}

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?
 
Last edited:
Top