Skip to main content

Turntables...using Stepper Motors!

Maybe a little out of my league since the TxNamib Railroad does not reach far enough south to connect to Luderitz, where the last, unconnected turntable is.

But a good friend Chip Romig had turntable trouble and we decided implement two manual controls for the "get it closer quicker" and then "align it perfectly, going slower, smoothly" Arduino.

Of course, people who has the turntable as the main focus point, want to have better control, including sensors to align the table, but certainly has spent the time setting it up and calibrating it. And quite a number of times doing it again, which is where Chip decided to let the operator do all the alignment work himself.

So here it is, the crude two center-off-toggle switch solution to move a stepper motor faster with the first toggle and then slower with the second, so you can align them by eye-sight, like I've seen them do in Durango.


/*
  4 inputs, pulled high internally. Clockwise, counterclock-
   wise and another set for going even slower.
  Cycle through the states to turn the stepper clockwise or 
   in reverse for ccw, using the 4 output pins to drive the
   FETs turning the coils on with the correct polarity.
  LED flash every 2.5 seconds or faster depending on 
   which mode it is in.
 
  Author: Gert 'Speed' Muller
  2016.03.28
*/

#define VERSION "Stepper r004"
#define LED                13
// 4 Stepper motor pins:
#define P1                  4
#define P2                  5
#define P3                  6
#define P4                  7

#define DELAYBETWEEN        3
#define DELAYBETWEENMAX    20
#define DELAYPIN            5

#define TIMETOCHANGE     2000

unsigned long before   =    0;
unsigned long now      =    0;

int timestep =   DELAYBETWEEN;
int readByte =              0;

// which pin is being toggled
int state =                 0;

// bigger than zero to avoid divide by 
zero, see LED toggle later
int mode =                  1;
    
// The setup function runs once when 
// you press reset or power the board
void setup( ) {
  pinMode( LED, OUTPUT );
  digitalWrite( LED, LOW );

  pinMode( P1, OUTPUT );
  pinMode( P2, OUTPUT );
  pinMode( P3, OUTPUT );
  pinMode( P4, OUTPUT );
  digitalWrite( P1, LOW );      // turn pin off
  digitalWrite( P2, LOW );      // turn pin off
  digitalWrite( P3, LOW );      // turn pin off
  digitalWrite( P4, LOW );      // turn pin off

  Serial.begin( 115200 );
  Serial.println( VERSION );

// need a GND pin on the same side as the A1-4 inputs
  pinMode( A0, OUTPUT );         
  digitalWrite( A0, LOW );      // so drive it low.

  pinMode( A1, INPUT_PULLUP );  // CW slow
  pinMode( A2, INPUT_PULLUP );  // CCW slow
  pinMode( A3, INPUT_PULLUP );  // CW slower
  pinMode( A4, INPUT_PULLUP );  // CCW slower

  // check inputs on startup, no real 
  // other use, just debug!
  if ( digitalRead( A1 ) != HIGH ) {
    Serial.println( "A1 LOW" );
  } // if
  if ( digitalRead( A2 ) != HIGH ) {
    Serial.println( "A2 LOW" );
  } // if
  if ( digitalRead( A3 ) != HIGH ) {
    Serial.println( "A3 LOW" );
  } // if
  if ( digitalRead( A4 ) != HIGH ) {
    Serial.println( "A4 LOW" );
  } // if

  now = millis( );
  before = now;

  state = 0;
  mode = 1;
} // setup


void stepPin( char pin, char multiply ) {
  digitalWrite( pin, HIGH );      // coil on
  delay( DELAYPIN );              // wait
  digitalWrite( pin, LOW );       // coil off
  delay( DELAYPIN );              // wait
  delay( timestep * multiply ); 
} // stepPin


void counterOrClockWise( bool COUNTER, char multiply ) {
  if ( COUNTER ) {
    state -= 1;  // states decreases going counter clockwise
    if ( state < 0 ) state = 3; // roll under goes to 3
  } else {
    state += 1;  // states increases going clockwise
    if ( state > 3 ) state = 0; // roll over goes to 0    
  } // else
  
  switch ( state ) {
    case 0:
      stepPin( P1, multiply ); 
      break;
    case 1:
      stepPin( P3, multiply );
      break;
    case 2:
      stepPin( P2, multiply );
      break;
    case 3:
      stepPin( P4, multiply );
      break;
    default:
      state = 0;
      break;      
  } // switch
} // clockwise


// the loop function runs over and over again, forever
void loop( ) {
  // sometimes you missed the VERSION, 
  // so sending a '?' repeats it
  if ( Serial.available( ) > 0 ) {
    readByte = Serial.read( );
    if ( readByte == '?' ) {
      Serial.println( VERSION );
    } // if help    
  } // if available
  
  // time tells if we need to toggle the LED
  now = millis( );

  // toggling the LED with a rate based on the mode
  if ( ( now - before ) > ( TIMETOCHANGE / mode ) ) {
    before = now;
    digitalWrite( LED, !digitalRead( LED ) );  // toggle LED
  } // if

  // look which pin is connected to ground and do the highest 
  // priority one, clockwise slow, ccw slow, cw slower, 
  // ccw slower, else do nothing
  if ( digitalRead( A1 ) != HIGH ) {
    counterOrClockWise( false, 0 );  // false:CW, 0:slow
    mode = 2;
  } else {
    if ( digitalRead( A2 ) != HIGH ) {
      counterOrClockWise( true, 0 ); // true:Counter, 0:slow
      mode = 3;
    } else {
      if ( digitalRead( A3 ) != HIGH ) {
        counterOrClockWise( false, 1 );   // false:CW, 1:slower
        mode = 4;
      } else {
        if ( digitalRead( A4 ) != HIGH ) {
          counterOrClockWise( true, 1 );  // true:Counter, 
                                          // 1:slower    
          mode = 5;
        } else {
          mode = 1;  // no button, flash LED slowest!  
        } // else, not cw0
      } // else, not ccw0
    } // else, not cw1
  } // else, not ccw1
} // loop

// Sketch uses 3042 bytes (9%) of program 
//   storage space. Maximum is 30720 bytes.
// Global variables use 240 bytes (11%) of dynamic 
//   memory, leaving 1808 bytes for local variables. 
//   Maximum is 2048 bytes.


The Stepper motor for the code above, has 5 or 6 wires, which can be controlled with the 5th or the 5th and 6th wire connected to Vcc and then turning any of the 4 coils on by connecting the other wires to ground. Of course, not all at the same time, but by following the sequence diagram as shown in the motor's datasheet. This is called the Unipolar motor configuration.


So WHT and YEL gets connected to 12Vdc and BLK, GRN, RED and BLU is switched to GND when needed.

In the Bipolar motor configuration you only get 4 wires (see BLK,GRN,RED and BLU below).

 The polarity of BLK/GRN and RED/BLU is switched when needed.

To turn the coils on with current flowing in all four steps, you need to be able to control the direction the current through the 2 coils as well. This requires an H-bridge circuit ($0.15 on Ebay), and shown below:

One coil shown, blue and green for opposite current flows

Two H-bridge circuit for less than $1
And here is a short sketch to control the Stepper motor in your floppy disk or CDROM drive in one direction:


/*
  2 inputs, pulled high internally. Clockwise/Counterclock-
   wise and On/Off.
  Cycle through the states to turn the stepper clockwise or 
   in reverse for ccw, using the 4 output pins to drive the
   H-bridge turning the bi-polar coils on with the correct polarity.
  LED is on in the first phase (well for DELAY milliseconds)
 
  Author: Gert 'Speed' Muller
  2017.10.31
*/

#define Coil1A    4
#define Coil1B    5
#define Coil2A    6
#define Coil2B    7
#define InputPin A0
#define LEDPin   13
#define DELAY    50

unsigned int myPhase;

void setup() {
  pinMode( Coil1A, OUTPUT );
  pinMode( Coil1B, OUTPUT );
  pinMode( Coil2A, OUTPUT );
  pinMode( Coil2B, OUTPUT );
  pinMode( LEDPin, OUTPUT );
  pinMode( InputPin, INPUT_PULLUP );

  myPhase = 0;
  myStep( myPhase );

  digitalWrite( LEDPin, HIGH );   // flash with boot message
  Serial.begin( 115200 );
  delay( 100 );
  Serial.write( "StepperFan 0.1..\n" );
  digitalWrite( LEDPin, LOW );


} // setup

void myStep( unsigned int phase ) {
  digitalWrite( LEDPin, LOW );            // turn the LED off
  switch( phase ) {
    case 1: digitalWrite( LEDPin, HIGH ); // turn the LED on
            digitalWrite( Coil1A, LOW );
            digitalWrite( Coil1B, HIGH );
            digitalWrite( Coil2A, LOW );
            digitalWrite( Coil2B, HIGH );
            break;
            
    case 2: digitalWrite( Coil1A, HIGH );
            digitalWrite( Coil1B, LOW );
            digitalWrite( Coil2A, LOW );
            digitalWrite( Coil2B, HIGH );
            break;
            
    case 3: digitalWrite( Coil1A, HIGH );
            digitalWrite( Coil1B, LOW );
            digitalWrite( Coil2A, HIGH );
            digitalWrite( Coil2B, LOW );
            break;
            
    case 4: digitalWrite( Coil1A, LOW );
            digitalWrite( Coil1B, HIGH );
            digitalWrite( Coil2A, HIGH );
            digitalWrite( Coil2B, LOW );
            break;
    default:
            digitalWrite( Coil1A, LOW );
            digitalWrite( Coil1B, LOW );
            digitalWrite( Coil2A, LOW );
            digitalWrite( Coil2B, LOW );
            break;
  } // switch
} // myStep

void loop() {
  delay( DELAY );
  if ( digitalRead( InputPin ) == LOW ) {
    myPhase++;
    if ( myPhase > 4 ) myPhase = 1;
  } else {
    myPhase--;
    if ( myPhase < 1 ) myPhase = 4;
  } // if LOW
  
  myStep( myPhase );
} // loop

// Sketch uses 2400 bytes (7%) of program 
//   storage space. Maximum is 30720 bytes.
// Global variables use 202 bytes (9%) of dynamic 
//   memory, leaving 1846 bytes for local variables. 
//   Maximum is 2048 bytes.

Who does not have an unused CDROM drive?

There are also three or four example Stepper motor sketches in the Arduino IDE’s Example folder if you only need to control a single Stepper motor. Please try them all out. They use the local Stepper library, defined in Stepper.h to create a Stepper object with the number of steps you complete a rotation and the 4 pins connected to the Stepper, or the switches controlling the Stepper windings, example:

#define STEPS 100
 Stepper stepper( STEPS, 8, 9, 10, 11 );

And in the setup( ) function they typically set the speed in RPMs:
stepper.setSpeed( 30 );

And what follows in some form or fashion in loop( ):
stepper.step( numberOfSteps );
You can now add a potentiometer and either increase the position by stepping forward, or decrease it be step backwards with negative numbers, or keep stepping and change the speed with setSpeed according to the rotation of the knob on the potentiometer.

For controlling more than one Stepper, or need acceleration and deceleration, then AccelStepper would be a better choice.

Moving a Stepper back and forth with AccelStepper’s bounce example:

#include <accelstepper.h&gt
AccelStepper stepper;             // Defaults to the 4 pins on 2,3,4 and 5

void setup( ) { 
  stepper.setMaxSpeed( 100 );     // set the speed
  stepper.setAcceleration( 20 );  // set the acceleration
  stepper.moveTo( 500 );          // set the target position
} // setup

void loop( ) {   
   if ( stepper.distanceToGo( ) == 0 ) // when it gets there
     stepper.moveTo( -stepper.currentPosition( ) );
                                  // change the target

   stepper.run( );                // make at most one step, call this often
} // loop
Something you need to keep in mind, a call to the Stepper.step( ) function is “blocking”, meaning that you will need to wait for that command to finish before you can do the next thing. Not important if you are simply commanding something scripted play by play, but if you also need to read an input like an Infrared TV remote control or an LCC command coming from JMRI, you might miss that completely while the Stepper code is “blocking”. On the other hand, a call to AccelStepper.run( ); will only check if a step is needed, make a step and return, or if not needed, just return. In this “non-blocking” way, you can quickly check if a command was sent over the Serial, Infrared or CAN bus and process that command as well.
When the coils in the motor have a DC resistance of 9 or 25 Ohms, you can calculate that a 5 Vdc supply would need to deliver 0.55 A or 0.2 A (from I = V/R) and the Arduino board or chip by itself cannot provide that current. So the Vs pin on the H-bridge circuit need to go straight to a good power supply and not the 5 Vdc on the Uno or Nano board.

Comments

Popular posts from this blog

Signal Masts in JMRI 4.14, for beginners...

Here are all the notes and files from the Plano Train Show Clinic. You need to have an internal sensor ( IMCOMPORT ) with the port in the value like COM10 or /dev/ttyUSB0 , whatever Device Manager (or /dev/ttyUSBx) shows for the connected Arduino. If you have Panels -> Script Output open, you will see the message:  !!! Please add IMCOMPORT with the port in the value!!! The basic idea here is to use the Comment in the Signal Head field to define the position of the Pixel in the string commented to the Arduino, as well as the RGB colors to show for Clear, Approach and Stop (00:00 FF 00: BBAA 00: FF 0000). With all the Heads in the Table, run the python script and now you can update the colors in the Comment "live", keeping in mind that a message is only sent when a Head changes color. We then put the Heads in Masts and use the Signal Mast Logic to do the magic. I like the original Panel Editor more and it works fine as long as you keep using Mast pairs, which require

The One Pin Signal System, using an Arduino

The One (RRRduino) Pin Signal System! Part I NeoPixels: Yes, you read that right, you can control a whole signal system with up to 200 lights by using a single Arduino pin. And this was not my idea, all the credit and the patent goes to my friend Tom, but since I already had something using the same technology up and running using only a Nano, I do not feel too bad telling you about it. Short video first! There is a tiny 8 pin chip out there called a WS2811 which is a three channel LED driver, and you might find them on the interweb as NeoPixel LED Driver Chips. They need power and ground, typically 5 Vdc, and data to be put in the three registers inside the chip, one for each LEDs it can control. So, in most cases out there today, the LEDs connected are Red, Green and Blue (RGB for short) and by setting the three registers each with an 8 bit value (which in English means a value from 0 to 255, since 2 8 = 256, but since 0 is an acceptable value too, the maximum is 2 8 - 1

Not RRRduino, unless you have an MQTT connection via Ethernet or WiFi

JMRI code to connect to an MQTT broker, to publish and subscribe to the messages for signal masts. Of course, we agree to a standard for the MQTT topics, and since you were not here, we settled on "mast.xxxx" where xxxx is a number starting at zero. This MQTT topic is used as the User Name in a JMRI signal mast. Created as a Virtual Mast with the Aspect used as the payload in the message. And, yes, the device under the real mast needs to be programmed with the same name and code for each of the "aspect" payloads to be implemented. A yellow and red LED'd dwarf signal can not do a "Clear" aspect, so make sure "Clear" is maybe set to yellow for "Approach" as well.    Virtual Masts with agreed upon User Names. Comments are for the One Wire Signals on the NeoPixel string (see prior post)   Virtual Mast The Jython (or Python) code shown here below, is to attach a Listener to each Signal Mast, and to publish a message