The turntable at Red Bluffs is up and running, bringing the lower level of module 1 to substantial completion. The L&NC is really starting to look and function like a railroad.
This has been a long project. In the first post about the turntable I described the construction of the pit and bridge, and the mechanism for moving the bridge created from off-the-shelf Actobotics parts. In the second post, I talked about completing and wiring the bridge, which includes two light features: a rotating beacon at the top of the arch, and a simulated stove fire in the hut which I’ve previously discussed.
In this post I will bring all the pieces together to create a fully functional turntable. The surprise is how simple the system actually is; primarily because of the way stepper motors work, the mechanical integrity of the Actobotics system and the control features provided by a Sparkfun Easy Driver. From a coding standpoint I’m at an intermediate stage: I’ve got working code but it hasn’t been integrated into the larger operating environment. For the time being, control is accomplished via the Serial Monitor and a menu option that allows me to tell it to go to a particular position by position number from 0 to 16. Position 0 is the calibration position; all others are the 16 possible ways the bridge can be oriented in the pit.
But First …..
Its the year end holiday period in this part of the world, so its allowed to have desert before your entree! Accordingly, here is a little 4 minute film demonstrating the turntable. Enjoy!
About Stepper Motors
Stepper motors are a variation on the brushless DC motor that split the coil into multiple groups arranged symmetrically. By selectively energizing coil groups, the rotor can be rotated a precise distance. Steppers are used wherever something needs to rotate at a precise speed (for example, a hard drive spindle); or where it needs to be moved precisely and repeatedly across a range (for example, the read/write heads of a hard drive).
Steppers are precise without the need for a feedback mechanism. Further, while the coils are energized, steppers hold position strongly. Accordingly, once positioning is calibrated with a known starting point, the stepper should be able to move accurately for an indefinite period.
Steppers can be controlled by an Arduino using a darlington array (if the stepper is unipolar) or an H-bridge (if bipolar); steppers draw too much current to be directly attached to the microcontroller. Stepper motors vary in size, current draw, voltage requirements and resolution. By resolution I mean the number of steps in a full 360 degree rotation: the more steps per revolution, the greater the precision. The stepper I am using has a resolution of 200 steps per revolution (the “step angle” is 1.8 degrees). The drive train has a 5 – 1 ratio, giving the assembly a resolution of 1000 steps per revolution of the bridge drive shaft. With the help of the Sparkfun Easy Driver 1/8 microstep mode, that resolution is improved to a final 8000 steps per revolution of the bridge.
Sparkfun Easy Driver.
The Sparkfun Easy Driver is a stepper motor controller card that interfaces with an Arduino. It provides control features you can’t get another way, including a microstep mode that can move a stepper a fraction of a step.
No special library is needed (3rd party libraries are available), but it does require 5 pins on the Arduino. You manipulate the Easy Driver, and thus the stepper, through the pins. MS1 and MS2 set the various modes of the driver; I use 1/8 microstep mode exclusively. DIR sets the rotation direction (HIGH for clockwise), and EN enables or disables the motor. STP is the step trigger. So, to set the default state I want, I use the following function:
void setEDPins(){ digitalWrite(stp, LOW); digitalWrite(dir, HIGH); // CLOCKWISE digitalWrite(ms1, HIGH); //Pull ms1, and ms2 high to set logic to 1/8th microstep resolution digitalWrite(ms2, HIGH); digitalWrite(en, LOW); // Enables motor control and turns coils on; HIGH disables }
To take one step,
void doStep(){ digitalWrite(stp,HIGH); //Trigger one step delay(1); // wait digitalWrite(stp,LOW); //Reset trigger }
Simple as that. Everything else in the turntable object builds off of those basics.
The Controller Board
With 5 hard connections between the microcontroller and the driver, it made sense to create a controller board to facilitate those cross connections plus additional external connections.
Calibration: Setting Position 0
Way back when I build the pit and drive assembly, I glued two magnetic reed switches to an arbitrary point on the outside of the pit. The hut end of the bridge has a neodymium magnet glued to the head of a nylon screw, making its position adjustable.
In the early construction stage, after gluing the sensors about a half inch apart, I set the magnet position so that it would trigger both switches consistently within a narrow position band. Single point accuracy isn’t possible, but isn’t necessary so long as you can trigger both switches consistently. To establish Position 0, I first move the bridge away from the sensors, then reverse direction and move until the first sensor triggers. Then I halve movement speed and continue stepping until switch 2 goes off. That gives me Position 0.
void FindAnchor(){ digitalWrite(dir, CLOCKWISE); // if sensors currently tripped, rotate until both open while(analogRead(sw1) > 1000 || analogRead(sw2) > 1000){ doStep(); delay(step_delay * 2); } // additional steps to clear sensors doSteps(100, step_delay); // find the sensors again digitalWrite(dir, COUNTERCLOCKWISE); while(analogRead(sw1) < 1000){ doStep(); delay(step_delay); } while(analogRead(sw2) < 1000){ doStep(); delay(step_delay * 2); } current_direction = COUNTERCLOCKWISE; current_position = 0; current_step = 0; digitalWrite(ROTATING_BEACON, LOW); in_motion = false; }
From there, it was simply a matter of determining the index of the 16 possible bridge positions. That index is expressed as a number of steps from Position 0, running clockwise.
I say simply somewhat facetiously. It took many hours of trial and error to establish the first 8 positions. The remaining ones were easier to find because they were 180 degrees offset from the first eight positions. The video, showing the accurate reversal of a locomotive, doesn’t cheat — the turntable really does move between positions accurately and seamlessly.
Each position requires both the step offset and the correct track polarity for that position. I store that information in a data array built from a simple structure:
#define NUM_POSITIONS 16 typedef struct ttp { unsigned int steps; byte polarity; }; ttp positions[NUM_POSITIONS] = { {1797, REVERSE}, {1967, REVERSE}, {1973, REVERSE}, {2122, REVERSE}, {2264, REVERSE}, {2417, REVERSE}, {2672, REVERSE}, {2916, REVERSE}, {5804, NORMAL}, {5961, NORMAL}, {5973, NORMAL}, {6122, NORMAL}, {6279, NORMAL}, {6429, NORMAL}, {6672, NORMAL}, {6925, NORMAL} };
To move between positions, the system determines the positional difference and the direction to go then moves.
Challenges
Well, its almost that easy, with one catch.
Slack. The inherent play in the drive train mechanism; mostly play in the gears. The system is amazingly tight, but you can’t eliminate all slack without making it immovable. Lord knows I tried. So long as you always move in the same direction, you don’t even know its there. But when you reverse directions, the slack has to unwind and rewind, eating steps without moving the bridge and leaving you short of your destination.
On my turntable, the SLACK factor that is working is 20 steps. The function that sets a new position for the turntable and starts motion automatically adds/subtracts the SLACK factor whenever the bridge changes direction of rotation.
void GoToPosition(int id){ struct ttp *pdata; digitalWrite(ROTATING_BEACON, HIGH); if(id == 0) { FindAnchor(); } else { if(id > 0 && id <= NUM_POSITIONS && id != current_position) { pdata = &positions[id - 1]; in_motion = true; destination = pdata->steps; if(destination < current_step) { // counterclockwise to destination digitalWrite(dir, COUNTERCLOCKWISE); if(current_direction != COUNTERCLOCKWISE){ current_direction = COUNTERCLOCKWISE; current_step += SLACK; // compensate for change in direction } } else { // clockwise to destination digitalWrite(dir, CLOCKWISE); if(current_direction != CLOCKWISE){ current_direction = CLOCKWISE; current_step -= SLACK; // compensate for change in direction } } pdata->polarity == NORMAL ? reverser->setNormal(): reverser->setReverse(); } } }
The Reverser or Polarity Manager
GoToPosition() automatically sets the track polarity as required for the new position. The polarity manager is a DPDT relay with crossover wiring that reverses polarity. Nothing odd about that. To control it, I use a darlington array as the intermediary because of the current draw of the relay.
Relays come in two types — standard which always reverts position when the coil is off, or bistable which hold position without power between coil uses. I use both types on the layout, though I have come to the conclusion that the bistable type is best for track power management. From a logic standpoint, bistable relays require two connections to the coils, but standard require only 1 coil connection. The downside of the standard relay is that it has to draw power to maintain the non-default position The turntable reverser uses a bistable relay which only requires brief coil energizing to change relay position.
To run this or other reversers, I’ve created a simple polarity_manager class that can use either relay type.
#define RELAY_BISTABLE 1 #define RELAY_REVERTS 2 #define POLARITY_NORMAL 1 #define POLARITY_REVERSE 0 #define TRIGGER_PERIOD 50 class polarity_manager { // Properties private: int relayType; byte pinNormal; byte pinReverse; int state; public: // Constructor polarity_manager(int rtype, byte pnormal, byte preverse){ relayType = rtype; pinNormal = pnormal; pinReverse = preverse; if(relayType == RELAY_BISTABLE && pnormal >= 0){ pinMode(pinNormal, OUTPUT); } pinMode(pinReverse, OUTPUT); digitalWrite(pinReverse, LOW); setNormal(); } int getPolarity(){ return state; } void setNormal(){ switch(relayType){ case RELAY_BISTABLE: digitalWrite(pinNormal, HIGH); delay(TRIGGER_PERIOD); digitalWrite(pinNormal, LOW); break; case RELAY_REVERTS: digitalWrite(pinReverse, LOW); break; } state = POLARITY_NORMAL; } void setReverse(){ switch(relayType){ case RELAY_BISTABLE: digitalWrite(pinReverse, HIGH); delay(TRIGGER_PERIOD); digitalWrite(pinReverse, LOW); break; case RELAY_REVERTS: digitalWrite(pinReverse, HIGH); break; } state = POLARITY_REVERSE; } };
Things that Slowed Me Down
Once I had the Easy Driver I had complete control of positioning. So the electronics were straight forward and, though finding positions was tedious, that part went pretty fast.
The bridge, however, almost did me in, forcing me to remove and reinstall it several times.
The arch proved a little too delicate (and my adult arms a tad careless when reaching across the layout) and was broken twice. The beacon was broken off the first time and had to be remade. The beacon is made from three very small SMD leds with magnet wire; it is difficult to make to say the least.
After the second rebuild, one element of the beacon went dead. So I had to make it a third time.
In the mean time, I had some initial trouble getting the bridge rails even with the rails on the edge of the pit. If I were to start over, I’d come up with a way to raise/lower/level the deck with screws. In this case, I used a blob of clear acrylic caulk at each end of the deck to adhere it to the girders below. I positioned the bridge so it lined up with rails on both ends, then set and held the bridge level with the rim tracks using hemostats as clamps while the caulk set. One advantage of the caulk method is I can remove the deck by slicing through the caulk at both ends.
The Turntable Class
Like the turnout class and the fire class, the turntable class creates a multitasking object. So, the turntable uses my standard multitasking method of incrementally acting through frequent calls to its update function in the main loop. If you have been following along, the code should look similar to code you’ve seen before:
void update(unsigned long current_millis){ if(in_motion){ if(current_millis - last_update >= step_delay){ last_update = current_millis; doStep(); current_direction == CLOCKWISE ? current_step++: current_step--; in_motion = current_step == destination ? false: true; if(!in_motion){ digitalWrite(ROTATING_BEACON, LOW); setEDPins(); } } } }
Code
The class definitions for the turntable (turntable.h) and the reverser (polarity_manager.h) are available on the github site. To use these copy them to your sketch directory, then put #include “turntable.h” and #include “polarity_manager.h” at the top of your main sketch file.
Coming Next
In the course of integrating the software pieces, I made a small but important modification in the way the the fire class is updated. I will blog about that shortly.
Then, it’s time to introduce you to the lower level of module 2. Its small but full of detail. The most special feature of module 2 is I’m starting to experiment with sound. I’m already certain that I will be retrofitting module 1 for sound and will have sound throughout the upper level. As usual, with the help of a few inexpensive specialized parts, its far easier to add this feature than you might have imagined.
Until then, Happy Railroading!
I enjoyed your write up very much. You did a wonderful job but have one question. I’ve never used stepper motors so please excuse my ignorance. I’m just trying to understand the decisions you made so I can be better informed when I build my own turntable. Why couldn’t you just use a normal 180 degree hobby servo? Is it really necessary for the turntable to rotate 360 degrees or could you have gotten by with just 180? I suspect using a normal servo would eliminate the need for the slip ring and the reed switches and if you us an Arduino to control it you could also eliminate a motor controller as the Arduino can natively operate servos.
I chose the stepper motor approach for precision. Steppers are very precise, which is why they are used in computer disk drives both to spin the platter and move the read/write heads. So long as you can guarantee your starting position, no feedback mechanism is needed for accurate positioning. Steppers, with a good drive train and the help of a good control board like the Sparkfun Easy Driver, can produce smooth, accurate movement over a large range of speeds.
Even with a stepper, in order to achieve accurate rail alignment every time you need reduction gears to improve positioning resolution.
Can it be done with a servo? Sure, but there are a few issues. First, 180 degrees of movement will not give you the ability to go 360 degrees in either direction, which is what I want for a turntable. So, you would want to use the type of servo that spins 360 degrees — to do that the servo has to omit the position sensing features.
Even with a standard servo, position resolution is about one degree with a servo. You need a fraction of a degree resolution to accurately position rails every time, so you need gearing but that means no position sensing. So, the bottom line with a servo is that you have to create a position sensing mechanism in order to get the results you are looking for.
With reduction gears, a full rotation of the turntable requires multiple rotations of the motor. At this point, since there is no way to know position either by counting steps or reading a sensor, servos become very hard to use for this purpose. My conclusion is that a servo run turntable would be completely dependent on position sensors to be able to work.
So really, it was an easy decision to go the stepper route, and that’s what I would recommend for this application.
Cheers,
Rob