With the Help of an A4988 Stepper Motor Driver
This lesson teaches you how to:
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.
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.
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.
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.
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.
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 |
# 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. |
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 }
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.
<!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>
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!