Hello all, I'm building a model railway and wanting to use the Arduino to control my colour light signals, it seemed quite easy, the signal shows a green light, a train is detected passing the signal, and then the signal shows a red light for 5 seconds then an amber light for 5 seconds and then back to green. I did that with the delay command and it worked no problem, but I've been trying to do it with the millis() command so the Arduino can do other things - control more than one signal and also detect a train passing the signal at amber. So far I've been trying with various options but can't get any of them to work, I'd assumed the way to go is the switch / case command but so far I've not had any luck working out how to do it. Can anyone point me in the direction I need to go - is the switch / case approach correct? Many thanks
Post your best attempt.
I don't know how helpful it is but I've had some success with this technique for millis():
set a variable with the delay time you want, outside of the loop() (eg delaytime). Then make a new variable inside the loop that is set to millis() every time the thing happens that you want to base the delay on (eg begintime). Then you can put an if statement in which is active only when millis() - begintime > delaytime. That way the loop should be able to go round and do other things while this delay is happening.
What you're looking for is a finite state machine; you can implement that with a switch/case. First define your states (you have already done that).
- Green
- Amber
- Red
In each state test if an 'event' occurs that will cause it to change to another state; e.g.
- In state Green you will check if a train passed the signal; if so set the start time (millis()) indicating when you switched to the Amber state, set the signal to amber and change the state to the Amber state.
- In state Amber, check if the time (5 seconds) has lapsed. Set the new start time indicating when you switched to the Red state, set the signal to red and change the state to the Red state.
- In state Red, check if the time (5 seconds) has lapsed. Set the new start time indicating when you switched to the Green state, set the signal to green and change the state to Green.
You can use any conditions in the states and you can jump around as you see fit; you can e.g. in the Amber state detect that the train passes the signal and take an appropriate action like cutting the power to prevent accidents.
Once you have mastered the idea with one signal, we can guide you through the process of using arrays of objects (structs, classes) where each object represents a signal and contains the relevant information like current state, start time and so on.
Hi there wildbill, the millis attempt turned into quite a mess and didn't seem to be an approach that was going to work, as sterretje has suggested below, I'll have a go at the finite state machine approach, once I've come up with something that might look like it'll work, I'll post. Thanks for responding
Many thanks for that, it's certainly something I'm going to try next
Thanks for the input, lots of ways to consider making this work if my brain it up to it!
Many thanks for the link, I shall certainly look at that
Just add in case I wasn't clear, passing green light, light goes to red (danger), then amber (caution) then green (clear), passing amber, light goes to red, then amber and green but your explanation of the states is very good so thank you
Sorry, my mistake. Good luck in implementing the idea. Let us know.
No problem, give me a couple of weeks to try and get my V1.0 brain around it and I'll come back with what I've done!
I say it a lot, but this a great time to learn about structs and arrays.
A struct to hold all the values for each signal - the pin number, perhaps a long millis value for each of red green and yellow.
During operation, these could be set to your desired on-time, and counted down in loop() until they reach zero.
All lights are processed in each cycle of loop(), so they can operate ‘independently - you could have red and yellow on for an overlapping period if desired.
Now… arrays. An array of your ‘signal’ struct - lets you define all your signals in one place and operate them as members of the array.
e.g.
signal[2].redMs = 2500; // turn red on for 2.5secs
if (signal[2].greenMs == 0 { // if time is elapsed
digitalwrite(signal[2].greenPin, LOW);
}
and similar.
Instead of being timed, wouldn't it better if a second sensor in the next block set the signal back to green?
That's a great idea and some people do it that way, but my layout is mostly complete and putting in the extra sensors would be impractical now. If you have a really big layout you can go the whole hog, train passes signal 1 and green goes red, train passes signal 2 and signal 2 goes red and signal 1 goes amber, train passes signal 3 and signal 2 goes amber and signal one goes green. That would look really smart but my layout is no where near big enough and on some loops I only have a single signal. Thanks for the input though
Thanks, something else to look into, I'm a beginner in all this and it's quite exciting to think of the possibilities
Well thank you everyone, I don't suppose this is the most beautiful solution but it pretty much is the limit of my brain power and it works! If a train is detected the signal goes from green to red, then a user defined time passes and the light sequences to amber, double amber and back to green. If a subsequent train passes the signal at amber or double amber then the signal goes to red
#define signal1time 4000 // [ms] time for lamps to be on (4 seconds)
#define green1_pin 3 // green LED is in pin 3
#define amberlower1_pin 4 // lower amber LED is in pin 4
#define amberupper1_pin 5 // upper amber LED is in pin 5
#define red1_pin 6 // red LED is in pin 6
#define train_detection_pin1 2 // train detection sensor is in pin 2
byte green1_led = 1;
byte amberl1_led = 0;
byte amberu1_led = 0;
byte red1_led = 0;
byte green1_enabled, amberl1_enabled, amberu1_enabled, red1_enabled; //
byte state = 1, transition;
unsigned long start_timer; // variable to turn lights on at a specfic time
unsigned long end_timer; // variable to turn lights on at a specfic time
void setup() {
// put your setup code here, to run once:
pinMode (green1_pin, OUTPUT);
pinMode (amberlower1_pin, OUTPUT);
pinMode (amberupper1_pin, OUTPUT);
pinMode (red1_pin, OUTPUT);
pinMode (train_detection_pin1, INPUT_PULLUP);
Serial.begin(9600);
Serial.println("waiting for train detection");
digitalWrite(green1_pin, HIGH);
}
void loop() {
// put your main code here, to run repeatedly:
switch (state) {
case 1: //green idle
// code to run if state = 1
if (digitalRead(train_detection_pin1)==LOW){ //if train is detected set transition state to 12
transition =12;
start_timer=millis();
end_timer=millis() + (unsigned long)signal1time;
}
break;
case 2: //red
// code to run if state = 2
start_timer=millis();
end_timer=millis() + (unsigned long)signal1time;
transition=23;
state=0;
break;
case 3: //amber lower
// code to run if state = 3
start_timer=millis();
end_timer=millis() + (unsigned long)signal1time;
transition =34;
state=0;
break;
case 4: //amber upper
// code to run if state = 4
start_timer=millis();
end_timer= millis() + (unsigned long)signal1time;
transition =41;
state=0;
break;
}
switch (transition) {
case 12:
//code for transition 12 goes here
Serial.print("red light ");Serial.print(state);Serial.print(" ");Serial.println(transition);Serial.print(" ");Serial.print(end_timer);
digitalWrite(red1_pin, HIGH);
digitalWrite(green1_pin, LOW);
if (millis() > end_timer) {
transition=0;
state=2;
}
break;
case 23:
//code for transition 23 goes here
Serial.print("lower amber ");Serial.print(state);Serial.print(" ");Serial.println(transition);Serial.print(" ");Serial.print(end_timer);
digitalWrite(red1_pin, LOW);
digitalWrite(amberlower1_pin, HIGH);
if (digitalRead(train_detection_pin1)==LOW){ //if train is detected set transition state to 12
transition =12;
start_timer=millis();
end_timer=millis() + (unsigned long)signal1time;
digitalWrite(amberlower1_pin, LOW);
break;
}
if (millis() > end_timer) {
transition=0;
state=3;
}
break;
case 34:
//code for transition 34 goes here
Serial.print("upper amber ");Serial.print(state);Serial.print(" ");Serial.println(transition);Serial.print(" ");Serial.print(end_timer);
digitalWrite(amberupper1_pin, HIGH);
if (digitalRead(train_detection_pin1)==LOW){ //if train is detected set transition state to 12
transition =12;
start_timer=millis();
end_timer=millis() + (unsigned long)signal1time;
digitalWrite(amberlower1_pin, LOW);
digitalWrite(amberupper1_pin, LOW);
break;
}
if (millis() > end_timer) {
transition=0;
state=4;
}
break;
case 41:
//code for transition 41 goes here
digitalWrite(amberupper1_pin, LOW);
digitalWrite(amberlower1_pin, LOW);
digitalWrite(green1_pin, HIGH);
transition =0;
state=1;
break;
}
}
Please use code tags when posting code. Edit your post, select all code and click the </>
button;next save your post It makes it easier to read and copy and prevents the forum software from interpreting the code as instructions to format the text in e.g. italics.
Fixed