chipKIT® Development Platform

Inspired by Arduino™

Last edit: 2021-03-21 22:34 by Majenko

Driving Steppers from RC Reciever

  1. Overview

  2. Sketch

Overview

Here's a video showing the operation of this system:

YouTube video showing two servos controlled by RC transmitter

There is an earlier version of this system with only one channel here : One stepper controlled by one RC transmitter channel

This system consists of a chipKIT Fubarino Mini, an RC transmitter and receiver, two stepper motor drivers and two stepper motors.

The purpose is to control the speed and direction of both steppers independently with two different RC channels from the transmitter.

The sketch code below uses Timer2 and Timer3 to generate step pulses from OC4 and OC1 respectively. The RC pulses are read in the loop() function using the pulseIn() call, and then manipulated based on the defined constants at the top of the sketch to produce new values for the PRx registers for Timer2 and Timer3. OC4 and OC1 are set to toggle their outputs on each timer overflow, so by changing the PR value, the timers overflow at different speeds and the steppers rotate at different speeds.

The speeds are configurable using the #define values in the code to account for different RC pulse widths and different desired min/max speeds.

Sketch

Here is the complete sketch:

/* Example sketch for reading two RC servo inputs and driving two stepper motors 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 18,
 * then scales those input based on the constants below, and outputs two step/dir type stepper
 * output streams to be used with a driver like the Big Easy Driver or Easy Driver from Sparkfun.
 * For channel 1 the step signal is on pin 9 and the direction signal is on pin 8.
 * For channel 2 the step signal is on pin 11 and the direction signal is on pin 10.
 *
 * The whole idea here is to allow somebody with an RC transmitter to precisely control the 
 * speed and direction of two stepper motors, 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, two stepper motors appropriate for your driver (like NEMA 17s for Easy Driver)
 *    a power supply (like 12V DC, 2A) and two stepper motor drivers 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 drivers, RC receiver, and power supply together.
 *    Connect motors to stepper drivers.
 *    Connect 5V output from one of the drivers 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 ch1 servo output signal to Mini pin 17.
 *    Connect RC receiver ch2 servo output signal to Mini pin 18
 *    Connect Mini pin 8 to stepper channel 1 driver DIRECTION input.
 *    Connect Mini pin 9 to stepper channel 1 driver STEP input.
 *    Connect Mini pin 10 to stepper channel 2 driver DIRECTION input.
 *    Connect Mini pin 11 to stepper channel 2 driver STEP input.
 *    Power up system and watch stepper motors 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), or much larger (i.e. hundreds or thousands of watts) motor drivers too.
 *      
 *    The RC transmitter/receiver I'm using produces pulseIn() values from about 1180 (min), to 1530 (center)
 *      to 1900 (max). I have scaled my values in this sketch appropriately - you can use the serial output to
 *      see what your system produces and change those values to suit.
 */

// 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      1900 // Units of uS, maximum RC servo input pulse width accepted
#define RC_MIN_US      1180 // 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_INPUT1_PIN    17 // This can be any pin
#define MOTOR_STEP1_PIN   9 // This is not arbitrary, modify code below to use different Output Compare if you need to change this
#define MOTOR_DIR1_PIN    8 // This can be any pin
#define RC_INPUT2_PIN    18 // This can be any pin
#define MOTOR_STEP2_PIN  11 // This is not arbitrary, modify code below to use different Output Compare if you need to change this
#define MOTOR_DIR2_PIN   10 // 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 / 16 /(MIN_SPEED_SPS * 2))  // Minimum speed for PR2 register
#define PR2_MIN   (F_CPU / 16 /(MAX_SPEED_SPS * 2))  // Maximum speed for PR2 register


void setup() 
{
  // Set up timer2 and timer3 to be independent 16 bit timers
  // We will drive OC4 with timer2 on pin RB2 (Fubarino Mini pin 9) 
  // We will drive OC1 with timer3 on pin RC0 (Fubarino Mini pin 11)
  // We will use a divide by 16, so that the 24MHz of PBCLK can give us
  // step rates of 23 Hz (at a count of 65535) down to 750KHz
  T2CONbits.ON = 0;             // Turn off the timers
  T3CONbits.ON = 0;
  T2CONbits.TCS = 0;            // Internal peripheral clock source
  T3CONbits.TCS = 0;
  T2CONbits.T32 = 0;            // 16-bit mode
  T2CONbits.TCKPS = 0b100;      // 1:16 prescale
  T3CONbits.TCKPS = 0b100;      // 1:16 prescale
  TMR2 = 0x00000000;
  TMR3 = 0x00000000;
  PR2 =  0x00000000;
  PR3 =  0x00000000;

  // Set up OC4 to be PWM clocked on TMR2
  OC4CONbits.OCM = 0b011; // Compare toggles OC4 output
  OC4CONbits.OCTSEL = 0;  // Timer2 is clock source
  OC4CONbits.OC32 = 0;    // Use 16-bit mode
  OC4RS = 0x00000000;
  OC4CONbits.ON = 1;      // Turn OC4 on
  
  // Set up OC1 to be PWM clocked on TMR3
  OC1CONbits.OCM = 0b011; // Compare toggles OC1 output
  OC1CONbits.OCTSEL = 1;  // Timer3 is clock source
  OC1CONbits.OC32 = 0;    // Use 16-bit mode
  OC1RS = 0x00000000;
  OC1CONbits.ON = 1;      // Turn OC1 on
  
  T2CONbits.ON = 1;       // Turn the timer on and start it running 
  T3CONbits.ON = 1;       // Turn the timer on and start it running 

  // All step/dir pins must be outputs
  pinMode(MOTOR_STEP1_PIN, OUTPUT);
  pinMode(MOTOR_DIR1_PIN, OUTPUT);
  pinMode(MOTOR_STEP2_PIN, OUTPUT);
  pinMode(MOTOR_DIR2_PIN, OUTPUT);

  // Re-route PPS of OC4 to pin RB2 (Mini pin 9)
  mapPps(9, PPS_OUT_OC4);
  // Re-route PPS of OC1 to pin RC0 (Mini pin 11)
  mapPps(11, PPS_OUT_OC1);

  // Both RC servo input pins must be inputs
  pinMode(RC_INPUT1_PIN, INPUT);
  pinMode(RC_INPUT2_PIN, INPUT);

  Serial.begin(9600);
}

// Take an RC value in, which ranges from RC_MAX_US to RC_MIN_US,
// and map it to a PRx value (in units of 1/1500000 s, which is
// PCLK (24MHz) divided by 16)
// 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 so no output happens
  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, so also return zero
  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_speed1(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(MOTOR_DIR1_PIN, HIGH);
  }
  else {
    digitalWrite(MOTOR_DIR1_PIN, LOW);
  }
  
  // This is a 16 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 = 0x0000;
  }
  T2CONbits.ON = 1; 
}

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

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

void loop() 
{
  int ch1_rc, ch2_rc, ch1_speed, ch2_speed;

  // Read in ch1 RC pulse from receiver
  ch1_rc = pulseIn(RC_INPUT1_PIN, HIGH, 25000);
  // Do math on it to convert it to PR value for timer
  ch1_speed = calc_speed(ch1_rc);
  // Send new PR value to timer
  update_speed1(ch1_speed);

  ch2_rc = pulseIn(RC_INPUT2_PIN, HIGH, 25000);
  ch2_speed = calc_speed(ch2_rc);
  update_speed2(ch2_speed);

  // Do a little debug output to serial port
  Serial.print("ch1: ");
  Serial.print(ch1_rc);
  Serial.print(" ch2: ");
  Serial.print(ch2_rc);
  Serial.print(" spd1: ");
  Serial.print(ch1_speed);
  Serial.print(" spd2: ");
  Serial.println(ch2_speed);
}