Using an MPU6050 to stop a NEMA 17 stepper motor from spinning

Hey! I am using an MPU6050 and want to engage the brake for our motor when there is any sudden acceleration in any direction - x,y, or z. Do I have to take the resultant? How do I calculate the slope (rate of change) between two values so that the Arduino can sense that there has been a sudden "unwanted jolt" and cuts off power to the motor?

Here is some code to start:

// defining the pins 
const int dirPin = 4; 
const int stepPin = 3;
const int buttonPin = 10;  // the number of the pushbutton pin
const int stepsPerRevolution = 200;

//Setting up the accelerometer - allows access MPU6050 related functions in the library 
/*
#include <Adafruit_MPU6050.h>
#include <Adafruit_Sensor.h> 
*/
#include <MPU6050_tockn.h>
#include <Wire.h> 

MPU6050 mpu6050(Wire);
long timer = 0;

//Adafruit_MPU6050 mpu;
 
int buttonState;

void setup() {

  Serial.begin(9600);
  Wire.begin();
  mpu6050.begin();
  mpu6050.calcGyroOffsets(true);
  Serial.println();

  // initialize the pushbutton pin as an input:
  pinMode(buttonPin, INPUT);

  // Declare pins as Outputs
  pinMode(stepPin, OUTPUT);
  pinMode(dirPin, OUTPUT);
}

/*
// set accelerometer range to +-8G
	mpu.setAccelerometerRange(MPU6050_RANGE_8_G);

	// set gyro range to +- 500 deg/s
	mpu.setGyroRange(MPU6050_RANGE_500_DEG);

	// set filter bandwidth to 21 Hz
	mpu.setFilterBandwidth(MPU6050_BAND_21_HZ);

	delay(100);
  


*/ 

void loop() {

/*
  	// Get new sensor events with the readings 
sensors_event_t a, g, temp;
mpu.getEvent(&a, &g, &temp);

// Print out acceleration values
Serial.print("Acceleration X: ");
	Serial.print(a.acceleration.x);
	Serial.print(", Y: ");
	Serial.print(a.acceleration.y);
	Serial.print(", Z: ");
	Serial.print(a.acceleration.z);
	Serial.println(" m/s^2");

Serial.print("Rotation X: ");
	Serial.print(g.gyro.x);
	Serial.print(", Y: ");
	Serial.print(g.gyro.y);
	Serial.print(", Z: ");
	Serial.print(g.gyro.z);
	Serial.println(" rad/s");



  Serial.begin(9600);
  Wire.begin();
  mpu6050.begin();
  mpu6050.calcGyroOffsets(true);
  Serial.println();

  mpu6050.update(); 
   if (millis () - timer > 200){
    Serial.print(mpu6050.getAngleX()); 
    Serial.Print (","); 
    Serial.print(mpu6050.getAccAngleY()); 
    Serial.Print (","); 
    Serial.print(mpu6050.getAccAngleZ());
    timer = millis (); 
   } 
*/



  // Set motor direction clockwise
  digitalWrite(dirPin, HIGH);

  buttonState = digitalRead(buttonPin);

  if (buttonState == LOW) // if button is pressed, the motor will turn 
    //digitalWrite(stepPin, HIGH)
    for (int i = 0; i < stepsPerRevolution; i++) {
      // These four lines result in 1 step:
      digitalWrite(stepPin, HIGH);
      delayMicroseconds(450);
      digitalWrite(stepPin, LOW);
      delayMicroseconds(450);
    }

  if (buttonState == HIGH) // if button is not pressed, turn the motor off
    digitalWrite(stepPin, LOW);
       
}

I also have this code from an Arduino library that reads the accelerometer data. 

#include <MPU6050_tockn.h>
#include <Wire.h>

MPU6050 mpu6050(Wire);

long timer = 0;

void setup() {
  Serial.begin(9600);
  Wire.begin();
  mpu6050.begin();
  mpu6050.calcGyroOffsets(true);
}

void loop() {
  mpu6050.update();

  if(millis() - timer > 1000){
    
    Serial.println("=======================================================");
    Serial.print("temp : ");Serial.println(mpu6050.getTemp());
    Serial.print("accX : ");Serial.print(mpu6050.getAccX());
    Serial.print("\taccY : ");Serial.print(mpu6050.getAccY());
    Serial.print("\taccZ : ");Serial.println(mpu6050.getAccZ());
  
    Serial.print("gyroX : ");Serial.print(mpu6050.getGyroX());
    Serial.print("\tgyroY : ");Serial.print(mpu6050.getGyroY());
    Serial.print("\tgyroZ : ");Serial.println(mpu6050.getGyroZ());
  
    Serial.print("accAngleX : ");Serial.print(mpu6050.getAccAngleX());
    Serial.print("\taccAngleY : ");Serial.println(mpu6050.getAccAngleY());
  
    Serial.print("gyroAngleX : ");Serial.print(mpu6050.getGyroAngleX());
    Serial.print("\tgyroAngleY : ");Serial.print(mpu6050.getGyroAngleY());
    Serial.print("\tgyroAngleZ : ");Serial.println(mpu6050.getGyroAngleZ());
    
    Serial.print("angleX : ");Serial.print(mpu6050.getAngleX());
    Serial.print("\tangleY : ");Serial.print(mpu6050.getAngleY());
    Serial.print("\tangleZ : ");Serial.println(mpu6050.getAngleZ());
    Serial.println("=======================================================\n");
    timer = millis();
    
  }

}


"Sudden" requires timing two measurements and taking two differences (time and acceleration values), in your case for each axis.

If this function call is blocking, and returns data only when they are available,

mpu.getEvent(&a, &g, &temp);

then add a call to millis() to get a time stamp.

mpu.getEvent(&a, &g, &temp);
uint32_t timestamp = millis();

Compare the just now obtained values with the last set of measurements to make decisions. It should help to calculate the ratio (change in acceleration)/(time difference).

Avoid printing between successive measurements, as printing takes variable amounts of time and makes direct comparisons difficult.

so say the acceleration changes by 0.5 m/s^2 over one second, how would I write the code to reflect this?

if a.acceleration.x, a.acceleration.y, acceleration.z > 0.05;

digitalWrite(stepPin, LOW);

How would you consider acceleration in all three directions?

To treat each axis separately in C/C++, something like this would work (assuming you are interested in either large acceleration or large deceleration):

if (fabs (Xacc_now - Xacc_last))/time_between_measurements) > some_limit ) take_action();

if (fabs (Yacc_now - Yacc_last))/time_between_measurements) > some_limit ) take_action();

if (fabs (Zacc_now - Zacc_last))/time_between_measurements) > some_limit ) take_action();

If time_between_measurements is a constant, division is not required.

You can also combine all of the above into a big, ugly "if" statement using logical or

if (
fabs (Xacc_now - Xacc_last)/time_between_measurements > some_limit or
fabs (Yacc_now - Yacc_last)/time_between_measurements > some_limit or
fabs (Xacc_now - Xacc_last)/time_between_measurements > some_limit
) take_action();

I will try this, thank you!

how would you distinguish between Xacc_now and Xacc_last for continuous reading every 0.5 milliseconds?

would this code work?

#include <MPU6050_tockn.h>
#include <Wire.h>

MPU6050 mpu6050(Wire);

const int dirPin = 4; 
const int stepPin = 3;
const int buttonPin = 10;  
const int stepsPerRevolution = 200;

long timer = 0;
float prevAccX = 0;
float prevAccY = 0;
float prevAccZ = 0;
bool rapidChangeDetected = false;

void setup() {
  Serial.begin(9600);
  Wire.begin();
  mpu6050.begin();
  mpu6050.calcGyroOffsets(true);
  
  pinMode(buttonPin, INPUT);
  pinMode(stepPin, OUTPUT);
  pinMode(dirPin, OUTPUT);
}

void loop() {
  mpu6050.update();

  // Read acceleration values
  float accX = mpu6050.getAccX();
  float accY = mpu6050.getAccY();
  float accZ = mpu6050.getAccZ();

  // Check for rapid change in acceleration
  if (millis() - timer > 100) {
    float deltaX = abs(accX - prevAccX);
    float deltaY = abs(accY - prevAccY);
    float deltaZ = abs(accZ - prevAccZ);
    float deltaAcc = sqrt(deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ);

    if (deltaAcc > 5.0) { // Adjust threshold as needed
      rapidChangeDetected = true;
      float timeDiff = (millis() - timer) / 1000.0; // Convert to seconds
      float slope = deltaAcc / timeDiff;
      Serial.print("Slope: ");
      Serial.println(slope);
    }

    prevAccX = accX;
    prevAccY = accY;
    prevAccZ = accZ;
    timer = millis();
  }

  // Set motor direction clockwise
  digitalWrite(dirPin, HIGH);

  // Read button state
  int buttonState = digitalRead(buttonPin);

  // Rotate motor if button is pressed and no rapid change detected
  if (buttonState == LOW && !rapidChangeDetected) {
    for (int i = 0; i < stepsPerRevolution; i++) {
      digitalWrite(stepPin, HIGH);
      delayMicroseconds(450);
      digitalWrite(stepPin, LOW);
      delayMicroseconds(450);
    }
  } else { // Stop motor if rapid change detected
    digitalWrite(stepPin, LOW);
    rapidChangeDetected = false; // Reset the flag
  }
}

Forum members strongly recommend to start with the minimum code required to detect something of interest and get that working to your satisfaction, before adding in anything else like motor control.

Hey! I am using an MPU6050 and want to stop the NEMA 17 Steppermotor when there is any sudden acceleration in any direction - x,y, or z. I am having trouble getting the slope and the resultant to work to detect this rapid change

Here is some code to start:

/*
Accelerometer code - Automatic shutoff of NEMA 17 Stepper Motor 
April 1, 2024 
*/
#include <MPU6050_tockn.h> // Include the MPU6050 library
#include <Wire.h> // Include the Wire library for I2C communication

MPU6050 mpu6050(Wire); // Initialize the MPU6050 object with the Wire library

const int dirPin = 3; // Define the pin for stepper motor direction control
const int stepPin = 4; // Define the pin for stepper motor step control
const int buttonPin = 13;  // Define the pin for the pushbutton
const int stepsPerRevolution = 200; // Define the number of steps per revolution for the stepper motor

long timer = 0; // Initialize a variable to store the current time
float prevAccX = 0; // Initialize a variable to store the previous X-axis acceleration
float prevAccY = 0; // Initialize a variable to store the previous Y-axis acceleration
float prevAccZ = 0; // Initialize a variable to store the previous Z-axis acceleration
bool rapidChangeDetected = false; // Initialize a flag to indicate if a rapid change in acceleration is detected

void setup() {
  Serial.begin(9600); // Start serial communication at 9600 baud rate
  Wire.begin(); // Initialize the I2C communication
  mpu6050.begin(); // Initialize the MPU6050 sensor
  mpu6050.calcGyroOffsets(true); // Calibrate the gyroscope offsets
  
  pinMode(buttonPin, INPUT); // Set the button pin as input
  pinMode(stepPin, OUTPUT); // Set the step pin for the stepper motor as output
  pinMode(dirPin, OUTPUT); // Set the direction pin for the stepper motor as output
}

void loop() {
  mpu6050.update(); // Update MPU6050 sensor readings

  // Read acceleration values
  float accX = mpu6050.getAccX(); // Read X-axis acceleration
  float accY = mpu6050.getAccY(); // Read Y-axis acceleration
  float accZ = mpu6050.getAccZ(); // Read Z-axis acceleration

  Serial.println("=======================================================");
    Serial.print("temp : ");Serial.println(mpu6050.getTemp());
    Serial.print("accX : ");Serial.print(mpu6050.getAccX());
    Serial.print("\taccY : ");Serial.print(mpu6050.getAccY());
    Serial.print("\taccZ : ");Serial.println(mpu6050.getAccZ());

  // Check for rapid change in acceleration
  if (millis() - timer > 1000) { // Check if it's time to check for a rapid change
    float deltaX = abs(accX - prevAccX); // Calculate change in X-axis acceleration
    float deltaY = abs(accY - prevAccY); // Calculate change in Y-axis acceleration
    float deltaZ = abs(accZ - prevAccZ); // Calculate change in Z-axis acceleration
    float deltaAcc = sqrt(deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ); // Calculate total change in acceleration

    if (deltaAcc > 0.3) { // Check if rapid change in acceleration exceeds threshold
      rapidChangeDetected = true; // Set flag to indicate rapid change detected
      float timeDiff = (millis() - timer) / 1000.0; // Calculate time difference since last check
      float slope = deltaAcc / timeDiff; // Calculate slope of acceleration change
      Serial.print("Slope: "); // Print slope to serial monitor
      Serial.println(slope);
    }

    prevAccX = accX; // Update previous X-axis acceleration
    prevAccY = accY; // Update previous Y-axis acceleration
    prevAccZ = accZ; // Update previous Z-axis acceleration
    timer = millis(); // Update timer to current time
  }

  // Set motor direction clockwise
  digitalWrite(dirPin, HIGH); // Set direction pin to control motor direction

  // Read button state
  int buttonState = digitalRead(buttonPin); // Read state of pushbutton

  // Rotate motor if button is pressed and no rapid change detected
  if (buttonState == LOW ) { // Check if button is pressed and no rapid change detected
    for (int i = 0; i < stepsPerRevolution &&rapidChangeDetected; i++) { // Loop for specified number of steps per revolution
      digitalWrite(stepPin, HIGH); // Set step pin to high to trigger a step
      delayMicroseconds(450); // Delay to control step timing
      digitalWrite(stepPin, LOW); // Set step pin back to low
      delayMicroseconds(450); // Delay to control step timing
    }

      if (buttonState == HIGH) // if button is not pressed, turn the motor off
    digitalWrite(stepPin, LOW);

  } else { // Stop motor if rapid change detected
    digitalWrite(stepPin, LOW); // Set step pin low to stop the motor
    rapidChangeDetected = false; // Reset the flag
  }
}

Your two topics on the same or similar subject have been merged.

Cross-posting is against the Arduino forum rules. The reason is that duplicate posts can waste the time of the people trying to help. Someone might spend a lot of time investigating and writing a detailed answer on one topic, without knowing that someone else already did the same in the other topic.

Please create one topic only for your question and choose the forum category carefully. If you have multiple questions about the same project then please ask your questions in the one topic as the answers to one question provide useful context for the others, and also you won’t have to keep explaining your project repeatedly.

Repeated duplicate posting could result in a temporary or permanent ban from the forum.

Could you take a few moments to Learn How To Use The Forum. It will help you get the best out of the forum in the future.

Thank you.