SparkFun Forums 

Where electronics enthusiasts find answers.

For the discussion of Arduino related topics.
By seanbai2008
#191069
Hi everyone!

I am currently working on a project where I am going to use Arduino Uno to control two IMU. I am using the IMU library post on Sparkfun and that library works well when I just connect one IMU to the board. However, when I connect another IMU to the board(they share the same SDL and SDA pins on the board and I am sure I have set up the different address for each IMU), it was super slow when I did reading from both IMU. What is interesting that I can read them individually(I mean only put one read() function in the loop()),but not when I put two.

Does the library support two IMU working simultaneously?

The library I use is this one: https://github.com/sparkfun/SparkFun_LS ... no_Library

Thanks!
By SJCCRAC
#197826
Hello there! I imagine this issue is no longer relevant to the original person who posted the question, I will provide an answer for others that may come across this same issue.

Ok, so the LSM9DS1 (IMU) can use one of two possible addresses. The default is: 0x1E for the magnetometer and 0x6B for the accelerometer/gyroscope. The second possible address combination is: 0x1C for the magnetometer and 0x6A for the accelerometer/gyroscope, this will only work with SDOM and SDOAG pins pulled down to ground, more on this later.

If you are only hooking up one IMU and connecting it via I2C then default configuration will work fine.

If you want to hook up two, you'll need to modify the SparkFunLSM9DS1 library by adding some code to the SparkFunLSM9DS1.cpp file.

Setting up breakout boards:
imuA: default config
imuB: cut traces on back of breakout board, as instructed on this page:
Image
https://learn.sparkfun.com/tutorials/ls ... e-assembly
Here is a close up of what it looks like after the traces are cut:
Image
For imuB, make sure you connect pins SDOM and SDOAG to ground.

Modifying the SparkFunLSM9DS1 Library to accommodate two imus:
Search for "SparkFunLSM9DS1.cpp" on your computer > open the file in a text editor > locate the following code and highlight it:
Code: Select all
LSM9DS1::LSM9DS1()
{
	init(IMU_MODE_I2C, LSM9DS1_AG_ADDR(1), LSM9DS1_M_ADDR(1));
}
Paste in the following code > save file:
Code: Select all
//update this code from here....
bool addySwitch; //this was added by Joseph to get two addresses working

LSM9DS1::LSM9DS1()
{
	//added by Joseph to get two addresses working
	if (addySwitch == 1)
		addySwitch = 0;
	else if (addySwitch == 0)
		addySwitch = 1;
	else
		addySwitch = 1;
	
	init(IMU_MODE_I2C, LSM9DS1_AG_ADDR(addySwitch), LSM9DS1_M_ADDR(addySwitch));
	Serial.println("addySwitch: " + String(addySwitch));//remember to comment this out, just here for debugging

}
//...to here
Lastly, you can use the example sketch that came with the SparkFun library and just expand on it to suit your needs. Just make sure you have a separate instance of the LSM9DS1 object for each address that you are using.

For your convenience, here is my code so you can see how I set things up:
Code: Select all
/*****************************************************************
Built off of LSM9DS1_Basic_I2C.ino and LSM9DS1_Settings.ino
Modified by Joseph Heady @ sjccrobotics.com
Contact info: president@sjccrobotics.com

Original LSM9DS1 Breakout Board library can be found at:
https://github.com/sparkfun/LSM9DS1_Breakout

Hardware setup: This library supports communicating with the
LSM9DS1 over either I2C or SPI. This example demonstrates how
to use I2C. The pin-out is as follows:
  First LSM9DS1 --------- Arduino
   SCL ---------- SCL (A5 on older 'Duinos')
   SDA ---------- SDA (A4 on older 'Duinos')
   VDD ------------- 3.3V
   GND ------------- GND
  Second LSM9DS1 --------- Arduino
    Should be connected in the same way, traces on back of board should be cut, and once 
    cut SDOM and SDOAG should be pulled low by connecting them to ground.
    Example image can be found here: https://sjccrobotics.com/media/LSM9DS1_CloseUpCut.jpg
    
The LSM9DS1 has a maximum voltage of 3.6V. Make sure you power it
off the 3.3V rail! I2C pins are open-drain, so you'll be 
(mostly) safe connecting the LSM9DS1's SCL and SDA pins 
directly to the Arduino.
Development environment specifics:
  IDE: Arduino 1.6.3
  Hardware Platform: SparkFun Redboard
  LSM9DS1 Breakout Version: 1.0
This code is beerware. If you see me (or any other SparkFun 
employee) at the local, and you've found our code helpful, 
please buy us a round!
Distributed as-is; no warranty is given.
*****************************************************************/

// The SFE_LSM9DS1 library requires both Wire and SPI be
// included BEFORE including the 9DS1 library.
#include <Wire.h>
#include <SPI.h>
#include <SparkFunLSM9DS1.h>
//For dataLogging to processing
#include <SoftwareSerial.h>

//////////////////////////
// LSM9DS1 Library Init //
//////////////////////////
// Use the LSM9DS1 class to create an object. [imu] can be
// named anything, we'll refer to that throught the sketch.
LSM9DS1 imuA; // Addresses: (M)0x1C & (AG)0x6A
LSM9DS1 imuB; // Addresses: (M)0x1E & (AG)0x6B


// Global variables to keep track of update rates
unsigned long startTime;
unsigned int accelReadCounter = 0;
unsigned int gyroReadCounter = 0;
unsigned int magReadCounter = 0;
unsigned int tempReadCounter = 0;

////////////////////////////
// Sketch Output Settings //
////////////////////////////
#define PRINT_CALCULATED
//#define PRINT_RAW
#define PRINT_SPEED 100 // 100 ms between prints
static unsigned long lastPrintA = 0; // Keep track of print time
static unsigned long lastPrintB = 0; // Keep track of print time

// Earth's magnetic field varies by location. Add or subtract 
// a declination to get a more accurate heading. Calculate 
// your's here:
// http://www.ngdc.noaa.gov/geomag-web/#declination
#define DECLINATION -13.28 // Declination (degrees) in San Jose, CA.




////CONFIGURE IMU/////
void setupDevice()
{
  // [commInterface] determines whether we'll use I2C or SPI
  // to communicate with the LSM9DS1.
  // Use either IMU_MODE_I2C or IMU_MODE_SPI
  imuA.settings.device.commInterface = IMU_MODE_I2C;
  imuB.settings.device.commInterface = IMU_MODE_I2C;
  // [mAddress] sets the I2C address or SPI CS pin of the
  // LSM9DS1's magnetometer.
  imuA.settings.device.mAddress = 0x1E; // Use I2C addres 0x1E
  imuB.settings.device.mAddress = 0x1C; // Use I2C addres 0x1E
  // [agAddress] sets the I2C address or SPI CS pin of the
  // LSM9DS1's accelerometer/gyroscope.
  imuA.settings.device.agAddress = 0x6B; // I2C address 0x6C
  imuB.settings.device.agAddress = 0x6A; // I2C address 0x6C
}

void setupGyro()
{
  // [enabled] turns the gyro on or off.
  imuA.settings.gyro.enabled = true;  // Enable the gyro
  imuB.settings.gyro.enabled = true;  // Enable the gyro
  // [scale] sets the full-scale range of the gyroscope.
  // scale can be set to either 245, 500, or 2000
  imuA.settings.gyro.scale = 2000; // Set scale to +/-500dps
  imuB.settings.gyro.scale = 2000; // Set scale to +/-500dps
  // [sampleRate] sets the output data rate (ODR) of the gyro
  // sampleRate can be set between 1-6
  // 1 = 14.9    4 = 238
  // 2 = 59.5    5 = 476
  // 3 = 119     6 = 952
  imuA.settings.gyro.sampleRate = 5; // 476Hz ODR
  imuB.settings.gyro.sampleRate = 5; // 476Hz ODR
  // [bandwidth] can set the cutoff frequency of the gyro.
  // Allowed values: 0-3. Actual value of cutoff frequency
  // depends on the sample rate. (Datasheet section 7.12)
  imuA.settings.gyro.bandwidth = 3;
  imuB.settings.gyro.bandwidth = 3;
  // [lowPowerEnable] turns low-power mode on or off.
  imuA.settings.gyro.lowPowerEnable = false; // LP mode off
  imuB.settings.gyro.lowPowerEnable = false; // LP mode off
  // [HPFEnable] enables or disables the high-pass filter
  imuA.settings.gyro.HPFEnable = false; // HPF disabled
  imuB.settings.gyro.HPFEnable = false; // HPF disabled
  // [HPFCutoff] sets the HPF cutoff frequency (if enabled)
  // Allowable values are 0-9. Value depends on ODR.
  // (Datasheet section 7.14)
  imuA.settings.gyro.HPFCutoff = 1; // HPF cutoff = 4Hz
  imuB.settings.gyro.HPFCutoff = 1; // HPF cutoff = 4Hz
  // [flipX], [flipY], and [flipZ] are booleans that can
  // automatically switch the positive/negative orientation
  // of the three gyro axes.
  //imuA
  imuA.settings.gyro.flipX = true; // Don't flip X
  imuA.settings.gyro.flipY = false; // Don't flip Y
  imuA.settings.gyro.flipZ = false; // Don't flip Z
//imuB
  imuB.settings.gyro.flipX = true; // Don't flip X
  imuB.settings.gyro.flipY = false; // Don't flip Y
  imuB.settings.gyro.flipZ = false; // Don't flip Z
}

void setupAccel()
{
  // [enabled] turns the acclerometer on or off.
  imuA.settings.accel.enabled = true; // Enable accelerometer
  imuB.settings.accel.enabled = true; // Enable accelerometer
  // [enableX], [enableY], and [enableZ] can turn on or off
  // select axes of the acclerometer.
  //imuA
  imuA.settings.accel.enableX = true; // Enable X
  imuA.settings.accel.enableY = true; // Enable Y
  imuA.settings.accel.enableZ = true; // Enable Z
  //imuB
  imuB.settings.accel.enableX = true; // Enable X
  imuB.settings.accel.enableY = true; // Enable Y
  imuB.settings.accel.enableZ = true; // Enable Z
  // [scale] sets the full-scale range of the accelerometer.
  // accel scale can be 2, 4, 8, or 16
  imuA.settings.accel.scale = 8; // Set accel scale to +/-8g.
  imuB.settings.accel.scale = 8; // Set accel scale to +/-8g.
  // [sampleRate] sets the output data rate (ODR) of the
  // accelerometer. ONLY APPLICABLE WHEN THE GYROSCOPE IS
  // DISABLED! Otherwise accel sample rate = gyro sample rate.
  // accel sample rate can be 1-6
  // 1 = 10 Hz    4 = 238 Hz
  // 2 = 50 Hz    5 = 476 Hz
  // 3 = 119 Hz   6 = 952 Hz
  imuA.settings.accel.sampleRate = 1; // Set accel to 10Hz.
  imuB.settings.accel.sampleRate = 1; // Set accel to 10Hz.
  // [bandwidth] sets the anti-aliasing filter bandwidth.
  // Accel cutoff freqeuncy can be any value between -1 - 3. 
  // -1 = bandwidth determined by sample rate
  // 0 = 408 Hz   2 = 105 Hz
  // 1 = 211 Hz   3 = 50 Hz
  imuA.settings.accel.bandwidth = -1; // BW = determined by sample rate
  imuB.settings.accel.bandwidth = -1; // BW = determined by sample rate
  // [highResEnable] enables or disables high resolution 
  // mode for the acclerometer.
  imuA.settings.accel.highResEnable = true; // Enabled HR
  imuB.settings.accel.highResEnable = true; // Enabled HR
  // [highResBandwidth] sets the LP cutoff frequency of
  // the accelerometer if it's in high-res mode.
  // can be any value between 0-3
  // LP cutoff is set to a factor of sample rate
  // 0 = ODR/50    2 = ODR/9
  // 1 = ODR/100   3 = ODR/400
  imuA.settings.accel.highResBandwidth = 0;
  imuB.settings.accel.highResBandwidth = 0;  
}

void setupMag()
{
  // [enabled] turns the magnetometer on or off.
  imuA.settings.mag.enabled = true; // Disabled magnetometer
  imuB.settings.mag.enabled = true; // Disabled magnetometer
  // [scale] sets the full-scale range of the magnetometer
  // mag scale can be 4, 8, 12, or 16
  imuA.settings.mag.scale = 12; // Set mag scale to +/-12 Gs
  imuB.settings.mag.scale = 12; // Set mag scale to +/-12 Gs
  // [sampleRate] sets the output data rate (ODR) of the
  // magnetometer.
  // mag data rate can be 0-7:
  // 0 = 0.625 Hz  4 = 10 Hz
  // 1 = 1.25 Hz   5 = 20 Hz
  // 2 = 2.5 Hz    6 = 40 Hz
  // 3 = 5 Hz      7 = 80 Hz
  imuA.settings.mag.sampleRate = 7; // Set OD rate to 80Hz
  imuB.settings.mag.sampleRate = 7; // Set OD rate to 80Hz
  // [tempCompensationEnable] enables or disables 
  // temperature compensation of the magnetometer.
  imuA.settings.mag.tempCompensationEnable = false;
  imuB.settings.mag.tempCompensationEnable = false;
  // [XYPerformance] sets the x and y-axis performance of the
  // magnetometer to either:
  // 0 = Low power mode      2 = high performance
  // 1 = medium performance  3 = ultra-high performance
  imuA.settings.mag.XYPerformance = 3; // Ultra-high perform.
  imuB.settings.mag.XYPerformance = 3; // Ultra-high perform.
  // [ZPerformance] does the same thing, but only for the z
  imuA.settings.mag.ZPerformance = 3; // Ultra-high perform.
  imuB.settings.mag.ZPerformance = 3; // Ultra-high perform.
  // [lowPowerEnable] enables or disables low power mode in
  // the magnetometer.
  imuA.settings.mag.lowPowerEnable = false;
  imuB.settings.mag.lowPowerEnable = false;
  // [operatingMode] sets the operating mode of the
  // magnetometer. operatingMode can be 0-2:
  // 0 = continuous conversion
  // 1 = single-conversion
  // 2 = power down
  imuA.settings.mag.operatingMode = 0; // Continuous mode
  imuB.settings.mag.operatingMode = 0; // Continuous mode
}


void setupTemperature()
{
  // [enabled] turns the temperature sensor on or off.
  imuA.settings.temp.enabled = true;
  imuB.settings.temp.enabled = true;
}


uint16_t initLSM9DS1()
{
  setupDevice(); // Setup general device parameters
  setupGyro(); // Set up gyroscope parameters
  setupAccel(); // Set up accelerometer parameters
  setupMag(); // Set up magnetometer parameters
  setupTemperature(); // Set up temp sensor parameter
  
  //return imu.begin();//REMOVE AFTER TESTING
}
////CONFIGURE IMU/////


void setup() 
{
  //MAKE SURE THIS VALUE MATCHES YOUR NEEDS
  Serial.begin(115200);
  
  if (!imuB.begin())
  {
    Serial.println("Failed to communicate with imuB LSM9DS1.");
    Serial.println("Double-check wiring imaB.");
    Serial.println("(imuB)Default settings in this sketch will " \
                  "work for an out of the box LSM9DS1 " \
                  "Breakout, but may need to be modified " \
                  "if the board jumpers are.");
    while (1)
      ;
  }

  if (!imuA.begin())
  {
    Serial.println("Failed to communicate with imuA LSM9DS1.");
    Serial.println("Double-check wiring imuA.");
    Serial.println("(imuA)Default settings in this sketch will " \
                  "work for an out of the box LSM9DS1 " \
                  "Breakout, but may need to be modified " \
                  "if the board jumpers are.");
    while (1)
      ;
  }
}


void loop()
{
  //imuA Begin//
  // Update the sensor values whenever new data is available
  if ( imuA.gyroAvailable() )
  {
    // To read from the gyroscope,  first call the
    // readGyro() function. When it exits, it'll update the
    // gx, gy, and gz variables with the most current data.
    imuA.readGyro();
  }
  if ( imuA.accelAvailable() )
  {
    // To read from the accelerometer, first call the
    // readAccel() function. When it exits, it'll update the
    // ax, ay, and az variables with the most current data.
    imuA.readAccel();
  }
  if ( imuA.magAvailable() )
  {
    // To read from the magnetometer, first call the
    // readMag() function. When it exits, it'll update the
    // mx, my, and mz variables with the most current data.
    imuA.readMag();
    imuA.readTemp();
  }
  
  if ((lastPrintA + PRINT_SPEED) < millis())
  {
    printGyroA(imuA.gx, imuA.gy, imuA.gz);  // Print "G: gx, gy, gz"
    printAccelA(imuA.ax, imuA.ay, imuA.az); // Print "A: ax, ay, az"
    printMagB(imuA.mx, imuA.my, imuA.mz);   // Print "M: mx, my, mz"
    // Print the heading and orientation for fun!
    // Call print attitude. The LSM9DS1's mag x and y
    // axes are opposite to the accelerometer, so my, mx are
    // substituted for each other.
    printAttitudeA(imuA.ax, imuA.ay, imuA.az, -imuA.my, -imuA.mx, imuA.mz, imuA.temperature);
    //Serial.println();
    
    lastPrintA = millis(); // Update lastPrint time
  }
  //imuA End//



  //imuB Begin//
    // Update the sensor values whenever new data is available
  if ( imuB.gyroAvailable() )
  {
    // To read from the gyroscope,  first call the
    // readGyro() function. When it exits, it'll update the
    // gx, gy, and gz variables with the most current data.
    imuB.readGyro();
  }
  if ( imuB.accelAvailable() )
  {
    // To read from the accelerometer, first call the
    // readAccel() function. When it exits, it'll update the
    // ax, ay, and az variables with the most current data.
    imuB.readAccel();
  }
  if ( imuB.magAvailable() )
  {
    // To read from the magnetometer, first call the
    // readMag() function. When it exits, it'll update the
    // mx, my, and mz variables with the most current data.
    imuB.readMag();
    imuB.readTemp();
  }
  if ((lastPrintB + PRINT_SPEED) < millis())
  {
    printGyroB(imuB.gx, imuB.gy, imuB.gz);  // Print "G: gx, gy, gz"
    printAccelB(imuB.ax, imuB.ay, imuB.az); // Print "A: ax, ay, az"
    printMagB(imuB.mx, imuB.my, imuB.mz);   // Print "M: mx, my, mz"
    // Print the heading and orientation for fun!
    // Call print attitude. The LSM9DS1's mag x and y
    // axes are opposite to the accelerometer, so my, mx are
    // substituted for each other.
    printAttitudeB(imuB.ax, imuB.ay, imuB.az, -imuB.my, -imuB.mx, imuB.mz, imuB.temperature);
    //Serial.println();
    
    lastPrintB = millis(); // Update lastPrint time
  }
  //imuB End//
}


void printGyroA(float gx, float gy, float gz)
{
  // Now we can use the gx, gy, and gz variables as we please.
  // Either print them as raw ADC values, or calculated in DPS.
  //Serial.print("G: ");
#ifdef PRINT_CALCULATED
  // If you want to print calculated values, you can use the
  // calcGyro helper function to convert a raw ADC value to
  // DPS. Give the function the value that you want to convert.
  Serial.print(imuA.calcGyro(gx), 2);
  Serial.print(",");
  Serial.print(imuA.calcGyro(gy), 2);
  Serial.print(",");
  Serial.print(imuA.calcGyro(gz), 2);
  Serial.print(",");
  //Serial.println(imuA.settings.device.agAddress);
  //Serial.println(" deg/s");
#elif defined PRINT_RAW
  Serial.print(gx);
  Serial.println(",");
  Serial.print(gy);
  Serial.println(",");
  Serial.print(gz);
  Serial.println(",");
#endif
}


void printGyroB(float gx, float gy, float gz)
{
  // Now we can use the gx, gy, and gz variables as we please.
  // Either print them as raw ADC values, or calculated in DPS.
  //Serial.print("G: ");
#ifdef PRINT_CALCULATED
  // If you want to print calculated values, you can use the
  // calcGyro helper function to convert a raw ADC value to
  // DPS. Give the function the value that you want to convert.
  Serial.print(imuB.calcGyro(gx), 2);
  Serial.print(",");
  Serial.print(imuB.calcGyro(gy), 2);
  Serial.print(",");
  Serial.print(imuB.calcGyro(gz), 2);
  Serial.print(",");
  //Serial.println(imuB.settings.device.agAddress);
  //Serial.println(" deg/s");
#elif defined PRINT_RAW
  Serial.print(gx);
  Serial.println(",");
  Serial.print(gy);
  Serial.println(",");
  Serial.print(gz);
  Serial.println(",");
#endif
}


void printAccelA(float ax, float ay, float az)
{  
  // Now we can use the ax, ay, and az variables as we please.
  // Either print them as raw ADC values, or calculated in g's.
  //Serial.print("A: ");
#ifdef PRINT_CALCULATED
  // If you want to print calculated values, you can use the
  // calcAccel helper function to convert a raw ADC value to
  // g's. Give the function the value that you want to convert.
  Serial.print(imuA.calcAccel(ax), 2);
  Serial.print(",");
  Serial.print(imuA.calcAccel(ay), 2);
  Serial.print(",");
  Serial.print(imuA.calcAccel(az), 2);
  Serial.print(",");
  //Serial.println(imuA.settings.device.agAddress);
  //Serial.println(" g");
#elif defined PRINT_RAW 
  Serial.print(ax);
  Serial.print(", ");
  Serial.print(ay);
  Serial.print(", ");
  Serial.println(az);
#endif
}

void printAccelB(float ax, float ay, float az)
{  
  // Now we can use the ax, ay, and az variables as we please.
  // Either print them as raw ADC values, or calculated in g's.
  //Serial.print("A: ");
#ifdef PRINT_CALCULATED
  // If you want to print calculated values, you can use the
  // calcAccel helper function to convert a raw ADC value to
  // g's. Give the function the value that you want to convert.
  Serial.print(imuB.calcAccel(ax), 2);
  Serial.print(",");
  Serial.print(imuB.calcAccel(ay), 2);
  Serial.print(",");
  Serial.print(imuB.calcAccel(az), 2);
  Serial.print(",");
  //Serial.println(imuB.settings.device.agAddress);
  //Serial.println(" g");
#elif defined PRINT_RAW 
  Serial.print(ax);
  Serial.print(", ");
  Serial.print(ay);
  Serial.print(", ");
  Serial.println(az);
#endif
}



void printMagA(float mx, float my, float mz)
{  
  // Now we can use the mx, my, and mz variables as we please.
  // Either print them as raw ADC values, or calculated in Gauss.
  //Serial.print("M: ");
#ifdef PRINT_CALCULATED
  // If you want to print calculated values, you can use the
  // calcMag helper function to convert a raw ADC value to
  // Gauss. Give the function the value that you want to convert.
  Serial.print(imuA.calcMag(mx), 2);
  Serial.print(",");
  Serial.print(imuA.calcMag(my), 2);
  Serial.print(",");
  Serial.print(imuA.calcMag(mz), 2);
  Serial.print(",");
  //Serial.println(imuA.settings.device.agAddress);
  //Serial.println(" gauss");
#elif defined PRINT_RAW
  Serial.print(mx);
  Serial.print(", ");
  Serial.print(my);
  Serial.print(", ");
  Serial.println(mz);
#endif
}

void printMagB(float mx, float my, float mz)
{  
  // Now we can use the mx, my, and mz variables as we please.
  // Either print them as raw ADC values, or calculated in Gauss.
  //Serial.print("M: ");
#ifdef PRINT_CALCULATED
  // If you want to print calculated values, you can use the
  // calcMag helper function to convert a raw ADC value to
  // Gauss. Give the function the value that you want to convert.
  Serial.print(imuB.calcMag(mx), 2);
  Serial.print(",");
  Serial.print(imuB.calcMag(my), 2);
  Serial.print(",");
  Serial.print(imuB.calcMag(mz), 2);
  Serial.print(",");
  //Serial.println(imuB.settings.device.agAddress);
  //Serial.println(" gauss");
#elif defined PRINT_RAW
  Serial.print(mx);
  Serial.print(", ");
  Serial.print(my);
  Serial.print(", ");
  Serial.println(mz);
#endif
}



// Calculate pitch, roll, and heading.
// Pitch/roll calculations take from this app note:
// http://cache.freescale.com/files/sensors/doc/app_note/AN3461.pdf?fpsp=1
// Heading calculations taken from this app note:
// http://www51.honeywell.com/aero/common/documents/myaerospacecatalog-documents/Defense_Brochures-documents/Magnetic__Literature_Application_notes-documents/AN203_Compass_Heading_Using_Magnetometers.pdf
void printAttitudeA(float ax, float ay, float az, float mx, float my, float mz, float temperature) 
{
  float rollA = atan2(ay, az);
  float pitchA = atan2(-ax, sqrt(ay * ay + az * az));
  float headingA;


  ///////HEADING RELATED STUFF///////
  if (my == 0)
    headingA = (mx < 0) ? PI : 0;
  else
    headingA = atan2(mx, my);
    
  headingA -= DECLINATION * PI / 180;
  
  if (headingA > PI) headingA -= (2 * PI);
  else if (headingA < -PI) headingA += (2 * PI);
  else if (headingA < 0) headingA += 2 * PI;
  ///////HEADING REALTED STUFF///////


  
  
  // Convert everything from radians to degrees:
  headingA *= 180.0 / PI;
  pitchA *= 180.0 / PI;
  rollA  *= 180.0 / PI;
  
  //Serial.print("Pitch: ");
  Serial.print(pitchA, 2);
  Serial.print(",");
  //Serial.print("Roll: ");
  Serial.print(rollA, 2);
  Serial.print(",");
  //Serial.print("Heading: "); 
  Serial.println(headingA, 2);
  //Serial.print(",");
  //Serial.print(imuA.settings.device.agAddress);
  //Serial.print(temperature, 2);// Enable to see raw temp reading
}



void printAttitudeB(float ax, float ay, float az, float mx, float my, float mz, float temperature) 
{
  float rollB = atan2(ay, az);
  float pitchB = atan2(-ax, sqrt(ay * ay + az * az));
  float headingB;


  ///////HEADING RELATED STUFF///////
  if (my == 0)
    headingB = (mx < 0) ? PI : 0;
  else
    headingB = atan2(mx, my);
    
  headingB -= DECLINATION * PI / 180;
  
  if (headingB > PI) headingB -= (2 * PI);
  else if (headingB < -PI) headingB += (2 * PI);
  else if (headingB < 0) headingB += 2 * PI;
  ///////HEADING REALTED STUFF///////


  
  
  // Convert everything from radians to degrees:
  headingB *= 180.0 / PI;
  pitchB *= 180.0 / PI;
  rollB  *= 180.0 / PI;
  
  //Serial.print("Pitch: ");
  Serial.print(pitchB, 2);
  Serial.print(",");
  //Serial.print("Roll: ");
  Serial.print(rollB, 2);
  Serial.print(",");
  //Serial.print("Heading: "); 
  Serial.println(headingB, 2);
  //Serial.print(",");
  //Serial.print(imuB.settings.device.agAddress);
  //Serial.print(temperature, 2);// Enable to see raw temp reading
}
Now you should be able to upload your sketch, view serial monitor, and see data coming in from both LSM9DS1 IMUs. Each line represents a full set of sensor readings from one IMU in the following order gx, gy, gz, ax, ay, az, mx, my, mz, pitch, roll, heading(yaw).

I hope this helps! If you like it, please reference our club's site: sjccrobotics.com

Thank you!
Joseph Heady