Connecting an IMU GY N601N1, reading angles

If you use the MPU-6050, the library you want is called Grove IMU 9D0F. You can find it in the Arduino IDE library manager. Then, the example sketch you need is called MPU6050_DMP6. This delivers yaw, pitch and roll values as calculated by the onboard digital motion processor, in the variables they've called ypr[0], ypr[1] and ypr[2].

These values are suitable to use if your project application moves. Other examples in the library deliver the raw values of acceleration along x, y and z axes, from which the orientation can be calculated with some reasonably simple trigonometry.

However, these are not suitable for an application that moves, because accelerations due to movement will add to the acceleration due to gravity, making any calculated tilt angle incorrect. Stick to using the DMP in that case.

Yes, only the original ESP32 supported Classic.

Thank you guys. This looks like a good start. The sensor reacts very strongly when I just rotate it a little but not very strong when I shake it. This is as expected since the plot is one of the gyros.

Last year I played with this and put an Electronic Tilt Switch on the bike. Degree of tilt could be set. That was probably a good item for its purpose on cranes, fork lifts etc. But when a motorcycle accelerates, the tilt switch mistook acceleration for tilt.

These acc/gyro cards can separate acceleration and rotation better, hopefully.

But much work left to make this into useful angles.



Last year:

I'm not sure what you mean by this. If you use the MPU-6050 and DMP sketch I mentioned, you will get the pure tilt angle value, with the effects of motion filtered out by the DMP. There is literally nothing more you need to do.

The tilt value is very clean, accurate and repeatable with 100Hz update. It is good enough to use unchanged in real time in a demanding application like a self-balancing robot. It would be fine for on your bike.

I think you have a really good suggestion. I will probably come back to it as soon as my MPU-6050 arrives. The chip has been extensively tried on different projects I understand.

In the meantime I played a little with the BMI323. I just "integrated" the rotational speeds to angles

position_x += gyro.x;
position_y += gyro.y;
position_z += gyro.z;

(I just asked Google if this is z/z-1 but Google crashed :rofl: I knew it long time ago.


I think your suggestion sounds very good. Reading both your posts I now understand that the gyroscope signals are contaminated by acceleration and the approach you describe will reduce that problem. I will definitely try it.

I was thinking of just putting the gyroscope data through low pass IIR and make DC removal of the integrated signal. But I will probably wait for the chip and use your suggestion. I will probably have to come back with more questions to you then.

Thanks a lot. Very helpful.

It’s more that the acceleration signals are contaminated by motion. If there was no motion, you could work out from how much acceleration due to gravity you could see on x, y and z axes, what the orientation of the chip was.

But as soon as you have acceleration due to motion, that adds to the acceleration due to gravity and your calculation isn’t valid. The DMP uses the gyros and some clever maths to work out how much motion is contaminating the acceleration signals, and removes it.

I had thought the gyroscopes themselves computed rotational orientation and the accelerometers only added information to know what is "up and down".

But this is obviously not right. The gyroscopes need information about acceleration to work out the rotational speeds. And when things start moving around, the gyroscopes need computational help.

I had thought that a gyroscope in theory was just a rotating disc that was freely suspended and balanced and independent of gravity or other acceleration.

But as I interpret your explanation, a gyroscope is affected by acceleration/gravity and mathematical algorithms can clean up the gyroscope data with the help of accelerometer data.

The way I understand it is that the gyro sensors only measure rotational velocity and don't know about acceleration. I assume that the DMP calculates the acceleration due to motion from the rate of change of gyro speed.

This acceleration can then be subtracted from the measured acceleration, to leave only the acceleration due to gravity. From there it's just trigonometry to work out the yaw / pitch / roll angles at any instant (the library / microcontroller does this, not the DMP).

I may have misunderstood this - I'm no expert, it's just what I've picked up by reading the library code and comments.

It is interesting to try to understand. Linear acceleration has to be taken into consideration in modern Micro-Electro-Mechanical Systems building gyroscopes.

The modern gyroscopes do not use rotating masses but instead use vibrating.

I have already activated the Grove IMU 9DOF and opened the MPU6050_DMP6. Look forward to reporting the results and probably asking for more help when the chip arrives.

Thanks a lot, everyone.

I got the MPU6050 today. It gives off data on
mpu.getEvent(&a, &g, &temp);
image
I linked two zip libraries: Adafruit Sensor master and Adafruit MPU6050 master. Also Adafruit BusIO from the IDE library manager. Also had to enable USB CDC and press reset on the board again. Is there any way I can store these actions so I don't have to do them every time? Now I just had to include the 3 libraries again after shutting down the computer a while.

These libraries came with the instructions I followed. I will try the Grove library later. I assume the Digital Motion Processor is at work in the MPU6050? I will try to compare the two cards later. Two cards work on the same I2C bus and I will probably try to put in the same graph later. For more IMU cards maybe you can open another I2C bus?

Thanks to you and all in the thread for good input.

I ran the BMI323 and MPU6050 paralell. Both give good data, of course. MPU6050 was drifting and I high-pass filtered but maybe that isn't necessary. After a while it seemed like the DMP got things organized.
It is probably a great chip if I learn all the settings, I havent had time for that yet. This was Adafruit library. Will try Grove next.

Any further suggestions welcome.

= = = =

Red, green, blue are pitch, roll and yaw of the BMI323 and the other corresponding curves (yellow, cyan, black) are from MPU6050. The yaw curves probably have different levels since the chips don't carry compass. To synchronize the yaw you have to point them the same way at program start.

My apologies, it looks like the Grove library based on Jeff Rowberg's MPU6050_6Axis_MotionApps20.h development will only work on Arduino boards, not ESP32. It's a shame, because it gives you the DMP output which is a really good quality yaw/ pitch / roll signal without having to mess around with settings.

This plot shows the MPU6050 used to control a self-balancing robot in real time. The orange trace is the measured tilt from the DMP. The variation is due to the robot oscillating a bit, not due to noise or inaccuracy in the measured signal.

The green trace is the speed of the motor. You can see that it is measuring and allowing control within 0.2 degrees (not radians). Not bad.

And this is straight out of the box, no post-processing, no need for experimenting with filters, accelerometer ranges etc.

You never explained what you are trying to do.
Whydo you need a 9dof imu or any imu for that matter.?

Unfortunately, no. You will have been using raw data. The only way to get all the sensor fusion goodness from the DMP is to use a library based on Jeff Rowberg's work in MPU6050_6Axis_MotionApps20.h. And as mentioned, that won't run on ESP32 as far as I know.

I just go slowly and start by seeing if I can get reliable measurements. Acceleration and lean are crucial on a bike. I don't ride high speed with high lean angles but the front wheel comes up at high acceleration. If I get good data I can try to cut ignition if it lifts too much. Just reliable recordings will help the rider.

Even non racing bikes have this where you can set how strongly the ECU will interfere ("traction control") but I want more control over it.

Beyond my level of expertise.

Hi,

I have switched to an ESP32 S3 N16R8 that also works under ESP32S3 Dev module

I understand it is not straightforward to use the MPU6050 DMP with ESP32 but it should be possible.

USB CDC enabled
pressed reset on the board
QPSI PSRAM selected

SCL pin 9
SDA pin 8

Interrupt -> 2

I2C scan finds the device, but when the sketch for the MPU-6050 starts it just gives off a little gibberish each time I press reset. Can someone please guide how to approach this?

EDIT: I just commented and uncommented each of the two libraries as described in the code. No change.


/*
  MPU6050 DMP6

  Digital Motion Processor or DMP performs complex motion processing tasks.
  - Fuses the data from the accel, gyro, and external magnetometer if applied, 
  compensating individual sensor noise and errors.
  - Detect specific types of motion without the need to continuously monitor 
  raw sensor data with a microcontroller.
  - Reduce workload on the microprocessor.
  - Output processed data such as quaternions, Euler angles, and gravity vectors.

  The code includes an auto-calibration and offsets generator tasks. Different 
  output formats available.

  This code is compatible with the teapot project by using the teapot output format.

  Circuit: In addition to connection 3.3v, GND, SDA, and SCL, this sketch
  depends on the MPU6050's INT pin being connected to the Arduino's
  external interrupt #0 pin.

  The teapot processing example may be broken due FIFO structure change if using DMP
  6.12 firmware version. 
    
  Find the full MPU6050 library documentation here:
  https://github.com/ElectronicCats/mpu6050/wiki

*/


#include "I2Cdev.h"
#include "MPU6050_6Axis_MotionApps20.h"
//#include "MPU6050_6Axis_MotionApps612.h" // Uncomment this library to work with DMP 6.12 and comment on the above library.

/* MPU6050 default I2C address is 0x68*/
MPU6050 mpu;
//MPU6050 mpu(0x69); //Use for AD0 high
//MPU6050 mpu(0x68, &Wire1); //Use for AD0 low, but 2nd Wire (TWI/I2C) object.



/* OUTPUT FORMAT DEFINITION-------------------------------------------------------------------------------------------
- Use "OUTPUT_READABLE_QUATERNION" for quaternion commponents in [w, x, y, z] format. Quaternion does not 
suffer from gimbal lock problems but is harder to parse or process efficiently on a remote host or software 
environment like Processing.

- Use "OUTPUT_READABLE_EULER" for Euler angles (in degrees) output, calculated from the quaternions coming 
from the FIFO. EULER ANGLES SUFFER FROM GIMBAL LOCK PROBLEM.

- Use "OUTPUT_READABLE_YAWPITCHROLL" for yaw/pitch/roll angles (in degrees) calculated from the quaternions
coming from the FIFO. THIS REQUIRES GRAVITY VECTOR CALCULATION.
YAW/PITCH/ROLL ANGLES SUFFER FROM GIMBAL LOCK PROBLEM.

- Use "OUTPUT_READABLE_REALACCEL" for acceleration components with gravity removed. The accel reference frame
is not compensated for orientation. +X will always be +X according to the sensor.

- Use "OUTPUT_READABLE_WORLDACCEL" for acceleration components with gravity removed and adjusted for the world
reference frame. Yaw is relative if there is no magnetometer present.

-  Use "OUTPUT_TEAPOT" for output that matches the InvenSense teapot demo. 
-------------------------------------------------------------------------------------------------------------------------------*/ 
#define OUTPUT_READABLE_YAWPITCHROLL
//#define OUTPUT_READABLE_QUATERNION
//#define OUTPUT_READABLE_EULER
//#define OUTPUT_READABLE_REALACCEL
//#define OUTPUT_READABLE_WORLDACCEL
//#define OUTPUT_TEAPOT

int const INTERRUPT_PIN = 2;  // Define the interruption #0 pin
bool blinkState;

/*---MPU6050 Control/Status Variables---*/
bool DMPReady = false;  // Set true if DMP init was successful
uint8_t MPUIntStatus;   // Holds actual interrupt status byte from MPU
uint8_t devStatus;      // Return status after each device operation (0 = success, !0 = error)
uint16_t packetSize;    // Expected DMP packet size (default is 42 bytes)
uint8_t FIFOBuffer[64]; // FIFO storage buffer

/*---Orientation/Motion Variables---*/ 
Quaternion q;           // [w, x, y, z]         Quaternion container
VectorInt16 aa;         // [x, y, z]            Accel sensor measurements
VectorInt16 gy;         // [x, y, z]            Gyro sensor measurements
VectorInt16 aaReal;     // [x, y, z]            Gravity-free accel sensor measurements
VectorInt16 aaWorld;    // [x, y, z]            World-frame accel sensor measurements
VectorFloat gravity;    // [x, y, z]            Gravity vector
float euler[3];         // [psi, theta, phi]    Euler angle container
float ypr[3];           // [yaw, pitch, roll]   Yaw/Pitch/Roll container and gravity vector

/*-Packet structure for InvenSense teapot demo-*/ 
uint8_t teapotPacket[14] = { '$', 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0x00, 0x00, '\r', '\n' };

/*------Interrupt detection routine------*/
volatile bool MPUInterrupt = false;     // Indicates whether MPU6050 interrupt pin has gone high
void DMPDataReady() {
  MPUInterrupt = true;
}

void setup() {
  #if I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE


//    Wire.begin();
// my additions
#define I2C_SDA 8
#define I2C_SCL 9
Wire.begin(I2C_SDA, I2C_SCL);
//



    Wire.setClock(400000); // 400kHz I2C clock. Comment on this line if having compilation difficulties
  #elif I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_FASTWIRE
    Fastwire::setup(400, true);
  #endif
  Serial.begin(115200); //115200 is required for Teapot Demo output
  while (!Serial);

  /*Initialize device*/
  Serial.println(F("Initializing I2C devices..."));
  mpu.initialize();
  pinMode(INTERRUPT_PIN, INPUT);

  /*Verify connection*/
  Serial.println(F("Testing MPU6050 connection..."));
  if(mpu.testConnection() == false){
    Serial.println("MPU6050 connection failed");
    while(true);
  }
  else {
    Serial.println("MPU6050 connection successful");
  }

  /*Wait for Serial input*/
  Serial.println(F("\nSend any character to begin: "));
  while (Serial.available() && Serial.read()); // Empty buffer
  while (!Serial.available());                 // Wait for data
  while (Serial.available() && Serial.read()); // Empty buffer again

  /* Initializate and configure the DMP*/
  Serial.println(F("Initializing DMP..."));
  devStatus = mpu.dmpInitialize();

  /* Supply your gyro offsets here, scaled for min sensitivity */
  mpu.setXGyroOffset(0);
  mpu.setYGyroOffset(0);
  mpu.setZGyroOffset(0);
  mpu.setXAccelOffset(0);
  mpu.setYAccelOffset(0);
  mpu.setZAccelOffset(0);

  /* Making sure it worked (returns 0 if so) */ 
  if (devStatus == 0) {
    mpu.CalibrateAccel(6);  // Calibration Time: generate offsets and calibrate our MPU6050
    mpu.CalibrateGyro(6);
    Serial.println("These are the Active offsets: ");
    mpu.PrintActiveOffsets();
    Serial.println(F("Enabling DMP..."));   //Turning ON DMP
    mpu.setDMPEnabled(true);

    /*Enable Arduino interrupt detection*/
    Serial.print(F("Enabling interrupt detection (Arduino external interrupt "));
    Serial.print(digitalPinToInterrupt(INTERRUPT_PIN));
    Serial.println(F(")..."));
    attachInterrupt(digitalPinToInterrupt(INTERRUPT_PIN), DMPDataReady, RISING);
    MPUIntStatus = mpu.getIntStatus();

    /* Set the DMP Ready flag so the main loop() function knows it is okay to use it */
    Serial.println(F("DMP ready! Waiting for first interrupt..."));
    DMPReady = true;
    packetSize = mpu.dmpGetFIFOPacketSize(); //Get expected DMP packet size for later comparison
  } 
  else {
    Serial.print(F("DMP Initialization failed (code ")); //Print the error code
    Serial.print(devStatus);
    Serial.println(F(")"));
    // 1 = initial memory load failed
    // 2 = DMP configuration updates failed
  }
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop() {
  if (!DMPReady) return; // Stop the program if DMP programming fails.
    
  /* Read a packet from FIFO */
  if (mpu.dmpGetCurrentFIFOPacket(FIFOBuffer)) { // Get the Latest packet 
    #ifdef OUTPUT_READABLE_YAWPITCHROLL
      /* Display Euler angles in degrees */
      mpu.dmpGetQuaternion(&q, FIFOBuffer);
      mpu.dmpGetGravity(&gravity, &q);
      mpu.dmpGetYawPitchRoll(ypr, &q, &gravity);
      Serial.print("ypr\t");
      Serial.print(ypr[0] * 180/M_PI);
      Serial.print("\t");
      Serial.print(ypr[1] * 180/M_PI);
      Serial.print("\t");
      Serial.println(ypr[2] * 180/M_PI);
    #endif
        
    #ifdef OUTPUT_READABLE_QUATERNION
      /* Display Quaternion values in easy matrix form: [w, x, y, z] */
      mpu.dmpGetQuaternion(&q, FIFOBuffer);
      Serial.print("quat\t");
      Serial.print(q.w);
      Serial.print("\t");
      Serial.print(q.x);
      Serial.print("\t");
      Serial.print(q.y);
      Serial.print("\t");
      Serial.println(q.z);
    #endif

    #ifdef OUTPUT_READABLE_EULER
      /* Display Euler angles in degrees */
      mpu.dmpGetQuaternion(&q, FIFOBuffer);
      mpu.dmpGetEuler(euler, &q);
      Serial.print("euler\t");
      Serial.print(euler[0] * 180/M_PI);
      Serial.print("\t");
      Serial.print(euler[1] * 180/M_PI);
      Serial.print("\t");
      Serial.println(euler[2] * 180/M_PI);
    #endif

    #ifdef OUTPUT_READABLE_REALACCEL
      /* Display real acceleration, adjusted to remove gravity */
      mpu.dmpGetQuaternion(&q, FIFOBuffer);
      mpu.dmpGetAccel(&aa, FIFOBuffer);
      mpu.dmpGetGravity(&gravity, &q);
      mpu.dmpGetLinearAccel(&aaReal, &aa, &gravity);
      Serial.print("areal\t");
      Serial.print(aaReal.x);
      Serial.print("\t");
      Serial.print(aaReal.y);
      Serial.print("\t");
      Serial.println(aaReal.z);
    #endif

    #ifdef OUTPUT_READABLE_WORLDACCEL
      /* Display initial world-frame acceleration, adjusted to remove gravity
      and rotated based on known orientation from Quaternion */
      mpu.dmpGetQuaternion(&q, FIFOBuffer);
      mpu.dmpGetAccel(&aa, FIFOBuffer);
      mpu.dmpGetGravity(&gravity, &q);
      mpu.dmpGetLinearAccel(&aaReal, &aa, &gravity);
      mpu.dmpGetLinearAccelInWorld(&aaWorld, &aaReal, &q);
      Serial.print("aworld\t");
      Serial.print(aaWorld.x);
      Serial.print("\t");
      Serial.print(aaWorld.y);
      Serial.print("\t");
      Serial.println(aaWorld.z);
    #endif
    
    #ifdef OUTPUT_TEAPOT
      /* Display quaternion values in InvenSense Teapot demo format */
      teapotPacket[2] = FIFOBuffer[0];
      teapotPacket[3] = FIFOBuffer[1];
      teapotPacket[4] = FIFOBuffer[4];
      teapotPacket[5] = FIFOBuffer[5];
      teapotPacket[6] = FIFOBuffer[8];
      teapotPacket[7] = FIFOBuffer[9];
      teapotPacket[8] = FIFOBuffer[12];
      teapotPacket[9] = FIFOBuffer[13];
      Serial.write(teapotPacket, 14);
      teapotPacket[11]++; // PacketCount, loops at 0xFF on purpose
     #endif

  /* Blink LED to indicate activity */
  blinkState = !blinkState;
  digitalWrite(LED_BUILTIN, blinkState);
  }
}

Since your program never prints "Initializing I2C devices..."
seems like it has problems somewhere else.

Thanks that gave input.

I changed baud rate and now we are waiting for interrupt

EDIT:
I tried setting the interrupt pin hi and low manually but didn't help.
Could I have misunderstood the pin numbering.

  // my additions
  //    Serial.begin(115200);
  Serial.begin(9600);

It is running now, but so far garbage. I changed the interrupt pin to 2.

    // my additions
    // Serial.println(F("Enabling interrupt detection (Arduino external interrupt 0)..."));
    // attachInterrupt(0, dmpDataReady, RISING);
    Serial.println(F("MODIFIED TO ESP PIN 2 Enabling interrupt detection (Arduino external interrupt 0)..."));
    attachInterrupt(2, dmpDataReady, RISING);

It is starting to work now. Output was defined for some 3D program.
Thanks to everyone reading this.
I am good for now and must read up on this DMP.
Any suggestions how to make best use of the DMP appreciated.