Last Updated: 20/03/23

Project 10 - Arduino Indexing DCC Model Railway Traverser

Projects >> Project 10 - Arduino Indexing DCC Model Railway Traverser

Arduino Indexing DCC Model Railway Traverser

On the latest module of my railway I wanted an engine works (Hope Works), the type of place were new rolling stock is built rather than maintained. As I was short on space I needed either a turntable or a traverser. As the station next to this module already has a turntable I decided to have a go at building a traverser. I had built one before for my fiddle yard (staging area) but that what built for function rather than looks.

What is a traverser?

It's a device that allows a loco or piece of rolling stock to be moved to a track parallel to the track the item has arrived on. Historically they tend to be used in railway works although some have been placed in stations when there is no space for a run round including a brand new one at the Corris railway in Wales.

The first set of photos show a typical application, these are on the Vall de Núria railway in Spain, as can be seen the traverser moves between a line of workshops on either side.

Vall de Núria railway traverser

Vall de Núria railway traverser

Vall de Núria railway traverser

The following photos are of the newly installed.. man powered... traverser on the Corris railway in Wales.

Corris railway traverser

Corris railway traverser

Corris railway traverser

Issues with Traversers

Traversers have some very big challenges because as can be seen from the photos, when there is a motor it is built into a very small space. There are some older traversers that had another track in the centre that used a special steam engine to push the traverser backwards and forwards, but even then building an indexing system is not straightforward.

As well as fitting the mechanism into the traverser there are some issues that must be overcome for the traverser to work smoothly.

As can be seen from the images below, the first problem is horizontal alignment. This is the need for the traverser bridge (the bit that moves) being horizontal and in alignment horizontally as well as vertically with the different tracks. It is therefore essential that all wheels are at the correct centres when building the bridge.

The 2 nd issue shown below is the need for the bridge to be at exactly 90 degrees to the movement direction. If it isn't the bridge ends will move at a slight angle that if more than a very small amount will be apparent to the eye but will also force you to increase the gap between the bridge rail and the entrance track making for a poor movement on and off of the bridge.

The final issue is getting power to the track. With sound chipped locos it's important that power is supplied to the loco at all times.

My Specifications

  1. Traverser to be a separate unit that can be removed for maintenance/Upgrades
  2. Exhibition standard in looks and performance
  3. Traverser to run as a DCC accessory
  4. Always on Power to track so locos with sound decoders would keep running while moving.
  5. Traverser to have prototypical looks
  6. Don't want mechanism to be viewable when fitted.
  7. Adjustments to stopping points to be possible without reprogramming.
  8. Unit must Auto index and update it's index point throughout use.

I would like to say that I got everything right first time... but I didn't. I will therefore show you not only what I did to build the traverser, but what I wish I had done instead.

The Plan

Construction would be in two parts.

The above baseboard level section would be the traverser bridge. This would have extensions at each end of the bridge that would move above a brick wall and be hidden by an overhand so that hopefully they will deceive the eye. The two extensions would have arms that went through slots to below baseboard level to atach the the movement mechanism. The two half bolting together to allow for either prat to be removed for maintainance.

Underneath the bopard a stepper motor would drive some pulleys that would have a belt attached to each end of the traverser so that it moved smoothly without twisting in a simiar way to how a flat bed scanner works.

The main components are made from 1.9mm plasticard 80,000 thousands for those using imperial measurements.

The unit would have a photo interrupter at one end of it to index the unit.

The whole thing would be powered using an Arduino UNO that would also control a couple of points on the same module all controlled by DCC.

From my point of view the Arduino part was the easy bit having already built a few turntables, however the physical construction would be all new to me.

Building the Bridge

I model in 7mm narrow gauge (0-16.5) so O Gauge but on the equivelent of 00/HO track to represent approx 2' 6" track gauge. As such most of this would easily transfer to OO/HO gauge with few modifications.

Most of the construction is from 1.9mm plasticard. This is to keep the whole thing rigid.

I have a 3018 CNC milling machine that I used to make myself a kit of parts. It would have probably been easier with a 3D printer. The wheels are normal pin point axles that are set in brass bearings in the frame. It works fine but if I built it again I would probably use brass top hat bearings as I think they would last longer.

traverser bridge1

This photo shows the wheels fitted for testing, you can just see the head of the brass pin point bearings.You will notice that the wheels interfere with the level the deck. This is prototypical for many traversers as there was always a need to keep the centre of gravity as low as possible.

traverser bridge1

I then made a test piece sso make sure the wheel spaciongs are correct and that the grooves for the rails are at the correct gauge.

Traverser bridge 4

Once that was done the bridge deck was machined out. The holes at the end are to help align the extensions and some planking would be fitted on top.

traverser bridge 5

This is the bridge with the wheel guards and decking in place.

Traverser bridge

The bridge then had extensions and the arms that go below the basebaord added and the pick ups fitted. Each track supplying opposite connections

Power pick up

Next I built the control mechanism. It consists of a centre bridge that attaches to the GT2 belts.

bridge 9

I made bearing housings for 6mm internal diameter bearings.I machines these from a number of pieces of 1.9mm plasticard and stuck them together.

The underboard bridge showing how it would connect to the GT2 belt

If I was building this again I would not use 6mm, I would have used 8mm as that would have enabled me to use pillow bearings that would have saved a lot of work. They are self aligning and available on Ebay or AliExpress for under £2 and are probably harder wearing than the smaller bearings I used.

8mm pillow bearing

Once that was done and the movement tested by hand I added the Nema 17 stepper motor that has a 16 tooth pulley driving a 60 tooth to get some reduction in speed and an increase in torque.. You will notice that the traverser is being tested in situ and that it fits tight against one side of the basebaord... I don't make life easy for myself.

After running a test script that just moved the bridge back and forth for a couple of hours I added the photo interrupter and a plasticard blade fitted to the underside bridge.

Once this was done the system was again tested with the photo interrupter being used to index the system. I ran it for several hours to make sure it was reliable and consistent in it's stopping positions.

Finally it was fitted into the main baseboard.and tested for an hour.

As the unit is designed to be removable It has a couple of dowel locators to make sure it always goes back into the same position.

Locating dowels

Traverser Bridge

Traverser Approach Track Test

Traverser Approach Tracks

New Engine Shed Base

New Engine shed

Old Shed Front

Traverer Before Ballast

Traverser ballasted

Traverser ballasted

Electronics

Just before we look at the code let's take a look at the electronics.

The DCC circuit is based on the Mynabay example.

Then it's an A4988 stepper motor driver , a couple of capacitative touch switches for fine tuning. Any switch would do but I just had these handy and they don't need pull down resistors.

There are a couple of servos that control 2 points/turnouts on the same module.

Finally there is a photo interrupter. This is used to create an index postion for the traverser from which all steps are measured.

Traverser Control Board

The whole set up was tested on the bench to make everything was working before being fitted to the layout.

test rig

Designing the Code

I wrote the code one component at a time.

First I get the DCC decoder circuit working, then some stepper motor movement, then the photo interrupter as the index and then the adjuster switches.

I've used servos for points so often I just added them at the last minute as I wasn't expecting any problems.

The traverser also uses the Arduino built in EEPROM, this is used to store any position adjustments made through the switches.

The code has been written in a State machine type format using millis() for timings rather than delay. This means that even when the traverser is moving it can still receive DCC commands for the 2 servos.

HopeWorksV4.ino


Click to Download code:HopeWorksV4.ino

The sketch is called HopeWorks as that is the name of the module it controls.


 
/*
  HopeWorksV4
  09/11/22
  Servos set and fully tested with controller

  This sketch adds the DCC Acc controller code.
  This board is addresses 21-30 range
  point to engine shed = 21
  point to traverser = 22
  Traverser at Back (entrance line) = 23
  Traverser centre track 24
  Traverser front track 25


  Unit built and run in.
  Photointerrupter fitted.

  Stop locations and indexing working correctly
  index auto reset working
  EEPROM settings correct and updating

  Arduino Uno

   Traverser
   Photo Interrupter
   2 x adjuster buttons?
   2 points
  EEPROM
  DCC decoder on pin 2

  Power Required
  12 DC for Arduino/Stepper
  DCC track power

  Pins
  A0 = photointerrupter

  2 - interrupt pin for DCC

  7 = enablePin...stepper
  8 = stepPin...stepper
  9 = dirPin...stepper

  10 = Adjuster switch
  11 = Adjuster switch

  5 servo
  6 servo
*/

//Other Variables
int moveToTrackPos = 100;//if 100 do not move traverser...no command issued.
int currentTrackPos = 100;//100 means it's lost
int indexed = 0;//1 means unit is indexed
const int indexStepReset = 2000;//the value steps is set to when indexed.
unsigned int currentSteps; //traverser current step position
unsigned int targetSteps;  //position the traverser is moving to.
int traverserDirection;
int interrupterState;

#include "NmraDcc.h"
NmraDcc  Dcc ;
DCC_MSG  Packet ;

//EEPROM
//system will use built in eeprom as it should not be written to very much...limited lifecycle.
#include "EEPROM.h"

long eepromUpdatePeriod = 10000;//60 secs between updates
unsigned long eepromUpdateTimer;
const int eepromOffset = 100;//initial adress that is looked at to find info
/* EEPROM data structure
    First three bytes ensure EEPROM has had proper values installed
    100 = 3
    101 = 11
    102 = 63
    103-104 Track 1 step position...to main line
    105-106 Track 2 step position...shed centre
    107-108 Track 3 step position...shed front
    109-110 Track Spare step position...spare
*/
unsigned int trackPositions[4] = {1000, 19000, 37000, 1000}; //1/4 step default positions if nothing programmed

//Servos

const int PointAPin = 5;//entrance to traverser
const int PointBPin = 6;//seperate works point
const int servoLiveTime = 1500;//1.5 secs before reset
unsigned long servoTimer;
byte servoDetached = 0;//1 means attached
#include "Servo.h"
Servo PointA;
Servo PointB;

//Adjuster switch
const int adjustBackPin = 10;
const int adjustForwardPin = 11;

//Stepper Motor
const int enablePin = 7;//taken high to disable stepper
const int stepPin = 8;
const int dirPin = 9;
const int poweron = 0;
const int poweroff = 1;
const int forwards = 0;//moving to front of layout
const int backwards = 1;//moving to back of layout...zero step position is at back.
byte stepInState = 0;//high or low state for next step
unsigned long stepperTimer;//timer until next step in micros();
int stepperSpeedDelay = 1000;//delay in micros() between steps...controls speed, Good max speed

void moveTraverser() {
  unsigned long currentMicros;
  int photointerruptRead;
  currentMicros = micros();
  if (indexed > 0) { //if it knows where it is
    if (moveToTrackPos < 100) {
      if (currentSteps != targetSteps) {
        if ((currentMicros - stepperTimer) >= stepperSpeedDelay) { //if sufficient time has gone next step
          stepperTimer = currentMicros;
          if (stepInState > 0) {
            stepInState = 0;
          } else {
            stepInState = 1;
          }
          digitalWrite(stepPin, stepInState);//take a step
          photointerruptRead = analogRead(A0);
          if (interrupterState > 0 && photointerruptRead > 100) {
            currentSteps = indexStepReset;
            interrupterState = 0;
            Serial.println("indexed");
          }
          if (interrupterState < 1 && photointerruptRead < 100) {
            currentSteps = indexStepReset;
            interrupterState = 1;
            Serial.println("indexed");
          }
          if (traverserDirection == forwards) {
            currentSteps++;
          } else {
            currentSteps--;
          }
        }
      } else {
        digitalWrite(enablePin, poweroff);//turn the power off...it's arrived
        Serial.println("Arrived...power off");
        Serial.print("Current Position Steps = ");
        Serial.println(currentSteps);
        currentTrackPos = moveToTrackPos;
        Serial.print("currentTrackPos: ");
        Serial.println(currentTrackPos);
        moveToTrackPos = 100;
      }
    }
  } else { //unit is NOT indexed yet
    photointerruptRead = analogRead(A0);
    indexed = 1;
    if (photointerruptRead < 100) {

      currentSteps = trackPositions[0];//Extreme back track
      interrupterState = 1;
      setTraverserTarget(1);//move to centre track
    } else {
      currentSteps = trackPositions[2];//Extreme front
      interrupterState = 0;
      setTraverserTarget(0);//move to rear track
    }

  }
}

void setTraverserTarget(int moveToTrack) {

  targetSteps = trackPositions[moveToTrack];
  if (targetSteps != currentSteps) { //only turn power on if it's going to move
    digitalWrite(enablePin, poweron);
    if (moveToTrack = 0) {//set system to re index if moving to rear track
      interrupterState = 0;
    }
  }
  if (targetSteps < currentSteps) {
    traverserDirection = backwards;
  } else {
    traverserDirection = forwards;
  }
  digitalWrite(dirPin, traverserDirection);
  Serial.print("Direction = ");
  Serial.println(traverserDirection);
  Serial.print("Steps Target = ");
  Serial.println(targetSteps);
}

//gets eeprom data and puts in trackpositions[4] array
void geteepromdata() {
  int q;
  byte getbytes[11];//2 bytes per position + 3 data check bytes
  Serial.println("getbytes[]: ");
  //get the data from eeprom and store in array
  for (q = 0; q < 11; q++) {
    getbytes[q] = EEPROM.read(q + eepromOffset);
    Serial.println( getbytes[q]);
  }
  //check if the Arduino has had values put in...if not set them
  if (getbytes[0] == 3 && getbytes[1] == 11 && getbytes[2] == 64) {

    Serial.println("trackPositions[]: ");
    //now assemble the data and put into trackpositions[4]
    for (q = 0; q < 4; q++) {
      trackPositions[q] = (getbytes[(q * 2) + 3] * 256) + (getbytes[(q * 2) + 4]);
      Serial.println(trackPositions[q]);
    }
  } else { //values not set so needs defaults
    updateeeprom();
  }
}

//update the eeprom with current trackpositions
void updateeeprom() {
  int q;
  //put check values in
  EEPROM.update(eepromOffset, 3);
  EEPROM.update(eepromOffset + 1, 11);
  EEPROM.update(eepromOffset + 2, 64);
  //now for the track values
  for (q = 0; q < 4; q++) {
    EEPROM.update((q * 2) + eepromOffset + 3, highByte(trackPositions[q]));
    EEPROM.update((q * 2) + eepromOffset + 4, lowByte(trackPositions[q]));
  }
  geteepromdata();
}

// This function is called whenever a normal DCC Turnout Packet is received and we're in Output Addressing Mode
void notifyDccAccTurnoutOutput( uint16_t Addr, uint8_t Direction, uint8_t OutputPower)
{
  switch (Addr) {
    case 21://point to engine shed
      PointA.attach(PointAPin);
      servoTimer = millis() + servoLiveTime;
      servoDetached = 1;//let system know servos are attached
      delay(30);//shouldn't upset DCC as too fast to press 2nd button
      if (Direction == 0) {
        PointA.write(95); //siding
      } else {
        PointA.write(57); //main
      }
      break;
    case 22://point to traverser
      PointB.attach(PointBPin);
      servoTimer = millis() + servoLiveTime;
      servoDetached = 1;//let system know servos are attached
      delay(30);//shouldn't upset DCC as too fast to press 2nd button
      if (Direction == 0) {
        PointB.write(87); //traverser
      } else {
        PointB.write(115); //main
      }
      break;


    //Traverser have an address for each location, they will activate whatever direction is selected.
    case 23: // Rear entrance track
      Serial.println("rear track");
      if (moveToTrackPos == 100) { //make sure last movement has stopped before starting a new one
        moveToTrackPos = 0;
        setTraverserTarget(0);
      }
      break;
    case 24: // centre track
      Serial.println("centre track");
      if (moveToTrackPos == 100) { //make sure last movement has stopped before starting a new one
        moveToTrackPos = 1;
        setTraverserTarget(1);
      }
      break;
    case 25: //front track
      Serial.println("front track");
      if (moveToTrackPos == 100) { //make sure last movement has stopped before starting a new one
        moveToTrackPos = 2;
        setTraverserTarget(2);
      }
      break;
    default:
      break;
  }
  Serial.print("notifyDccAccTurnoutOutput: ") ;
  Serial.print(Addr, DEC) ;
  Serial.print(',');
  Serial.println(Direction, DEC) ;
}


void setup() {
  Serial.begin(9600);
  Serial.println("HopeWorksV4");
  //Adjuster switches
  pinMode(adjustBackPin , INPUT);
  pinMode(adjustForwardPin, INPUT);
  //stepper set up pins as outputs
  pinMode(stepPin, OUTPUT);
  pinMode(dirPin, OUTPUT);

  pinMode(enablePin, OUTPUT); //turndriver on and off to get rid of the noise
  digitalWrite(enablePin, poweroff);
  digitalWrite(dirPin, forwards); // LOW for forwards

  //get trackposition data from eeprom
  geteepromdata();

  // Setup which External Interrupt, the Pin it's associated with that we're using and enable the Pull-Up
  Dcc.pin(0, 2, 1);
  // Call the main DCC Init function to enable the DCC Receiver
  Dcc.init( MAN_ID_DIY, 10, CV29_ACCESSORY_DECODER | CV29_OUTPUT_ADDRESS_MODE, 0 );


  Serial.println("Set Up Complete...");

}

void adjusterSwitches() {
  //only move if no track movement has been selected and it's in an indexed track pos
  if (moveToTrackPos == 100 && currentTrackPos != 100) {
    if (digitalRead(adjustForwardPin) == 1) { //forward selected
      digitalWrite(enablePin, poweron);
      digitalWrite(dirPin, forwards);
      if (stepInState > 0) {
        stepInState = 0;
      } else {
        stepInState = 1;
      }
      digitalWrite(stepPin, stepInState);//take a step
      //delayMicroseconds used as it will help to block any incoming DCC requests
      delayMicroseconds(stepperSpeedDelay * 10);
      currentSteps++;
      trackPositions[currentTrackPos] = currentSteps;//update the location array
    } else {
      //done this way so that it doesn't try to process touching both buttons at once
      if (digitalRead(adjustBackPin) == 1) { //forward selected
        digitalWrite(enablePin, poweron);
        digitalWrite(dirPin, backwards);
        if (stepInState > 0) {
          stepInState = 0;
        } else {
          stepInState = 1;
        }
        digitalWrite(stepPin, stepInState);//take a step
        delayMicroseconds(stepperSpeedDelay * 10);
        currentSteps--;
        trackPositions[currentTrackPos] = currentSteps;//update the location array
      }else{
        //if no buttons being pressed check if EEPROM needs updating
        if(millis() > eepromUpdateTimer){
          eepromUpdateTimer = millis() + eepromUpdatePeriod;
          updateeeprom();  
        }
        
      }
    }


  }
}

void loop() {
  
    //this is some test code that auto selects a destination for the traverser
    //Used for testing and running in
   /* 
    if (moveToTrackPos == 100) { //temp delay code and track position selection
    delay(4000);// a wait between movements so it's clear it stopped.
    moveToTrackPos = random(0, 3); //pick a random position
    setTraverserTarget(moveToTrackPos);
    }
    
  */

  Dcc.process();//process incoming DCC Commands
  moveTraverser();//deals with traverser control
  adjusterSwitches();
  if (servoDetached > 0 && millis() >  servoTimer) {
    servoDetached = 0;
    PointA.detach();//detach all the point servos
    PointB.detach();
  }

}

Designing the Code Part 2



Servos for points (turnouts)

In the code below you will also notice that I am controlling 5 servos for points (turnouts) off the same Arduino (a mega 2560). The Mega was used as I needed some extra pins for the servos.
Because the stepper motor is using micros() instead of delay there is no problem in handling a call to a point while the turntable is in operatino.

You will notice that I attach and detach the servos after use. This is because ome of the leads are very long and the servos "chatter" so I just detach them 1500 milliseconds after they have made their movement to turn the noise off.

The final code probably still needs a few tweaks and I will break it out into seperate tabs to make it more readable, but should work as a good starting point for a turntable project.

HopeStationV5.ino


Click to Download code:HopeStationV5.ino

This is the current sketch that my Hope Station module is running. It has servos that control 5 points/turnouts. This is why I needed to use millis() for the stepper motor as using delay() would mean that I could not change a point while the turntable was operating..

You will notice that servos are attached before use and then detached about 0.5 seconds after use. This stops any servo chatter as my leads are quite long.

 
/*  HopeStationV5
    23/12/21 Updated to controller
    Addresses set correctly in the 10-20 range for this board/module

    programmed on a Mega for the extra opins for the servos

    Final Programming against board
    micros() rollover code on turntable move...WORKING :-)
    Turntable adjuster with cap switches...WORKING
    servos coded in...working

     Station DCC Controller
     Turntable
     EEPROM
     2 x touch button turntable adjusters
     5 point servos
     2 x photointerrupter

     Turntable has 4 exits

  Pins
  2 - interrupt pin for DCC

  7 = enablePin...stepper
  8 = stepPin...stepper
  9 = dirPin...stepper

  23 = cap switch
  25 = cap switch
  27  servo
  29 servo
  31 servo
  33 servo
  35 servo

  A0 = photointerrupter....far corner
  A1 = photointerrupter...station end

*/

#include "NmraDcc.h"
NmraDcc  Dcc ;
DCC_MSG  Packet ;

//EEPROM
//system will use built in eeprom as it should not be written to very much...limited lifecycle.
#include "EEPROM.h"

long eepromUpdatePeriod = 10000;//60 secs between updates...also used to turn the power off
unsigned long eepromUpdateTimer;
const int eepromOffset = 200;//position of 1st bit of data...prevents eeprom wear in same place from previous use place
int trackPositions[4] = {247, 10747, 13047, 23547}; //default positions in steps of track exits from end 1



#include "Servo.h"
const int PointAPin = 27;
const int PointBPin = 29;
const int PointCPin = 31;
const int PointDPin = 33;
const int PointEPin = 35;
const int servoLiveTime = 1500;//1.5 secs before reset
unsigned long servoTimer;
byte servoDetached = 0;//1 means attached
Servo PointA;
Servo PointB;
Servo PointC;
Servo PointD;
Servo PointE;


//stepper motor
const int maxSteps = 25600;//number of steps in rotation
const int enablePin = 7;//taken high to disable stepper
const int stepPin = 8;
const int dirPin = 9;
const int poweron = 0;
const int poweroff = 1;
const int clockwise = 0;
const int anticlockwise = 1;

const int capClockPin = 23;
const int capantiClockPin = 25;

int moveToTrackPos = 100; //default when not looking for a track
int currentTrackPos;
byte turntableIndexState = 0; //1 means it has indexed
byte turntableDirection;
byte stepInState = 0;//high or low state for next step
unsigned long stepperTimer;//timer until next step in micros();
int stepperSpeedDelay = 1000;//delay in micros() between steps...controls speed, Good max speed
int stepperStartSpeedDelay = 30000;//nice and slow for start speed...adjustment speed
int stepCounter;//current step position of the turntable
byte photointerruptReadState = 0;//photointyerrupter state...triggers if hit
int currentStepperSpeedDelay;
//counters to deal with acceleration timings
int findDirection;
int stepsTaken;//count up from start

//gets eeprom data and puts in trackpositions[4] array
void geteepromdata() {
  int q;
  byte getbytes[8];//2 bytes per position
  Serial.println("getbytes[]: ");
  //get the data from eeprom and store in array
  for (q = 0; q < 8; q++) {
    getbytes[q] = EEPROM.read(q + eepromOffset);
    Serial.println( getbytes[q]);
  }
  Serial.println("trackPositions[]: ");
  //now assemble the data and put into trackpositions[4]
  for (q = 0; q < 4; q++) {
    trackPositions[q] = (getbytes[q * 2] * 256) + (getbytes[(q * 2) + 1]);
    Serial.println(trackPositions[q]);
  }
}

//update the eeprom with current trackpositions
void updateeeprom() {
  int q;

  for (q = 0; q < 4; q++) {
    EEPROM.update((q * 2) + eepromOffset, highByte(trackPositions[q]));
    EEPROM.update((q * 2) + eepromOffset + 1, lowByte(trackPositions[q]));
  }
  geteepromdata();
}

void moveTurntable() {
  unsigned long currentMicros;
  currentMicros = micros();

  //only do something of a movement has been commanded
  if (moveToTrackPos < 5) {
    //changes here to how timing works, this avoids the micros tollover issue at 70 minutes
    if ((currentMicros - stepperTimer) >= currentStepperSpeedDelay) {
      stepperTimer = currentMicros;
      if (stepInState > 0) {
        stepInState = 0;
      } else {
        stepInState = 1;
      }
      digitalWrite(stepPin, stepInState);
      stepsTaken++;


      if (turntableIndexState > 0) { //don't adjust speed if not indexed
        //acceleration

        if (stepsTaken < 500) {
          currentStepperSpeedDelay = stepperSpeedDelay + 2000 - (stepsTaken * 4);
        }
        if ((findDirection - stepsTaken) < 500) {
          currentStepperSpeedDelay = stepperSpeedDelay + 2000 - ((findDirection - stepsTaken) * 4);
        }

      } else {
        stepsTaken = 0;
      }
      
      if (turntableDirection == clockwise ) {
        stepCounter++;
        if (stepCounter > maxSteps) {
          stepCounter = 0;
        }
      } else {
        stepCounter--;
        if (stepCounter < 0) {
          stepCounter = maxSteps;
        }
      }


      //this resets the index everytime the indexing point is crossed in a clockwise direction
      int photointerruptRead = analogRead(A0);
      if (photointerruptRead < 250 && turntableDirection == clockwise && photointerruptReadState < 1) { //only do this check in clockwise direction
        photointerruptReadState = 1;

        Serial.println(stepCounter);
        stepCounter = 0;
      }
      if (photointerruptRead > 250 && photointerruptReadState > 0) {
        photointerruptReadState = 0;
      }
    }

    //stop the table at preset points
    if (stepCounter == trackPositions[moveToTrackPos]) {
      Serial.print("Target: ");
      Serial.print(trackPositions[moveToTrackPos]);
      Serial.print(" : ");
      Serial.print(stepCounter);
      Serial.println(" ");
      currentTrackPos = moveToTrackPos;
      moveToTrackPos = 100;
      turntableIndexState = 1;
    }
  }
}

//finds out what direction to spin the turntable...shortest route
//turns power on if there is a change of position
void getTurntableDirection() {

  findDirection = trackPositions[moveToTrackPos] - stepCounter;
  if (findDirection < 0) {
    findDirection = findDirection + maxSteps;
  }
  if (findDirection > (maxSteps / 2)) {
    Serial.println("anticlock: ");
    digitalWrite(dirPin, anticlockwise); // LOW for anticlockwise
    turntableDirection = anticlockwise;
  } else {
    Serial.println("clock: ");
    digitalWrite(dirPin, clockwise); // LOW for anticlockwise
    turntableDirection = clockwise;
  }
  Serial.println("findDirection: " + String(findDirection));
  //turn the power on if the stepper needs to move
  if (trackPositions[moveToTrackPos] != stepCounter) {
    stepsTaken = 0;//reset
    currentStepperSpeedDelay = stepperStartSpeedDelay;
    digitalWrite(enablePin, poweron);//turn motor driver on
  } else {//is the track already at the position selected...if so do nothing
    moveToTrackPos = 100;
  }
}

// This function is called whenever a normal DCC Turnout Packet is received and we're in Output Addressing Mode
void notifyDccAccTurnoutOutput( uint16_t Addr, uint8_t Direction, uint8_t OutputPower)
{
  switch (Addr) {
    case 10://entrance point coming onto module
      PointA.attach(PointAPin);
      servoTimer = millis() + servoLiveTime;
      servoDetached = 1;//let system know servos are attached
      delay(30);//shouldn't upset DCC as too fast to press 2nd button
      if (Direction == 0) {
        PointA.write(130); //board entrance
      } else {
        PointA.write(85); //ok
      }
      break;
    case 11://Main line to platform/turntable
      PointB.attach(PointBPin);
      servoTimer = millis() + servoLiveTime;
      servoDetached = 1;//let system know servos are attached
      delay(30);//shouldn't upset DCC as too fast to press 2nd button
      if (Direction == 0) {
        PointB.write(80); //platform
      } else {
        PointB.write(100); //ok
      }
      break;
    case 12://Freight siding
      PointC.attach(PointCPin);
      servoTimer = millis() + servoLiveTime;
      servoDetached = 1;//let system know servos are attached
      delay(30);//shouldn't upset DCC as too fast to press 2nd button
      if (Direction == 0) {
        PointC.write(105); //freight siding
      } else {
        PointC.write(50); //ok
      }
      break;
    case 13://Goods shed
      PointD.attach(PointDPin);
      servoTimer = millis() + servoLiveTime;
      servoDetached = 1;//let system know servos are attached
      delay(30);//shouldn't upset DCC as too fast to press 2nd button
      if (Direction == 0) {
        PointD.write(105); //goods shed shed
      } else {
        PointD.write(75); //ok
      }
      break;

    case 14://Front siding/coal
      PointE.attach(PointEPin);
      servoTimer = millis() + servoLiveTime;
      servoDetached = 1;//let system know servos are attached
      delay(30);//shouldn't upset DCC as too fast to press 2nd button
      if (Direction == 0) {
        PointE.write(110); //engine shed
      } else {
        PointE.write(80); //ok
      }
      break;

    //Turntables have an address for each location, they will activate whatever direction is selected.
    case 15: // Track to station
      if (moveToTrackPos == 100) { //make sure last movement has stopped before starting a new one
        moveToTrackPos = 2;//left
        getTurntableDirection();
      }
      break;
    case 16: // Track to station other end
      if (moveToTrackPos == 100) { //make sure last movement has stopped before starting a new one
        moveToTrackPos = 0;//left
        getTurntableDirection();
      }
      break;
    case 17: //loop at front of station
      if (moveToTrackPos == 100) { //make sure last movement has stopped before starting a new one
        moveToTrackPos = 1;//left
        getTurntableDirection();
      }
      break;
    case 18: //loop at front of station opposite end
      if (moveToTrackPos == 100) { //make sure last movement has stopped before starting a new one
        moveToTrackPos = 3;//left
        getTurntableDirection();
      }
      break;
    default:
      break;
  }
  Serial.print("notifyDccAccTurnoutOutput: ") ;
  Serial.print(Addr, DEC) ;
  Serial.print(',');
  Serial.println(Direction, DEC) ;
}

void adjustTurntablePostionSettings() {
  //can only manually move when not in a movement
  if (digitalRead(capClockPin) > 0 && moveToTrackPos == 100) {
    //Serial.println("capClockPin");
    digitalWrite(enablePin, poweron);
    digitalWrite(dirPin, clockwise); //direction
    delayMicroseconds(stepperStartSpeedDelay);
    digitalWrite(stepPin, HIGH);
    trackPositions[currentTrackPos]++;//adjust the position
    stepCounter++;//update the turntable position
    delayMicroseconds(stepperStartSpeedDelay);
    digitalWrite(stepPin, LOW);
    trackPositions[currentTrackPos]++;
    stepCounter++;//update the turntable position
  }
  if (digitalRead(capantiClockPin) > 0) {
    digitalWrite(enablePin, poweron);
    digitalWrite(dirPin, anticlockwise); //direction
    delayMicroseconds(stepperStartSpeedDelay);
    digitalWrite(stepPin, HIGH);
    trackPositions[currentTrackPos]--;//adjust the position
    stepCounter--;//update the turntable position
    delayMicroseconds(stepperStartSpeedDelay);
    digitalWrite(stepPin, LOW);
    trackPositions[currentTrackPos]--;
    stepCounter--;//update the turntable position
  }
}

void setup() {
  Serial.begin(9600);
  Serial.println("HopeStationV5");
  //stepper set up pins as outputs
  pinMode(stepPin, OUTPUT);
  pinMode(dirPin, OUTPUT);
  pinMode(enablePin, OUTPUT); //turndriver on and off to get rid of the noise
  digitalWrite(enablePin, poweroff);
  digitalWrite(dirPin, clockwise); // LOW for anticlockwise
  turntableDirection = clockwise;

  //cap touch switches for adjustment
  pinMode(capClockPin, INPUT);
  pinMode(capantiClockPin, INPUT);

  //get trackposition data from eeprom
  geteepromdata();

  // Setup which External Interrupt, the Pin it's associated with that we're using and enable the Pull-Up
  Dcc.pin(0, 2, 1);
  // Call the main DCC Init function to enable the DCC Receiver
  Dcc.init( MAN_ID_DIY, 10, CV29_ACCESSORY_DECODER | CV29_OUTPUT_ADDRESS_MODE, 0 );

  //index the unit
  digitalWrite(dirPin, clockwise); // LOW for anticlockwise
  turntableDirection = clockwise;
  moveToTrackPos = 3;//always index clockwise to position 0
  digitalWrite(enablePin, poweron);//turn motor driver on
}



void loop() {
  Dcc.process();//process incoming DCC Commands
  moveTurntable();//deals with turntable control
  adjustTurntablePostionSettings();//touch switches to make adjustments

  //do the eeprom updates
  if (millis() > eepromUpdateTimer && moveToTrackPos == 100) { // not moving so can be updated
    digitalWrite(enablePin, poweroff);//turn the power off as it's not moving
    eepromUpdateTimer = millis() + eepromUpdatePeriod;
    updateeeprom();
  }
  //detach any servos to stop chatter
  if (servoDetached > 0 && millis() >  servoTimer) {
    servoDetached = 0;
    PointA.detach();//detach all the point servos
    PointB.detach();
    PointC.detach();
    PointD.detach();
    PointE.detach();
  }

}

Additional Resource Links

Comments


This site has been designed to be child friendly, this means that comments cannot be added to videos or directly to the site.
To add a comment or ask a question please email the address in this image: and use Project 10 - Arduino Indexing DCC Model Railway Traverser as a reference.