Need help debugging Teensy LC and MPU-9250 (via i2c)

Status
Not open for further replies.
I wired my Teensy LC up to an MPU-9250 using i2c, and for some reason I can't pry anything out of the sensor. I'm using this library for the MPU-9250, and the most recent versions of Teensyduino (1.40) and Arduino IDE (1.8.5).

Here's a picture of my wiring (5v for the MPU-9250 coming from other Arduino Uno hooked up to a USB port). I haven't added any pullup resistors because the MPU-9250 I linked from Amazon has onboard pullup resistors between its onboard 3.3v LDO regulator output and the i2c SCL/SDA lines. Again, the MPU-9250 is powered from a separate 5v source, it has an onboard LDO regulator (maybe this somehow is causing my problem?)

The code I'm running is practically an example, the only change being the addition of I2C_PINS_18_19 to the MPU9250's constructor and a couple of debugging print lines. When I run the code, I get one of the Serial.println() messages I added: "Starting..." and then nada after that.

Here's the code I'm currently using:

Code:
#include "MPU9250.h"

// an MPU9250 object with its I2C address 
// of 0x68 (ADDR to GRND) and on Teensy bus 0
MPU9250 IMU(0x68, 0, I2C_PINS_18_19);

float ax, ay, az, gx, gy, gz, hx, hy, hz, t;
int beginStatus;

void setup() {
  // serial to display data
  Serial.begin(115200);

  // start communication with IMU and 
  // set the accelerometer and gyro ranges.
  // ACCELEROMETER 2G 4G 8G 16G
  // GYRO 250DPS 500DPS 1000DPS 2000DPS
  Serial.println("Starting...");
  beginStatus = IMU.begin(ACCEL_RANGE_4G,GYRO_RANGE_250DPS);
  Serial.println("Started");
}

void loop() {
  if(beginStatus < 0) {
    delay(1000);
    Serial.println("IMU initialization unsuccessful");
    Serial.println("Check IMU wiring or try cycling power");
    delay(10000);
  }
  else{
    /* get the individual data sources */
    /* This approach is only recommended if you only
     *  would like the specified data source (i.e. only
     *  want accel data) since multiple data sources
     *  would have a time skew between them.
     */
    // get the accelerometer data (m/s/s)
    IMU.getAccel(&ax, &ay, &az);

    // get the gyro data (rad/s)
    IMU.getGyro(&gx, &gy, &gz);

    // get the magnetometer data (uT)
    IMU.getMag(&hx, &hy, &hz);

    // get the temperature data (C)
    IMU.getTemp(&t);

    // print the data
    printData();

    // delay a frame
    delay(50);

    /* get multiple data sources */
    /* In this approach we get data from multiple data
     *  sources (i.e. both gyro and accel). This is 
     *  the recommended approach since there is no time
     *  skew between sources - they are all synced.
     *  Demonstrated are:
     *  1. getMotion6: accel + gyro
     *  2. getMotion7: accel + gyro + temp
     *  3. getMotion9: accel + gyro + mag
     *  4. getMotion10: accel + gyro + mag + temp
     */

     /* getMotion6 */
    // get both the accel (m/s/s) and gyro (rad/s) data
    IMU.getMotion6(&ax, &ay, &az, &gx, &gy, &gz);

    // get the magnetometer data (uT)
    IMU.getMag(&hx, &hy, &hz);

    // get the temperature data (C)
    IMU.getTemp(&t);

    // print the data
    printData();

    // delay a frame
    delay(50);

    /* getMotion7 */
    // get the accel (m/s/s), gyro (rad/s), and temperature (C) data
    IMU.getMotion7(&ax, &ay, &az, &gx, &gy, &gz, &t);

    // get the magnetometer data (uT)
    IMU.getMag(&hx, &hy, &hz);

    // print the data
    printData();

    // delay a frame
    delay(50);

    /* getMotion9 */
    // get the accel (m/s/s), gyro (rad/s), and magnetometer (uT) data
    IMU.getMotion9(&ax, &ay, &az, &gx, &gy, &gz, &hx, &hy, &hz);

    // get the temperature data (C)
    IMU.getTemp(&t);

    // print the data
    printData();

    // delay a frame
    delay(50);

    // get the accel (m/s/s), gyro (rad/s), and magnetometer (uT), and temperature (C) data
    IMU.getMotion10(&ax, &ay, &az, &gx, &gy, &gz, &hx, &hy, &hz, &t);

    // print the data
    printData();

    // delay a frame
    delay(50);
  }
}

void printData(){

  // print the data
  Serial.print(ax,6);
  Serial.print("\t");
  Serial.print(ay,6);
  Serial.print("\t");
  Serial.print(az,6);
  Serial.print("\t");

  Serial.print(gx,6);
  Serial.print("\t");
  Serial.print(gy,6);
  Serial.print("\t");
  Serial.print(gz,6);
  Serial.print("\t");

  Serial.print(hx,6);
  Serial.print("\t");
  Serial.print(hy,6);
  Serial.print("\t");
  Serial.print(hz,6);
  Serial.print("\t");

  Serial.println(t,6);
}

Thanks in advance.
 
I don't see any wire from Teensy's GND to the sensor GND.

Welp, that should have been obvious. That's now fixed. Now it's getting beyond the initial "beginStatus = IMU.begin(ACCEL_RANGE_4G,GYRO_RANGE_250DPS);" line, and entering the main loop. Sadly, it isn't getting any farther than that, beginStatus never exceeds 0.
 
Now I'm a software guy, not a hardware guy. So take what I say with appropriate grains of salt.

From your description, it sounds like the pull-up resistors are between the 3.3v on the voltage regulator on the MPU-9250 and ground. While you've connected the grounds, the power comes from a different source for the MPU-9250 and for the Teensy. I suspect if both the MPU-9250 and Teensy were on the same source, the pull-up resistors would work.

However, you mentioned 5v. Unlike the Teensy 3.2 or 3.5, the Teensy LC (and 3.6) are NOT 5v tolerant. If the i2c connection delivers 5v to the LC, it may cause hardware failure in the LC (BTDT with a Teensy 3.6 and a different 5v device). Similarly when the LC sends a 3.3v signal, the MPU-9250 may not see the signal. So, you may need to read the datasheet to make sure it is expecting and writing 3.3v for the SCL and SDA pins. There may be a solder jumper you need to adjust to get it to work properly.

Assuming the MPU-9250 is set up for 3.3v SCL/SDA, but it wants 5v power, I would say connect it to the VIN of your Teensy LC, so it shares the same power source. Alternatively, you might try putting in separate 2.2k pull-up resistors between each of pins 18/19 and the Teensy's 3.3v.
 
Last edited:
I think you can power it from the 3.3V pin on the teensy. If that doesn't work, you could use the 5V, but I would wait until someone else chimes in as I'm not positive.

Also, I think the "INT" or "ADO" pin (forget which) can be held high or low to change the address. Check the sketch for the address needed.
 
Hardware thoughts:

- ditch the external arduino as power supply

- use the teensy +3.3V supply and ground to power the sensor.
The Teensy 3.3V supply provides up to 100ma; less than 10ma is needed here. In fact, it's good to think in terms of 3.3V power supplies from now on. 5V is so 1990's, dude!

- cnnect sda/scl from sensor to teensy

- The module you have already has pullup
resistors on sda/scl
, if I am not mistaken.

- use the shortest possible wires for all these connections.

- run the example code.

If it does not work, you no longer have to worry that it's a hardware problem, and you can concentrate on code.
(Although, As one last resort in the hardware area, you could add a 10K pullup on each of sda and scl to +3.3V.)
 
Last edited:
Try running the i2c scanner (examples>wire>scanner) to see if the 9250 responds on addr 0x68. If not, then you have a hookup problem. If it responds on a different addr then you need to change the address in your code. If it responds at addr 0x68 then you have a code problem, probably somewhere in IMU.begin ().
 
Try running the i2c scanner (examples>wire>scanner) to see if the 9250 responds on addr 0x68. If not, then you have a hookup problem. If it responds on a different addr then you need to change the address in your code. If it responds at addr 0x68 then you have a code problem, probably somewhere in IMU.begin ().

It does respond to the address 0x68 with the Wire library scanner, but the i2c_t3 library (used in MPU-9250 library) scanner doesn't pick up the address. Any ideas?

Edit: Now i2c_t3 sees it. Definitely a code issue somewhere...
 
Last edited:
Assuming the MPU-9250 is set up for 3.3v SCL/SDA, but it wants 5v power, I would say connect it to the VIN of your Teensy LC, so it shares the same power source. Alternatively, you might try putting in separate 2.2k pull-up resistors between each of pins 18/19 and the Teensy's 3.3v.

This is correct, it's set up for 3.3v SCL/SDA but can take 3.3v/5v power. I've rewired it to share the VIN and the gnd of the Teensy, and the Wire library i2c scanner sees a device at 0x68. i2c_t3 scanner doesn't see it for some reason, though :confused:

Hardware thoughts:

- ditch the external arduino as power supply.

- use the teensy +3.3V supply and ground to power the sensor.
The Teensy 3.3V supply provides up to 100ma; less than 10ma is needed here. In fact, it's good to think in terms of 3.3V power supplies from now on. 5V is so 1990's, dude!

- cnnect sda/scl from sensor to teensy


Done. Tried with 5v and 3.3v :confused:

- The module you have already has pullup
resistors on sda/scl
, if I am not mistaken.

That was my understanding too. I might try adding in my own pullup resistors later, however, if nothing else works.

If it does not work, you no longer have to worry that it's a hardware problem, and you can concentrate on code.
(Although, As one last resort in the hardware area, you could add a 10K pullup on each of sda and scl to +3.3V.)

I think it's a code issue, given that the Wire library sees the MPU-9250 at 0x68. Why might i2c_t3 not see it?

Edit: Now i2c_t3 sees it. Definitely a code issue...
 
Last edited:
Well that's pretty strange. If the i2c_t3 code doesn't see the 9250 then for sure your library wont.

Looking at the scanner code for Wire & i2c_t3 they both do essentially the same thing, look for an Ack to beginTransmission to a particular address. However, Wire defaults to a 100kHz clock whereas the i2c_t3 scanner sets the clock to 400kHz.

The faster the clock, the more important the pullups become. I see that the schematic on Amazon shows 10k pullups on the board but I'd try adding a couple of pullups to the Teensy 3v3. For short lines you can use pretty much anything from around 2 - 5 kOhms.
 
Last edited:
Well that's pretty strange. If the i2c_t3 code doesn't see the 9250 then for sure your library wont.

Looking at the scanner code for Wire & i2c_t3 they both do essentially the same thing, look for an Ack to beginTransmission to a particular address. However, Wire defaults to a 100kHz clock whereas the i2c_t3 scanner sets the clock to 400kHz.

The faster the clock, the more important the pullups become. I see that the schematic on Amazon shows 10k pullups on the board but I'd try adding a couple of pullups to the Teensy 3v3. For short lines you can use pretty much anything from around 2 - 5 kOhms.

Sorry, I should have waited a bit before posting- see my edit. After a little bit of debugging, I got i2c_t3 to see the sensor at 0x68. It's most likely a code issue in the library somewhere...
 
I'm maintaining that library. I wired up a Teensy LC to an MPU-9250 and ran the following code, it works:

Code:
#include "MPU9250.h"

// an MPU9250 object with its I2C address 
// of 0x68 (ADDR to GRND) and on Teensy bus 0
uint8_t Addr = 0x68;
uint8_t I2c = 0;
MPU9250 IMU(Addr, I2c);

float ax, ay, az, gx, gy, gz, hx, hy, hz, t;
int beginStatus;

void setup() {
  // serial to display data
  Serial.begin(115200);

  // start communication with IMU and 
  // set the accelerometer and gyro ranges.
  // ACCELEROMETER 2G 4G 8G 16G
  // GYRO 250DPS 500DPS 1000DPS 2000DPS
  beginStatus = IMU.begin(ACCEL_RANGE_4G,GYRO_RANGE_250DPS);
}

void loop() {
  if(beginStatus < 0) {
    delay(1000);
    Serial.println("IMU initialization unsuccessful");
    Serial.println("Check IMU wiring or try cycling power");
    delay(10000);
  }
  else{
    /* get the individual data sources */
    /* This approach is only recommended if you only
     *  would like the specified data source (i.e. only
     *  want accel data) since multiple data sources
     *  would have a time skew between them.
     */
    // get the accelerometer data (m/s/s)
    IMU.getAccel(&ax, &ay, &az);
  
    // get the gyro data (rad/s)
    IMU.getGyro(&gx, &gy, &gz);
  
    // get the magnetometer data (uT)
    IMU.getMag(&hx, &hy, &hz);
  
    // get the temperature data (C)
    IMU.getTemp(&t);
  
    // print the data
    printData();
  
    // delay a frame
    delay(50);
  
    /* get multiple data sources */
    /* In this approach we get data from multiple data
     *  sources (i.e. both gyro and accel). This is 
     *  the recommended approach since there is no time
     *  skew between sources - they are all synced.
     *  Demonstrated are:
     *  1. getMotion6: accel + gyro
     *  2. getMotion7: accel + gyro + temp
     *  3. getMotion9: accel + gyro + mag
     *  4. getMotion10: accel + gyro + mag + temp
     */
  
     /* getMotion6 */
    // get both the accel (m/s/s) and gyro (rad/s) data
    IMU.getMotion6(&ax, &ay, &az, &gx, &gy, &gz);
  
    // get the magnetometer data (uT)
    IMU.getMag(&hx, &hy, &hz);
  
    // get the temperature data (C)
    IMU.getTemp(&t);
  
    // print the data
    printData();
  
    // delay a frame
    delay(50);
  
    /* getMotion7 */
    // get the accel (m/s/s), gyro (rad/s), and temperature (C) data
    IMU.getMotion7(&ax, &ay, &az, &gx, &gy, &gz, &t);
    
    // get the magnetometer data (uT)
    IMU.getMag(&hx, &hy, &hz);
  
    // print the data
    printData();
  
    // delay a frame
    delay(50);
  
    /* getMotion9 */
    // get the accel (m/s/s), gyro (rad/s), and magnetometer (uT) data
    IMU.getMotion9(&ax, &ay, &az, &gx, &gy, &gz, &hx, &hy, &hz);
  
    // get the temperature data (C)
    IMU.getTemp(&t);
  
    // print the data
    printData();
  
    // delay a frame
    delay(50);
  
    // get the accel (m/s/s), gyro (rad/s), and magnetometer (uT), and temperature (C) data
    IMU.getMotion10(&ax, &ay, &az, &gx, &gy, &gz, &hx, &hy, &hz, &t);
  
    // print the data
    printData();
  
    // delay a frame
    delay(50);
  }
}

void printData(){

  // print the data
  Serial.print(ax,6);
  Serial.print("\t");
  Serial.print(ay,6);
  Serial.print("\t");
  Serial.print(az,6);
  Serial.print("\t");

  Serial.print(gx,6);
  Serial.print("\t");
  Serial.print(gy,6);
  Serial.print("\t");
  Serial.print(gz,6);
  Serial.print("\t");

  Serial.print(hx,6);
  Serial.print("\t");
  Serial.print(hy,6);
  Serial.print("\t");
  Serial.print(hz,6);
  Serial.print("\t");

  Serial.println(t,6);
}

Here is a picture of my Teensy LC setup:
FullSizeRender.jpg

I have 3.3V power from the Teensy LC to VDD, VDDIO, and nCS. GND from the Teensy LC to GND, FSYNC, and AD0. SDA to Teensy LC pin 18 and SCL to Teensy LC pin 19. I'm using 4.7 kOhm pullups from the Teensy 3.3V source.

Arduino v1.8.5 and Teensyduino 1.40 with the master branch from https://github.com/bolderflight/MPU9250.
 
If you try the above and you're still having issues, can you please replace MPU9250.cpp in the library with the attached? Please run this code and post the serial output:

Code:
#include "MPU9250.h"

// an MPU9250 object with its I2C address 
// of 0x68 (ADDR to GRND) and on Teensy bus 0
uint8_t Addr = 0x68;
uint8_t I2c = 0;
MPU9250 IMU(Addr, I2c);

float ax, ay, az, gx, gy, gz, hx, hy, hz, t;
int beginStatus;

void setup() {
  // serial to display data
  Serial.begin(115200);

  // start communication with IMU and 
  // set the accelerometer and gyro ranges.
  // ACCELEROMETER 2G 4G 8G 16G
  // GYRO 250DPS 500DPS 1000DPS 2000DPS
  beginStatus = IMU.begin(ACCEL_RANGE_4G,GYRO_RANGE_250DPS);
}

void loop() {
  if(beginStatus < 0) {
    delay(1000);
    Serial.println("IMU initialization unsuccessful");
    Serial.print("Begin status: ");
    Serial.println(beginStatus);
    while(1){}
  }
  else{
    /* get the individual data sources */
    /* This approach is only recommended if you only
     *  would like the specified data source (i.e. only
     *  want accel data) since multiple data sources
     *  would have a time skew between them.
     */
    // get the accelerometer data (m/s/s)
    IMU.getAccel(&ax, &ay, &az);
  
    // get the gyro data (rad/s)
    IMU.getGyro(&gx, &gy, &gz);
  
    // get the magnetometer data (uT)
    IMU.getMag(&hx, &hy, &hz);
  
    // get the temperature data (C)
    IMU.getTemp(&t);
  
    // print the data
    printData();
  
    // delay a frame
    delay(50);
  
    /* get multiple data sources */
    /* In this approach we get data from multiple data
     *  sources (i.e. both gyro and accel). This is 
     *  the recommended approach since there is no time
     *  skew between sources - they are all synced.
     *  Demonstrated are:
     *  1. getMotion6: accel + gyro
     *  2. getMotion7: accel + gyro + temp
     *  3. getMotion9: accel + gyro + mag
     *  4. getMotion10: accel + gyro + mag + temp
     */
  
     /* getMotion6 */
    // get both the accel (m/s/s) and gyro (rad/s) data
    IMU.getMotion6(&ax, &ay, &az, &gx, &gy, &gz);
  
    // get the magnetometer data (uT)
    IMU.getMag(&hx, &hy, &hz);
  
    // get the temperature data (C)
    IMU.getTemp(&t);
  
    // print the data
    printData();
  
    // delay a frame
    delay(50);
  
    /* getMotion7 */
    // get the accel (m/s/s), gyro (rad/s), and temperature (C) data
    IMU.getMotion7(&ax, &ay, &az, &gx, &gy, &gz, &t);
    
    // get the magnetometer data (uT)
    IMU.getMag(&hx, &hy, &hz);
  
    // print the data
    printData();
  
    // delay a frame
    delay(50);
  
    /* getMotion9 */
    // get the accel (m/s/s), gyro (rad/s), and magnetometer (uT) data
    IMU.getMotion9(&ax, &ay, &az, &gx, &gy, &gz, &hx, &hy, &hz);
  
    // get the temperature data (C)
    IMU.getTemp(&t);
  
    // print the data
    printData();
  
    // delay a frame
    delay(50);
  
    // get the accel (m/s/s), gyro (rad/s), and magnetometer (uT), and temperature (C) data
    IMU.getMotion10(&ax, &ay, &az, &gx, &gy, &gz, &hx, &hy, &hz, &t);
  
    // print the data
    printData();
  
    // delay a frame
    delay(50);
  }
}

void printData(){

  // print the data
  Serial.print(ax,6);
  Serial.print("\t");
  Serial.print(ay,6);
  Serial.print("\t");
  Serial.print(az,6);
  Serial.print("\t");

  Serial.print(gx,6);
  Serial.print("\t");
  Serial.print(gy,6);
  Serial.print("\t");
  Serial.print(gz,6);
  Serial.print("\t");

  Serial.print(hx,6);
  Serial.print("\t");
  Serial.print(hy,6);
  Serial.print("\t");
  Serial.print(hz,6);
  Serial.print("\t");

  Serial.println(t,6);
}
 

Attachments

  • MPU9250.cpp
    29.7 KB · Views: 239
Since several of your tests had 5v going to the MPU, I suspect you may have fried the LC I2C pins. As Michael noted, the LC is NOT 5v tolerant. The pullups on the I2C board would have pulled the LC I2C pins to 5v, maybe there was enough resistance to limit damage?? do you have another LC or Teensy 3*?
 
Since several of your tests had 5v going to the MPU, I suspect you may have fried the LC I2C pins. As Michael noted, the LC is NOT 5v tolerant. The pullups on the I2C board would have pulled the LC I2C pins to 5v, maybe there was enough resistance to limit damage?? do you have another LC or Teensy 3*?

I know this isn't the case, because the MPU-9250 pulls its SCL/SDA lines to 3.3v, as shown in this schematic. Additionally, the Teensy LC does successfully scan and find the address of the sensor.
 
Tried this out, here's the serial output:

Code:
IMU initialization unsuccessful
Begin status: -5

Here's a picture of my wiring (added 4.7k pullups to 3.3v, switched sensor power over to Teensy LC's 3.3v):

Thanks! So, you don't actually have an MPU-9250. The Teensy LC is communicating with your sensor fine, but failing the WHOAMI check. My guess is that the board manufacturer used an MPU-9255 instead of an MPU-9250. The attached MPU9250.cpp code has a patch which allows the MPU-9255 to pass the WHOAMI check, hopefully that works for you.
 

Attachments

  • MPU9250.cpp
    29.8 KB · Views: 208
Thanks! So, you don't actually have an MPU-9250. The Teensy LC is communicating with your sensor fine, but failing the WHOAMI check. My guess is that the board manufacturer used an MPU-9255 instead of an MPU-9250. The attached MPU9250.cpp code has a patch which allows the MPU-9255 to pass the WHOAMI check, hopefully that works for you.

Thank you so much! This worked. I don't think I would have figured this out myself.

Note to self: manufacturer may use a slightly different chip than what is advertised. I think that's why the board is labeled MPU-92/65 instead of MPU-9250 :D
 
Thanks! So, you don't actually have an MPU-9250. The Teensy LC is communicating with your sensor fine, but failing the WHOAMI check. My guess is that the board manufacturer used an MPU-9255 instead of an MPU-9250. The attached MPU9250.cpp code has a patch which allows the MPU-9255 to pass the WHOAMI check, hopefully that works for you.

In your library's readme, it says that "The gyroscope and temperature sensor are sampled at a rate of 32 kHz, the accelerometer at a rate of 4 kHz, and the magnetometer at a rate of 100 Hz." when DLPF is not enabled. Is this assuming you are using the getMotion9 function in the main Arduino loop?

My ultimate goal here is to integrate the gyro readings, and I'm not sure how to do that because I don't know the time between samples...

Code:
void loop() {
  if(beginStatus < 0) {
    delay(1000);
    Serial.println("IMU initialization unsuccessful");
    Serial.println("Check IMU wiring or try cycling power");
    delay(10000);
  }
  

  heading += convertRawGyro(gz)/32000; //add degrees per sample <-- this is the line I'm not sure about...
  //Serial.println(heading);


  IMU.getGyro(&gx, &gy, &gz);
  IMU.getMag(&hx, &hy, &hz);
}

float convertRawGyro(float gRaw) { //radians to degrees conversion
  return gRaw * (180 / 3.1415926535);
}
 
The MPU-9250 is doing a lot internally. By default, if the DLPF is not being used, the MPU-9250 is polling its sensor at the fastest rate that it can and populating registers with that sensor data. When you call any of the get* functions in my library, the Teensy is grabbing the newest data from those registers, scaling the data to engineering units and rotating it so all of the data is referenced to the same coordinate system. So, internally, with the DLPF off the MPU-9250 is updating its gyroscope and temperature sensor at a rate of 32 kHz, the accelerometer at a rate of 4 kHz, and the magnetometer at a rate of 100 Hz and the data returned to the Teensy by any of the get* functions is the latest and greatest when that function was called.

For doing something like integrations, there's a few things that you should consider:
1. Use the setFilt function to set a DLPF bandwidth and SRD. More on this in a second.
2. Use the interrupt pin from the MPU-9250 to drive an interrupt on the Teensy. When you run some data collection in loop, the timing of that data collection is not constant at all, the Teensy is just trying to run it as quickly as possible. Using an interrupt like IntervalTimer is better, since your data collection function will now run at a fixed rate, but the Teensy clock is able to drift from the MPU-9250 clock source, so the MPU-9250 data may not be updated and ready when you call your data collection function and you'll have a little uncertainty in your timing. Attaching an interrupt from the MPU-9250 to the Teensy runs the Teensy at a fixed rate and ensures that new data is ready virtually eliminating any timing uncertainty. See the Interrupt_I2C example and set the SRD to get your desired update frequency.
3. Ideally you know or have a good guess at the frequency range of the dynamics that you're measuring, in that case you can set your SRD to get a frequency of at least four times the frequency of the dynamics, but ideally much higher (10 to 100 times the frequency of the dynamics). If you don't know, then set the SRD to get as high of a frequency as you can while still allowing your function to complete before the interrupt is called again.
4. Be sure to set the DLPF bandwidth to a cutoff frequency of at least half of your sampling frequency or lower to avoid aliasing.
5. You'll want to sample some gyro data in your setup function while the IMU is stationary to estimate the gyro biases and remove them from your measurements. Any errors present that are integrated will quickly corrupt the solution. MEMS gyros have some temperature dependence and always end up with some bias when you start the sensor. So you'll always want to start by estimating and removing those biases.
6. I would read up on Madgwick, Mahony, AHRS, and EKF filters as you go down this rabbit hole...
 
Also, 100 Hz might be the best target frequency for setting the SRD if that works well for the dynamics you're measuring. The magnetometer is only updated at 100 Hz or 10 Hz. 100 Hz is used for SRD's resulting in an update frequency of 100 Hz and above and 10 Hz for SRD's resulting in a frequency below 100 Hz. Setting an SRD for a target frequency of exactly 100 Hz (SRD of 9) gives you a setting where all of the MPU-9250 sensors are being updated at the same rate.
 
The MPU-9250 is doing a lot internally. By default, if the DLPF is not being used, the MPU-9250 is polling its sensor at the fastest rate that it can and populating registers with that sensor data. When you call any of the get* functions in my library, the Teensy is grabbing the newest data from those registers, scaling the data to engineering units and rotating it so all of the data is referenced to the same coordinate system. So, internally, with the DLPF off the MPU-9250 is updating its gyroscope and temperature sensor at a rate of 32 kHz, the accelerometer at a rate of 4 kHz, and the magnetometer at a rate of 100 Hz and the data returned to the Teensy by any of the get* functions is the latest and greatest when that function was called.

For doing something like integrations, there's a few things that you should consider:
1. Use the setFilt function to set a DLPF bandwidth and SRD. More on this in a second.
2. Use the interrupt pin from the MPU-9250 to drive an interrupt on the Teensy. When you run some data collection in loop, the timing of that data collection is not constant at all, the Teensy is just trying to run it as quickly as possible. Using an interrupt like IntervalTimer is better, since your data collection function will now run at a fixed rate, but the Teensy clock is able to drift from the MPU-9250 clock source, so the MPU-9250 data may not be updated and ready when you call your data collection function and you'll have a little uncertainty in your timing. Attaching an interrupt from the MPU-9250 to the Teensy runs the Teensy at a fixed rate and ensures that new data is ready virtually eliminating any timing uncertainty. See the Interrupt_I2C example and set the SRD to get your desired update frequency.
3. Ideally you know or have a good guess at the frequency range of the dynamics that you're measuring, in that case you can set your SRD to get a frequency of at least four times the frequency of the dynamics, but ideally much higher (10 to 100 times the frequency of the dynamics). If you don't know, then set the SRD to get as high of a frequency as you can while still allowing your function to complete before the interrupt is called again.
4. Be sure to set the DLPF bandwidth to a cutoff frequency of at least half of your sampling frequency or lower to avoid aliasing.
5. You'll want to sample some gyro data in your setup function while the IMU is stationary to estimate the gyro biases and remove them from your measurements. Any errors present that are integrated will quickly corrupt the solution. MEMS gyros have some temperature dependence and always end up with some bias when you start the sensor. So you'll always want to start by estimating and removing those biases.
6. I would read up on Madgwick, Mahony, AHRS, and EKF filters as you go down this rabbit hole...

So if I'm wrapping my head around this correctly... SRD parameter is responsible for data output/collection rate (i.e. the matched frequency between Teensy/MPU-9250), and DLPF is responsible for removing noise from measurements? Does this mean that the maximum data output rate is 1000hz (given SRD = 0)? My understanding is that the point of adding the connection between the interrupt pin on the MPU-9250 and a pin on the Teensy is to make sure that the MPU-9250 and the Teensy are on the same page when it comes to timings?

Because I'm intending to run this heading into a PID controller, why wouldn't I want to run the lowest SRD possible (0) and the highest DLPF possible (184hz)?
 
So if I'm wrapping my head around this correctly... SRD parameter is responsible for data output/collection rate (i.e. the matched frequency between Teensy/MPU-9250), and DLPF is responsible for removing noise from measurements? Does this mean that the maximum data output rate is 1000hz (given SRD = 0)? My understanding is that the point of adding the connection between the interrupt pin on the MPU-9250 and a pin on the Teensy is to make sure that the MPU-9250 and the Teensy are on the same page when it comes to timings?

Because I'm intending to run this heading into a PID controller, why wouldn't I want to run the lowest SRD possible (0) and the highest DLPF possible (184hz)?

Your understanding of the DLPF, SRD, and interrupt are generally correct. WRT the update rate, there's a few reasons:
1. The Teensy LC takes about 980 us just to get the data from the MPU9250 over I2C, which doesn't leave much time in a 1000 us frame to do anything else. Your attitude estimation algorithms and control laws will take some time. Running the update rate at a lower frequency gives more time for these algorithms to run.
2. If you're using servos for actuation, I researched a lot of them to find the highest frequency ones available for a structural control research project, and the best have a cutoff frequency around 25 Hz. Most have a cutoff frequency below 10 Hz. Granted, you would like to measure dynamics at a higher frequency than you control, but you can get away with a frequency a lot lower than 1000 Hz if all you can control at is 25 Hz.
3. Again it all depends on the dynamics involved. For example, most aircraft have rigid vehicle dynamics in the 1 Hz range and actuators in the 10 Hz range. Update rates for the IMU are typically around 50 Hz with a DLPF of 20 Hz. There's no reason to run at higher rates for that application.
 
Your understanding of the DLPF, SRD, and interrupt are generally correct. WRT the update rate, there's a few reasons:
1. The Teensy LC takes about 980 us just to get the data from the MPU9250 over I2C, which doesn't leave much time in a 1000 us frame to do anything else. Your attitude estimation algorithms and control laws will take some time. Running the update rate at a lower frequency gives more time for these algorithms to run.
2. If you're using servos for actuation, I researched a lot of them to find the highest frequency ones available for a structural control research project, and the best have a cutoff frequency around 25 Hz. Most have a cutoff frequency below 10 Hz. Granted, you would like to measure dynamics at a higher frequency than you control, but you can get away with a frequency a lot lower than 1000 Hz if all you can control at is 25 Hz.
3. Again it all depends on the dynamics involved. For example, most aircraft have rigid vehicle dynamics in the 1 Hz range and actuators in the 10 Hz range. Update rates for the IMU are typically around 50 Hz with a DLPF of 20 Hz. There's no reason to run at higher rates for that application.

Thanks for the response, I'm learning a lot and having fun too. :)

I *think* I had success getting a Madgwick filter up and running with this in mind. It's only using the gyro/accel, but that should be plenty. I have my SRD set to 4 and DLPF set to 41hz. The last thing I need to do is get rid of that initial gyro error... my outputs only stabilize to usable values after the sensor has been sitting for a while, they just spike a lot when the sensor starts up. Serial print order in that GIF is roll, pitch, heading respectively.

Here's the code I'm currently using:

Code:
#include "MPU9250.h"
#include "MadgwickAHRS.h"

// an MPU9250 object with its I2C address 
// of 0x68 (ADDR to GRND) and on Teensy bus 0
uint8_t addr = 0x68;
uint8_t bus = 0;
MPU9250 IMU(addr, bus);

float ax, ay, az, gx, gy, gz, hx, hy, hz, t;
float aix, aiy, aiz, gix, giy, giz;
int beginStatus, setFiltStatus;
volatile int i = 0;

Madgwick filter;
unsigned long microsPerReading, microsPrevious;
float accelScale, gyroScale;
float roll, pitch, heading;

void setup() {
  // serial to display data
  Serial.begin(115200);

  // start communication with IMU and 
  // set the accelerometer and gyro ranges.
  // ACCELEROMETER 2G 4G 8G 16G
  // GYRO 250DPS 500DPS 1000DPS 2000DPS

  beginStatus = IMU.begin(ACCEL_RANGE_2G,GYRO_RANGE_250DPS);

  if(beginStatus < 0) {
    delay(1000);
    Serial.println("IMU initialization unsuccessful");
    Serial.println("Check IMU wiring or try cycling power");
    while(1){};
  }
 
  setFiltStatus = IMU.setFilt(DLPF_BANDWIDTH_41HZ, 4); //200hz
  if(setFiltStatus < 0) {
    delay(1000);
    Serial.println("Filter initialization unsuccessful");
    while(1){};
  }
  pinMode(2,INPUT);
  attachInterrupt(2, getIMU, RISING);

  filter.begin(200);
  microsPerReading = 1000000 / 200;
  microsPrevious = micros();
}

void loop() {
  
  unsigned long microsNow;
  microsNow = micros();
  if (microsNow - microsPrevious >= microsPerReading) {
    ax = convertRawAcceleration(aix);
    ay = convertRawAcceleration(aiy);
    az = convertRawAcceleration(aiz);
    gx = convertRawGyro(gix);
    gy = convertRawGyro(giy);
    gz = convertRawGyro(giz);

    filter.updateIMU(gx, gy, gz, ax, ay, az);

    roll = filter.getRoll();
    pitch = filter.getPitch();
    heading = filter.getYaw();

    microsPrevious = microsPrevious + microsPerReading;
  }
}

float convertRawGyro(float gRaw) { //converts from radians to degrees
  return (180/3.1415926536) * gRaw;
}

float convertRawAcceleration(float aRaw) { //converts from m/s/s to g
  return 0.101972 * aRaw;
}

void getIMU(){
  // get the accel (m/s/s), gyro (rad/s), and magnetometer (uT), and temperature (C) data
  IMU.getMotion10(&aix, &aiy, &aiz, &gix, &giy, &giz, &hx, &hy, &hz, &t);
  i++;
  // print the results every 10 frames
  if(i > 9){
    //printData();
    Serial.print(roll);
    Serial.print("\t");
    Serial.print(pitch);
    Serial.print("\t");
    Serial.println(heading);

    i = 0;
  }  
}
 
Status
Not open for further replies.
Back
Top