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.
The following photos are of the newly installed.. man powered... traverser on the Corris railway in Wales.
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
- Traverser to be a separate unit that can be removed for maintenance/Upgrades
- Exhibition standard in looks and performance
- Traverser to run as a DCC accessory
- Always on Power to track so locos with sound decoders would keep running while moving.
- Traverser to have prototypical looks
- Don't want mechanism to be viewable when fitted.
- Adjustments to stopping points to be possible without reprogramming.
- 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.
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.
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.
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.
This is the bridge with the wheel guards and decking in place.
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.
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.
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.
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.
The whole set up was tested on the bench to make everything was working before being fitted to the layout.
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.