Magnometer driving me insane

I’ve been at this for a couple days and I believe my code is solid. I’m using the AltIMU10 with a raspberry pi v2 (ic2 Bus 1). The AltMU10 v4 uses the LSM303D sensor array. I’ve been working on the math quite a bit and I cannot seem to get anything resembling good directional data from the compass sensors. I have no reason to believe the sensor data is bad, I ran calibration and have those values stored. To provide comparisons I placed a cell phone, with the GPS deactivated, in the same position as my sensor to get a reference reading on around where everything should be calculating. My data is all over the place, I get readings that span from 303 deg to 14 deg. A couple of the columns appear to be cumulative where they shouldn’t be. Can anyone offer some insight as to where my readings are going astray? If it is something simple, berate me but please give me an idea of where to look, a hint would be just as helpful as an answer. I can provide the header files I created but they are all definitions defined by the LSM303D datasheet.

#include <iostream>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <bitset>
#include <linux/i2c-dev.h>
#include <fcntl.h>
#include <math.h>
#include "/home/pi/Desktop/currentProject/altIMU10.h"

using namespace std;

int file;
int smoothed_output[6];
float lowpass_val = 0.015f;
void shutdown_sensors();
void initialize_sensors();
void ReadTemp();
void checkHeading();
void RegWrite(uint8_t reg, uint8_t regValue);
void readMag(int *m);
void ReadReg(uint8_t command, uint8_t size, uint8_t *data);
void ReadTilt(uint8_t command, uint8_t size, uint8_t *data);
int selDevice(uint8_t device);


int main()
{
    cout << "Program started. \n" << endl;

    char filename[20] = "/dev/i2c-1";
    int n = 0;


    file = open (filename, O_RDWR);

    if (file<0)
    {
        printf("unable to open I2C Bus ");
        perror("The following error occured");
        return(1);
    }
    else
    {
        cout << "Bus Opened" << endl;
    }

    initialize_sensors();

    // AI and sensor reading loop.

    while (n < 30)
    {
        selDevice(MAG_ADD);
        ReadTemp();
        checkHeading();
        sleep(1);
        //usleep(50000);
        n++;

    }


    // End of AI and sensor loop.


    shutdown_sensors();
    return 0;
}

void initialize_sensors()
{
    printf("Initializing Sensors...\n");

    if (ioctl(file, I2C_SLAVE, MAG_ADD) <0)
    {
        printf("Magnometer failed!\n");
    }
    else
    {
        RegWrite(MAG_CTRL1, 0b01000111);
        RegWrite(MAG_CTRL5, 0b11110000);
        RegWrite(MAG_CTRL7, 0b10000000);
        RegWrite(MAG_CTRL6, 0b00100000);
        RegWrite(MAG_OFFSET_X_L_M,0b01100111);
        RegWrite(MAG_OFFSET_Y_L_M, 0b10111001);
        RegWrite(MAG_OFFSET_Y_H_M, 0b11111111);
        RegWrite(MAG_OFFSET_Z_L_M, 0b01011101);
        printf("Magnometer initialized.\n");
    }

}

void RegWrite(uint8_t reg, uint8_t regValue)
{
    int result =0;
    result = i2c_smbus_write_byte_data(file,reg,regValue);
    if (result == -1)
    {
        printf("Failure writing to register.\n");
    }
}

void ReadReg(uint8_t command, uint8_t size, uint8_t *data)
{
    int result = i2c_smbus_read_i2c_block_data(file,command, size, data);

    if (result != size)
    {
        printf("Reading Failed. \n");
    }
}

void shutdown_sensors()
{
    printf("Shutting down sensors...\n");

    if (ioctl(file, I2C_SLAVE, MAG_ADD) <0)
    {
        printf("Magnometer not shut down!\n");
    }
    else
    {
        RegWrite(MAG_CTRL1, 0b00000111);
        RegWrite(MAG_CTRL5, 0b00011000);
        RegWrite(MAG_CTRL7, 0b00000010);
        printf("Magnometer shut down.\n");
    }


}

int selDevice(uint8_t device)
{
    int result = ioctl(file, I2C_SLAVE, device);
    return result;
}

void ReadTemp()
{
    uint8_t block[2];
    uint16_t temp;
    int display_temp;

    ReadReg(0x80 | MAG_TEMP_OUT_L, sizeof(block), block);

    temp = (((block[0] << 8) | block[1]) >> 4) * .125;
    display_temp = ((temp*9)/5) + 32;

    printf("%d F \t",display_temp);

}

void checkHeading()
{
    int magRaw[6];

    float heading;

    readMag(magRaw);

    int mX = magRaw[0];
    int mY = magRaw[1];
    int mZ = magRaw[2];
    int aX = magRaw[3];
    int aY = magRaw[4];
    int aZ = magRaw[5];

    float xh;
    float yh;
    float ayf;
    float axf;

    ayf = aY/57.0; //convert to radians
    axf = aX/57.0; //convert to radians

    xh = mX*cos(ayf) + mY*sin(axf)-mZ*cos(axf)*sin(ayf);
    yh = mY*cos(axf) + mZ*sin(axf);

    heading = atan2((double)yh,(double)xh) * (180/PI ) -90; //angle in degrees

    if (heading > 0){heading = heading - 360;}

    heading = 360 + heading;

    printf("%7.3f deg\t x %i \t y %i \t z %i \n", heading, mX,mY,mZ);
    //cout.flush();

}

void readMag(int *m)
{
    uint8_t block[6];
    uint8_t accBlock[6];
    int newData[6];
    int x;

    ReadReg(0x80 | MAG_OUT_X_L_M, sizeof(block), block);
    ReadReg(0x80 | MAG_OUT_X_L_A, sizeof(accBlock),accBlock);

    newData[0] = ((int)(block[0] << 8 | block[1]));
    newData[1] = ((int)(block[2] << 8 | block[3]));
    newData[2] = ((int)(block[4] << 8 | block[5]));
    newData[3] = ((int)(accBlock[0] << 8 | accBlock[1]));
    newData[4] = ((int)(accBlock[2] << 8 | accBlock[3]));
    newData[5] = ((int)(accBlock[4] << 8 | accBlock[5]));

    if (smoothed_output[0] == 0 && smoothed_output[1] == 0 && smoothed_output[2] ==0 && smoothed_output[3] == 0 && smoothed_output[4] == 0 && smoothed_output[5] == 0)
    {
         for (int i = 0; i < sizeof(newData); i++)
        {
            smoothed_output[i] = newData[i];
        }
    }
    else
    {
         for (int i = 0; i < sizeof(newData); i++)
        {
            smoothed_output[i] = smoothed_output[i] + lowpass_val * (newData[i] - smoothed_output[i]);
        }
    }

    *m = smoothed_output[0];
    *(m+1) = smoothed_output[1];
    *(m+2) = smoothed_output[2];
    *(m+3) = smoothed_output[3];
    *(m+4) = smoothed_output[4];
    *(m+5) = smoothed_output[5];

    cout << newData[3] << endl;

}

Thank you for any help.

I’d check out the github library for example programming:

https://github.com/pololu/lsm303-arduino

Yup, i’ve been there. My problem, as far as I can see is not my libraries and I think my programming is solid which is why I’m asking for help. I’ve reviewed those and bounced it against my code, heck I used some of those to form my template. I just can’t seem to get a steady reading.

How, exactly, did you do the magnetometer calibration, and where are the results applied?

If you are working with the raw data, there should be an offset and scale factor for each axis. This offline approach to calibration is superior: http://sailboatinstruments.blogspot.com … ation.html

Incidentally, this package seems to be state of the art for open source AHRS software, and works with the ALTIMU-10 https://github.com/richards-tech/RTIMULib

It might not have anything to do with your problem but this bit of code bugs me:

((int)(block[0] << 8 | block[1]));

The problem is with the left shift. It is being applied to a value that is an 8 bit integer (uint8_t). I tried it out on my RPi and the compiler didn’t whine even with -Wall and the results were correct. So the compiler coerced the 8 bit type into a larger size quietly and without telling you.

But you appear to have not been expecting it too. The cast to integer does nothing because the data has already been promoted. A more portable and paranoid version is:

(((int)block[0]) << 8) | block[1]; 

This casts the data into an integer type explicitly before the shift. The bit wise or will then coerce the other value into an integer before performing the operation.

Your low pass filtering of the data is also problematic. While I like the use of a recursive filter and use them frequently myself, the implementation sucks. For example, if smoothed_output = 50 and newdata = 100, there will be no change. But if newdata is 49, smoothed_ouput will change to 49. Hardly what you expect.

My preferred method would be to use fixed point numbers having 16 integer bits and 16 fractional bits. But if you prefer floats, declare smoothed_output to be float. The penalty for using floating point on an RPi is a lot less than for the micro-controllers I usually use.

And your loop indices are wrong. sizeof(newdata) is 24, not 6. What you want is (sizeof(newdata)/sizeof(int)). Or better yet use #define to set the size of the data array. As in:

#define DATASIZE 6
int newdata[DATASIZE];

Your code is mangling other data besides what you expected it too. Consider yourself berated. :slight_smile:

If you are referring to the lines I think you are referring to, the left shift is applying to the 8bit integer because I am assigning a second 8 bits from raw data right behind it and feeding the entire schabang into a 16bit integer. All the raw data is 8 bit blocks of 16 bit two’s compliment raw data. This is my first coding project involving I2c chipsets so I’m winging most of it. The low pass filter was a last ditch attempt to get something resembling workable data out of it. I’ll look at and refine my code using your suggestions.

As for the magnometer calibration I used a seperate package to get my highs and lows and then assigned them in the header file for each axis. Regardless of the calibration the sensory data, while expected to be jittery, shouldn’t be feeding me the range of angles that it is. Thank you for those packages, i’m going to take a look, I have’t seen this one before.