Multiple MinIMU-9 v2 on Arduino Due

Hello everyone!

I’ve been reading this forum for a while (several months) and so far I have been able to solve all my problems by just reading, but now I’ve run into more serious problems and my knowledge level is not that high and I don’t fully understand the threads.

I’ve taken the AHRS code and studied it to produce my own quaternion Kalman filter on top of it. I’ve managed to get it working on Arduino Uno with just one MinIMU but my algorithm uses pretty much the entire memory of the Uno, so I decided that I can easily just buy a better microcontroller and just plug in another MinIMU… I was so wrong…

So just as the title says - I am trying to use several (5 or 6) MinIMU-9 v2 on an Arduino Due. From what I managed to gather, first problem is that they need to be on separate I2C lines (referring to this) and according to this the Due has two SDA and SCL lines, but what happens when I need to add another one? Second problem is that the L3G and LSM303 libraries don’t work with the Due, from what I understand it’s because they have some sort of autodetect functions and the Due has two I2C lines (I absolutely made this up so this is probably not the reason why). But then this guys managed to get them to work, the problem is I don’t really understand how.

So my question is simple: What needs to be done for this to work? My understanding of how exactly the Arduino, MinIMU and I2C communication work is limited, but I think I have the necessary technical background read and learn the parts I need. I am writing here in hope to get some pointers about where I should start since these topics are pretty extensive and I wanted to save some time by starting in the right direction.

This is the code I use for the gyro:

#include <L3G.h>

// L3G4200D gyro is set up at 2000 dps full scale, so therefore the following constants
// 70 mdps/digit; 1 dps = 0.07
#define Gyro_Gain 0.07 //Gyro gain on all axes
#define Gyro_Scaled(x) ((x)*ToRad(Gyro_Gain)) //Return the scaled ADC raw data of the gyro in radians for second

#define ToRad(x) ((x)*0.01745329252)  // *pi/180
#define ToDeg(x) ((x)*57.2957795131)  // *180/pi

L3G gyro;

void gyro_init(){
  gyro.init();
  gyro.writeReg(L3G_CTRL_REG1, 0x0F); // normal power mode, all axes enabled, 100 Hz
  gyro.writeReg(L3G_CTRL_REG4, 0x20); // 2000 dps full scale
}

void read_gyro(){
  gyro.read();
  gyro_c[0][0]=(float)(Gyro_Scaled(gyro.g.x)-GYRO_OFF[0][0]);
  gyro_c[1][0]=(float)(Gyro_Scaled(gyro.g.y)-GYRO_OFF[1][0]);
  gyro_c[2][0]=(float)(Gyro_Scaled(gyro.g.z)-GYRO_OFF[2][0]);
}

And this is the code I use for the compass:

#include <LSM303.h>

// LSM303 accelerometer is set up at 8 g sensitivity, so therefore the following constants
// 3.8 mg/digit; 1 g = 256
#define GRAVITY 256  //this equivalent to 1G in the raw data coming from the accelerometer

LSM303 compass;

void acc_init(){
  
  compass.init();
  
  if (compass.getDeviceType() == LSM303DLHC_DEVICE){
    compass.writeAccReg(LSM303_CTRL_REG1_A, 0x47); // normal power mode, all axes enabled, 50 Hz
    compass.writeAccReg(LSM303_CTRL_REG4_A, 0x28); // 8 g full scale: FS = 10 on DLHC; high resolution output mode
  }
  else{
    compass.writeAccReg(LSM303_CTRL_REG1_A, 0x27); // normal power mode, all axes enabled, 50 Hz
    compass.writeAccReg(LSM303_CTRL_REG4_A, 0x30); // 8 g full scale: FS = 11 on DLH, DLM
  }
}

void mag_init(){
  compass.writeMagReg(LSM303_MR_REG_M, 0x00); // continuous conversion mode
  // 15 Hz default
}

void read_acc(){
  compass.readAcc();
  acc_c[0][0]=(float)(9.8*compass.a.x/GRAVITY);
  acc_c[1][0]=(float)(9.8*compass.a.y/GRAVITY);
  acc_c[2][0]=(float)(9.8*compass.a.z/GRAVITY);
}

void read_mag(){
  compass.readMag();
  mag_c[0][0]=((float)(compass.m.x) );//- (float)(-511)) / ((641) - (-511));
  mag_c[1][0]=((float)(compass.m.y) );//- (float)(-621)) / ((564) - (-621));
  mag_c[2][0]=((float)(compass.m.z) );//- (float)(-575)) / ((558) - (-575));
}

And this is in the main class:

#include <Wire.h>

#define STATUS_LED 13
#define LENGTH 15

float GYRO_OFF[3][1]={{-0.01918}, {0.03493}, {-0.01011}};
float acc_c[3][1];    // Accelerometer current values
float mag_c[3][1];    //Magnetometer current values
float gyro_c[3][1];   //Gyroscope current values

float dt=0.02;
long timer, timer_old;

void setup(){
  Serial.begin(115200);
  
  pinMode (STATUS_LED,OUTPUT);
  Wire.begin();
  digitalWrite(STATUS_LED,LOW);
  
  delay(500);
  acc_init();
  mag_init();
  gyro_init();
  delay(500);
  
  digitalWrite(STATUS_LED,HIGH);
  
  timer = micros();
}

void loop(){
  
  if ((micros()-timer)>=20000){
    timer_old = timer;
    timer=micros();
    if (timer>timer_old)
      dt = (timer-timer_old)/2000000.0;
    else
      dt = 0;
    
    read_acc();    //Get data from the accelerometer
    read_mag();    //Get data from the magnetometer
    read_gyro();   //Get data from the gyroscope

    //Operations with the data
}

I apologise if I haven’t done enough research or if the answer is simply that I need to learn everything about the things I’m having problems with. Any tips and/or opinions are highly appreciated! Thanks!

Hello, jondoe.

There is a problem with the autodetect functions on the Due, but you can work around it by just specifying the device and SA0 state manually (e.g. compass.init(LSM303::device_LSM303DLM, LSM303::sa0_high) instead of just compass.init()).

Back to your first question…

Our existing libraries are written to use the Arduino Wire object, which corresponds to the first I2C bus on the Due and the only I2C bus on most other Arduinos. You might be able to get a second instance of both sensors working by making copies of the L3G and LSM303 libraries and changing them to use Wire1 instead (it sounds like this is what the original poster in this thread did). However, like you said, even this will only allow you to use only 2 IMUs on one Due.

If you really need to use 5 or 6 MinIMUs, here are some ideas:

  • Use an I2C multiplexer (though we haven’t tried this before).
  • Instead of having a single microcontroller handle all of the IMUs, have a separate microcontroller take care of each IMU and then process and report the results to a central master microcontroller that runs the whole system.
  • Use SPI. Although none of our MinIMUs support SPI, you could use our new L3GD20H and LSM303D carriers separately (note that some of the older LSM303s don’t support SPI). With SPI, you use a separate CS (chip select, sometimes called SS, or slave select) line for each chip to tell it when you’re talking to it, which avoids the problem of address conflicts. However, we don’t provide SPI libraries for interfacing with our inertial sensors, so you would have to implement the SPI communication yourself.

- Kevin

Hi kevin and thanks for the reply!

Sorry I am posting after such a long time, I just wanted to read as much as I can and try and do something so I could report some progress, so here’s what’s happened:

I am initialising and reading the compass with the following functions:

#include <LSM303.h>

#define GRAVITY 256  //this equivalent to 1G in the raw data coming from the accelerometer

LSM303 compass;

void acc_init(){
//  compass.init();
  compass.init(LSM303DLHC_DEVICE, LSM303_SA0_A_HIGH);
  compass.writeAccReg(LSM303_CTRL_REG1_A, 0x47); // normal power mode, all axes enabled, 50 Hz
  compass.writeAccReg(LSM303_CTRL_REG4_A, 0x28); // 8 g full scale: FS = 10 on DLHC; high resolution output mode
}

void mag_init(){
  compass.writeMagReg(LSM303_MR_REG_M, 0x00); // continuous conversion mode
  // 15 Hz default
}

void read_acc(){
  compass.readAcc();
  acc_c[0][0]=(float)(compass.a.x/GRAVITY);
  acc_c[1][0]=(float)(compass.a.y/GRAVITY);
  acc_c[2][0]=(float)(compass.a.z/GRAVITY);
}

void read_mag(){
  compass.readMag();
  mag_c[0][0]=((float)(compass.m.x) );//- (float)(-511)) / ((641) - (-511));
  mag_c[1][0]=((float)(compass.m.y) );//- (float)(-621)) / ((564) - (-621));
  mag_c[2][0]=((float)(compass.m.z) );//- (float)(-575)) / ((558) - (-575));
}

I am initialising and reading the gyro with the following functions:

#include <L3G.h>

// L3G4200D gyro is set up at 2000 dps full scale, so therefore the following constants
// 70 mdps/digit; 1 dps = 0.07
#define Gyro_Gain 0.07 //Gyro gain on all axes
#define Gyro_Scaled(x) ((x)*ToRad(Gyro_Gain)) //Return the scaled ADC raw data of the gyro in radians for second

#define ToRad(x) ((x)*0.01745329252)  // *pi/180
#define ToDeg(x) ((x)*57.2957795131)  // *180/pi

L3G gyro;

void gyro_init(){
  gyro.init(L3GD20_DEVICE, L3G_SA0_HIGH);
  gyro.writeReg(L3G_CTRL_REG1, 0x0F); // normal power mode, all axes enabled, 100 Hz
  gyro.writeReg(L3G_CTRL_REG4, 0x20); // 2000 dps full scale
}

void read_gyro(){
  gyro.read();
  gyro_c[0][0]=(float)(Gyro_Scaled(gyro.g.x)-GYRO_OFF[0][0]);
  gyro_c[1][0]=(float)(Gyro_Scaled(gyro.g.y)-GYRO_OFF[1][0]);
  gyro_c[2][0]=(float)(Gyro_Scaled(gyro.g.z)-GYRO_OFF[2][0]);
}

And this is how I initialise and read them in the main:

#include <Wire.h>

#define S1 22
#define S2 24
#define S3 26

float GYRO_OFF[3][1]={{-0.01918}, {0.03493}, {-0.01011}};
                                                         
float acc_c[3][1];
float mag_c[3][1];
float gyro_c[3][1];

void setup(){
                                    
  pinMode(S1, OUTPUT);
  pinMode(S2, OUTPUT);
  pinMode(S3, OUTPUT);
  
  Serial.begin(115200);
  Wire.begin();
  delay(250);
  //Initialise minIMU connected to Y1
  activateY1();
  acc_init();
  mag_init();
  gyro_init();
  //Initialise minIMU connected to Y2
  activateY2();
  acc_init();
  mag_init();
  gyro_init();
  delay(250);
}

void loop(){
  
  //Read minIMU connected to Y1
  activateY1();
  read_acc();
  read_mag();
  read_gyro();
  output();
  //Read minIMU connected to Y2
  activateY2();
  read_acc();
  read_mag();
  read_gyro();
  output();
  
}

void activateY1(){  //Activate HEF4051 Y1 pin
  digitalWrite(S1, HIGH);
  digitalWrite(S2, LOW);
  digitalWrite(S3, LOW);
}
void activateY2(){  //Activate HEF4051 Y2 pin
  digitalWrite(S1, LOW);
  digitalWrite(S2, HIGH);
  digitalWrite(S3, LOW);
}

void output(){

  Serial.print(F("acc_c: "));
  Serial.print(acc_c[0][0]);
  Serial.print(F(", "));
  Serial.print(acc_c[1][0]);
  Serial.print(F(", "));
  Serial.println(acc_c[2][0]);
  
  Serial.print(F("mag_c: "));
  Serial.print(mag_c[0][0]);
  Serial.print(F(", "));
  Serial.print(mag_c[1][0]);
  Serial.print(F(", "));
  Serial.println(mag_c[2][0]);
  
  Serial.print(F("gyro_c: "));
  Serial.print(gyro_c[0][0]);
  Serial.print(F(", "));
  Serial.print(gyro_c[1][0]);
  Serial.print(F(", "));
  Serial.println(gyro_c[2][0]);
  
  Serial.println();
  
}

I’ve taken your advice on specifying the device when initialising it, so I have used compass.init(LSM303DLHC_DEVICE, LSM303_SA0_A_HIGH) and gyro.init(L3GD20_DEVICE, L3G_SA0_HIGH). Because I still want to use 5-6 minIMUs with one microcontroller, I’ve decided to multiplex the SDA line with HEF4051BP (datasheet). The way HEF4051 works, if you’re not familiar with it, is that it has three logic pins (S1 to S3) and depending on their state it transmits the ‘signal’ from that particular Y pin to Z (Z is the common input/output that’s connected to the Arduino SDA line), but I still keep the SCL line the same for both minIMUs.

All this works quite well when there is only one minIMU connected to the SCL, but when I try and attach a second one, the Serial output slows down/kinda almost stops.

When I comment out the lines that initialise and read Y1 for example, I get the expected output:

acc_c: -0.02, 0.97, 0.12
mag_c: 86.00, -494.00, 180.00
gyro_c: 0.00, 0.02, -0.00

acc_c: -0.02, 0.98, 0.13
mag_c: 86.00, -494.00, 180.00
gyro_c: -0.00, 0.02, -0.00

acc_c: -0.02, 0.98, 0.13
mag_c: 86.00, -494.00, 180.00
gyro_c: 0.00, 0.02, -0.00

acc_c: -0.02, 0.98, 0.13
mag_c: 86.00, -494.00, 180.00
gyro_c: -0.00, 0.02, -0.01

acc_c: -0.02, 0.98, 0.13
mag_c: 85.00, -486.00, 185.00
gyro_c: -0.00, 0.02, -0.00

acc_c: -0.02, 0.98, 0.12
mag_c: 85.00, -486.00, 185.00
gyro_c: -0.00, 0.02, -0.01

In this case I keep the second IMU connected to Y2 (since it is never activated) but I keep the SCL line disconnected. The moment I connect it, the output changes:

acc_c: -0.02, 0.99, 0.11
mag_c: 83.00, -478.00, 182.00
gyro_c: -0.00, 0.02, -0.01

acc_c: -0.02, 0.99, 0.11
mag_c: 83.00, -478.00, 182.00
gyro_c: -0.00, 0.02, -0.01

acc_c: -0.00, -0.00, -0.00
mag_c: -1.00, -1.00, -1.00
gyro_c: 0.02, -0.04, 0.01

acc_c: -0.00, -0.00, -0.00
mag_c: -1.00, -1.00, -1.00
gyro_c: 0.02, -0.04, 0.01

acc_c: -0.00, -0.00, -0.00
mag_c: -1.00, -1.00, -1.00
gyro_c: 0.02, -0.04, 0.01

acc_c: -0.00, -0.00, -0.00
mag_c: -1.00, -1.00, -1.00
gyro_c: 0.02, -0.04, 0.01

So I’ve deduced that I can’t connect two minIMUs to the same SCL for some reason.

Before deciding to use HEF4051, I read this thread, where that guy was trying to communicate with two nunchucks with the same address using transistor switches, but he still keeps the SCL line common (there’s a picture somewhere halfway through the page).

An obvious solution (I guess) would be to multiplex the SCL line as well and just keep the right SCL and SDA lines connected for each IMU. Is the SCL controlling the minIMU in some way so that having more than one connected screws up the addressing or something? I am really lost…

PS: If this doesn’t make sense I can draw a diagram that might make it clearer (:

I haven’t looked through your code carefully, but based on the behavior you’re describing, I suspect connecting multiple IMUs is somehow affecting the SCL signal characteristics. Do you have an oscilloscope you can use to try probing the SCL line? It might be that the pull-up strength on SCL is too high with more than one IMU connected.

- Kevin