SchmalzHaus logoRC Servo Input to Stepper Output Sketch


Description:

An e-mail came in to me asking if I knew anywhere that sold an RC servo input to stepper output driver board. It turns out that this board http://www.cunningturtle.com/wiki/index.php?title=Radio_Controlled_Stepper performs this function, but it is for uni-polar stepper motors only, and has a very limited maximum step speed. It's also not configurable. On the plus side, it's cheap ($17 USD) and has both a position and speed mode. The only mode we needed was speed mode.

So the use case for this sketch is as follows: A user has an RC aircraft transmitter and receiver that already controls a bunch of other stuff (RC stuff), and they need to have one of their stick axes control the speed and direction of a stepper motor. This seemed like a really simple thing to do, and could be a great example of using chipKIT, a Fubarino Mini board, and a Big Easy Driver stepper motor drive together. How often does that happen!

Update (12/17/2017) : There is now a two-channel version of this same system available. Here's a chipKIT Wiki page describing it and the sketch needed : http://chipkit.net/wiki/index.php?title=Driving_Steppers_from_RC_Reciever

Update (09/01/2019) : This page was updated to point to Arduino IDE + chipKIT core as the preferred IDE rather than the no longer supported MPIDE. The sketch was also updated to add a blinking green LED on the Fubarino Mini and serial output (over USB) of the input and output values.

How does it work?

For some awesome RC servo to stepper motor action, check out the video:

As I move the right stick forward, the stepper motor starts turning forward, faster and faster the further I push the stick. When I pull the stick back, it goes in the other direction. Simple.

The sketch code (See below) has fully configurable maximum and minimum steps per second, as well as a configurable deadband in the center position. It also allows you to set the RC servo pulse duration that's considered 'full forward' and 'full reverse', so you can really customize it for your own use.

I've used a hardware timer module and an output compare module on the PIC32 to generate the step pulses to the driver. This means that if you want it can go ridiculously fast (up to 12 million steps per second) and all the way down to 1 step per second (it could be much slower with just a little change in the math).

Because there are a ton of driver boards out that that take step and direction inputs (which is what this sketch outputs) you could use this to drive huge servo motors, or harmonic drives, or BLDC motors, etc. etc.

How can I make my own?

To duplicate this, you'll need the following things:

Then follow the wiring instructions in the comments at the top of the sketch. Remember not to ever connect or disconnect a stepper motor from a driver board with power applied. In the video, I've wired the 5V output from the Big Easy Driver to power both the Fubarino Mini and the RC receiver, just cause it was simple.

I hope you enjoy this sketch - let me know if you make anything really cool with it!!

Sketch:

(This sketch has been tested to work in Arduino 1.8.9 + chipKIT core. It uses straight up register manipulation to set up the timer and output compare.
Copy and paste this into Arduino IDE, or download the RCServoInStepperOut.ino file below.


/* Example sketch for reading RC servo input and driving a stepper motor in speed mode
 * Written by Brian Schmalz of Schmalz Haus brian@schmalzhaus.com
 * This sketch runs on a Fubarino Mini, and reads an RC servo type pulse input on pin 17
 * And then scales that input based on the constants below, and outputs a step/dir type stepper
 * output stream to be used with a driver like the Big Easy Driver or Easy Driver from Sparkfun.
 * The step signal is on pin 9 and the direction signal is on pin 8.
 *
 * The whole idea here is to allow somebody with an RC transmitter to precisely control the 
 * speed and direction of a stepper motor, with configurable limits so that the max and min speed
 * can be changed for the application.
 * 
 * This sketch is in the public domain, and anybody can do anything with it. No warranty, etc.
 * 
 * Hardware setup:
 *    Take a Fubarino Mini, a stepper motor appropriate for your driver (like a NEMA 17 for Easy Driver)
 *    a power supply (like 12V DC, 2A) and a stepper motor driver like the Big Easy Driver or Easy Driver.
 *    Also you'll need some source of RC servo signals - like an RC receiver.
 *    Download this sketch to the Fubarino Mini.
 *    Connect grounds of Fubarino Mini, stepper driver, RC receiver, and power supply together.
 *    Connect motor to stepper driver.
 *    Connect 5V output from Big Easy Driver to battery input of RC receiver, as well as VIN pin of Mini.
 *    (You can use another 5V source if you want.)
 *    Connect RC receiver servo output signal to Mini pin 17.
 *    Connect Mini pin 8 to stepper driver DIRECTION input.
 *    Connect Mini pin 9 to stepper driver STEP input.
 *    Power up system and watch stepper motor move based on stick input.
 *    
 * Notes:
 *    This sketch controls the speed of the stepper motor, not it's position. In other words, it will NOT
 *      operate like an RC servo (that would just take a little different math actually). It basically 
 *      turns the stepper motor into a DC motor who's speed and direction you can control with an RC servo input.
 *    If the stick is centered (within the dead zone) then the stepper motor will not move.
 *    If the stick is advanced towards RC_MAX_US, the speed of the stepper motor will linearly increase in forward direction.
 *    If the stick is pulled back towards RC_MIN_US, the speed of the stepper will increase in reverse.
 *    There are limits to the maximum number of steps per second that your driver/motor can achieve. So don't set
 *        MAX_SPEED_SPS too high. Experiment a lot. Remember that torque is approximately inversely proportional 
 *        to speed with steppers, so your torque will drop off the faster you go.
 *    Because we are using a hardware timer and output compare to generate the step signal in this sketch, it can
 *      go _very_ fast, with zero jitter.
 *    This sketch can also be used to drive ANYTHING that takes step/dir inputs (many servo motor controllers
 *      can do this).
 */

// These are the user modifiable defines that you can use to tweak the performance of the system

#define MAX_SPEED_SPS    12000    // Units of steps per second, maximum stepper speed (at RC_MAX_US and RC_MIN_US)
#define MIN_SPEED_SPS     1400    // Units of steps per second, minimum stepper speed (at +/-RC_DEAD_US from RC_CENTER_US)
#define RC_MAX_US         2300    // Units of uS, maximum RC servo input pulse width accepted
#define RC_MIN_US         1300    // Units of uS, minimum RC servo input pulse width accepted
#define RC_DEAD_US          50    // Units of uS, value on either side of RC_CENTER_US to be considered 'no motor movement'
#define RC_INPUT_PIN        17    // This can be any pin
#define MOTOR_STEP_PIN       9    // This is not arbitrary, modify code below to use different Output Compare if you need to change this
#define MOTOR_DIR_PIN        8    // This can be any pin

// These are defines that the user should not ever have to modify 
#define RC_CENTER_US    ((RC_MAX_US + RC_MIN_US)/2)       // Half way between Min and Max RC value
#define PR2_MAX         (F_CPU / 2 /(MIN_SPEED_SPS * 2))  // Minimum speed for PR2 register
#define PR2_MIN         (F_CPU / 2 /(MAX_SPEED_SPS * 2))  // Maximum speed for PR2 register


void setup() {
  // Set up timer2 and timer3 to be a 32 bit timer
  // This 32 bit timer will be used to drive OC4 on
  // on pin RB2 (Fubarino Mini pin 9)    
  T2CONbits.ON = 0;             // Turn off the timers
  T3CONbits.ON = 0;
  T2CONbits.TCS = 0;            // Internal peripheral clock source
  T2CONbits.T32 = 1;            // 32-bit mode turned on for Timer2 and 3
  T2CONbits.TCKPS = 0b001;      // 1:2 prescale
  TMR2 = 0x00000000;
  PR2 =  0x00000000;

  // Set up OC4 to be PWM clocked on TMR2/3
  OC4CONbits.OCM = 0b011;    // Compare toggles OC4 output
  OC4CONbits.OCTSEL = 0;    // Timer2 is clock source
  OC4CONbits.OC32 = 1;        // Use 32-bit mode
  OC4RS = 0x00000000;
  OC4CONbits.ON = 1;        // Turn OC4 on
  T2CONbits.ON = 1;        // Turn the timer(s) on and start them running    

  pinMode(9, OUTPUT);
  pinMode(8, OUTPUT);

  // Re-route PPS ofo OC4 to pin RB2 (Mini pin 9)
  mapPps(9, PPS_OUT_OC4);
    
  pinMode(17, INPUT);

  Serial.begin(9600);
  pinMode(LED_BUILTIN, OUTPUT);
}

// Take an RC value in, which ranges from 2320 to 1260,
// and map it to a PR2 value (in units of 1/24000000 s)
// Add a sign for direction, and handle dead zone.
int calc_speed(int rc_in) {
  int temp;

  // If the receiver is turned off, it won't be generating
  // any pulses, so rc_in will be zero. In that case, return
  // zero
  if (rc_in == 0) {
    return (0);
  }
  
  // If the receiver is turned on, but the transmitter is not,
  // the receiver may be sending us runt pulses that we need
  // to ignore
  if (rc_in < 1000) {
    return (0);
  }

  // First limit the input
  if (rc_in > RC_MAX_US) {
    rc_in = RC_MAX_US;
  }
  else if (rc_in < RC_MIN_US) {
    rc_in = RC_MIN_US;
  }

  // If rc_in is in the dead zone, then return zero
  if (
    (rc_in < (RC_CENTER_US + RC_DEAD_US))
    &&
    (rc_in > (RC_CENTER_US - RC_DEAD_US))
  ) {
    return (0);
  }

  // Break apart the math into above and below center
  if (rc_in >= (RC_CENTER_US + RC_DEAD_US)) {
    // Bring it down so it starts at zero to (RC_MAX_US - RC_CENTER_US - RC_DEAD_US)
    rc_in = rc_in - (RC_CENTER_US + RC_DEAD_US);
      
    // Now scale so that rc_in of 0 becomes PR2_MIN and 
    // rc_in of (RC_MAX_US - RC_CENTER_US - RC_DEAD_US) becomes PR2_MAX
    rc_in = (RC_MAX_US - (RC_CENTER_US + RC_DEAD_US)) - rc_in;
    temp = rc_in * ((PR2_MAX - PR2_MIN)/(RC_MAX_US - (RC_CENTER_US + RC_DEAD_US)));
    temp = temp + PR2_MIN;
  }
  else {
    // Bring rc_in down so it starts at zero to (RC_CENTER_US - RC_DEAD_US)
    rc_in = rc_in - RC_MIN_US;
        
    // Now scale so that rc_in of 0 becomes PR2_MIN and 
    // rc_in of (RC_MAX_US - RC_CENTER_US - RC_DEAD_US) becomes PR2_MAX
    temp = rc_in * ((PR2_MAX - PR2_MIN)/(RC_MAX_US - (RC_CENTER_US + RC_DEAD_US)));
    temp = temp + PR2_MIN;
    temp = -temp;
  }

  return (temp);
}

// Take a new value for PR2 and strip off the sign, handle
// the direction bit, and update PR2 with the new value.
void update_speed(int new_speed) {
  // Only update timer if it's not running
  T2CONbits.ON = 0;

  // Read sign for direction, and make new_speed positive
  if (new_speed < 0) {
    new_speed = -new_speed;
    digitalWrite(8, HIGH);
  }
  else {
    digitalWrite(8, LOW);
  }
    
  // This is a 32 bit write
  PR2 = new_speed;
  // And if TMR2 is already higher than the new PR2, zero it out
  // so it can count up and match PR2
  if (TMR2 >= PR2) {
    TMR2 = 0x00000000;
  }
  T2CONbits.ON = 1;    
}

void loop() {
  int ch1, new_speed;
  static int blink_time = 0;
    
  ch1 = pulseIn(17, HIGH, 25000);
  new_speed = calc_speed(ch1);
  update_speed(new_speed);
  Serial.print("In:");
  Serial.print(ch1);
  Serial.print(" Out:");
  Serial.println(new_speed);

  blink_time++;
  if (blink_time > 100)
  {
    digitalWrite(LED_BUILTIN, HIGH);
  }
  else
  {
    digitalWrite(LED_BUILTIN, LOW);
  }
  if (blink_time > 200)
  {
    blink_time = 0;
  }
}
      

Download the above sketch here.

Questions? E-mail me at brian@schmalzhaus.com