Introduction: $5 Stepper Driver
Note: I recently redesigned and improved the stepper driver, as this has no microstepping, current limiting, and is prone to change direction erratically. The improved version is here: https://www.instructables.com/id/45-better-stepper-driver/
I have wanted to make a 3D printer or a CNC for quite a while, but as I don't have a lot of money, nor can I frequently order components online, I always ran into one obstacle. Almost all projects required stepper drivers with step-direction control. Those sorts of parts aren't avaliabe at stores, and the cheapest and most popular are around $15 a piece. I don't want to spend nearly that much on a single component that could fry in a second, so I decided to make something a little cheaper and more expendable. I ended up deciding on making my driver with an attiny and separate H-bridge chip. It uses an attiny85, and the ever-common L293 motor driver. together, they cost less than $5, and need almost no support circuitry.
I have wanted to make a 3D printer or a CNC for quite a while, but as I don't have a lot of money, nor can I frequently order components online, I always ran into one obstacle. Almost all projects required stepper drivers with step-direction control. Those sorts of parts aren't avaliabe at stores, and the cheapest and most popular are around $15 a piece. I don't want to spend nearly that much on a single component that could fry in a second, so I decided to make something a little cheaper and more expendable. I ended up deciding on making my driver with an attiny and separate H-bridge chip. It uses an attiny85, and the ever-common L293 motor driver. together, they cost less than $5, and need almost no support circuitry.
Step 1: Other Parts
All parts except the attiny and L293 are optional, but highly recommended. In quantity, most cost as little as a few cents.
1x >47uf capacitor-the bigger the better
8x ~1k ohm resistor-doesn't have to be 1K, any value 330-2K2 will probably work
1x 1N4001 diode-or any diode that can take >=1 amp, again, the bigger the better
protoboard-you could free-form it, but it's best to use protoboard/perfboard.
header pins-if you want to breadboard with the driver or make it easy to replace. I didn't use them, but its generally a good idea to do so.
IC sockets-for the two chips, one 8 pin, one 16 pin. Again, I didn't use them, but highly recommended.
Finially, you will need an arduino. Any flavor, homemade or whatever, as long as it has serial and an atmega it will work.
1x >47uf capacitor-the bigger the better
8x ~1k ohm resistor-doesn't have to be 1K, any value 330-2K2 will probably work
1x 1N4001 diode-or any diode that can take >=1 amp, again, the bigger the better
protoboard-you could free-form it, but it's best to use protoboard/perfboard.
header pins-if you want to breadboard with the driver or make it easy to replace. I didn't use them, but its generally a good idea to do so.
IC sockets-for the two chips, one 8 pin, one 16 pin. Again, I didn't use them, but highly recommended.
Finially, you will need an arduino. Any flavor, homemade or whatever, as long as it has serial and an atmega it will work.
Step 2: Programming Attiny.
It's been shown many, many times in other tutorials, and its fairly simple. Here's a link to the original blog post about it: http://toasterbotics.blogspot.com/2011/08/using-arduino-with-attinys.html then here: http://toasterbotics.blogspot.com/2011/08/programming-attiny85-arduino-style.html
After you have some test code running, move on to the next step.
After you have some test code running, move on to the next step.
Step 3: Stepper Code.
Now, on to the code! Copy and past this into arduino IDE and upload.
This code is just a test, and will step the motor foward and backward to test the electronics. We'll upload the real code later.
Note that the step-direction input will not work with this code.
//this is a test of the driver. Note that if you change pins, you must also
//change them in the real code. NEVER change a pin to pin 2,
//as that pin is needed for incrementing the desired distance to move
byte stepporty;
float val = 15; //inverse of the speed, lower value means faster movement.
short x;
byte db = 4;
byte da = 3;
byte pb = 1;
byte pa = 0;
byte in = 5;//not used here
void setup() {
pinMode(db, OUTPUT);
pinMode(da, OUTPUT);
pinMode(pb, OUTPUT);
pinMode(pa, OUTPUT);
pinMode(in, INPUT);
}
void loop() {
for(int x = 0; x < 408; x++){ //steps 408 half-steps, or one full rotation on 200 step-rev motors (as most of the NEMA motors are)
fwd();
delay(val);
}
delay(200);
for(x = 0; x < 408; x++){
bck();
delay(val);
}
delay(200);
}
void fwd() {
switch (stepporty) {
case 0:
one();
stepporty = 1;
break;
case 1:
onehalf();
stepporty = 2;
break;
case 2:
two();
stepporty = 3;
break;
case 3:
twohalf();
stepporty = 4;
break;
case 4:
three();
stepporty = 5;
break;
case 5:
threehalf();
stepporty = 6;
break;
case 6:
four();
stepporty = 7;
break;
case 7:
fourhalf();
stepporty = 0;
break;
} }
void bck() {
switch (stepporty) {
case 2:
one();
stepporty = 1;
break;
case 3:
onehalf();
stepporty = 2;
break;
case 4:
two();
stepporty = 3;
break;
case 5:
twohalf();
stepporty = 4;
break;
case 6:
three();
stepporty = 5;
break;
case 7:
threehalf();
stepporty = 6;
break;
case 0:
four();
stepporty = 7;
break;
case 1:
fourhalf();
stepporty = 0;
break;
} }
void fourhalf(){
digitalWrite(db, HIGH);//4
digitalWrite(da, LOW);
digitalWrite(pb, LOW);
digitalWrite(pa, HIGH);
}
void four(){
digitalWrite(db, HIGH);
digitalWrite(da, LOW);
digitalWrite(pb, LOW);
digitalWrite(pa, LOW);
}
void threehalf(){
digitalWrite(db, HIGH);
digitalWrite(da, HIGH);
digitalWrite(pb, LOW);
digitalWrite(pa, LOW);
}
void three(){
digitalWrite(db, LOW);
digitalWrite(da, HIGH);
digitalWrite(pb, LOW);
digitalWrite(pa, LOW);
}
void twohalf(){
digitalWrite(db, LOW);
digitalWrite(da, HIGH);
digitalWrite(pb, HIGH);
digitalWrite(pa, LOW);
}
void two(){
digitalWrite(db, LOW);
digitalWrite(da, LOW);
digitalWrite(pb, HIGH);
digitalWrite(pa, LOW);
}
void onehalf(){
digitalWrite(db, LOW);
digitalWrite(da, LOW);
digitalWrite(pb, HIGH);
digitalWrite(pa, HIGH);
}
void one(){
digitalWrite(db, LOW);//1
digitalWrite(da, LOW);
digitalWrite(pb, LOW);
digitalWrite(pa, HIGH);
}
This code is just a test, and will step the motor foward and backward to test the electronics. We'll upload the real code later.
Note that the step-direction input will not work with this code.
//this is a test of the driver. Note that if you change pins, you must also
//change them in the real code. NEVER change a pin to pin 2,
//as that pin is needed for incrementing the desired distance to move
byte stepporty;
float val = 15; //inverse of the speed, lower value means faster movement.
short x;
byte db = 4;
byte da = 3;
byte pb = 1;
byte pa = 0;
byte in = 5;//not used here
void setup() {
pinMode(db, OUTPUT);
pinMode(da, OUTPUT);
pinMode(pb, OUTPUT);
pinMode(pa, OUTPUT);
pinMode(in, INPUT);
}
void loop() {
for(int x = 0; x < 408; x++){ //steps 408 half-steps, or one full rotation on 200 step-rev motors (as most of the NEMA motors are)
fwd();
delay(val);
}
delay(200);
for(x = 0; x < 408; x++){
bck();
delay(val);
}
delay(200);
}
void fwd() {
switch (stepporty) {
case 0:
one();
stepporty = 1;
break;
case 1:
onehalf();
stepporty = 2;
break;
case 2:
two();
stepporty = 3;
break;
case 3:
twohalf();
stepporty = 4;
break;
case 4:
three();
stepporty = 5;
break;
case 5:
threehalf();
stepporty = 6;
break;
case 6:
four();
stepporty = 7;
break;
case 7:
fourhalf();
stepporty = 0;
break;
} }
void bck() {
switch (stepporty) {
case 2:
one();
stepporty = 1;
break;
case 3:
onehalf();
stepporty = 2;
break;
case 4:
two();
stepporty = 3;
break;
case 5:
twohalf();
stepporty = 4;
break;
case 6:
three();
stepporty = 5;
break;
case 7:
threehalf();
stepporty = 6;
break;
case 0:
four();
stepporty = 7;
break;
case 1:
fourhalf();
stepporty = 0;
break;
} }
void fourhalf(){
digitalWrite(db, HIGH);//4
digitalWrite(da, LOW);
digitalWrite(pb, LOW);
digitalWrite(pa, HIGH);
}
void four(){
digitalWrite(db, HIGH);
digitalWrite(da, LOW);
digitalWrite(pb, LOW);
digitalWrite(pa, LOW);
}
void threehalf(){
digitalWrite(db, HIGH);
digitalWrite(da, HIGH);
digitalWrite(pb, LOW);
digitalWrite(pa, LOW);
}
void three(){
digitalWrite(db, LOW);
digitalWrite(da, HIGH);
digitalWrite(pb, LOW);
digitalWrite(pa, LOW);
}
void twohalf(){
digitalWrite(db, LOW);
digitalWrite(da, HIGH);
digitalWrite(pb, HIGH);
digitalWrite(pa, LOW);
}
void two(){
digitalWrite(db, LOW);
digitalWrite(da, LOW);
digitalWrite(pb, HIGH);
digitalWrite(pa, LOW);
}
void onehalf(){
digitalWrite(db, LOW);
digitalWrite(da, LOW);
digitalWrite(pb, HIGH);
digitalWrite(pa, HIGH);
}
void one(){
digitalWrite(db, LOW);//1
digitalWrite(da, LOW);
digitalWrite(pb, LOW);
digitalWrite(pa, HIGH);
}
Step 4: Circuit
Not much of a circuit, just connecting pins and some resistors. I don't have any schematic/CAD software, and can't seem to get any to work, so it's hand drawn. I would prototype on a breadboard before soldering, to correct for your motor phase, and make sure the attiny is programmed correctly. The numbers in boxes are paired, and these are connected. I find it much less confusing this way than drawing each wire. All resistors are 1K except the 10K and 33K in the upper-right hand corner. Note that all pins except the dir-step pin can be re-ordered, but you MUST use only pins 0,1,3, and 4. "IN" MUST remain 1
i would also suggest some high-wattage resistors (maybe about 10ohm worth) between the supply and diode, just to make sure the current stays down.
i would also suggest some high-wattage resistors (maybe about 10ohm worth) between the supply and diode, just to make sure the current stays down.
Step 5: Real Code
One the electronics are working, upload this code. Then, connect the step-direction pins to your input (I used an arduino running grbl). Once you've checked that this code is working, solder up your circuit. and test it again.
byte stepporty;//what part of the step to go to
byte val = 3;//delay between steps
byte db = 4;//db, da, pb, pa, and in can all be any pin EXCEPT pin 7 (digital pin2, andalog in1)on the standard pinout
//that pin MUST be the clock/step input pin.
//db is out4
byte da = 3;//out3
byte pa = 0;//out1
byte pb = 1;//out2
byte in = 1;//direction input pin DO NOT CHANGE THIS
long dsd = 100;//where we want the motor to be
long pos;//where it is
void setup() {
// Serial.begin(9600);
attachInterrupt(0, count, RISING);//executes "void count" whenever pin 7 (interrupt 0) is brought HIGH
pinMode(db, OUTPUT);//sets outputs and inputs
pinMode(da, OUTPUT);
pinMode(pb, OUTPUT);
pinMode(pa, OUTPUT);
// pinMode(in, INPUT);
}
void loop() {//this just checks to see if it needs to move foward or backward and moves accordingly
// if (dsd != pos){
if(dsd < pos){
bck();
delay(val);
}
if (dsd > pos){
fwd();
delay(val);
// }
}}
void count(){//this increments the desired position whenever this pin is brought high
//it must use the same pin because
//the attiny85 only has 5 pins (though reset COULD be used as an i/o pin, it cannot
//be reprogramed after this is done.
if (analogRead(in)>1){//this determines whether direction is high or low
//dont ask me why one works, but it does. After about 7 straight hours of trying to fix
//this ONE line of code, I dont give a rat's behind WHY it works as long as it does
dsd-=1; //foward if positive
}else{
dsd+=1;//backward if its not
}}
void fwd() {//both fwd and bck figure which part of the step is next, and increment the position
//and power the motor accordingly.
pos++;
switch (stepporty) {
case 0:
one();
stepporty = 1;
break;
case 1:
onehalf();
stepporty = 2;
break;
case 2:
two();
stepporty = 3;
break;
case 3:
twohalf();
stepporty = 4;
break;
case 4:
three();
stepporty = 5;
break;
case 5:
threehalf();
stepporty = 6;
break;
case 6:
four();
stepporty = 7;
break;
case 7:
fourhalf();
stepporty = 0;
break;
} }
void bck() {
pos--;
switch (stepporty) {
case 2:
one();
stepporty = 1;
break;
case 3:
onehalf();
stepporty = 2;
break;
case 4:
two();
stepporty = 3;
break;
case 5:
twohalf();
stepporty = 4;
break;
case 6:
three();
stepporty = 5;
break;
case 7:
threehalf();
stepporty = 6;
break;
case 0:
four();
stepporty = 7;
break;
case 1:
fourhalf();
stepporty = 0;
break;
} }
void fourhalf(){
digitalWrite(db, HIGH);//4
digitalWrite(da, LOW);
digitalWrite(pb, LOW);
digitalWrite(pa, HIGH);
}
void four(){
digitalWrite(db, HIGH);
digitalWrite(da, LOW);
digitalWrite(pb, LOW);
digitalWrite(pa, LOW);
}
void threehalf(){
digitalWrite(db, HIGH);
digitalWrite(da, HIGH);
digitalWrite(pb, LOW);
digitalWrite(pa, LOW);
}
void three(){
digitalWrite(db, LOW);
digitalWrite(da, HIGH);
digitalWrite(pb, LOW);
digitalWrite(pa, LOW);
}
void twohalf(){
digitalWrite(db, LOW);
digitalWrite(da, HIGH);
digitalWrite(pb, HIGH);
digitalWrite(pa, LOW);
}
void two(){
digitalWrite(db, LOW);
digitalWrite(da, LOW);
digitalWrite(pb, HIGH);
digitalWrite(pa, LOW);
}
void onehalf(){
digitalWrite(db, LOW);
digitalWrite(da, LOW);
digitalWrite(pb, HIGH);
digitalWrite(pa, HIGH);
}
void one(){
digitalWrite(db, LOW);//1
digitalWrite(da, LOW);
digitalWrite(pb, LOW);
digitalWrite(pa, HIGH);
}
byte stepporty;//what part of the step to go to
byte val = 3;//delay between steps
byte db = 4;//db, da, pb, pa, and in can all be any pin EXCEPT pin 7 (digital pin2, andalog in1)on the standard pinout
//that pin MUST be the clock/step input pin.
//db is out4
byte da = 3;//out3
byte pa = 0;//out1
byte pb = 1;//out2
byte in = 1;//direction input pin DO NOT CHANGE THIS
long dsd = 100;//where we want the motor to be
long pos;//where it is
void setup() {
// Serial.begin(9600);
attachInterrupt(0, count, RISING);//executes "void count" whenever pin 7 (interrupt 0) is brought HIGH
pinMode(db, OUTPUT);//sets outputs and inputs
pinMode(da, OUTPUT);
pinMode(pb, OUTPUT);
pinMode(pa, OUTPUT);
// pinMode(in, INPUT);
}
void loop() {//this just checks to see if it needs to move foward or backward and moves accordingly
// if (dsd != pos){
if(dsd < pos){
bck();
delay(val);
}
if (dsd > pos){
fwd();
delay(val);
// }
}}
void count(){//this increments the desired position whenever this pin is brought high
//it must use the same pin because
//the attiny85 only has 5 pins (though reset COULD be used as an i/o pin, it cannot
//be reprogramed after this is done.
if (analogRead(in)>1){//this determines whether direction is high or low
//dont ask me why one works, but it does. After about 7 straight hours of trying to fix
//this ONE line of code, I dont give a rat's behind WHY it works as long as it does
dsd-=1; //foward if positive
}else{
dsd+=1;//backward if its not
}}
void fwd() {//both fwd and bck figure which part of the step is next, and increment the position
//and power the motor accordingly.
pos++;
switch (stepporty) {
case 0:
one();
stepporty = 1;
break;
case 1:
onehalf();
stepporty = 2;
break;
case 2:
two();
stepporty = 3;
break;
case 3:
twohalf();
stepporty = 4;
break;
case 4:
three();
stepporty = 5;
break;
case 5:
threehalf();
stepporty = 6;
break;
case 6:
four();
stepporty = 7;
break;
case 7:
fourhalf();
stepporty = 0;
break;
} }
void bck() {
pos--;
switch (stepporty) {
case 2:
one();
stepporty = 1;
break;
case 3:
onehalf();
stepporty = 2;
break;
case 4:
two();
stepporty = 3;
break;
case 5:
twohalf();
stepporty = 4;
break;
case 6:
three();
stepporty = 5;
break;
case 7:
threehalf();
stepporty = 6;
break;
case 0:
four();
stepporty = 7;
break;
case 1:
fourhalf();
stepporty = 0;
break;
} }
void fourhalf(){
digitalWrite(db, HIGH);//4
digitalWrite(da, LOW);
digitalWrite(pb, LOW);
digitalWrite(pa, HIGH);
}
void four(){
digitalWrite(db, HIGH);
digitalWrite(da, LOW);
digitalWrite(pb, LOW);
digitalWrite(pa, LOW);
}
void threehalf(){
digitalWrite(db, HIGH);
digitalWrite(da, HIGH);
digitalWrite(pb, LOW);
digitalWrite(pa, LOW);
}
void three(){
digitalWrite(db, LOW);
digitalWrite(da, HIGH);
digitalWrite(pb, LOW);
digitalWrite(pa, LOW);
}
void twohalf(){
digitalWrite(db, LOW);
digitalWrite(da, HIGH);
digitalWrite(pb, HIGH);
digitalWrite(pa, LOW);
}
void two(){
digitalWrite(db, LOW);
digitalWrite(da, LOW);
digitalWrite(pb, HIGH);
digitalWrite(pa, LOW);
}
void onehalf(){
digitalWrite(db, LOW);
digitalWrite(da, LOW);
digitalWrite(pb, HIGH);
digitalWrite(pa, HIGH);
}
void one(){
digitalWrite(db, LOW);//1
digitalWrite(da, LOW);
digitalWrite(pb, LOW);
digitalWrite(pa, HIGH);
}
Step 6: El Fin
Now that you finished, bask in the sweet glory of your easy to move stepper motors. I would add some duct tape so that there's no way that the pins could short, because it's not good for microcontrollers when they're exposed directly to power. When you connect the drivers to power, make sure to connect ground, then 5V, then motor - to ground, then motor +. If the steppers move 50 steps (1/4 turn on 200 step motors), then check that you connected 5V first, as when the attinys recieve logic power, they will quickly take 50 steps. This will not affect your accuracy if everthing is connected properly, but will if you connect power out of order.