Understanding the LSM303 library code

Hello,

I’m having troubles understanding the LSM303::heading(vector from) procedure used in LSM303 Arduino Library.
First of all, where does the shift and scale formulas come from? I can’t figure it out (the first 3 lines of the procedure:

    // shift and scale
    m.x = (m.x - m_min.x) / (m_max.x - m_min.x) * 2 - 1.0;
    m.y = (m.y - m_min.y) / (m_max.y - m_min.y) * 2 - 1.0;
    m.z = (m.z - m_min.z) / (m_max.z - m_min.z) * 2 - 1.0;

)
Secondly, I have doubts about the vector product used in the code. Shouldn’t vector product of North and Down used in determing the horizontal plane result in vector West, not East ? Additionally, why is the North vector (the vector product of Down and (!)East) computed? Isn’t that the same vector as m vector?

Best regards
Gr4jp3r

Hello,

The code you quoted first subtracts the minimum raw value from the reading, then divides it by the range between the minimum and maximum raw values, which puts the reading into the range of 0 to 1. It then multiplies the reading by 2 and subtracts 1 to put it in the range of -1 to 1.

You are right that the description of the heading code sounds wrong. The vectors a and temp_a, which come from the accelerometer readings, are described in the comments as the “Down” vector, but they actually represent the “Up” vector (the pull of gravity appears as an upward acceleration to the accelerometer). The product of the North and Up vectors produces East. We will probably fix the comments for a future release of the library; thanks for pointing out the error.

The North vector is not the same as the m vector. The m vector comes from the magnetometer reading, and since the Earth’s magnetic field lines are generally not parallel to its surface, m will often point into or out of the ground (this is called magnetic inclination or dip). Because we want to represent the heading of the compass as an angle on a horizontal plane tangential (“parallel”) to the Earth’s surface, we need to find a North vector that is on that plane, and that is the reason for all the cross products. (I am sure there are other mathematically equivalent ways to achieve the same result.)

- Kevin

Hi, thanks for the response, and for the explanations.

I couldn’t find the “minumum\maximum raw reading” definition. Is it a value obtained during calibration ? What does a calibration routine look like? Is it rotating the sensor and collecting the samples at the same time? Why there’s no calibration procedure in the library?

I’m still not sure is the North vector computed correctly.

vector_cross(&m, &temp_a, &E);
\*...*\
vector_cross(&temp_a, &E, &N);

since &m x &temp_a = &E than &temp_a x &E must equal to &m!
(i x j = k; j x k =i ; k x i = j , http://en.wikipedia.org/wiki/Cross_product)

is true only in the case that i,j,k are vectors of unit magnitude and mutually perpendicular, defining a conventional “right handed” 3D system. This is not the case for the experimentally determined vectors mentioned in the previous posts. There are, however, errors in the comments describing the vector operations in the LSM303 code, which can be confusing and should be corrected.

To get the right calibration constants, you should run the “Calibrate” example that comes with the library. This program will print the minimum and maximum readings on each axis as you rotate the LSM303, so you should make sure to point it in as many different directions as you can to make sure each axis is subjected to the whole range of magnetic field strength. Then, you can copy those values into the appropriate place in another program. In the “Heading” example, the calibration offsets are set in lines 12-15:

  // Calibration values. Use the Calibrate example program to get the values for
  // your compass.
  compass.m_min.x = -520; compass.m_min.y = -570; compass.m_min.z = -770;
  compass.m_max.x = +540; compass.m_max.y = +500; compass.m_max.z = 180;

- Kevin

Thank you very much for all the explanations.

Best regards
Gr4jp3r

Kevin,
Something that perplexes me about the calibration code is whether it provides compensation. By compensation, I mean the digital equivalent of adjusting compensating iron or magnets on a conventional compass to correct readings for magnetic influences on it, such as steel hulls and engines on boats.

Other compass software sometimes provides a utility which you use with the compass as you turn the boat with compass attached through 720 degrees. I have the impression that that software writes correction factors directly to the chip.

What am i missing here?

best regards, john ferguson

Hello, John.

Our board is basically just a breakout or carrier board for the bare LSM303 sensor chip. The magnetometer in the LSM303 tends to give readings that are not centered around zero; for example, the readings on one axis might range from -200 to +400. All our Calibrate code does is try to get a minimum and maximum reading on each axis, and that information can then be used by another program to shift and scale the raw readings so they are consistent in magnitude and centered around zero.

This depends on the user to manually copy the results of the Calibrate program into a set of constants in the other program, and the other program to actually use the constants to modify the raw readings. As far as I know, there is no way to actually write these values into the LSM303 to save them and allow it to output calibrated data.

This is a very simple calibration method, and while it does not specifically try to do the type of compensation you describe, I imagine that it could do a reasonable job of correcting for external influences if you do the calibration correctly with the influences present. You might want to look at this app note from ST to get an idea about some more complex calibration methods.

I suspect that the other compass software you mentioned is usually designed to interface with more sophisticated compass modules that have built-in logic, like a microcontroller, that can store calibration data and process the raw sensor readings. You would probably have to build the LSM303 into such a system in order to get similar functionality out of it.

- Kevin

Hi kevin, the compass I had in mind was a honeywell hmc6352 which does indeed do what i referred to and a year or two back was available on a tiny board for use with Arduinos and the like. I realized after getting it that it was not tilt-corrected. I bought an accelerometer, mounted both on the same board and recorded the compass deflection associated with a vast array of tilts. I had maybe 600 combinations. Then I curve-fit the corrections, discovered that the only curve which began to do the job was 9th order, and decided to build a look-up table which would do the correction based on the tilt. Then I thought it would be better to just buy your product. I don’t need high accuracy. the thing is being used to derive magnetic wind direction from an anemometer which gives relative direction - being off 10 or 15 degrees is not a big deal.

So it’s mounted on the radar arch of our boat and when i get the time, I’m going to collect the calibration points while driving the boat all over, and see what I get.

If i can’t get the 10 degrees or so, i’ll go back to the honeywell, compensate it and see what it does. Or maybe mount both - they’re both I2C and it will be interesting to see how they compare.

who knows?

thanks for your thoughts - they are helpful.

john ferguson

Hello,
Thanks to Pololu and Kevin, I have the LSM303D up and running! I used the Arduino code example which was perfect however I would now like to sense the temperature. I believe I have set CTRL5 correctly to E4 to enable the temp sensor but how do I read it? I am Reading…
A: 250 86 -15940 M: 1008 -1477 1162 and I would like to add the temperature.
thanks!

Hello, SensitMarc.

Section 4.2 of the LSM303 datasheet explains how to read the temperature data, and Table 4 on page 11 states that the raw value can be converted to degrees Celsius with a conversion factor of 8 LSB/deg C. If you’re using the latest version of our library, you should be able to read the two registers and combine their values like this:

byte tl = compass.readReg(LSM303::TEMP_OUT_L);
byte th = compass.readReg(LSM303::TEMP_OUT_H);
int temperature_raw = (int16_t)(th << 8 | tl);
float temperature = (float)temperature_raw / 8;

- Kevin

Kevin,
I am using the latest version of your library, thanks! i have added your temp code and I believe I am close :slight_smile: however I am reading values like this "909127968"
Any idea why?
Thanks for the help!

Could you post the code you’re using to set up CTRL5 and print the output? (You could also post your entire program with [code] tags or as an attachment.) I did find a problem with my code above, which I’ve corrected: temperature_raw needs to be cast as a float before being divided. Also, there seems to be an offset involved in converting the raw value to degrees C, since I’m getting values between around 2 and 6, but I don’t see that offset specified in the datasheet. You might have to find it yourself by calibrating with another thermometer or just use the temperature reading by itself as a relative measurement.

- Kevin

Hey Kevin,
I am not sure if the temp sensor is enabled. The result is the same using 64 or E4, see below

// 0x64 = 0b01100100 now 0xE4 = 0b11100100 to enable temp sensor
// M_RES = 11 (high resolution mode); M_ODR = 001 (6.25 Hz ODR)
writeReg(CTRL5, 0xE4);

I updated my code with (float) before temperature. My print code is:
//
char report[80];
int temperature = 0;

void loop()
snprintf(report, sizeof(report), “A: %6d %6d %6d M: %6d %6d %6d Temp:%d”,
compass.a.x, compass.a.y, compass.a.z,
compass.m.x, compass.m.y, compass.m.z, temperature);
Serial.print(report);
//

As for output, I get this:
A:40 25 -1334 M: 947 756 951 Temp:5000

or when I “GET”,

“cmd”: “VarReturn”,
“name”: “temperature”,
“result”: 926490656,
“coreInfo”: {
“last_app”: “”,
“last_heard”: “2014-03-28T15:13:51.351Z”,
“connected”: true,
“deviceID”:xxxxxxxxxxxxxxxxxxx

Thanks!
Marc

It looks like “GET” is something specific to the Spark Core; is that what you’re using?

Here is a complete sketch that worked for me on an Arduino Leonardo:

#include <Wire.h>
#include <LSM303.h>

LSM303 compass;

char report[100];

void setup()
{
  Serial.begin(9600);
  Wire.begin();
  compass.init();
  compass.enableDefault();
  compass.writeReg(LSM303::CTRL5, 0xE4);
}

void loop()
{
  compass.read();

  byte tl = compass.readReg(LSM303::TEMP_OUT_L);
  byte th = compass.readReg(LSM303::TEMP_OUT_H);
  int temperature_raw = (int16_t)(th << 8 | tl);
  float temperature = (float)temperature_raw / 8;

  snprintf(report, sizeof(report), "A: %6d %6d %6d    M: %6d %6d %6d %d",
    compass.a.x, compass.a.y, compass.a.z,
    compass.m.x, compass.m.y, compass.m.z, temperature_raw);
  Serial.println(report);
  Serial.println(temperature);

  delay(100);
}

Note the scope qualifier (LSM303::) before CTRL5; this should be necessary for it to compile. Also, I’m printing temperature_raw (which is an int) with snprintf but using Serial.println() separately to print the temperature (which is a float), since the default Arduino snprintf doesn’t support floats.

If this still doesn’t help, could you post your complete program so I can try running it?

- Kevin

Thanks Kevin!
Yes, Spark Core to get the data onto the web

I now have a temperature reading coming through! As you said, the readings are off and seem to be very sensitive. If I simply blow on the sensor, the readings go up to 65 an the ambient value does not necessarily return to original. I will keep messing with it and I will let you know my findings.

BTW, Polulu supplied me with 3 LSM303 carrier boards :slight_smile:

Thanks for your help!
Marc