Last Updated: 26/12/2021
Project 7 - Arduino Model Railway Turntable
Projects >> Project 7 - Arduino Model Railway Turntable
Arduino Model Railway Turntable
I have built a few turntables over the last few years and learnt the hard way that mistakes are easily made so the last few I have made have worked very well so this page will share the technique I use.
My requirements are that the turntable is reliable as it's on an exhibition layout. As such this turntable is a bit over engineered.
I wanted to run the turntable as I DCC accessory so it needed to be fully indexing.
I wanted the ability to adjust settings easily if needed.
The unit needs to be able to function through a range of conditions, from a freezing garage to a hot exhibition hall and be able to cope with expansion and contraction.
The version I have built for this layout is a small 6 inch (150mm)diameter version that fits in the front corner of my layout.as can be seen below waiting for the scenics to be added.
This is built to 0-16.5 narrow gauge so will work in OO or HO and and various other gauges.
The same technique can be used for bigger turntables, thisis the turntable bridge from an older version 12 inches (300mm) diameter.
This version had more of the turntable pit exposed so extra thought is needed when placing the photinterrupter. Also make sure the bridge is wider than the pick up diameter.
This page will explain how I build turntables for my Model Railways. I don't have a great set of tools to work with so my designs work on the principle that there will be errors in the way I drill, cut etc. So don't panic if you don't have a mega workshop or special jigs or gauges.
The expensive bits are the stepper motor and an Auto Current reversing module such as the Digitrax AR1 unless you build your own.
I have also used very basic materials that should be cheap and easy to get hold of.
My final versions are DCC controlled, indexing with acceleration and deceleration programmed in and are designed to be exhibition ready and reliable. I've broken the project into sections as you may only want to build part of the project.
This first video explains the building of the turntable, I'll add a second on how to program it next.
Issues with Turntables
There are some big issues when building turntables.
The first is getting the turntable square to the baseboard. It's crucial that one end of the turntable is not higher than the other. This will be the most difficult part of the build, but using the techniques below you will see how this can be slightly adjusted to set this correctly.
The second issue is getting the track totally centred. It's easy to get it set in one direction, but when the turntable turns 180 degrees we find the tracks do not line up. For this reason the track section will be built separate to the main turntable to allow some adjustment to get this right.
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. Also we need to deal with the problem of turning the track 180 degrees and the power in the tracks being out of sync with the track the loco will exit at creating a short circuit.
Components and Tools
Tools used. Although on this version I used a CNC milling machine to cut certain parts out, fancy equipment is not required. The previous version used the following tools.
Hacksaw
Screw drivers
Electric drill
Various metal files.
soldering Iron
Scalpel/Craft knife for cutting the plasticard to make the scenic look of the turntable
Materials
Box section aluminium (I used 30mm x 20mm x 2mm thick)
Replacement brushes for electric drill or grinder, find a size and shape to suit your turntable
Copper Clad PCB board (for track bed).
8mm Steel bar (I used staniless) steel).
2 x 8mm Pillow Bearing (Available on ebay).
8mm Flange coupling (Connects steel shaft to Aluminium section)
GT2 Belt and Pulleys. Large diameter pulley needs 8mm diameter hole. Small one needs the same as your stepper motor usually 5mm if Nema 17.
Stepper motor driver (I used an A4988
but others are just as good).
Arduino (I used a Mega for my project but an UNO or even a Nano will work).. Originals or cheap clone/copies all work.
If you are using a UNO get an Arduino Prototype shield
Photointerrupters (get a couple in case you damage one).
2 x 330ohm resistors for the photointerrupter circuit
If you are making adjuster buttons a couple of cheap push to contact switches and a couple of 330 ohm resistors.
DCC Auto Reversing Unit (I used a Digitrax AR1 but there are others or you may be able to build your own).
9mm plywood for main plates.
Off cuts of wood to build up the rest of the frame (I used stuff from my scrap pile).
Assortments of nuts, bolts and screws.
Various bits of wire from the scrap box that most of us have.
Plasticard for building the scenic side of the turntable deck.
Building the Deck
The main structure I use is based on some aluminium box section, connected to a piece of 8mm steel bar using a flange coupling. The components are cheap and gives you a set up that will as close as possible to 90 decgrees between the deck and the main shaft.
Once I have attached the flange coupling (about £4 on ebay) to the aluminium box section as centrally as possible. I then drill a couple of holes further out into the box section and file it out into a rectangular hole that the pick ups will fit into.
Make sure you mark a central line top and bottom of the aluminium box section so that all the holes you drill are central.
I use a number of bits of copper clad board to soldee the rails to. This allows me to cut out the sleepers in the positions the copper cad board will go and so I can then solder the track using the plastic sleepers between the copper clad to keep the track straight and in gauge. Once everything is soldered the remaining plastic sleepers are removed.
This is a view of another turntable I built using the same principle, this shows the top and bottom of the deck.
Notice the two holes that allow the deck to be screwed to the Aluminium box section. They are slightly oversize to allow some adjustment of the deck to get it totally cetral and square in both directions.
Power Pick Ups
I use two power pick ups, they are carbon brushes that are meant for an electric drill available cheaply online. The brushes come with a spring so I build a plastic sleeve out of plasticard that allows the brush to move freely and then solder a wire onto the brush lead.
Inside the aluminium box section I solder the leads together and then pass the power to one of the rails via the copper clad PCB.
.
Chassis and Drive system
Now the deck is built it's time to concentrate on the main chassis and drive system.
I use a Nema 17 stepper motor running at 1/8th steps as well as a gear reduction due to the belt and pully gearing.
When building a layout a build the turntable first as track can be adjusted, moving a turntable is a real pain. Also be aware that turntables can be quite deep so make sure your baseboard has clearance.
On this example I have made the job a bit harder by placing the turntable no the front right corner of the board.
First cut a hole in the baseboard that is bigger than your turntable deck. Give yourself some clearance, I fill in the gaps later.
As you can see I just jigsaw a hole (badly). You will notice in the hole that I am very close to the side of the baseboard, this is not ideal but was needed for the way this layout works.
The piece of track is there to gve me a height indicator for the finished turntable. I put cork sheet under my track so included that to get the height correct.
Next I take a piece of 9mm playwood and cut it to a size that is at least 50mm wider than the turntable on all sides. This is to give the larets area to make sure the chassis is square to the baseboard top. Also it adds strength as some tirntables require holes over 300mm in diameter.
In this project I have deliberatly made the job harder by being in a corner and I also have a point very close to the turntable that also requires a cut out.
I marke the corner that fits into the corner of my baseboard. I screw this in place (it will be removed later) and mark the centre of the turntable.
Next I drill a hole in the centre for the pillow bearing to fit into. Normally I would just drill the hole and mount the plat side of the pillor bearing to the board but I have been testing a CNC milling machine so did something a bit fancier.
I then fit the pillow bearing in place. Pillow bearnings are sealed bearings (so no dust gets in them) mounted in a flange. The bearings have some rotational movement so that you can align things perfectly later.
Once the bearing is in place I put the deck in place, push it down and rotate it. The carbon brushes then draw a nice line to show me the pick up location. I also marked out the rotation of the blade for the photointerrupter that will be used to index the turntable
I then install some copper sheet for the pick ups. You can use a bit of aluminium or steel sheet, even a bit of an old can. I screw it in place making sure the screws are outside the distance the picvk ups will rotate on and screw a lead for the power supply. I then drill a hole to feed the lead through under the top plate.
Now I move onto the bottom plate. I try to have a least 30mm between the top and bottom plates, the greater the distance the more stable the turntable will be but of course there are limts due to the depth of the assembly.
I work out the approximate location of the stepper motor. You will probably need to attach the pulleys and do some trials but the finished version will look like this.
I then make the hole for the 2nd pillow bearing and the stepper motor. Again I used a CNC machine but would have normally drilled and jigsawed it out.
I the fit the bearing, it is one of these bearing screws that I use to provide power for the other rail through the main shaft and aluminium box section.
I the build up a chassis sandwich. The wood I have used is about 30mm square section. It may seem a bit over the top but means that the chassis will be very rigid.
I the hold the parts roughly in place.
Now for the important part, take your time and get this right as this is the bit that will make or break the project.
Put everything together and make sure the deck is sqaure and level. Move the top around until it is as close as you can get it. Then screw it together.
Once this part is done you can make up a spacer to go between the top plate and the bottom of the baseboard. If you don't have the right sized wood get as close as possible with the turntable slightly lower than the track and then use a washer on the shaft to raise the deck to the correct height. You can file this down to size if needed. I had to use a couple of washers.
Once it's in place and at the right height I marked the track exit that will be selected most often. I set the indexing photointerrupter in the middle of this track so that the turntable is constantly reindexing when in use.
I the remove the deck, connect the lines and work out the location of the photointerupter. I put two in this project but only use one in the code as the indexcing has been so accurate a second was not needed.
I then filed out the holes to fit the photointerrupter.
I then fit the photointerrupters that have been mounted onto a bit of veroboard. The ciruit is avaiialable at Photointerrupter HY301.
These are then mounted into the slots
Now it's time to drill a few holes to pass the various wires through and then mount the stepper motor and pulleys. These are GT2 belt and pulleys. Tension should be fairly tight for cirrect indexing.
I mounted the stepper motor in a metal plate to allow me to adjust the tension, the metal wa cut from an old computer case.
You will notice a black wire under the pulley. This attaches to the bottom bearing screw so that the power passes up through the main shaft and deck to get to the rail.
The final underside set up looks like this.
The 4 wire ribbon cable is for the stepper motor and goes to the Arduino.
The white cable is a 4 wire cable that connects the photointerupters to the Arduino.
Finally the expensive bit,. I fitted a Digitrax AR1 Auto Reversing Unit to switch the track power if the turntable rotates 180 degrees. It switches the track power on the turntable. You can build your own and groups such as MERG can help.
It's then connected to the Arduino via a circuit built on a shield that will control the unit.
The code for this is explained below.
Electronics
Just before we look at the code let's take a look at the electronics.
I used an Arduino Mega as I was a bit concerned about running out of pins.
Pin 2 connects to the interrupt pin that is receiving the DCC signals from the Optoisoltaor circuit. I use the one from mynabay.com but as that site is offline at the time of writing this I found the same images at http://www.scale-n.nl/ScaleN_Naslag_Arduino_DCCMonitor.aspx
Although I could have used other pins for the interrupt on a Mega 2560 I stayed with pin 2 as it keeps a consistancy with my other Arduino decoders.
Pins 7-9 are used for the A4988 stepper motor driver
Pins 23&25 are connected to a couple of switches that are used for adjusting the turntable position manually.
Pins 27,29,31,33 & 35 are all used for controlling servo for points/turnouts.
A0 (Analog 0) is used for the photointerrupter.
As can be seen in the picture below I used an old prototype shield from a previous project and built an extra expansion board to plug into the extra Mega pins.
12v input is used for the Stepper motor, the extra 5V circuit powers the servos.
Designing the Code
Once everything has been physically built it can be very easy to just rush ahead and write the code.
However, there are things that need to be thought about that will have a huge impact on the way the turntable works.
As I have mentioned before, I don't have a mega workshop that produces everything to fine tolerances so the first thing that we need to do is work out the turntable exit points and to do that we need to know exactly how many steps it takes to rotate our turntable.
I used a nema 17 stepper with an A4988 motor driver, sample code for that including how to run the stepper using micros() instead of delay can be found at Nema 17 stepper motor.
Once you have your stepper motor running you need to check your photointerrupter is working,so check out the tutorial at Photointerrupter HY301
Next write yourself a sketch that moves the stepper motror until the photointerrupter triggers and then counts the steps until it triggers again. In doing this you will find out the exact number of steps in a single rotation taking into account your pulley gearing, 1/2, 1/4, 1/8 or 1/16 stepping. Write the sketch so that it keeps repeating because if you are using 1/8 stepping etc you may see some error. I saw a difference of about +/- 5 steps. Now before you start to panic let's put that in persepctive. My turntable makes 25600 steps per revelution, so about 71 steps per degree of movement. 5 steps becomes 0.07 degrees or a misalignment on a 150mm diameter turntable of approx 0.09mm or 0.18mm error on a 300mm (12") turntable. This is better than most of us can lay track, especially at baseboard joins. You will also notice that if one revolution was high, the next is often low.
To put this in persective, steel expands by 0.16mm per metre for a 10 degree celcius increase in temperature.
Now you know the number of steps it takes to rotate your motor you can start to work on your track exit positions relative to your Zero point, the point at which the photointerrupter is first triggered. I only do this in a clockwise direction these days as I have found this to be very reliable and my turntable resets it's index position using the photointerrupter everytime it passes it in a clockwise rotation. This means if the turntable gets knocked or jambed for some reason and misses some steps I can rotate the turntable and it will recalibrate itself.
On my turntable there are only 4 exits and I know their exact positions already as shown in the diagram below.
However, when you start you will have to have a rough guess, don't panic as this is software and we will add adjuster buttons into the design that will allow us to fine tune the exits whenever we want. The most importnat thing at this point is that the tracks align correctly when the turntable is rotated 180 degrees. If it doesn't adjust your turntable deck position until it does.
Initialising the turntable.
Because stepper motors, unlike servos, have no idea of their position on start up, we need to run a routine that will calibrate the turntable on start up.
You may want this to happen as soon as you start the turntable or when you select the first move.
Calibrating on start up in theory is the best option, however you need to consider a power failure scenario when a loco is halfway onto the turntable. If the turntable immediately calibrates itself when the power is restored that loco is going to have a bad day so keep that in mind when making your decision.
My calibration technique is very simple, I have a variable ( byte turntableIndexState ) that is set to Zero until the turntable has calibrated. On start up I set the turntable to move clockwise to the highest value step value track, in my case it has a value of 23547.
As the turntable starts to count the steps it's taken towards it's 23547 target (stored in variable int stepsTaken) it keeps being reset to Zero until the turntable has indexed correctly for the first time. At this point the Zero point is set and the turntable keeps moving until it has counted to 23547 ending at the correct exit point.
The turntable now knows exactly where it is and some simple maths allows us to work out if the turntable should turn clockwise or anti clockwise to get to it's next position that is stored in the trackPositions[4] array. If you have more positions just increase the size of the array.
To test everything was working I wrote a script that created a random number between 0 and 3 that selected the next position the turntable should move to and watched it happily move from one position to the next, clockwise or anti clockwise as well as reindexing itself when turning clockwise through the photointerrupter.
I then replaced the random number generator with some code from NmraDcc library so that when a certain accessory address was passed it gave the system the correct exit address. I use the address to select the track and the direction to select which end of the turntable is being selected so in my code id address 61 is selected if int Direction = 0 it will go to trackPositions[0] (247 steps) while if the direction is 1 it will go to trackPositions[2] (13047 steps).
You will notice that when the track gets to it's final location I set the variable moveToTrackPos = 100 as the function moveTurntable() is set in my code to ignore any value below 5, adjust this to the number of tracks you are using. It could not be set to Zero as the array trackPositions[0] is a real value.
TurntableV1.ino
Click to Download code:TurntableV1.ino
This sketch controls the Turntable, please see the video for more information on how it works.
The sketch requires the NmraDcc library and the EEPROM library.
I used an Arduino Mega as I have an extended version that also controls servo for point control as well as the turntable.
Track position adjuster buttons
I used some capacitative touch sensor switches in the code but normal push to contact switches will work just as well. My layout is in a very cold garage and can be put in a car and taken to a very warm exhibition hall. This means my layout probably has to deal with a temperature range from -5C up to 30C, as such expansion and contraction takes place. Also wood is not stable and may move very slightly so I added a couple of adjustment buttons. These only work when the turntable does not have a selected direction (moveToTrackPos = 100).
Pressing the clockwise or anticlockwise button moves the track very slowly and adjusts the value in the trackPositions[] array. Then every 60 seconds that data is updated into the EEPROM and the new position is stored in memeory for the next restert.
/* TurnTableV1
06/12/2021
Final Programming against board
micros() rollover code on turntable move...WORKING :-)
Turntable adjuster with cap switches...WORKING
Station DCC Controller
Turntable
EEPROM
2 x touch screen turntable adjusters
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
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 = 60000;//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
//stores the positions of the exits
int trackPositions[4] = {247, 10747, 13047, 23547}; //default positions in steps of track exits from end 1
//stepper motor
const int maxSteps = 25600;//number of steps in rotation...1/8 stepping plus pulley reduction
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;
}
//stepperTimer = micros() + currentStepperSpeedDelay;//this is the adjustable version
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) {
//Using old track panel codes for testing so 61 & 64
case 61: // Track to station
if (moveToTrackPos == 100) { //make sure last movement has stopped before strting a new one
if (Direction == 0) {
moveToTrackPos = 0;//left
} else {
moveToTrackPos = 2;//right
}
getTurntableDirection();
}
break;
case 64: //loop at front of station
if (moveToTrackPos == 100) { //make sure last movement has stopped before strting a new one
if (Direction == 0) {
moveToTrackPos = 1;//left
} else {
moveToTrackPos = 3;//right
}
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]++;
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]--;
stepCounter--;//update the turntable position
delayMicroseconds(stepperStartSpeedDelay);
digitalWrite(stepPin, LOW);
trackPositions[currentTrackPos]--;
stepCounter--;//update the turntable position
}
}
void setup() {
Serial.begin(9600);
Serial.println("TurnTableV1");
//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();
}
}
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 7 - Arduino Model Railway Turntable as a reference.