Controlling a train with on-board Arduino

Welcome – This technology was demonstrated at the Seattle Mini Maker Faire March 22nd and 23rd, 2014 at the EMP museum.

We’ve seen Lionel trains controlled with your typical speed controller or even via radio control. This project takes it up a few notches and puts an Arduino on the locomotive to sense speed and make autonomous adjustments so that the train runs fast on the straightaways but slows down for and around curves.

Code use to run the train, as demoed at the Maker Faire. Please look at train_imu.cpp towards the bottom. The rest is modified multiwii code.

Here is the track controller code, also as demoed at Maker Faire.

First attempt at computer control of locomotive, tethered.

Code used to run the locomotive back and forth using the Mega 2560 in the foreground as shown in video above.

Building the track.

Detailed pictures of the tethered locomotive and Arduino.

This is my experimentation station for this project – the smallest track I can make!:

Read the build log of the proof-of-concept I created a few years ago that jump-stared this project.

That 8×8 LED RGB display you see below is actually hiding an Arduino called Rainbowduino.

The project is partially complete. So far I have:

  • Rainbowduino getting motion data from an IMU. The Rainbowduino is an Arduino variant that allows an 8×8 RGB LED matrix to plug into it. The matrix provides useful status updates that are visible from a distance (no tiny LCD here). The Arduino receives information about curves via an IMU (Inertial Measurement Unit). The unit is a GY-80 and is read and processed by a minimal version of MultiWii (normally used to fly Quadcopters).
  • Modified locomotive that uses a special wheel crank to break the beam of an opto sensor. Each time the wheel goes around it is counted and timed so that position and velocity can be computed. Both are derived from the wheel circumference and the pulse timing and counts.The sensor is connected to ISR0 (Interrupt Service Routine) to allow accurate counting, and also ensuring that no pulses are lost.
  • A fully functional DC motor controller soldered to a small board and bolted to the back of the locomotive. The desired motor speed is currently controlled via a potentiometer on the breadboard and read using the Arduino analog in. From there a pulse width modulation and control signals are sent through two optocouplers to the motor controller, allowing speed and direction control.
  • A separate power system for the Arduino using a custom-wound transformer to decouple the power to the motor from the power to the Arduino to avoid interference (I’ve experienced noise from the motor affecting the Arduino in other experiments). The power source to the track will be a constant 16 VAC – this will be converted to DC and sent to the motor and Arduino. I’m using a rechargable Li-Ion battery pack (used for cell phone recharging) to power the Arduino due to constant voltage requirements (i.e. keep it powered even if train derails).
Now that I have the train working with a constant speed algorithm, what’s next? Develop the track mapping software for the arduino. I will be spending most of my time writing the code to get the train to detect curves and slow down, then speed up for straightaways.

 

// PWM Motor controller with direction control via
// input potentiometer
//
// (C) 2014 zapmaker
//
// Apache 2.0 License
//
const int analogIn0Pin = 0;
const int ledPin = 13;
const int interruptNumber = 0;// usually connects to pin 2
const int pwmPin = 3;
const int dir1Pin = 4;
const int dir2Pin = 5;

volatile int state = LOW;
volatile int lastTime = 0;

int last = 0;
int motorTime = 0;
int serialTime = 0;
int delta = 0;
int pwmOut = 0;
int lastDir1 = 0;
int lastDir2 = 0;
float detectedSpeed = 0;
boolean useBrakingOnReversal = true;

#define BUFSIZE 200
char buf[BUFSIZE];

void setup()
{
  Serial.begin(9600);
  //
  pinMode(ledPin, OUTPUT);
  attachInterrupt(interruptNumber, blink, RISING);
  digitalWrite(pwmPin, pwmOut);
  pinMode(pwmPin, OUTPUT);
  digitalWrite(dir1Pin, lastDir1);
  pinMode(dir1Pin, OUTPUT);
  digitalWrite(dir2Pin, lastDir2);
  pinMode(dir2Pin, OUTPUT);
}

void loop()
{
  int curr = millis();
  if (last != lastTime) {
    int tmp = lastTime - last;
    if (tmp > 30) {
      delta = tmp;
    }
    last = lastTime;
  }
  else if ((curr - lastTime) > 3000) {
    delta = 0;
  }  

  if ((curr - motorTime) > 100) {
    int a = analogRead(analogIn0Pin);
    a /= 4;
    // split the potentiometer in half, incrementing
    // the speed in opposite motor direction from the
    // middle of the pot
    if (a <= 127) {
      // bottom half of pot
      if (lastDir1 == 0 && lastDir2 == 1) {
        // reversal occurred, stop motor to protect 
        // motor and controller
        stop();
        // ensure no pwm signal, just dc
        a = 255;
      }
      else {
        // change direction
        lastDir1 = 1;
        lastDir2 = 0;
        a = (127 - a) * 2;
      }
    }
    else {
      // top half of pot
      if (lastDir1 == 1 && lastDir2 == 0) {
        // reversal occurred, stop motor to protect 
        // motor and controller
        stop();
        // ensure no pwm signal, just dc
        a = 255;
      }
      else {
        // change direction
        lastDir1 = 0;
        lastDir2 = 1;
        a = (a - 128) * 2;
      }
    }

    // motor direction
    digitalWrite(dir1Pin, lastDir1);
    digitalWrite(dir2Pin, lastDir2);

    pwmOut = a;
    // pwm 'speed'
    analogWrite(pwmPin, pwmOut);

    float d = delta;
    detectedSpeed = 0;
    if (d > 0)
      detectedSpeed = 10000 / d;

    motorTime = curr;
  } 

  if ((curr - serialTime) > 1000) {
    String msg = "L1: ";
    msg += lastDir1;
    msg += " L2: ";
    msg += lastDir2;
    msg += " A: ";
    msg += pwmOut;
    msg += "\n";
    msg.toCharArray(buf, BUFSIZE); 
    Serial.write(buf);

    msg = "Pulse Time: ";
    msg += delta;
    msg += " Speed: ";
    msg += (int)detectedSpeed;
    msg += "\n";
    msg.toCharArray(buf, BUFSIZE); 
    Serial.write(buf);

    serialTime = curr;
  }
  digitalWrite(ledPin, state);
}

void stop()
{
  // decide if we are braking on stop or
  // letting motor coast 
  if (useBrakingOnReversal) {
    lastDir1 = 0;
    lastDir2 = 0;
  }
  else {
    lastDir1 = 1;
    lastDir2 = 1;
  }
}

void blink()
{
  lastTime = millis();

  state = !state;
}

One thought on “Controlling a train with on-board Arduino

  1. Pingback: Autonomous train - myLargescale.com > Community > Forums

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>