Help with Daisy Chaining Multiple (~10) MPU6050 (GY-521) to Arduino Nano

I am working on a project that requires me to collect data from multiple IMUs (I am using the GY-521 Breakout board that utilizes the MPU6050).
So far, I have successfully daisy-chained up-to 7 MPU6050 to an Arduino Nano. I may need some help understanding the limits of I2C buses and accommodating up-to 10 sensors. The schematic is as follows: (for simplicity, I have shown only 3 sensors).

I am using the DMP library without interrupts, but the core concept is as follows:

  • Connect all the MPU6050 SDA/SCL (two wires) on the same buses to the Arduino. (As of now I have a 4.7kOhm pull-up resistor to 5V) -- Note: looking at the schematic of the GY-521 Breakout board, I am considering removing the 4.7kOhm pull-ups since they are already present on the breakout board (@see the image at the bottom).

  • Connect the AD0 pins from MPU6050 to Digital Pins on the Arduino Nano.

  • Initialize all the MPU6050's on the 0x68 address (AD0=LOW), then toggle (AD0=HIGH) each individual MPU6050, read data, untoggle (AD0=LOW).

All of this is fine, but the moment I add the 8th MPU6050 on the daisy chain, the entire system stops working -- MPU6050's get stuck on initialization and get stuck in a loop after the first iteration. I don't know if you need to see the code, as I don't think it is a software issue (but I can provide it if need be).

Some more information:

  • I am using the I2C buses clocked at 400kHz (somehow this gives me a more consistent DMP output rate compared to 100kHz)
  • I tried using an I2C Extender ( Adafruit LTC4311 I2C Extender / Active Terminator) to no avail.
  • I am not using an I2C level shifter, as the GY-521 breakout board has an on-board LDO.

Edit:

  • I have removed the pull-ups from 5V and shifted them to 3.3V on the Arduino Nano
  • I will be adding a bi-directional Level Shifter for the SDA/SCL Pins. The HV will be connected to the 5V (effectively ~4.6 when powered by USB) on the Arduino Nano, and the LV to 3.3V on the Arduino Nano

As requested, I am posting the code below:

#include "I2Cdev.h"							/// I2C Library
#include "MPU6050_6Axis_MotionApps612.h" 	/// 6 Axis Motion Apps Library


/** ==========[ VARIABLES ]========== **/
/// Sensor Details
const int MPU_ADDR = 0x68;						/// I2C address of the MPU-6050; AD0(HIGH) -> 0x69 | AD0(LOW) -> 0x68
const int num_sens = 7;							/// Number of sensors used

/// MPU Data Variables
MPU6050 MPU[num_sens];							/// Array of MPU6050 Objects
VectorInt16 accel[num_sens], gyro[num_sens];	/// Accelerometer & Gyroscope Data
Quaternion quat[num_sens];						/// Quaternion
VectorFloat gravity[num_sens];					/// Gravity
float rpy[num_sens][3];							/// Roll-Pitch-Yaw Data
unsigned long time_stamp[num_sens];				/// Time Stamp (ms)

/// DMP Variables
uint16_t packet_size[num_sens];					/// DMP Packet Size
uint16_t fifo_count[num_sens];					/// FIFO Count
uint8_t fifo_buffer[num_sens][64];				/// FIFO Buffer

/// Interrupt Variables
bool mpu_interrupt[num_sens];					/// MPU Interrupt
uint16_t fifo_alive[num_sens];					/// FIFO Alive


/** ==========[ SETUP ]========== **
 *	Initial Setup for Arduino & MPU6050
 */
void setup()
{
	/// Initialize Serial
	Serial.begin(115200);
	while (!Serial)
		; /// Wait for Serial to be ready

	/// Initialize I2C
	Wire.begin();
	Wire.setClock(400000); // 400kHz I2C clock.

	/// Configure AD0 Pins for All MPUs
	for (int pin = PinSelector(0); pin <= PinSelector(num_sens - 1); pin++) {
		pinMode(pin, OUTPUT);			/// Configure AD0 Pins as Output
		digitalWrite(pin, HIGH);		/// Set AD0 Pins to HIGH
	}

	/// Initialize All MPU6050 and Enable DMP
	for (int imu = 0; imu < num_sens; imu++) {
		Serial.print("[INFO] {IMU #");	Serial.print(imu);	Serial.print("} Initializing MPU...");
		ToggleMPU(imu);											/// Toggle MPU

		MPU[imu].initialize();									/// Initialize MPU6050
		MPU[imu].setFullScaleGyroRange(MPU6050_GYRO_FS_1000);	/// Set Gyro Range to 1000dps
		MPU[imu].setFullScaleAccelRange(MPU6050_ACCEL_FS_4);	/// Set Accel Range to 4g
		MPU[imu].CalibrateGyro(3);								/// Calibrate Gyro
		MPU[imu].CalibrateAccel(3);								/// Calibrate Accel

		MPU[imu].dmpInitialize();								/// Initialize DMP
		MPU[imu].setDMPEnabled(true);							/// Enable DMP
		packet_size[imu] = MPU[imu].dmpGetFIFOPacketSize();		/// Get DMP Packet Size
		fifo_count[imu] = MPU[imu].getFIFOCount();				/// Get FIFO Count
		Serial.println(" Done! (");

		UnToggleMPU(imu);										/// Untoggle MPU
	}
}


/** ==========[ LOOP ]========== **
 * 	Main Loop for Arduino
 */
void loop()
{
	/// Cycle through all MPUs and Set DMP Interrupts
	for (int imu = 0; imu < num_sens; imu++)
	{
		ToggleMPU(imu); 		/// Toggle MPU
		SetDMPInterrupt(imu); 	/// Set DMP Interrupt for the IMU
		UnToggleMPU(imu); 		/// Untoggle MPU
	}
	 Serial.println();


}


/** ==========[ SET DMP INTERRUPT ]========== **
 *	Set DMP Interrupt for the IMU
 *	@param IMU_NUM IMU Number
 */
void SetDMPInterrupt(int IMU_NUM)
{
	static unsigned long _ETimer[num_sens];

	if (millis() - _ETimer[IMU_NUM] >= (10)) { 	/// After 10ms, enable MPU Interrupt
		_ETimer[IMU_NUM] += (10);				/// Increment Timer by 10ms
		mpu_interrupt[IMU_NUM] = true;			/// Set MPU Interrupt to true
	}
	if (mpu_interrupt[IMU_NUM]) {				/// If MPU Interrupt is true
		GetDMP(IMU_NUM);						/// Get DMP Data
	}
}


/** ==========[ GET DMP ]========== **
 *	Get DMP Data from MPU6050
 *	If FIFO Count is 0 or not a multiple of packet size, reset FIFO
 *	Else, Proceed with Calculations
 *	@param IMU_NUM IMU Number
 */
void GetDMP(int IMU_NUM)
{

	/// Local Variables -- FIFO Count, Packet Size, Last Good Packet Time
	static unsigned long LastGoodPacketTime[num_sens];
	mpu_interrupt[IMU_NUM] = false;
	fifo_alive[IMU_NUM] = 1;
	fifo_count[IMU_NUM] = MPU[IMU_NUM].getFIFOCount();

	/// If FIFO Count is 0 or not a multiple of packet size
	if ((!fifo_count[IMU_NUM]) || (fifo_count[IMU_NUM] % packet_size[IMU_NUM])) {
		MPU[IMU_NUM].resetFIFO(); 													/// Failed to fetch DMP, reset FIFO

	} else {
		while (fifo_count[IMU_NUM] >= packet_size[IMU_NUM]) {						/// While FIFO Count is greater than packet size
			MPU[IMU_NUM].getFIFOBytes(fifo_buffer[IMU_NUM], packet_size[IMU_NUM]);	/// Get latest packet
			fifo_count[IMU_NUM] -= packet_size[IMU_NUM];							/// Update FIFO Count
		}
		LastGoodPacketTime[IMU_NUM] = millis();
		ComputeData(IMU_NUM);			/// On Success, Calculate MPU Math
	}
}


/** ========== [ MPU MATH ] ========== **
 * 	Using DMP, Obtain Accel/Gyro Data
 * 	Compute Yaw-Pitch-Roll
 * 	@param IMU_NUM IMU Number
*/
void ComputeData(int IMU_NUM)
{
	/// Quaternion, Gravity, Yaw-Pitch-Roll
	float ypr[3];
	MPU[IMU_NUM].dmpGetQuaternion(&quat[IMU_NUM], fifo_buffer[IMU_NUM]);
	MPU[IMU_NUM].dmpGetGravity(&gravity[IMU_NUM], &quat[IMU_NUM]);
	MPU[IMU_NUM].dmpGetYawPitchRoll(ypr, &quat[IMU_NUM], &gravity[IMU_NUM]);

	/// Acceleration, Gyro
	MPU[IMU_NUM].dmpGetAccel(&accel[IMU_NUM], fifo_buffer[IMU_NUM]);	/// Get Acceleration: x, y, z (gForces: m/s^2)
	MPU[IMU_NUM].dmpGetGyro(&gyro[IMU_NUM], fifo_buffer[IMU_NUM]);		/// Get Gyro: x, y, z (rad/s)

	/// Update Yaw-Pitch-Roll (radians)
	rpy[IMU_NUM][0] = ypr[2];		/// Roll
	rpy[IMU_NUM][1] = ypr[1];		/// Pitch
	rpy[IMU_NUM][2] = ypr[0];		/// Yaw

	time_stamp[IMU_NUM] = millis();

	PrintData(IMU_NUM);
}


/** ==========[ PRINT DATA ]========== **
 * Prints the Data for the corresponding IMU
 * format: "> ID: 0 | Tms: 0 | acc: 0.00, 0.00, 0.00 | gyro: 0.00, 0.00, 0.00 | rpy: 0.00, 0.00, 0.00"
 *	@param IMU_NUM IMU Number
 */
void PrintData(int IMU_NUM)
{
	Serial.print("$ ID: ");	Serial.print(IMU_NUM); 				Serial.print(" | ");
	Serial.print("Tms:\t");	Serial.print(time_stamp[IMU_NUM]); 	Serial.print(" | ");
	Serial.print("acc:\t");	Serial.print(accel[IMU_NUM].x);
	Serial.print(",\t"); 	Serial.print(accel[IMU_NUM].y);
	Serial.print(",\t"); 	Serial.print(accel[IMU_NUM].z); 	Serial.print("\t| ");

	Serial.print("rot:\t"); Serial.print(gyro[IMU_NUM].x);
	Serial.print(",\t"); 	Serial.print(gyro[IMU_NUM].y);
	Serial.print(",\t"); 	Serial.print(gyro[IMU_NUM].z); 		Serial.print("\t| ");

	Serial.print("rpy:\t"); Serial.print(rpy[IMU_NUM][0]);
	Serial.print(",\t"); 	Serial.print(rpy[IMU_NUM][1]);
	Serial.print(",\t"); 	Serial.println(rpy[IMU_NUM][2]);
}


/** ==========[ TOGGLE MPU ]========== **
 * 	Sets corresponding AD0 --> LOW
 * 	Toggling the MPU | I2C Address --> 0x68
 *	@param IMU_NUM IMU Number
 */
void ToggleMPU(int IMU_NUM)
{
	int pin = PinSelector(IMU_NUM);
	digitalWrite(pin, LOW);
}


/** ==========[ UNTOGGLE MPU ]========== **
 * 	Sets corresponding AD0 --> HIGH
 * 	Toggling the MPU | I2C Address --> 0x69
 *	@param IMU_NUM IMU Number
 */
void UnToggleMPU(int IMU_NUM)
{
	int pin = PinSelector(IMU_NUM);
	digitalWrite(pin, HIGH);
}


/** ==========[ PIN SELECTOR ]========== **
 * 	Returns the Pin Number corresponding to the IMU
 *	@param IMU_NUM IMU Number
 *	@return GPIO Pin Number (AD0)
 */
int PinSelector(int IMU_NUM)
{
	switch(IMU_NUM) {
		case 0: 	return 2;
		case 1: 	return 3;
		case 2: 	return 4;
		case 3: 	return 5;
		case 4: 	return 6;
		case 5: 	return 7;
		case 6: 	return 8;
		case 7: 	return 9;
		case 8: 	return 10;
		case 9: 	return 11;
	}
}

What am I doing wrong?

Hi, @ayushchinmay

What is your project?
Why do you need all those IMU's?

Can you please post your code?

Thanks.. Tom.. :smiley: :+1: :coffee: :australia:

I am troubled that you are putting a 5V Arduino signal into a 3V3 device, this could damage it. I would use a potential divider to cut each A0 signal down to 3V3 before you apply it to the MPU6050.

I would remove the 5V pull up on the Arduino end, otherwise you will need an I2C level converter. Also the normal I2C library powers up with the internal pull up resistors enabled, so I would set these two pins to just inputs ( not INPUT_PULLUP ) just after the wire begin call in the setup function.

The range of an I2C bus is about 1 meter.

As each MPU605 has its own pull up resistors and you want 10 of them then I would remove the pull up resistors on all but two of the units.

This makes no sense. Just because the GY-521 has a LOD then why would this preclude the use of a I2C shifter. The only to avoid a shifter is like I sad above is to make the whole bus a 3V3 bus.

Another option would be to use a TCA9548A i²c multiplexer. You could put 2x MPU6050 on each of 4 channels of the multiplexer, or one 1x MPU6050 modules each on their own channel. This should overcome the problems caused by limited i²c addresses and the need to remove pull-up-resistors on the MPU6050 modules

and the need to use level shifters.
Leo..

Well, I guess you would still need one pair of level shifters, between the Arduino and the multiplexer, and run the multiplexer and all the sensors with 3.3V.

The LDO does not remove the need for level shifters. The sensor chip itself runs at 3.3V and can theoretically be damaged by connecting the SDA/SCL pins to 5V signals from the Nano.

@TomGeorge

I am making a device that can measure deformation around bends/curves using the angles obtained from the DMP. Using some simple Trig functions, you can get the displacements in z-axis which can be used to figure out a very rough curvature.

The more IMU's I have, the higher the "resolution" of the curvature becomes -- essentially more points with deformation data.

I shall add the code to the post.

I am troubled that you are putting a 5V Arduino signal into a 3V3 device, this could damage it. I would use a potential divider to cut each A0 signal down to 3V3 before you apply it to the MPU6050.

I found these for fairly cheap: I can use 3 to accommodate 12 of them:

I would remove the 5V pull up on the Arduino end, otherwise you will need an I2C level converter. Also the normal I2C library powers up with the internal pull up resistors enabled, so I would set these two pins to just inputs ( not INPUT_PULLUP ) just after the wire begin call in the setup function.

Could you please elaborate on this?

This makes no sense. Just because the GY-521 has a LDO then why would this preclude the use of a I2C shifter. The only to avoid a shifter is like I said above is to make the whole bus a 3V3 bus.

In the GY-521 schematic posted above, it seems that the SDA/SCL connections on the breakout board are hooked up to the 3.3V output of the LDO. That is why I assumed I may not require a level shifter. That assumption may be incorrect though.

I will order these, and see if this allows me to work with more MPU6050s. I am a little concerned about how DMP will react to this (since I am polling it rather than using an interrupt method).

@PaulRB

The sensor chip itself runs at 3.3V and can theoretically be damaged by connecting the SDA/SCL pins to 5V signals from the Nano.

I see, so I will need a level shifter for the SDA/SCL pins, and the AD0 pins.
But the VCC can be hooked up to 5V? Or should I look into shifting that to 3.3V as well?

Yes for the SDA/SCL pins. For the AD0 pins, you need only a couple of resistors to make a voltage divider, but you could use the level shifter instead.

Yes, the power can be 5V because each module contains a regulator, as you know. This "shifts" the supply voltage from 5V to 3.3V.

Power is not measured in volts it is measured in watts. So that statement meaningless.
But if you mean the voltage can be 5V the answer is no.

Yes it incorrect.

I am not sure I can say more, what don't you specifically understand?

You do not need to go to the unnecessary expense of putting a level shifter on tha address outputs a simple voltage divider will do

If you power the TCA from 5volt, then you don't need level shifters.
The TCA is a muxer with eight built-in level shifters.
Leo..

I see that feature listed in the data sheet:

Allows voltage-level translation between 1.8-V,
2.5-V, 3.3-V, and 5-V buses

But something puzzles me...

According to the data sheet:

a high level input would need to be at least 5*0.7=3.5V

If the SCL & SCA pull-ups are only to 3.3V on the channels, the signals won't register reliably, will they?

I see what you mean. Confusing info. Those switch points should of course only apply for the 5volt master and the reset and address lines A0-A2.

But it should work. See the example on page 21, figure 13.
Every slave channel can clearly have a different pull up voltage.
Leo..

@PaulRB

The LDO does not remove the need for level shifters.

I see your point now. I made some measurements. When the pullup resistors are connected to the 3.3V pin on the Arduino Nano, the SCL/SDA (HIGH) reads about 2.3V. Which happens to match the V_LOGIC HIGH - 0.7 * 3.3 = 2.31V.

I tried using the Bi-Directional Level-Shifters for SDA/SCL pins, as well as the AD0 pins. With the same code... and it stopped working at 6 Devices. Removing the Level shifters made the system work with 7 devices. I am not sure what I am doing wrong.

I do not want to go the MUX route just yet as I have already invested too much time in this, and I do not have much time left to order more parts.

As of now, I have switched the SDA/SCL to 3.3V, and the MPU6050 is powered by an external power supply (3.3V, max 50mA). No Level-shifters are being used (I will do something about this -- but according to the Product Specification, I am still in the Voltage range) :

Another thing I just noticed, according to the Absolute Maximum Ratings, my devices should be able to handle the voltages:

Power is not measured in volts it is measured in watts. So that statement meaningless.
But if you mean the voltage can be 5V the answer is no.

My bad, the VOLTAGE is now connected to the 3.3V for POWER, SDA/SCL (With Pull-up Resistors 4.7kOhm). I tried using the Level Shifter for the AD0 Pins and SDA/SCL along with Pullup Resistors of 2.2kOhm.
But it seemed to be detrimental to my system, the DMP into an infinite loop of initializing the first MPU6050 (since I am using MPU[imu]CalibrateGyro(3), it fails to calibrate and keeps outputting ">***********"). When I comment out the Calibration Routine, the DMP fails to read anything, and outputs nothing.

I did not understand the I2C Library Powering up with Internal Pull-Up Resistors... Should I be defining A4/A5 as INPUTS in the void setup() ? I do not have any definitions for A4/A5 pins.

That does not make sense. 100kHz takes of course four times as long to read.
What is the total length of your I2C wiring, and what does it look like.
400kHz might not be reliable if it's messy (cross-talk) and is more than 30cm total.
With a muxer, that could increase to 30cm per device.
Leo..

This is because the

Wire.begin();

Takes care of all that, but in doing so also enables the pull up resistors. The Arduino designers did this in the belief that it would simplify matters for beginners, which in the end only causes more problems. This is because while you might get away with it just working for one device, it will cause problems of intermittent operation with more than one device.

To be fair, when they made this decision there were not any 3V3 Arduinos around.

Yes, but it is called "the setup function", tagging the "void" bit is wrong as this just says "this function returns no variables". It is a common beginner error.
So:-

#include <Wire.h>

void setup() {
  Wire.begin(); // join I2C bus (address optional for master)
 pinMode(A4, INPUT);
 pinMode(A5, INPUT);
}

Is what you need to do to remove the internal pull resistors.

I said not to use level shifters. A level shifter also includes the pull up resistor so it is silly to include both. Either use a voltage divider, like I said, or if you must use a level shifter, although this is a waste of silicon.

So it looks like your I2C system is not working. The big big problem with using a library to do this simple thing, is that it robs you of the learning experience you would get if you had to write the code yourself. It leaves you with a "black box" that either works or doesn't, and when it doesn't, you are left stuck not knowing where it is going wrong. So first try what I advised, and if you are still having problems try just one device or the I2C scanner code (found under the menu File -> Examples -> Wire) to see what is picked up.

You said in your first post:-

Now your code has a line in it:-

Do you think this has anything to do with the fact you can't get 8 sensors to work?

Plus what @Wawa said, is all good stuff.

I am concerned that this section of your start up function might be interfering with the I2C operation:-

/// Configure AD0 Pins for All MPUs
	for (int pin = PinSelector(0); pin <= PinSelector(num_sens - 1); pin++) {
		pinMode(pin, OUTPUT);			/// Configure AD0 Pins as Output
		digitalWrite(pin, HIGH);		/// Set AD0 Pins to HIGH
	}