Page 1 of 1

Razor IMU M0 : heading seems off

Posted: Fri Nov 24, 2017 9:34 pm
by jwatte
Running a Razor IMU M0, with the latest downloaded firmware, and Razor library (version 1.4.0) and board support package.

I have configured it to only show heading and time. Here is the config:

Code: Select all

////////////////////////////////
// Default Logging Parameters //
////////////////////////////////
#define ENABLE_TIME_LOG       true
#define ENABLE_CALCULATED_LOG true
#define ENABLE_ACCEL_LOG      false
#define ENABLE_GYRO_LOG       false
#define ENABLE_MAG_LOG        false
#define ENABLE_QUAT_LOG       false
#define ENABLE_EULER_LOG      false
#define ENABLE_HEADING_LOG    true

////////////////////////////////////////
// Enable Non-Volatile Memory Storage //
////////////////////////////////////////
// If defined, FlashStorage library must be installed
#define ENABLE_NVRAM_STORAGE

////////////////////////
// Serial Port Config //
////////////////////////
#define ENABLE_UART_LOGGING true
// Select the Serial port to log to. Either SERIAL_PORT_USBVIRTUAL
// or LOG_PORT SERIAL_PORT_HARDWARE (SerialUSB or Serial1)
#define LOG_PORT SERIAL_PORT_USBVIRTUAL
#define SERIAL_BAUD_RATE 921600 // Serial port baud

////////////////
// LED Config //
////////////////
#define HW_LED_PIN 13        // LED attached to pin 13
#define UART_BLINK_RATE 500 // Blink rate when only UART logging

/////////////////////////
// IMU Default Configs //
/////////////////////////
// Note: Some of these params can be overwritten using serial
//  commands. These are just defaults on initial programming
#define DMP_SAMPLE_RATE    100 // Logging/DMP sample rate(4-200 Hz)
#define IMU_COMPASS_SAMPLE_RATE 100 // Compass sample rate (4-100 Hz)
#define IMU_AG_SAMPLE_RATE 100 // Accel/gyro sample rate Must be between 4Hz and 1kHz
#define IMU_GYRO_FSR       2000 // Gyro full-scale range (250, 500, 1000, or 2000)
#define IMU_ACCEL_FSR      2 // Accel full-scale range (2, 4, 8, or 16)
#define IMU_AG_LPF         5
// Accel/Gyro LPF corner frequency (5, 10, 20, 42, 98, or 188 Hz)
#define ENABLE_GYRO_CALIBRATION true

///////////////////////
// SD Logging Config //
///////////////////////
#define ENABLE_SD_LOGGING true // Default SD logging (can be changed via serial menu)
#define LOG_FILE_INDEX_MAX 999 // Max number of "logXXX.txt" files
#define LOG_FILE_PREFIX "log"  // Prefix name for log files
#define LOG_FILE_SUFFIX "txt"  // Suffix name for log files
#define SD_MAX_FILE_SIZE 5000000 // 5MB max file size, increment to next file before surpassing
#define SD_LOG_WRITE_BUFFER_SIZE 1024 // Experimentally tested to produce 100Hz logs

/////////////////////
// Serial Commands //
/////////////////////
#define PAUSE_LOGGING     ' ' // Space - Pause SD/UART logging
#define ENABLE_TIME       't' // Enable/disable time log (milliseconds)
#define ENABLE_ACCEL      'a' // Enable/disable accelerometer log (ax, ay, az)
#define ENABLE_GYRO       'g' // Enable/disable gyroscope log (gx, gy, gz)
#define ENABLE_COMPASS    'm' // Enable/disable magnetometer log (mx, my, mz)
#define ENABLE_CALC       'c' // Enable/disable calculated values
#define ENABLE_QUAT       'q' // Enable/disable quaternion logging (qw, qx, qy, qz)
#define ENABLE_EULER      'e' // Enable/disable estimated euler angles (roll, pitch, yaw)
#define ENABLE_HEADING    'h' // Enable/disable estimated heading logging
#define SET_LOG_RATE      'r' // Adjust logging rate from 1-200 Hz (in 10 Hz increments)
#define SET_ACCEL_FSR     'A' // Set accelerometer FSR (2, 4, 8, 16g)
#define SET_GYRO_FSR      'G' // Set gyroscope FSR (250, 500, 1000, 2000 dps)
//#define ENABLE_SD_LOGGING 's' // Enable/disable SD-card logging

//////////////////////////
// Hardware Definitions //
//////////////////////////
// Danger - don't change unless using a different platform
#define MPU9250_INT_PIN 4
#define SD_CHIP_SELECT_PIN 38
#define MPU9250_INT_ACTIVE LOW

No matter how I turn the IMU, it only swings about +/- 20 degrees. It doesn't matter much which axis I keep up; for one axis, it swings only 10 degrees; for the others more like 20. (Some of that is probably me not being 100% stable in my hands)

I start up the IMU when it's in free air, at least 2 feet away from anything that could have magnets (speakers, computers, motors, etc.)

How can I make this IMU show an accurate compass heading? What am I missing?

Re: Razor IMU M0 : heading seems off

Posted: Fri Nov 24, 2017 10:03 pm
by jwatte
Oh, ouch, I just read the DMP library code.

First, it is written so it sometimes returns values between -180 and 360, because of a faulty three-way if.
Second, it doesn't have any affordance for calibrating the magnetometer. It assumes the magnetometer readings will be centered on zero, but no such luck in my case.
Adding code that calculates the minimum and maximum values seen from the X and Y axes, and then subtracting the average, makes the atan2() call much happier.
The values returned from the magnetometer are kind-of smallish, too, with a swing of about 40 units for a full circle. What does the data sheet for the magnetometer say? I'm not particularly far north (San Francisco area) so the earth's magnetic field shouldn't be pointing very steeply down ... (this is otherwise a problem when you're "pole circle" northern.)

Also, I would have assumed that the "DMP" part of the 9250 would have actually done things like calibration and integration for me, but I guess not?

Re: Razor IMU M0 : heading seems off

Posted: Sun Nov 26, 2017 10:02 am
by jremington
I don't use the MPU9250, but in general, magnetometers need to be calibrated to be useful. This involves at the minimum finding three offsets (one for each axis). Better to include two scale factors so that all axes are on a common scale.

Useful websites, among many describing calibration procedures:
http://sailboatinstruments.blogspot.com ... ation.html
https://edwardmallon.wordpress.com/2015 ... r-arduino/
https://github.com/kriswiner/MPU6050/wi ... alibration

Re: Razor IMU M0 : heading seems off

Posted: Sun Nov 26, 2017 10:51 am
by jremington
Added: the most sophisticated approach to on-line magnetometer calibration for Arduino is probably this series of blog posts that show how to implement the full sphere-fitting algorithm on an Uno. It ends with Uno code that fits in around 20K bytes. I haven't tried it but plan to soon.

https://chionophilous.wordpress.com/201 ... ng-1-of-3/

Re: Razor IMU M0 : heading seems off

Posted: Mon Nov 27, 2017 3:30 pm
by jwatte
I don't use the MPU9250, but in general, magnetometers need to be calibrated to be useful
the most sophisticated approach to on-line magnetometer calibration for Arduino is probably this series
I understand that you're trying to help. However, that's not what my concern is here.
I paid $50 for an Arm Cortex M0 with an integrated IMU, where the ARM board is billed as doing motion processing, and the IMU is billed as having on-board motion processing.

The three-way if that tries (unsuccessfully) to wrap the compass direction to a 360 degree range tells me that this code is unlikely to be written by someone who knows what they're doing and has any kind of experience in IMU processing. I had hoped for better.

What I actually got was, I feel, much less than that. There's a basic sketch that just reads data from the IMU and spits it out. The IMU doesn't even seem to make use of the magnetometer at all, which is super sad, because a Kalman filter with gyro + magnetometer would be super great!
I don't understand why the default software loaded onto this thing doesn't already do that -- getting a thing that works off the bat would be the main reason to get a $50 unit, and not just wire a separate 9DOF IMU to a Teensy LC for half the price.

Re: Razor IMU M0 : heading seems off

Posted: Tue Nov 28, 2017 8:32 pm
by jwatte
Does anyone have a sketch that actually makes use of the hardware that is on this board?
I had really hoped to save a couple of days of tuning my own integrators/filters :-(

Code: Select all

	if (heading > PI) heading -= (2 * PI);
	else if (heading < -PI) heading += (2 * PI);
	else if (heading < 0) heading += 2 * PI;
If the input value is greater than PI, this will return a value in -180 .. 0.
If the input value is between -2PI and PI, this will return a value in 0 .. 360.
Also, why is the second if() statement there? It's completely redundant with the third if() statement.

Code: Select all

float MPU9250_DMP::calcAccel(int axis)                                                                                  
{                                                                                                                       
    return (float) axis / (float) _aSense;                                                                              
}                                                                                                                       
                                                                                                                        
float MPU9250_DMP::calcGyro(int axis)                                                                                   
{                                                                                                                       
    return (float) axis / (float) _gSense;                                                                              
}                                                                                                                       
                                                                                                                        
float MPU9250_DMP::calcMag(int axis)                                                                                    
{                                                                                                                       
    return (float) axis / (float) _mSense;                                                                              
}                                                                                                                       
                                                                                                                        
float MPU9250_DMP::calcQuat(long axis)                                                                                  
{                                                                                                                       
    return qToFloat(axis, 30);                                                                                          
}                                                                                                                       
You know what's much faster than dividing by a variable on a MCU without floating point hardware (and even on CPUs with floating point hardware)?
Multiplying by a reciprocal.
These scaling factors are set once but used many times; do the division there if needed!

Code: Select all

                                                                                                              
float MPU9250_DMP::qToFloat(long number, unsigned char q)                                                               
{                                                                                                                       
    unsigned long mask;                                                                                                 
    for (int i=0; i<q; i++)                                                                                             
    {                                                                                                                   
        mask |= (1<<i);                                                                                                 
    }                                                                                                                   
    return (number >> q) + ((number & mask) / (float) (2<<(q-1)));                                                      
}                                                                                                                       
You know what's faster than a loop to generate a bit mask? Masking it left by one extra, and then subtracting one.

Re: Razor IMU M0 : heading seems off

Posted: Wed Nov 29, 2017 9:10 am
by jremington
getting a thing that works off the bat would be the main reason to get a $50 unit, and not just wire a separate 9DOF IMU to a Teensy LC for half the price.
Buy the BNO055 for $35, and it WILL work quite well "off the bat", using a built in Kalman filter. The calibration process is performed upon startup and a bit of a mystery, but can be skipped by preloading the proper offsets. https://learn.adafruit.com/adafruit-bno ... r/overview