MultiTasking Model Railroad block signals

I am new to the Arduino world and have decided to use the forced down time to tackle a project for my mid-sized HO model railroad. In general I am using the Arduino to monitor a section of track (block) for occupancy by a train. After looking at all the options to detect the occupancy I have decided to use a ACS712 current sensor to monitor track current. If a block is clear of traffic it will display a green LED. If the block is occupied then a red LED will be lighted. If a train leaves a block a yellow LED will flash for a set number of seconds. I have been able to write code that does that all for a single block but want to see if I can have one Arduino control more than one block. I believe the key capability I need is multitasking. I believe that somehow I need to replace the delay function to do the flashing of the yellow LED with the millis() function. But I am having a hard time figuring out how to change the code to allow for multitasking.

Sketch is attached.

Suggestions please

BV_signal_R5.ino (3.66 KB)

It's not multitasking, you search, you jimply need to replace the delays() with "look at the ewatch. if 500ms have passed, turn off the led"-stuff.

Hi,
Welcome to the forum.

Please read the post at the start of any forum , entitled "How to use this Forum".
OR
http://forum.arduino.cc/index.php/topic,148850.0.html.
Then look down to item #7 about how to post your code.
It will be formatted in a scrolling window that makes it easier to read.

Can you please post a copy of your circuit, in CAD or a picture of a hand drawn circuit in jpg, png?

Thanks.. Tom... :slight_smile:

Thanks for the replies so far. I did attach the complete sketch before but I guess I should have highlighted the specific code I was referencing. I will use the code feature and attach relavent code at the of this message.
As far as the suggestion on changing the delay code to something like "look at the ewatch. if 500ms have passed, turn off the led". My limited reference material does not have any syntax on how to write that statement. If you can point me to a source for the syntax that would be great.

I will work on getting a picture of the circuit but it is very simple.

Thanks

// Block not occupied right now
   else if ( prevState == 1) 
   {
        digitalWrite(Green1,LOW); // Turn off Green signal
        digitalWrite(Red1,LOW); // Turn off Red signal
        digitalWrite(Yellow1,HIGH);
        // flash Yellow for X seconds
        for (int j = 0; j <= yellowBlinkNum; j++) 
          {
            digitalWrite(Yellow1,LOW);  //Turn on Yellow
          delay (blinkDelay);
          digitalWrite(Yellow1,HIGH);
          delay (blinkDelay);
          }
/code]

You might want to contact me off list. I have already designed a prototypical signal system. You won't find it any simpler, nor, more correctly operational.

I am not posting an email here, because I do not know if that is allowed. I will monitor responses, and we will connect.
You CAN contact me through this website, however. I can save you a lot of headaches, and solve your problem, easy, and free.

Phandangle...

@srobb725, please include short programs directly in your Post so we don't have to download them - use the code button </>. See How to use the forum

The functions delay() and delayMicroseconds() block the Arduino until they complete.
Have a look at how millis() is used to manage timing without blocking in Several Things at a Time.

And see Using millis() for timing. A beginners guide if you need more explanation.

...R

Phandangle:
You might want to contact me off list.

It would be more helpful for other Forum users if you help the OP here in the public Forum.

...R

No, I do not have to. If someone wants it, they can contact me. I am the copyright owner, and although it is free, it is my choice, to share it here or not.

If this forum is about everything being shared, then y ou need to stop the moderators who are butt wipes while helping someone, as you are. THIS is the reason I do NOT share my designs, or ideas. The arrogance is astonishing. If people who DESERVE respect, aren't giving it, Then folks like myself, will enjoy the benefits, and the people here who have done WORSE to myself, like YOU CENSORING ME, deserve nothing in return.

NOW.... If you would have not censored me, I might have considered it. After this, you can count on me NOT sharing my hard work. If you do not allow him to contact me, you are doing no service to anyone but yourself!

Here is a link you can check out. THIS IS CALLED HELPING OTHERS!!!! NOT forcing someone to give away everything for someone else's profit..... This is a free society. PERIOD! If you want to have your little power trip, then, I refuse to help anyone like you. I would appreciate it, if you allowed the help you say you are doing.

Respectfully...

Phandangle....

PLUS!!! It may say I am a "NEWBIE", it doesn't even HAVE A DECIMAL POINT to the experience I COULD be sharing. I do NOT because of a totalitarian style moderation given here.

EVERYONE'S loss, don't you agree???

I prolly have more experience in Software/digital hardware design than you will have in the next life. I have been doing this for my career, for over 60 years.
I have a masters in digital design, and have been involved in designs for MAJOR corporations, during my career. I don't BRAG much about it, and I am not arrogant. Confidence is NOT arrogance, except to those who are insecure....

JUST SO YOU KNOW!!!

I can tell you, nothing in the program list will work correctly, if you want authenticity. I already have firmware written for any Arduino chip, and everything has been working JUST DUCKY on my layout, without any problems. Over complicating the firmware, is the wrong thing to do.

Now, he can contact me OFF LIST, as anyone else can here if they wish. If you do not allow it, then everyone but myself loses. I am a bit tired of these little tyrants on here, you can only control my posts... outside of that, you have NO CONTROL over me, nor my life, or designs.....

GIVE RESPECT ROBIN... AND YOU WILL GET IT IN RETURN!

YOU have to understand, there are TONS of people out here, who will not respond well to that kind of treatment.

What's all the fuss about? 6 Leds + the the typical beginner problems. Nice, to have a working example. If not, well, think about it: TO will solve the problem on his own in some days.

That's what I am complaining about... THE FUSS!!! Let people learn, they way they learn BEST. If someone offers a solution, let them use it!!! Maybe I will share it later down the road. I am sick and tired of dealing with young little tyrants, who think just because they can censor someones post they do not like, gives them power over someone else... NOT!!!!

You treat people like crap, you reap the benefits of NOT GETTING WHAT YOU WOULD LIKE TO LEARN.

If you p*** off an highly experienced person, who could simplify someones dream, and let them learn at the same time, then the problem is with the system.

I make NO PROFIT off of my design, and I will not release the firmware, in the foreseeable future. The kind of treatment I got here today, just solidified my belief.

You can delete my posts, you can get all p'od, cuz I don't conform to your "CONTROLLING moderators, but in the end, you are hurting NO ONE, but the person asking for help!!!

That is why I hardly ask a question here anymore. I have learned there are severe prejudices on here, from SOME of the moderators. If I get that kind of treatment, you can count on me being an A**wipe.

Phandangle:
No, I do not have to.

Of course you don't have to.

I just hoped you might be generous in the spirit of Open Source that is at the heart of the Arduino system.

...R

Phandangle:
You can delete my posts, you can get all p'od, cuz I don't conform to your "CONTROLLING moderators, but in the end, you are hurting NO ONE, but the person asking for help!!!

That is why I hardly ask a question here anymore. I have learned there are severe prejudices on here, from SOME of the moderators. If I get that kind of treatment, you can count on me being an A**wipe.

If you can't be polite and helpful that might be the best thing.

I have no idea what got into your head to give rise to that rant.

...R

Well, some people get a bit edgy in times like these. Make a fire, roast some potatoes, enjoy the sunset, better than getting upset about nothing.

I see the blocking code in here now... but it's such a common problem that it should be easy to fix. Forget about multitasking this or that... it's just about converting the delay() to millis().

// BV signal detection rev 5  April 2020
// Rev 4 includes flashing yellow, current sample averaging and added delays for green and red LEDs
// Rev 5 This version works.  Cleaned up code and documentation

// ACS712 wiring pins connection to Arduino
//   VCC pin to 5V
//   GND pin to GND
//   Out pin to A0

//Initialize variables

const int Csensor1 = A0; //Current sensor set on pin A0
int Green1 = 2; //Green signal 1 is set to digital pin 2
int Yellow1 = 3; //Yellow signal 1 is set to digital pin 3
int Red1 = 4; //Red signal 1 is set to digital pin 4
// Black signal wire goes to Arduino GND pin
int prevState = 0; // Set previous state to 0 for unoccupide
int sensitivity = 185;  // Because ACS712 is 5A version
int adcValue = 0;
int offsetVoltage = 2497;
double adcVoltage = 0;
double currentValue = 0;
int currentSample = 0;  // variable that returns current read
double sumCurrent = 0;  // current accumulator
double avgCurrent = 0;  // average current over sample

// Variables that can be tweaked

double sensorThreshold = .03354; // .0335 for Livermore front
int currentSampleSize = 15000;  //Sets number of samples of current to average out
double blinkDelay = 1000;  // sets delay between yellow blinks
int yellowBlinkNum = 15; // number of times the yellow LED blinks

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  // Establish all of our pins as inputs or outputs
  pinMode(Green1,OUTPUT);
  pinMode(Yellow1,OUTPUT);
  pinMode(Red1,OUTPUT);

// set initial state of LEDs to Green on and rest off
 digitalWrite(Green1,HIGH);  // Green signal on
  digitalWrite(Yellow1,LOW);  // Yellow signal off
  digitalWrite(Red1,LOW);  //Red signal off

}

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

// Read and compute track block current
// Sample current and average results
sumCurrent = 0;
for (int i = 0; i <= currentSampleSize; i++)
    { currentSample = analogRead(Csensor1);
    // Compute voltage and current
       adcVoltage = (currentSample / 1024.0) * 5000;
    currentValue = ((adcVoltage - offsetVoltage) / sensitivity);
    if (currentValue < 0) {currentValue = currentValue * -1;}
    sumCurrent = sumCurrent + currentValue;}
  avgCurrent = sumCurrent / currentSampleSize;


// Test for Occupany
if (avgCurrent > sensorThreshold ) {
  // Block occupied
  digitalWrite(Green1,LOW);  // Green signal off
  digitalWrite(Yellow1,LOW);  // Yellow signal off
  digitalWrite(Red1,HIGH);  //Red signal on
  prevState = 1;  // Set occupancy to occupied

}
    
    // Block not occupied right now
   else if ( prevState == 1) 
   {
        digitalWrite(Green1,LOW); // Turn off Green signal
        digitalWrite(Red1,LOW); // Turn off Red signal
        digitalWrite(Yellow1,HIGH);
        // flash Yellow for X seconds
        for (int j = 0; j <= yellowBlinkNum; j++) 
          {
            digitalWrite(Yellow1,LOW);  //Turn on Yellow
          delay (blinkDelay);
          digitalWrite(Yellow1,HIGH);
          delay (blinkDelay);
          }
        prevState = 0;  // Set occupancy to unoccupied

        }
        else 
          {// Block was not occupied before
            digitalWrite(Green1,HIGH);  // Turn on Green signal
            digitalWrite(Yellow1,LOW);  // Turn off Yellow signal
            digitalWrite(Red1,LOW);  // Turn off Red  signal
            prevState = 0;  // Set occupancy to unoccupied  
            delay (5000);
          }
}

I suggest you start using an enum for your state machine so the states can have meaningful names.

It looks to me (although I haven't dug deeply yet) that you could use the state machine itself to control timing. Right now it's underutilized. But I don't think you've explained, inside or outside the code, how it operates. Does it go through a timed sequence of signal aspects after occupancy is released? If so, you can just define states DANGER, APPROACH, CLEAR for which you have appropriate LED sequence handlers. I'm also not sure what signal system you are using but a flashing yellow is usually an ADVANCE and if you watched the signal after a train passes you would see the sequence DANGER, APPROACH, ADVANCE, CLEAR.

I have both proprietary and non-proprietary code for signals. The former is so because of the restrictive licensing agreement of the IDE I'm using (not Arduino). The Arduino implementation will be public some day, but it's just not ready for prime time. It's fully cooperative multitasking and features software LED brightness adjustment, LED gamma correction, tungsten filament lag simulation as well as searchlight filter servo simulation.

I've decided to hack this code for you. Look for an update later. Meanwhile, it would be great if you could give a very simple flowchart/pseudocode explanation of the desired behaviour.

Thanks for your reply. Let me emphasize that I am super new to all this and last programming I did was years ago in Basic. So learning how to program and use the Arduino is both exciting and scary. I am unfamiliar with state machines. I will need to do more research on that approach.

What I am trying to is this: If the ACS712 detects current above a threshold then the Red LED is lit. When the current goes back below the threshold then the Red LED goes out and the yellow LED flashes for a set period of time. Once the time expires then the Yellow LED goes out and the Green LED comes on until the next current is detected. Conceptually this seems simple and I am sure that those of you who are experts think this is a trivial. To me not so much.

As I have it now each block would need its own Arduino because there is no multitasking. I think my use of the delay function prevents me from being able to put another section of code to deal with another block with associated sensor and LEDs.

I have read in some other reference material on how to string the Arduinos together to pass occupancy data forward and backward on the block chain. But this is only on my list for future development.

Appreciate the time.

srobb725:
What I am trying to is this: If the ACS712 detects current above a threshold then the Red LED is lit. When the current goes back below the threshold then the Red LED goes out and the yellow LED flashes for a set period of time. Once the time expires then the Yellow LED goes out and the Green LED comes on until the next current is detected. Conceptually this seems simple and I am sure that those of you who are experts think this is a trivial. To me not so much.

As I have it now each block would need its own Arduino because there is no multitasking. I think my use of the delay function prevents me from being able to put another section of code to deal with another block with associated sensor and LEDs.

I have read in some other reference material on how to string the Arduinos together to pass occupancy data forward and backward on the block chain. But this is only on my list for future development.

Appreciate the time.

Aha, understood. I have a sketch started, had to go out and clear my head. It makes sense to control multiple masts from a single controller.

May I ask, the ACS712 is a bipolar device, the current readout will be opposite for DC powered trains running in opposite directions. Or is this a DCC application?

What is cool about this, I ordered some ACS712 a while back to try the same experiment, using it as an occupancy detector. How is that part of it working out for you?

DCC (Digitrax). So the current readings can be either + or -.

While my crude sketch is now working (sensing, controlling the LEDs) it took awhile to understand how to reliably sample the current. It would have been great to just do one current sample and then act on it but the ACS712 readings are all over the place. I needed to take a large number of samples (ie 15,000) each iteration and then average them to get a reading that I could use to have as a trigger for a non-sound DCC loco. If all you have are sound locos then I think you could get away with far fewer current sample. I arrived at the 15,000 sample size by just doing a lot of experiments on my own. I am sure there are ways to do this more elegantly with more precision. But I was self taught.

Okay, I will start to look at that too. In the meantime, here is the non-blocking version:

// BV signal detection rev 5  April 2020
// Rev 4 includes flashing yellow, current sample averaging and added delays for green and red LEDs
// Rev 5 This version works.  Cleaned up code and documentation
// modified by aarg on the Arduino forum 2020-04-10

// ACS712 wiring pins connection to Arduino
//   VCC pin to 5V
//   GND pin to GND
//   Out pin to A0

//Initialize variables

const int Csensor1 = A0; //Current sensor set on pin A0
int Green1 = 2; //Green signal 1 is set to digital pin 2
int Yellow1 = 3; //Yellow signal 1 is set to digital pin 3
int Red1 = 4; //Red signal 1 is set to digital pin 4
// Black signal wire goes to Arduino GND pin

// Variables that can be tweaked

double sensorThreshold = .03354; // .0335 for Livermore front
int currentSampleSize = 15000;  //Sets number of samples of current to average out
double blinkDelay = 1000;  // sets delay between yellow blinks
int yellowBlinkNum = 15; // number of times the yellow LED blinks

unsigned long blockReleasedTime;  // signal sequence timer
unsigned long blinkRelayPrevious;
bool blinkState;  // blink relay on/off

typedef enum { IDLE_STATE,
               CLEAR,
               ADVANCE,
               APPROACH,
               DANGER
             } states;

states blockState = CLEAR;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  // Establish all of our pins as inputs or outputs
  pinMode(Green1, OUTPUT);
  pinMode(Yellow1, OUTPUT);
  pinMode(Red1, OUTPUT);
}

void loop() {

  // Continually test for occupancy.
  // A train in the block over-rides any other signal aspect,
  // thus it over-rides any state so it's easier to handle outside of the switch-case statement
  
  if ( blockOccupied() ) {
    digitalWrite(Green1, LOW); // Green signal off
    digitalWrite(Yellow1, LOW); // Yellow signal off
    digitalWrite(Red1, HIGH); //Red signal on
    blockState = DANGER;
  }

  // handle the current state:
  switch (blockState) {

    // Block occupied, continue signal red to indicate stop
    // until block is released:
    case DANGER:
      if ( not blockOccupied() ) {
        digitalWrite(Green1, LOW); // Turn off Green signal
        digitalWrite(Red1, LOW); // Turn off Red signal
        digitalWrite(Yellow1, HIGH);
        blockReleasedTime = millis(); // set signal sequence timer
        blockState = APPROACH;
      }
      break;

    // Block released, signal yellow for approach stop
    case APPROACH:
      if (millis() - blockReleasedTime > 5000) {  // remain in this state for 5 seconds
        digitalWrite(Green1, LOW); // Turn off Green signal
        digitalWrite(Red1, LOW); // Turn off Red signal
        blockState = ADVANCE;
      }
      break;

    // Block released, signal flashing yellow for advance approach stop
    case ADVANCE:
      // flash Yellow
      if (blinkState)
      {
        digitalWrite(Yellow1, LOW); //Turn off Yellow
      }
      else
      {
        digitalWrite(Yellow1, HIGH); //Turn on Yellow
      }
      if (millis() - blockReleasedTime > 10000)  // remain in this state for 5 additional seconds
        blockState = CLEAR;
      break;

    case CLEAR:
      digitalWrite(Green1, HIGH); // Turn on Green signal
      digitalWrite(Yellow1, LOW); // Turn off Yellow signal
      digitalWrite(Red1, LOW); // Turn off Red  signal
      blockState = IDLE_STATE;
      break;

    case IDLE_STATE:
      break;
  }

  // always update the blink relay
  if (millis() - blinkRelayPrevious >= 500) {
    blinkRelayPrevious += 500;
    blinkState = not blinkState;
  }
}

bool blockOccupied() {
  // Read and compute track block current
  // Sample current and average results
  int sensitivity = 185;  // Because ACS712 is 5A version
  int adcValue = 0;
  const int offsetVoltage = 2497;
  double adcVoltage = 0;
  double currentValue = 0;
  int currentSample = 0;  // variable that returns current read
  double sumCurrent = 0;  // current accumulator
  double avgCurrent = 0;  // average current over sample

  sumCurrent = 0;
  for (int i = 0; i <= currentSampleSize; i++)
  { currentSample = analogRead(Csensor1);
    // Compute voltage and current
    adcVoltage = (currentSample / 1024.0) * 5000;
    currentValue = ((adcVoltage - offsetVoltage) / sensitivity);
    if (currentValue < 0) {
      currentValue = currentValue * -1;
    }
    sumCurrent = sumCurrent + currentValue;
  }
  avgCurrent = sumCurrent / currentSampleSize;

  // Serial.print ("\t Current = ");
  // Serial.println (currentValue,6);
  // Serial.println (sensorThreshold,3);

  // Test for Occupancy
  if (avgCurrent > sensorThreshold )
    return true;  // Set occupancy to occupied
  else return false;
}

I added a solid yellow signal state just because I would need it. You can cut it out if you don't need it.