Need Help Coding an Automated Tram

So here is the setup. Kato Unitram that I want to have a small automated layout with the tram making 2 stops. I would like the tram to stay at the stations for a random time between 2 and 6 seconds.

I am planing on using an IR sensor at each station to trigger the stops. I want this to work in all lighting conditions, thus the IR for the sensor.

OK so what I have accomplished, I can get the tram to run with my H Bridge, and I can get the sensor to trigger when the tram rolls by it. I have also gotten the IR to turn on a LED when tripped.

Now a bit about my programing background. I cannot just write code very well. But I can read code pretty well and modify already written code to do my bidding in most cases. My coding stopped back in the early 90's with Apple Basic. I at one time did learn a language called ATLAS back when I was in the Navy, which is why I can read and modify but not write C very well at all.

So my equipment is right now an Arduino Uno but I would prefer to use a Nano (my PC wont recognize them right now) A L298N H Bridge and a IR sensor with 4 channels and the LEDs are remote.

Ok so the tram is pretty slow so there will need to be a delay from when the tram starts moving again before the sensor trips. It takes the tram about 10 seconds or so at full speed to make a lap around the small loop, and about 2 seconds to clear the sensor (make it 3 to be safe). Could someone please help me add the needed code to stop the tram when it crosses one of the IR sensors, and start it backup again (hopefully with the random delay). This is where I am lost, I have not been able to adapt any IR code to stop the tram and start it back up the way I want.

int ena = 3; // Enable
int in1 = 4; // Motor Power 1
int in2 = 5; // Motor Power 2
int se1 = 7; // IR 1
int se2 = 8; // IR2


void setup() {
  
 pinMode(ena, OUTPUT);
 pinMode(in1, OUTPUT);
 pinMode(in2, OUTPUT);
 pinMode(se1, INPUT);
 pinMode(se2, INPUT);
 

}

void loop() {
 
digitalWrite(in1, HIGH);
digitalWrite(in2, LOW);
analogWrite(ena, 255);


}

There are a few concepts to grasp before going further.

Study and get an understanding of the first five demos in IDE -> file/examples/digital. It's time well spent.

Get an understanding of doing several things at the same time and the Using millis() for timing beginner’s guide.

Free advice: drop one of the sensors for now, just get the thing working with one sensor to prove to yourself that it can be done.

Nice try for posting code :wink:

Edit your post and replace < by [code] and /> by [/code] and it will look a lot better.

A concept you need to understand is that of state change detection. (Edit: it btw is the 5th one on dougp's list of 5.)

Let's just say for argument's sake your IR sensor is "light" (no tram) or "dark" (tram). The problem you are facing I suspect is that you stop the tram when the IR is "dark". That means when it's time to start it up again after the random time it's still "dark" and it won't move.

State change detection means that you check the IR to become dark, not be dark. That means as the tram is moving, the IR reads light light light light light dark and it's that underlined change from light to dark that stops it, not the fact that it is dark. After that, while it sits in the station it reads dark dark dark dark which doesn't prevent it moving again when the random time is up, since there's no light dark transition to prevent it.

OK I found a tutorial and was able to get most of what I wanted with some new code I modified. I got it to work with both sensors (yes I did get one to work then both to work). The Tram stops when either or both are tripped. Here is my new code. Now I just need to figure out how to create the code for the delays. I remember how to do it in BASIC but it doesn't work here.

This isn't a code question but it goes with this same project. This tram has LED lighting inside and when it is set for any speed other than 255 the lights flicker and the tram jitters. Can I put a capacitor across the motor leads to help prevent this and smooth out the tram's travel? When in full speed it is loud and too fast. Strange though it is faster and louder with the new code but still jitters when slower than 255

int ena = 3;  //Tram Speed
int mr1 = 4; //motor a = +
int mr2 = 5; //motor a = -
 
int se1 = 7; //IR #1
int se2 = 8; //IR #2
 
void setup() 
{

pinMode(mr1, OUTPUT);
pinMode(mr2, OUTPUT);
pinMode(ena, OUTPUT);

 
pinMode(se1, INPUT);
pinMode(se2, INPUT);

 
analogWrite(ena, 255);  
delay(200);
}
void loop()
{  
if ((digitalRead(se1) == 1)&&(digitalRead(se2) == 1)){forward();}
if ((digitalRead(se1) == 1)&&(digitalRead(se2) == 0)){Stop();}
if ((digitalRead(se1) == 0)&&(digitalRead(se2) == 1)){Stop();}
 

}
 
void forward(){
digitalWrite(mr1, HIGH);
digitalWrite(mr2, LOW);

}
 
void Stop(){
digitalWrite(mr1, LOW);
digitalWrite(mr2, LOW);

}

Your code there does exactly what I said you don't want to do. As it stands, the tram runs if both sensors are 1 (what I called "light") and stops if either is 0 (my "dark").

To...

just ... figure out how to create the code for the delays

... you need to look for a 1 to 0 transition, stop the tram, capture millis(), and a random time later run forward again. As it stands, if you try to run it forward again it will stop immediately since the sensor will still be 0.

Jubukraa, I know this would happen. I know there is a million ways to catch a cat in a mouse trap, and my solution initially was to have the tripped sensor be turned offline until 3 or so seconds after the tram starts moving again (if this is even possible) In theory I could even make it offline until after the second sensor is tripped, that would work too as the tram will always pass one sensor then the other. The tracks are a triangle shaped loop, and no matter which direction it goes it will always hit one then the other. I have not figured out millis() yet, I need to get to bed as work it way too early in the morning for me. I will read up more on it tomorrow.

Good news, I had removed the delay(200) from below the enable command when I tried the code earlier but had saved what I posted here before that(working from 2 different computers). When I added that line back into the code I am now able to slow the tram down with the code and there are no more jitters or flickering light.

So for now thank you all for the help, If anyone has any more suggestions I would really appreciate the help. I do learn this stuff fast, but I have always been more of a hardware type than a software type.

Mdmassey:
I know there is a million ways to catch a cat in a mouse trap

A properly designed mousetrap would specifically not catch cats.

Mdmassey:
If anyone has any more suggestions I would really appreciate the help.

I'll go out on a limb here and wager that 99/100 suggestions will be along the lines of state change detection.

I will post the sketch I wrote for that (including a random stopping time between limits of your choice) if you want it.

Sample output:

setup()...
.... tram with 2 stops for random times ....
Compiler: 7.3.0, Arduino IDE: 10810
Created: 08:40:39, May  1 2020
C:\Users\xxx Home\Desktop\xxx\Arduino\_681374_tram_v2\_681374_tram_v2.ino
setup() done
Tram stopping at station1 for 3802ms
Tram stopping at station2 for 2122ms
Tram stopping at station1 for 2370ms
Tram stopping at station2 for 2453ms
Tram stopping at station1 for 3688ms
Tram stopping at station2 for 4588ms

I'll go out on a limb here and wager that 99/100 suggestions will be along the lines of state change detection.

Change detection will be important, but this scenario just screams STATE MACHINE.

The tram can be in one of several states at any given time. But, it can only be in one state at a time. It can be MOVING, STOPPED AT STATION 1, STOPPED AT STATION 2, etc.

If the train is moving, the Arduino needs to be monitoring the station sensors, to know when to transition to stopped at station n.

If the train is stopped at a station, the limit switches are completely irrelevant. The only thing that matters then is the ticking of the clock. When the train has been stopped long enough, it transitions to moving again, and off it goes.

PaulS:
The tram can be in one of several states at any given time. But, it can only be in one state at a time. It can be MOVING, STOPPED AT STATION 1, STOPPED AT STATION 2, etc.

The way I coded it, I didn't bother drawing a distinction between stopped at station1 or at station2 since the OP gave no reason why that may be significant, so mine's simply tramIsInAStation, or not. If it's not in a station, it's moving and checking the sensors for the arrival of the tram with state change detection; if it is in a station the clock is, as you say, ticking.

jubukraa:
A properly designed mousetrap would specifically not catch cats.

I'll go out on a limb here and wager that 99/100 suggestions will be along the lines of state change.

The mousetrap comment I picked up from a college professor when learning IT security. I always found it funny so I use it from time to time. Anyway...

So you are correct that this version of the tram software doesn’t matter which station the tram is at, this layout has nothing but the tram on it, and some buildings, no other trains on these tracks, no signaling just a tram that goes around in circles making a desk decoration not so static and with the stops, making it have a bit more interesting than just an aimless tram.

I have been actually busy today at work so I have not been able to do any off time research.

Also if you posted the code that created the output it only gave a link to your own “c” drive but not the software itself. I don’t know if that was just part of the output or not so...

Ok been reading up on state change. So if I’m understanding the flow correctly it should go something like this:

Tram runs till it hits a sensor.
Sensor state changes and thus stops the tram
This triggers the random stop time before going again.

To get this to work properly then You would need the code to ignore the state change from “dark” to “light” but react when it goes from “light” to “dark”. And either sensor tripping would basically set the delay code off.

OK so I tried the state detection with mixed results, but I think I am close to getting what I want.

This is V1.5 It is a .5 version because 1.0 was only sensor 1 and worked perfectly. The tram stopped at the sensor, waited 2 seconds and then went on until it got back to the sensor. It didn't always stop at the exact same spot but close enough for what I'm doing. It ran that way for a good 10 minutes no fails. So I added another loop for the second sensor. Here is where things got strange. The tram would stop at sensor 1 correctly (with the nose of the tram as soon as it activated the sensor) wait 2 seconds and continue on to the second stop. It would then stop and stay stopped unless the sensor was cleared. I would push the tram past the sensor and then it would stop at station 1 with the middle of the tram. Next round same thing, push along, clear #2 then it would stop with the rear of the tram, next round it would stop about 6 or so inches away from the stop, and then back to stopping at the nose. Each time I would have to push it along away from sensor 2.

// this constant won't change:
int ena = 3;  //Tram Speed
int mr1 = 4; //motor a = +
int mr2 = 5; //motor a = -
int se1 = 7; //IR #1
int se2 = 8; //IR #2


// Variables will change:
int se1State = 1;         // current state of sensor 1
int se1LastState = 1;     // previous state of sensor 1
int se2State = 1;         // current state of sensor 2
int se2LastState = 1;     // previous state of sensor 2



void setup() {

pinMode(mr1, OUTPUT);
pinMode(mr2, OUTPUT);
pinMode(ena, OUTPUT);
pinMode(se1, INPUT);
pinMode(se2, INPUT);

 
analogWrite(ena, 65);  
delay(200);

  // initialize serial communication:
  Serial.begin(9600);
}


void loop() {
  // read Sensor 1:
  se1State = digitalRead(se1);
  // read Sensor 2
  se2State = digitalRead(se2);

  // compare the se1State to its previous state
 if (se1State != se1LastState)
  {

    if (se1State == 1) {
      digitalWrite(mr1, HIGH);
      digitalWrite(mr2, LOW);
     
    } else {
      digitalWrite(mr1, LOW);
      digitalWrite(mr2, LOW);
    }
    // Delay a little bit to load passengers
    delay(2000);
  }  
  
  {

    if (se2State == 1) {
      digitalWrite(mr1, HIGH);
      digitalWrite(mr2, LOW);
     
    } else {
      digitalWrite(mr1, LOW);
      digitalWrite(mr2, LOW);
    }
    // Delay a little bit to load passengers
    delay(2000);
  }
  // save the current state as the last state, for next time through the loop
  se1LastState = se1State;
  se2LastState = se2State;
  }

V1.6
So I looked over this code and noticed a line that se1 had that se2 didn't if (se2State != se2LastState) So I added this exactly how se1's was and this time the tram would not stop at either station even when both were activated. So decided to try the exact oppisite and // out the statements on both the se1 and se2. This caused a compiling error so I // the last 2 lines and it compiled. This time the tram would not move at all.

// this constant won't change:
int ena = 3;  //Tram Speed
int mr1 = 4; //motor a = +
int mr2 = 5; //motor a = -
int se1 = 7; //IR #1
int se2 = 8; //IR #2


// Variables will change:
int se1State = 1;         // current state of sensor 1
int se1LastState = 1;     // previous state of sensor 1
int se2State = 1;         // current state of sensor 2
int se2LastState = 1;     // previous state of sensor 2



void setup() {

pinMode(mr1, OUTPUT);
pinMode(mr2, OUTPUT);
pinMode(ena, OUTPUT);
pinMode(se1, INPUT);
pinMode(se2, INPUT);

 
analogWrite(ena, 65);  
delay(200);

  // initialize serial communication:
  Serial.begin(9600);
}


void loop() {
  // read Sensor 1:
  se1State = digitalRead(se1);
  // read Sensor 2
  se2State = digitalRead(se2);

  // compare the se1State to its previous state
if (se1State != se1LastState)
  {

    if (se1State == 1) {
      digitalWrite(mr1, HIGH);
      digitalWrite(mr2, LOW);
     
    } else {
      digitalWrite(mr1, LOW);
      digitalWrite(mr2, LOW);
    }
    // Delay a little bit to load passengers
    delay(2000);
  }  
if (se2State != se2LastState)  
  {

    if (se2State == 1) {
      digitalWrite(mr1, HIGH);
      digitalWrite(mr2, LOW);
     
    } else {
      digitalWrite(mr1, LOW);
      digitalWrite(mr2, LOW);
    }
    // Delay a little bit to load passengers
    delay(2000);
  }
  // save the current state as the last state, for next time through the loop
  se1LastState = se1State;
  se2LastState = se2State;
  }

I feel like I'm on the right track here but my lack of coding skills is holding me back. I know millis() will improve my delay function at the station but I'm thinking I'm missing something in the rest of the code that is causing the system to not stop the tram correctly. Could the tram be taking each delay into account somehow after it starts moving again and that is causing it to stop in the wrong spot over and over? It takes the tram less than a second the first time, just over a second by the 3rd time and about 2 seconds the 4th time, then everything resets.

Thanks in advance for everyone's help!

How are your sensors wired up? Do you have a datasheet for your IR sensor? You have them defined as INPUT so they could be floating unless tripped.

I figured out why in V1.5 it would start on one sensor and not the other, the code was being read for se2 and since it was not tripped it would continue and allow the tram to move. So I added a line to start the tram after the delay and now the tram works off both sensors. I still had one issue... The tram would not start unless both sensors were tripped. So I added a start motor command in the very beginning and all is well.

// this constant won't change:
int ena = 3;  //Tram Speed
int mr1 = 4; //motor a = +
int mr2 = 5; //motor a = -
int se1 = 7; //IR #1
int se2 = 8; //IR #2


// Variables will change:
int se1State = 1;         // current state of sensor 1
int se1LastState = 1;     // previous state of sensor 1
int se2State = 1;         // current state of sensor 2
int se2LastState = 1;     // previous state of sensor 2



void setup() {

pinMode(mr1, OUTPUT);
pinMode(mr2, OUTPUT);
pinMode(ena, OUTPUT);
pinMode(se1, INPUT);
pinMode(se2, INPUT);

 
analogWrite(ena, 65);  
delay(200);

  // initialize serial communication:
  Serial.begin(9600);
}


void loop() {
  // read Sensor 1:
  se1State = digitalRead(se1);
  // read Sensor 2
  se2State = digitalRead(se2);

      digitalWrite(mr1, HIGH);
      digitalWrite(mr2, LOW);
      
  // compare the se1State to its previous state
  if (se1State != se1LastState)
  {

    if (se1State == 1) {
      digitalWrite(mr1, HIGH);
      digitalWrite(mr2, LOW);
     
    } else {
      digitalWrite(mr1, LOW);
      digitalWrite(mr2, LOW);
    }
    // Delay a little bit to load passengers
    delay(2000);
      digitalWrite(mr1, HIGH);
      digitalWrite(mr2, LOW);
  }  
  if (se2State != se2LastState)  
  {

    if (se2State == 1) {
      digitalWrite(mr1, HIGH);
      digitalWrite(mr2, LOW);
     
    } else {
      digitalWrite(mr1, LOW);
      digitalWrite(mr2, LOW);
    }
    // Delay a little bit to load passengers
    delay(2000);
      digitalWrite(mr1, HIGH);
      digitalWrite(mr2, LOW);
  }
  // save the current state as the last state, for next time through the loop
  se1LastState = se1State;
  se2LastState = se2State;
  }

Now I just need a randomized stopping time!!

    // Delay a little bit to load passengers
    delay(2000);

Change that to:

    // Delay a little bit to load passengers
    delay(random(2000,6001); // wait for random time between 2 and 6 seconds

You might want to use randomSeed() in setup(), so you don't get the same series of wait time each time the Arduino is reset. Or not, if it doesn't matter.

Thanks for the code Paul, I will give it a try and report back here in a few.

Also if you posted the code that created the output it only gave a link to your own "c" drive but not the software itself. I don't know if that was just part of the output or not so...

No that wasn't a link, it was part of my output. I do that so if I pick up an Arduino from the mess of stuff on my desk, I can run the code with the serial monitor open so I can see what was last loaded into that Arduino.

Your code is pretty much what I was suggesting; only major thing I'd do is make the wait in the station delay()-less. That doesn't really matter though, if the tram is the only thing the sketch is controlling. But if and when you add more stuff to the system, a delay() in part may have a knock-on effect in part and mess the timings up. I sometimes (usually?) code stuff with no delay()s right from the start, even if it doesn't matter, so that if needs change later then it's already coded the "right" way.

And my only other comment would be that you will have noticed how tedious it is to code similar stuff for the 2 sensors, pretty much repeating code but with se1 vs se2 in various places. No biggy for 2 sensors, but will wear you down when you get to 10. That's when you'll want to look at arrays.

OH I remember arrays with ATLAS! It may have made coding a bit simpler for the software tech but it made my life a bit of a hell sometimes when trying to figure out what exactly is being tested in the F14D's various radar assemblies.

Right now for this project using a delay() is perfect for me as there is only one tram at a time on the layout, and it rolls in a small loop only. Hopefully I'm not breaking any rules here (I could not find any saying I shouldn't do this) but here is a link to the setup running. When the layout is all assembled the stops will be roughly 180° or so from each other and the sensors will be hidden in scenery.

Mdmassey:
https://youtu.be/fkYpDzYvBqM

I love that: I've been thinking recently of getting into model trains... maybe it's time.

Mdmassey:
It may have made coding a bit simpler for the software tech

Especially when you want to add new sensors, leds or whatever.

If you have an array:

mySensorPins[]={2,3,4};

... and do all the (eg) pinMode()-ing in a for() loop, when you decide you need another sensor, all you need to do is make it thus:

mySensorPins[]={2,3,4,5};

... and you're done.

I'm glad you made the progress that you did over the last couple of days, kudos.