This is an old revision of the document!
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 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.
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 they are used. For the purpose of this lesson, the important thing to understand is that they 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 (unless you use microstepping which the A4988 is capable of, but that's a topic for another day..).
How a stepper motor driver works: In order to force the motor to step from one position to the next, a driver circuit must energize the coil(s) 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 coil(s). Stepper motors are typically driven by individual current pulses or square waves, with each pulse corresponding to a single step.
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'll need to power each phase with 4 FETs each, configured as H-bridges. That's 8 FETs that you would need to signal in various combinations depending on direction.
The A4988 and similar stepper motor drivers include two H-bridge circuits, one for each phase, 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. Each pulse into the the step pin signals the A4988 to drive the motor 1 step in either the CW or CCW direction, depending on whether the direction pin is high or low.
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.
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. Try commanding a step rate of 2 (step/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).
Step 1: Connect the electrolytic capacitor
Step 2: Connect motor power supply to the A4988
Step 3: set coil current limits on A4988 driver
Step 4: Connect the stepper motor to the A4988 driver
| # 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: Test motor connections
Connect the Oak to the A4988
Power up!
const int pinStep = 1;
const int pinDirection = 0;
const int durationStepPulseMs = 1; //ms
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",setDirection);
// Setup Particle variable accessors:
Particle.variable("speed",stepSpeed);
Particle.variable("direction",stepDirection);
Particle.variable("do_motion",doMotion);
Particle.variable("step_period",stepPeriodMs);
Particle.variable("step_wait",waitBetweenStepsMs);
}
// the loop function runs over and over again forever
void loop() {
//
if( doMotion && stepSpeed>0 && (stepDirection==-1 || stepDirection==1) ){
doOneStep();
delay(waitBetweenStepsMs);
} else {
delay(100);
}
}
int motion(String startOrStop){
if( strcmp("start",startOrStop.c_str())==0){
doMotion = true;
return 1;
} else if( strcmp("stop",startOrStop.c_str())==0){
doMotion = false;
return 0;
} else {
doMotion = false;
Particle.publish("motion() error","expected \"start\" or \"stop\"");
return -1;
}
}
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;
}
// Calculate step period (ms between steps):
stepPeriodMs = (stepSpeed==0) ? 1000 : 1000/stepSpeed; //ms
waitBetweenStepsMs = stepPeriodMs - durationStepPulseMs;
return stepPeriodMs;
}
int setDirection(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==1)?HIGH:LOW ); // set direction pin
return stepDirection;
}
// This function applies a single STEP pulse:
void doOneStep(){
digitalWrite(pinStep, HIGH); // start step pulse
delay(durationStepPulseMs); // 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:
<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>
Where to go from here, potential uses, etc.