In my journey to make a simple robot arm, I came across another problem. How do you control both stepper motors at the same time. Today I will show you the code I used, so you can use it or modify it to make your own.
The Hardware
Before we start with the code, its import to mention the hardware I used with it.
For motors I am using two 28BYJ-48s and I am using the Arduino Stepper library to do the basic motor control. Then the new code runs on top and adds the multi motor and speed support.
The microcontroller I am using is a esp32. The code uses lots of floating point math, which is fine for an esp32 but If you use a different microcontroller that’s smaller, the program may run a bit slow.
Here is the code
class parallel_motor_driver {
private:
int total_steps = 0;
int SPR = 0;
int UPDATE_EVERY = 1000;
int sign = true;
unsigned int last_time = 0;
bool ON = false;
float steps_to_move = 0.00;
float addon = 0;
public:
parallel_motor_driver(int _SPR, int _UPDATE_EVERY) {
SPR = _SPR;
UPDATE_EVERY = _UPDATE_EVERY;
}
void start() {
ON = true;
last_time = millis();
addon = 0;
}
void resume() {
ON = true;
last_time = millis();
}
void stop() {
ON = false;
}
void set(int steps) {
total_steps = abs(steps);
if (steps < 0) {
sign = -1;
} else {
sign = 1;
}
}
void set_speed_RPM(float speed) {
float temp = float(float(SPR) * speed);
steps_to_move = temp / (60 * (1000 / UPDATE_EVERY));
if (steps_to_move == 0) {
steps_to_move == 1;
}
}
void update() {
if (total_steps <= 0) {
if (ON) {
Serial.printf("Steps: %i Difference: ",steps);
Serial.println(addon);
}
ON = false;
}
if (millis() - last_time >= UPDATE_EVERY && ON) {
int steps_to_move_pluss_error = int(round(steps_to_move));
addon += float(steps_to_move - steps_to_move_pluss_error);
while(addon >= 1) {
steps_to_move_pluss_error += 1;
addon -= 1;
}
while (addon <= -1) {
steps_to_move_pluss_error -= 1;
addon += 1;
}
int real_steps = min(total_steps, steps_to_move_pluss_error) * sign;
/*
Serial.printf("Total_steps: %i Real_steps: %i Step_error: %i Addon: ", total_steps, real_steps,steps_to_move_pluss_error);
Serial.print(addon);
Serial.print(" Cal: ");
Serial.print(float(steps_to_move - steps_to_move_pluss_error),6);
Serial.print(" Steps_to_move: ");
Serial.println(steps_to_move,6);
*/
total_steps -= real_steps * sign;
steps += real_steps;
last_time += UPDATE_EVERY;
}
}
bool state(){
return ON;
}
bool finished(){
return not bool(total_steps);
}
int steps = 0;
};
How to Use This Code
To use this code, you need to add it to your Arduino sketch preferably above the setup function. After that for every motor you will need run the following code.
First you will need to initialize it as follows.
parallel_motor_driver some_name(SPR, UPDATE);
You will want to replace some_name with what ever you want to call the driver.
Then, you will need to replace SPR (Steps Per Revolution) with the number of steps your motor takes to make one rotation. Usually you can find that number on the internet.
You will also want to replace UPDATE. It controls how often your motor updates its position. The lower this number, the smoother your motor will run, but as the number gets smaller, it will also take more and more of your computer’s power to run the program.
That number is actually how many millisecond to wait between moving your motor. I run my motors at 10, but going up to 50 is still pretty good.
The last thing you will need to do is run the following code somewhere in your loop function.
some_name.update();
if(some_name.steps){
my_motor.step(steps);
some_name.steps = 0;
}
That concludes the setup, but there are a few other functions you will probably want to use.
Setting the RPM
The following line of code is how you set the speed of you motor in rotations per minute(RPM). To make it as accurate as possible, you are allowed to give it fractional RPMs like 0.27.
some_name.set_speed_RPM(SOME_RPM);
The Set Function
The set function tells the driver how many steps you want your motor to move. Unlike a normal stepper motor driver, it does not actually start moving when you call set.
some_name.set(steps);
The rest of the functions are explained below.
Method | What it Does |
---|---|
some_name.start(); | Start the motor running |
some_name.stop(); | Pauses the motor |
some_name.resume(); | Turns motor back on |
some_name.state(); | Tells you if the motor is on or off. |
some_name.finished(); | Tells you if the motor has finished moving |
Below is the program I used to test this code. If you try running it, you will probably need to change the pins for the stepper motors.
#include <Stepper.h>
Stepper motor = Stepper(2038, 27, 12, 14, 13);
Stepper motor2 = Stepper(2038, 26, 33, 25, 32);
class parallel_motor_driver {
private:
int total_steps = 0;
int SPR = 0;
int UPDATE_EVERY = 1000;
int sign = true;
unsigned int last_time = 0;
bool ON = false;
float steps_to_move = 0.00;
float addon = 0;
public:
parallel_motor_driver(int _SPR, int _UPDATE_EVERY) {
SPR = _SPR;
UPDATE_EVERY = _UPDATE_EVERY;
}
void start() {
ON = true;
last_time = millis();
addon = 0;
}
void resume() {
ON = true;
last_time = millis();
}
void stop() {
ON = false;
}
void set(int steps) {
total_steps = abs(steps);
if (steps < 0) {
sign = -1;
} else {
sign = 1;
}
}
void set_speed_RPM(float speed) {
float temp = float(float(SPR) * speed);
steps_to_move = temp / (60 * (1000 / UPDATE_EVERY));
if (steps_to_move == 0) {
steps_to_move == 1;
}
}
void update() {
if (total_steps <= 0) {
if (ON) {
Serial.printf("Steps: %i Difference: ",steps);
Serial.println(addon);
}
ON = false;
}
if (millis() - last_time >= UPDATE_EVERY && ON) {
int steps_to_move_pluss_error = int(round(steps_to_move));
addon += float(steps_to_move - steps_to_move_pluss_error);
while(addon >= 1) {
steps_to_move_pluss_error += 1;
addon -= 1;
}
while (addon <= -1) {
steps_to_move_pluss_error -= 1;
addon += 1;
}
int real_steps = min(total_steps, steps_to_move_pluss_error) * sign;
/*
Serial.printf("Total_steps: %i Real_steps: %i Step_error: %i Addon: ", total_steps, real_steps,steps_to_move_pluss_error);
Serial.print(addon);
Serial.print(" Cal: ");
Serial.print(float(steps_to_move - steps_to_move_pluss_error),6);
Serial.print(" Steps_to_move: ");
Serial.println(steps_to_move,6);
*/
total_steps -= real_steps * sign;
steps += real_steps;
last_time += UPDATE_EVERY;
}
}
bool state(){
return ON;
}
bool finished(){
return not bool(total_steps);
}
int steps = 0;
};
parallel_motor_driver parallel(2038, 10);
parallel_motor_driver parallel2(11937, 10);//11937
void setup() {
Serial.begin(115200);
motor.setSpeed(12);
motor2.setSpeed(12);
}
void loop() {
if (Serial.available()) {
if(parallel.finished()){
int steps = Serial.parseInt();
float speed = Serial.parseFloat();
parallel.set(steps);
parallel.set_speed_RPM(speed);
steps = Serial.parseInt();
speed = Serial.parseFloat();
parallel2.set(steps);
parallel2.set_speed_RPM(speed);
parallel.start();
parallel2.start();
}else{
Serial.read();
if(parallel.state()){
parallel.stop();
}else{
parallel.resume();
}
}
}
parallel.update();
parallel2.update();
if (parallel.steps) {
motor.step(parallel.steps);
parallel.steps = 0;
}
if (parallel2.steps) {
motor2.step(parallel2.steps);
parallel2.steps = 0;
}
}
If you are thinking about building a robot arm or something similar, you might be interested in Accurately Position a Stepper Motor In Degrees or How to Control a Robot Arm With Arduino.