User Tools

Site Tools


oak:tutorials:stepper_motor

Oak: Controlling a Stepper Motor

With the Help of an A4988 Stepper Motor Driver

 Oak commanding stepper motor via A4988 driver.  A 6-wire 2-phase unipolar stepper motor is shown here, with its coil center tap leads (white/yellow) unconnected.  The A4988 driver (red) is upside-down in this photo, showing the backside of the board, which is opposite of the view shown in the Fritzing diagram.  The motor is powered from a common 12V DC wall wart transformer, with a 68uF cap connected at the jack.

This lesson teaches you how to:

  • Control a stepper motor using an A4988 stepper motor driver,
  • Expose functions and variables to the Particle Cloud, for remote commanding via either OakTerm, the Particle mobile app, or a custom web interface.

Components Used:

Part Quantity Identification
Oak with soldered headers 1
Stepper motor 1 *
Stepper motor driver 1 A4988
DC power supply, 8-35V 1
Jumper wires 5
Electrolytic capacitor, ~100uF 1 50-200 uF

*Note: Most common stepper motors in the ≤2A range will work for this lesson, including both bipolar and unipolar stepper motors with current and voltage ratings below 2A and 8V. The A4988 is not compatible with 5-lead unipolar motors. Refer to A4988 documentation and motor specifications when selecting a motor for this project.

Concepts:

Stepper Motors

A complete description of stepper motors is beyond the scope of this lesson. Adafruit's article All About Stepper Motors is an excellent summary of how stepper motors work and how to use them. For the purpose of this lesson, the important thing to understand is that, unlike a normal DC motor which turns smoothly, stepper motors step between discrete positions. For example, a stepper motor with 200 steps per revolution (typical), moves in 1.8° increments and may only turn to 200 specific positions in full-step mode.

By nature, stepper motors are exceptionally precise, but are not necessarily accurate. They cannot accurately turn to any arbitrary position you choose because they are confined to a finite set of start/stop positions. You can turn the motor to the step nearest to a target position, but that step might be as far as half a step away from the target, if the target position happens to be halfway between two steps. However, stepper motors are precise, in that they reliably return to specific (step) positions with very little error. Micro-stepping significantly improves accuracy by dividing each base step into smaller steps. The A4988 for example facilitates micro-stepping in half, quarter, eighth, and 1/16th steps. In the case of a 200 steps/rev motor, the full-step accuracy is +-0.9° (half of one step width), while the accuracy with 1/16th micro-stepping approaches 0.06° (it's actually a bit more complicated, but you get the idea). This lesson does not use this capability, but the reader is encouraged to research and experiment with this as future work.

Driving a Stepper Motor

In order to force the motor to step from one position to the next, a driver circuit must send a pulse of current through the coils in such a way to attract the rotor toward the next position and/or repel it from the current position. The direction the rotor steps depends on the polarity of the coils – that is, the direction that our pulse of current flows through each coil.

GPIO pins from any microcontroller, including the Oak, are intended to be used as transistor-to-transistor logic (TTL) or for extremely low current applications such as powering (very dim) LEDs and making them blink (we're way past that tutorial now, right?). GPIO pins are not capable of sourcing nearly enough current to drive a stepper motor directly, which is why it is necessary to employ an external driver circuit to route current to the motor at the will of the Oak. The Oak will be the brains of our project, the driver circuit provides the braun.

Why use a stepper driver module, rather than building a drive circuit from scratch? You could, in theory, drive a stepper motor in a single direction using a single field effect transistor (FET) controlled from a single GPIO pin from your Oak, but you'll probably want to be able to drive your motor in both directions. To do that, you would need to power each phase with 4 FETs in H-bridge configuration. That's 8 FETs total that you would need to signal in various combinations depending on which direction you want the motor to move. The Oak has enough GPIO pins to command such a circuit, but building the circuit and coordinating all 8 FETs from within your sketch would be extremely tedious.

Enter, the A4988 stepper motor driver carrier. The A4988 and similar stepper motor drivers include two H-bridge circuits, one for each phase (coil) of your motor, with additional logic that takes the burden of signaling the 8 individual H-bridge FETs and abstracts their coordinated operation to two signals: step and direction. How cool is that?!

In summary: We can apply a single pulse to the step pin of the A4988 to ask it to step the motor one step in either the CW or CCW direction, depending on whether we've set the direction pin high or low.

Circuit:

Wait! Before connecting your Oak to the A4988 stepper motor driver, consider flashing the sketch below and experimenting with it to become familiar with how it works. By default, this sketch uses pin1 for the step signal specifically to give you a visual indication of when the motor should step. Prior to connecting the Oak to the A4988, try commanding a step rate of 2 (steps/sec) and watch the LED flash twice per second, then try changing the speed. You can do similar experiments with commanding direction if you remap the direction signal to pin1 in the sketch in order to see the LED turn on or off as you command direction (-1 or 1). Be sure to restore your mapping to match the signals going to the A4988 before connect the devices later.

CAUTIONS:

This circuit includes parts that can get hot enough to burn you and parts which can cause damage if installed incorrectly. Exercise caution when assembling and operating this circuit. Proceed at your own risk.

The A4988 driver can get very hot!

  • The A4988 driver can get hot enough to burn you, even during normal operation.
  • If you have an IR gun, use it to monitor the A4988.
  • Do not use your finger as a temperature gauge.
  • Heat dissipation in the A4988 is a function of your motor's impedance, external torque on the motor, supply voltage, and drive speed. When you first start moving the motor, command at slow speeds for limited periods of time and gradually work up to faster speeds while monitoring the temperature of the A4988 driver. Max speed might not be much more than 60 RPM (1 rev/sec, or 200 steps/sec for 200 step/rev motor).

Do not install the capacitor backwards!

Do not apply motor power supply backwards!

  • The A4988 does not have reverse polarity protection.
  • Connecting power supply leads in backwards can destroy your A4988, or worse.
  • Double check power supply polarity before you connect to the A4988.
  • From personal experience (oops), plugging in 12Vdc backwards for 3 seconds, the chip got hot but was not damaged.

You MUST set the current limiting trim pot for your motor

  • The trim potentiometer on the A4988 is initially set to some random value.
  • The initial value may allow the A4988 to push more current than your motor can handle.
  • Follow instructions in Step 3 below.

Fritzing Diagram

Build Procedure

Step 1: Connect the electrolytic capacitor

  • Connect the ~100uF capacitor across the A4988 VMON and GND pins, being very careful to connect in the correct polarity. Electrolytics usually have a stripe or “-” indicator on the side that should connect to ground. If you connect the capacitor backwards, it may explode when you apply power.

Step 2: Connect motor power supply to the A4988

  • If you're using a wall-wart transformer, unplug it from the wall. If you're using a benchtop power supply, turn it off.
  • Connect supply+ to the A4988 VMOT pin, and supply- to the neighboring GND pin.
  • Be very careful not to get these backwards, otherwise your board will overheat.

Step 3: set coil current limits on A4988 driver (this is NOT optional)

  • The following steps outline this process. If any of this doesn't make sense, read the Current Limiting section on Pololu's A4988 page and watch their very informative video on youtube.
  • You need to know the maximum per-phase limit for your motor (use rating current, if per-phase current is not provided in your motor's datasheet).
  • Decide what current limit you want to use.
    • The current limit you choose must be less than or equal to your motor's rating to avoid overheating your motor and/or the driver. The motor and driver will run cooler if you operate with a current limit lower than your motor's rating. It is recommended that you start with a current limit equal to half of your motor's rating, monitor the temperature of the A4988 at this lower initial operating point, then gradually increase the limit later if you need more power and if the motor and driver do not get too hot.
  • Calculate reference voltage, Vref, based on your selected max current, Imax:
    • If your A4988 was manufactured by Pololu in Jan 2017 or later: Vref = 8*Imax*0.068
    • Otherwise: Vref = 8*Imax*0.050 = 0.4*Imax
    • Example Vref values:
Target Current Limit (A) 0.25 0.50 0.75 1.00 1.50 2.00
Target Vref (V) (for older A4988 models*) 0.10 0.20 0.30 0.40 0.60 0.80
Target Vref (V) (for Pololu 2017+ models) 0.14 0.27 0.41 0.54 0.82 1.09
  • At this point, only your motor power supply and capacitor should be plugged into the A4988.
  • Apply motor power.
  • Measure voltage between motor supply ground and the center of the trim pot on the A4988.
  • Adjust the trim pot until the voltage equals your calculated Vref.
    • Tip: to measure Vref as you adjust the trim pot at the same time, use an alligator clip to connect your multimeter Vin+ lead to the shaft of a small metal screwdriver which you use to turn the trim pot.
  • Turn your motor supply power OFF.

Step 4: Connect the stepper motor to the A4988 driver

  • You turned off or unplugged your motor power supply, right?
  • You'll need to figure out which leads from the stepper motor go to which pins on the A4988.
    • The A4988 driver powers two phases (coils) simultaneously - one through pins A1 and A2, and the other through pins B1 and B2. Your stepper motor may have more leads than you need. If you have access to the datasheet for your motor, look up its type and wire coloring scheme. Otherwise, you'll need to measure resistance between pairs of leads to determine which pair connects to the ends of one coil (coil “A”), and which pair of other leads connects to the other coil (coil “B”) (it doesn't matter which coil is A and which is B), per instructions in the following table.
  • Once you've identified end-leads for each coil, you can connect one coil's end leads to A1 and A2 (order doesn't matter), and the other coil's end leads to B1 and B2 (order doesn't matter). The Pololu A4988 FAQ provides handy diagrams to help with this.
# Leads (Probable) Type Connection Mapping
4 2-phase bipolar Easy! The four leads connect to two coils. Determine which leads pair together by measuring resistance/continuity between them. Connect the end-leads from one coil to A1 and A2, and the end-leads form the other coil to B1 and B2 (order doesn't matter).
5 2-phase unipolar Sorry, the A4988 cannot drive this type of motor.
6 2-phase unipolar Measure resistance between leads to group the leads into 2 sets of 3, according to continuity. Within each set, measure resistance across pairs of leads to find the pair with the highest resistance - these leads connect to the ends of one of the coils, while the third lead is a center tap, which we won't use (this diagram may be useful). One pair of end-leads will connect to A1 and A2, while the other set of end-leads connects to B1 and B2 (order doesn't matter).
8 2-phase unipolar This one is complicated; it has 2 coils per phase, and the coils can be configured in series (recommended by Pololu for use with A4988) or parallel. Refer to the connection guide in the Pololu FAQ.

Step 5: Connect the Oak to the A4988

  • Motor power is off, right?
  • If your Oak is on, turn it off.
  • At this point, only the capacitor, the (turned off) motor power supply, and the stepper motor should be plugged into the A4988.
  • Connect a jumper from the Oak's Vcc pin to the A4988 VDD pin. This powers the A4988 logic circuits.
  • Connect a jumper from the Oak's Gnd pin to the A4988 GND pin.
  • Connect a jumper from the Oak's P0 pin to the A4988 DIR pin.
  • Connect a jumper from the Oak's P1 pin to the A4988 STEP pin.
  • Connect a jumper between the A4988's RESET and SLEEP pins.
  • Do not power on your Oak yet.

Step 6: Power up! (aka Initial Checkout)

  • With the motor power supply and Oak turned OFF, try to twist the shaft of your motor with your fingers.
    • It will feel stiff but it should turn. You'll feel it “tick” through the detent (step) positions.
    • If you can't turn it at all, then you might have a problem. Make sure none of your motor leads are shorted together, and make sure that both the A4988 and Oak are off.
    • If you were able to turn the motor, then proceed.
  • Turn on or plug in your motor power supply.
  • Turn on or plug in your Oak.
    • You may or may not see/hear/feel the slightest twitch of the motor.
  • Make sure nothing is getting hot. The A4988 might get slightly warm. If it's hot at this point then you have a problem.
  • Try to twist the motor shaft again with your fingers. The shaft should be much harder, if not impossible to turn by hand. If that's true, then you're in good shape. The A4988 driver is actively counteracting the torque you apply to the motor, in order to hold motor position. Neat!
  • Turn off or unplug your motor power supply.
  • Check one last time if the A4988 is getting hot. If not, you're good to go, but you will want to keep an eye on the temperature once you start commanding the motor to move.

Code:

Oak Stepper Driver Driver Sketch

const int pinStep = 1;
const int pinDirection = 0;
const int durationStepPulseMicroseconds = 100; //us
const int maxStepsPerSec = 4;

int stepDirection = 0;       // -1 for CCW, 1 for CW, 0 for unset
double stepSpeed = 0;        // steps per sec
int stepPeriodMs = 0;   // ms per step
int waitBetweenStepsMs; // ms

bool doMotion = false;

char outStr[32] = "";  //buffer for strings to Particle.publish
char* str_end;  // needed for string conversion error checking


// the setup function runs once when you press reset or power the board
void setup() {
  // Initialize step and direction pins:
  pinMode(pinDirection, OUTPUT);
  digitalWrite(pinDirection, LOW);
  pinMode(pinStep, OUTPUT);
  digitalWrite(pinStep, LOW);

  // Setup Particle function handles:
  Particle.function("motion",motion);
  Particle.function("setspeed",setSpeed);
  Particle.function("setdir",selectDirection);

  // Setup Particle variable accessors:
  Particle.variable("speed",stepSpeed);
  Particle.variable("direction",stepDirection);
  Particle.variable("do_motion",doMotion);
  Particle.variable("stepperiod",stepPeriodMs);
}



// Main loop
void loop() {

  // If doMotion is true AND we have valid speed and direction values, then move the motor!
  if( doMotion && stepSpeed>0 && (stepDirection==-1 || stepDirection==1) ){
    doOneStep();  // send one pulse to the A4988 STEP pin
    
    // Pause here until it's time to send the next pulse
    // To be more precise, we would account for cpu time and step pulse duration,
    // both of which should be small relative to the step period until you get up
    // to speeds over 500 steps per second.  Future builds should correct this.
    delay(stepPeriodMs);  

  // Otherwise, pause briefly before checking again
  } else {
    delay(100);
  }
  
}


// The "motion" function expects a string argument, either "start" or "stop"
// This function sets the doMotion flag which the main loop monitors.
int motion(String strStartOrStop){
  if( strcmp("start",strStartOrStop.c_str())==0){
    doMotion = true;
  } else if( strcmp("stop",strStartOrStop.c_str())==0){
    doMotion = false;
  } else {
    doMotion = false;
    Particle.publish("motion() error","expected \"start\" or \"stop\"");
    return -1;  // return -1 to indicate error
  }
  return (doMotion)?1:0;  //return 1 or 0 indicating doMotion true or false
}


// setSpeed expects one argument:  steps per second, between -1000 and 1000
// The steps per second argument is received as a string but may indicate
// integer or fractional, positive or negative (i.g. "-100", "10", "0.25")
// Negative speed will be interpreted as negative direction
int setSpeed(String strStepsPerSecond){
  // Attempt to convert the string to a double:
  stepSpeed = strtod(strStepsPerSecond.c_str(), &str_end); // steps per sec
  
  // Check if the string converted properly:
  if (*str_end != '\0') {
    sprintf(outStr, "Unable to convert \"%s\" to float", strStepsPerSecond.c_str());
    Particle.publish("setSpeed() error", outStr);
    stepSpeed = 0;
    return -1;
  }
  // Reject speeds over 1000 steps/sec:
  if (stepSpeed<-1000 || stepSpeed>1000){
    Particle.publish("setSpeed() error", "speed must be between -1000.0 and 1000.0 steps/sec");
    stepSpeed = 0;
    return -1;
  }

  // Technically, signed speed is velocity.
  // Deconvolve "speed" into magnitude and direction:
  if( stepSpeed>0 ){
    stepDirection = 1;  // set positive direction
  } else {
    stepSpeed = stepSpeed*-1.0;  // make speed positive
    stepDirection = -1;  // set negative direction 
  }

  // Set direction pin:
  digitalWrite(pinDirection, (stepDirection>0) ? HIGH : LOW );
  
  // Calculate step period (ms between steps):
  stepPeriodMs = (stepSpeed==0) ? 1000 : 1000/stepSpeed;  //ms
  
  return stepPeriodMs;
}


// Configures the A4988 Diretion input according to received direction request.
// The direction request must be precisely "1" or "-1".
// Whether 1 corresponds to CW or CCW depends on which coils you connected to A and B.
// If the resulting convention does not suit your needs, you can either swap
// the A1/A2 connections with the B1/B2 connections at the A4988, or modify the code here.
// Note that setSpeed also configures the direction pin.
int selectDirection(String dirRequest){
  // Expect "1" or "-1"
  if( strcmp(dirRequest.c_str(),"-1")==0 || strcmp(dirRequest.c_str(),"1")==0 ){
    stepDirection = atoi(dirRequest.c_str());
  } else {
    Particle.publish("setDirection() Error","direction must be -1 or 1");
    stepDirection = 0;
  } 
  
  // Set direction pin:
  digitalWrite(pinDirection, (stepDirection>0) ? HIGH : LOW );

  // Make the sign of the speed parameter match the selected direction:
  stepSpeed = (stepDirection>0) ? stepSpeed : stepSpeed*-1.0;
  
  return stepDirection;
}



// This function applies a single STEP pulse:
void doOneStep(){
  digitalWrite(pinStep, HIGH);  // start step pulse
  delayMicroseconds(durationStepPulseMicroseconds); // hold step pulse
  digitalWrite(pinStep, LOW);   // stop step pulse
}

Remote Commanding

The sketch above exposes 3 functions to the Particle Cloud. You can access these functions via OakTerm, the Particle mobile app, or via direct http GET/POST exchanges.

  • motion(start) starts motion, if you previously configured speed and direction.
  • motion(stop) stops motion.
  • setspeed(stepsPerSecond) sets the motor speed in steps per second.
    • Accepts floating point values between -1000 and 1000.
    • Example: speed of 0.5 steps/sec will step once per 2 seconds.
    • Negative values set direction negative.
    • You can command a different speed while the motor is running.
  • setdir(-1 or 1) sets motor direction.
    • Whether 1 corresponds to CW or CCW depends on which coil you hooked up to A and B.
    • You can command a different direction while the motor is running.

Stepper Controller Web Interface

  • The following is a fairly crude demonstration of one way to send POST/GET commands to the Particle API to remotely interface with your Oak and the attached stepper motor.
    • Replace MY_DEVICE_ID with your device ID and MY_ACCESS_TOKEN with your access token, then save as stepper_controller.html, anywhere you'd like.
<!DOCTYPE>
<html>
  <head>
    <title>Stepper Motor Controller</title>
  </head>
  
  <body>
  
    <h3><a href=http://digistump.com/wiki/controlling_a_stepper_motor_via_a4988_driver>Oak Stepper Motor Controller</a></h3>
  
    <form action="https://api.particle.io/v1/devices/MY_DEVICE_ID/setspeed" method="POST" target="replyFrame">
      <input type="hidden" name="access_token" value="MY_ACCESS_TOKEN" /> 
	  Set speed (-1000.0 : 1000.0):  
      <input type="text" name="speed" value="">
      <input type="submit" value="Submit">
    </form>
	
    <form action="https://api.particle.io/v1/devices/MY_DEVICE_ID/setdir" method="POST" target="replyFrame">
      <input type="hidden" name="access_token" value="MY_ACCESS_TOKEN" /> 
	  Set direction:  
      <input type="submit" name="args" value="-1">
      <input type="submit" name="args" value="1">
    </form>
  
    <form action="https://api.particle.io/v1/devices/MY_DEVICE_ID/motion" method="POST" target="replyFrame">
      <input type="hidden" name="access_token" value="MY_ACCESS_TOKEN" /> 
      <input type="submit" name="args" value="start">
      <input type="submit" name="args" value="stop">
    </form>

    <br>

    <form action="https://api.particle.io/v1/devices/MY_DEVICE_ID/speed" method="GET" target="replyFrame">
      <input type="hidden" name="access_token" value="MY_ACCESS_TOKEN" /> 
      <input type="submit" value="check speed">
    </form>

    <form action="https://api.particle.io/v1/devices/MY_DEVICE_ID/direction" method="GET" target="replyFrame">
      <input type="hidden" name="access_token" value="MY_ACCESS_TOKEN" /> 
      <input type="submit" value="check direction">
    </form>
	
    <form action="https://api.particle.io/v1/devices/MY_DEVICE_ID/do_motion" method="GET" target="replyFrame">
      <input type="hidden" name="access_token" value="MY_ACCESS_TOKEN" /> 
      <input type="submit" value="check do_motion">
    </form>
	
    <form action="https://api.particle.io/v1/devices/MY_DEVICE_ID/step_period" method="GET" target="replyFrame">
      <input type="hidden" name="access_token" value="MY_ACCESS_TOKEN" /> 
      <input type="submit" value="check step_period">
    </form>
	
    <br>
    <iframe name="replyFrame" width="100%" height="100%" />
  </body>
</html>

Conclusion:

Congratulations! You should now have a basic understanding of how stepper motors work and how to operate them, and you have a really fun platform to play with and expand on. Stepper motors are ubiquitous in the world of robotics, and I hope this tutorial serves as your gateway into this field. You can easily expand this setup to control up to 5 stepper motors simultaneously (or more, if you find a creative way to mux or share the direction signals).

As you operate your motor, monitor the temperature of the A4988 motor driver until you're confident that you have correctly configured the circuit and current limit, and that you're not asking the driver to move the motor too quickly. Consult your datasheet, or use this handy calculator to estimate max speed for your motor. If the A4988 gets hot, trim the current limit using the instructions provided in Build Step 3, and/or command slower speeds.

As noted in the stepper motor overview above, stepper motors can only reach a finite set of positions. A stepper motor with 200 steps/rev, for example, can only position within 0.9° from an arbitrary target position, when operated in full-step mode. If you require better accuracy, consider researching and experimenting with micro-stepping. The A4988 facilitates micro-stepping to 1/16th-step resolution, and enabling this function is quite simple. If you want to use 1/4, 1/8, or 1/16th step sizes exclusively, then you can configure the A4988 for this with 1, 2, or 3 additional jumpers or resistors, respecively. If you want to toggle between step sizes, then connect 3 Oak GPIO pins to the A4988 MS1, MS2, and MS3 pins and update the sketch to manage these pins.

Hope you found this useful!

oak/tutorials/stepper_motor.txt · Last modified: 2017/01/14 16:52 by Rover#18