Driving stepper motors by interrupts (Nema17, A4988, MPU6050, Nano)

Hi, this is my first post, so please be understanding if I write silly things ;). I am also pretty new to Arduino. As in topic, I use Nema17 stepper motors, stepper motor driver A4988, MPU6050 and Arduino Nano.

I'm doing kind of balancing robot for my degree project. The snag is that I'm learning how to do it from scratch step by step. I faced a problem learning how the interrupts works - specifically I want the 8-bit Timer2 to call the ISR in compare mode which will set the state on the STEP pin on both stepper motors and drive them in the direction depending on the tilt angle from the gyroscope in the IMU MPU6050.

Here's the code:

#include "Wire.h"
#include <MPU6050_light.h>

MPU6050 mpu(Wire);

const int LEFT_DIR_PIN = 8; 
const int RIGHT_DIR_PIN = 7; 
const int LEFT_STEP_PIN = 6; 
const int RIGHT_STEP_PIN = 5; 
const int BOTH_ENABLE_PIN = 4;           // soldered together to save pins ;)

int motor_direction = 0;   // 1 forward, -1 backward, 0 stop


// --------------------------------- ISR for timer 2 - compare mode ----------------

ISR(TIMER2_COMPA_vect)
{
  if(motor_direction == 0){
    return;
  }
  digitalWrite(RIGHT_STEP_PIN, HIGH);
  digitalWrite(LEFT_STEP_PIN, HIGH);
  delayMicroseconds(1000);
  digitalWrite(RIGHT_STEP_PIN, LOW); 
  digitalWrite(LEFT_STEP_PIN, LOW);
}
  
// --------------------------------- ISR for timer 2 - compare mode ----------------

void setup() {
// --------------------------------- MPU6050 ---------------------------------------
  Serial.begin(9600);
  Wire.begin();
  
  byte status = mpu.begin();
  Serial.println("MPU6050 status: ");
  Serial.println(status);
  while(status!=0){ }               // stop everything if can't connect to MPU6050
  
  Serial.println("Calculating offsets, do not move MPU6050");
  delay(1000);
  mpu.calcOffsets();                // offsets for gyroscope and accelerometer
  Serial.println("Done!\n");
// --------------------------------- MPU6050 ---------------------------------------
  
  pinMode(LEFT_STEP_PIN, OUTPUT);
  pinMode(LEFT_DIR_PIN, OUTPUT);
  pinMode(RIGHT_STEP_PIN, OUTPUT);
  pinMode(RIGHT_DIR_PIN, OUTPUT);
  pinMode(BOTH_ENABLE_PIN, OUTPUT);

  // Timer 2  
  TCCR2A = 0;                            //Make sure that the TCCR2A register is set to zero
  TCCR2B = 0;                            //Make sure that the TCCR2A register is set to zero
  TIMSK2 |= (1 << OCIE2A);               //Set the interupt enable bit OCIE2A in the TIMSK2 register
  TCCR2B |= (1 << CS21);                 //Set the CS21 bit in the TCCRB register to set the prescaler to 8
  OCR2A = 39;                            //The compare register is set to 39 => 20us / (1s / (16.000.000MHz / 8)) - 1
  TCCR2A |= (1 << WGM21);                //Set counter 2 to CTC (clear timer on compare) mode
  // TCNT2 = 0; 
}

void loop() {
  mpu.update();
  
  if(mpu.getAngleX() >= 2){
    digitalWrite(BOTH_ENABLE_PIN, LOW);       // enable stepper motors
    
    digitalWrite(RIGHT_DIR_PIN, LOW);
    digitalWrite(LEFT_DIR_PIN, HIGH);
    motor_direction = -1;                    // backward
    mpu.update();
  }
  else if(mpu.getAngleX()<2 && mpu.getAngleX()>-2){
    digitalWrite(BOTH_ENABLE_PIN, HIGH);      // disable stepper motors
    motor_direction = 0;                     // stop
    mpu.update();
  }
  else{
    digitalWrite(BOTH_ENABLE_PIN, LOW);       // enable stepper motors
    
    digitalWrite(RIGHT_DIR_PIN, HIGH);
    digitalWrite(LEFT_DIR_PIN, LOW);
    motor_direction = 1;                     // forward
    mpu.update();
  }
}

When the MPU6050 starts up, it calibrates itself and sets the "zero" position for the robot's balance. Then when I tilt the robot to one side, the motors respond correctly, but they can no longer come out of this operation. Even when I tilt the robot to the other side, the motors still spin to the first tilted side.

Would anyone have any idea what was done wrong here and how to possibly improve the performance/smoothness for the future whole balancing robot program? Thank you for any help.

Welcome to the forum.
You made a good start, but I don't understand the high speed of the interrupt.

Ouch, that hurts, a delay in a interrupt. Is your interrupt running at 50kHz ? Every 20µs a interrupt and there is a delay in there of 1000µs ?
The I2C bus also uses interrupts.

Can you tell more about the stepper motor signals with the timer interrupt ? Are the stepper motors going from not moving to moving with 50kHz ? Without any acceleration ?
Why are there so many mpu.update() calls ?

If you replace the stepper motors with leds, does it work ?

Do you know that you are going to need better balancing code someday ? When you go full speed when the angle is more than 2 degrees, then I see parts and metals en nuts and bolts flying all over the place :scream:

1 Like

I made your circuit and added a Logic Analyzer to see the signals. There is catch, it is in a simulation :wink:
If you start the Wokwi simulation, then the Logic Analyzer wants to store data when the simulation is stopped. You can simply delete the Logic Analyzer to avoid that.
Click on the MPU-6050 module to change the sensor data when the simulation is running.
I have installed PulseView to see the data captured by the Logic Analyzer, but the signals make no sense to me.

Why do you need the interrupt ? Can you create the signals in the loop() ?

1 Like

Ouch, that hurts, a delay in a interrupt.

Ah, that's right, really forgot about that. Just wanted the stepper motors to turn slower to test it.

Is your interrupt running at 50kHz ? Every 20µs a interrupt and there is a delay in there of 1000µs ? The I2C bus also uses interrupts.

Yes, it was supposed to be 50kHz but maybe not a good idea when I look at it now :wink:
And I2C - I didn't think about either.

Can you tell more about the stepper motor signals with the timer interrupt ? Are the stepper motors going from not moving to moving with 50kHz ? Without any acceleration ?

Yes, they are starting without any implemented acceleration, i didn't think about it yet. This program was mean to teach me how to drive those steppers with interrupts and my knowledge about the subject is not very high.

Why are there so many mpu.update() calls ?

I thought it would be good to refresh the robot's yaw angle as often as possible. Maybe too often.

If you replace the stepper motors with leds, does it work ?

I would have to check it out.

Do you know that you are going to need better balancing code someday ? When you go full speed when the angle is more than 2 degrees, then I see parts and metals en nuts and bolts flying all over the place :scream:

Yes, I know. It's just a learning code, without any meaningful regulation, based only on two-position regulation

The Arduino Stepper library is not for a DIR/STEP control: https://www.arduino.cc/reference/en/libraries/stepper/
But the AccelStepper is compatible: https://www.airspayce.com/mikem/arduino/AccelStepper/

With the AccelStepper library it is possible to set a certain speed and do one step at a time. However, the update of the stepper signals runs in the loop(). You will not have the advantage of the interrupt that continues to step.

The MPU6050_light library is here: https://github.com/rfetick/MPU6050_light.

You can remove the interrupt, but then you have to adjust the sketch and calling mpu.update() might cause a hiccup in the stepper motor signals.
Or you can lower the frequency with the OCR2A register (a lot) and use the interrupt to toggle the step pin without any delay.

bool stepHigh = false;

ISR(TIMER2_COMPA_vect)
{
  if(motor_direction == 0)
  {
    return;
  }

  if(stepHigh)         // are the STEP signals high, then make them low.
  {
    digitalWrite(RIGHT_STEP_PIN, LOW); 
    digitalWrite(LEFT_STEP_PIN, LOW);
    stepHigh = false;
  }
  else
  {
    digitalWrite(RIGHT_STEP_PIN, HIGH);
    digitalWrite(LEFT_STEP_PIN, HIGH);
    stepHigh = true;
  }
}

There are more than 10 ways to toggle a pin, this is just one of them.

1 Like

Thanks, the problem is that I'm really fresh in the subject and don't really know what to do.

Why do you need the interrupt ? Can you create the signals in the loop() ?

I've seen a lot of projects on the internet that relied on interrupts and I wanted to understand how it works. Considering that there will be a lot more going on in the final version of the project (PID adjustment, speed calculation on the fly, complementary filter, etc.) interrupts would guarantee constant execution regardless of which moment the code from the main loop is in.

Running the steps with a timer interrupt is a commonly used method. Typically you do not need any delay between the digitalWrite() commands inside the ISR.

ISR(TIMER2_COMPA_vect)
{
  if(motor_direction == 0){
    return;
  }
  digitalWrite(RIGHT_STEP_PIN, HIGH);
  digitalWrite(LEFT_STEP_PIN, HIGH);
  //delayMicroseconds(1000);
  digitalWrite(RIGHT_STEP_PIN, LOW); 
  digitalWrite(LEFT_STEP_PIN, LOW);
}
2 Likes

I'll look into this Stepper library more, but I'm afraid that by the final version of the project the solution without interrupts may not work. Maybe I'm wrong, do you have any more experience with balancing robots?
Thank You for the example. Looks good, I will try to test it all :wink: .

Thanks for the advice. In this case, after this change, the motors only buzz and there is no movement.

You need to control the speed of the steps with the frequency of the timer interrupt and not the delay between the steps. 20us between steps is too fast.

2 Likes

Ok, thank You guys for help. I changed the prescaler to 64 and set compare register to 249 so I got 1kHz frequency. I also used your code for ISR, Koepel. Thanks a lot for the idea :smiley: .

@cattledog is right in post #7 and I was wrong

The manufacturer's page of the A4988: A4988: DMOS Microstepping Driver with Translator
According to the datasheet the minimum pulse length of the STEP signal is 1µs. If I remember it well a digitalWrite() on a Arduino Nano takes at least 3µs, so you can toggle the STEP signal up and down without delay for a single step.

1 Like

Thanks once again for the advice :wink: will come in handy!

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.