3

I use the Adafruit LSM303DLHC sensor. It is made of 2 sensors, an accelerometer and a magnetometer. I am currently writing a driver for this sensor to work with the gobot.io package through i2c interface on a Raspberry Pi 2.

Problem: The accelerometer part works. The magnetometer sensor part doesn't. I can read the magnetic field registers but I get nonsensical values. Those values are updating between each reading cycle, but they are not varying much and doesn't make sense.

Devices used:

  • LSM303DLHC sensor - Datasheet
  • Raspberry Pi 2
  • Arduino Uno

Details about reading the magnetometer output:

LSM303DLHC output 6 bytes representing 3 magnetic field values along the 3 axis. Each value is made of 2 bytes (16bits) for each axis. The output order is the following:

  • X high byte
  • X low byte
  • Z high byte
  • Z low byte
  • Y high byte
  • Y low byte

In order to set up the sensor we write in the following registers in that order:

  • Reset the magnetometer gain: write 0x00 in CRB_REG_M register(0x01)
  • Set the magnetometer gain: Write 0x60 (+/- 2.5 gauss) to CRB_REG_M register(0x01)
  • Set the output data rate: Write 0x05 (30 Hz) in CRA_REG_M register(0x00)
  • Enable continuous mode: Write 0x00 in MR_REG_M register

After setting up the sensor we can read the output. In order to read it, we write to the 1st of the 6 output registers. Then we read the 6 registers output in one sweep, putting the 6 bytes in a buffer.

Test already done

  1. Using Adafruit's library and Arduino platform: OK - output is normal
  2. Using Raspberry Pi 2 + python example: OK - output is normal
  3. Using Raspberry Pi 2 + gobot.io + lsm303DLHC driver: Error
  4. Using io.ReadFull() instead of io.Read() as suggested in the comment: Error

The first 2 tests (1 & 2) tell me that the sensor work. It's not broken. The i2c speed is not the culprit here because the python program (2) works too.

I suspect something is wrong in my code when it comes to properly form int16 values from the bytes. My driver code part to read the sensor output and form the resulting values

This code live in ~/go/src/gobot.io/x/gobot/drivers/i2c/lsm303DLHC.go (a.k.a the driver)

func (d *LSM303Driver) MagneticField() (x, z, y float32, err error) {
    // Write to the first output register to start the reading procedure
    if _, err = d.Magnetometer.connection.Write([]byte{lsm303RegisterMagOutXLSB}); err != nil {
        return 0, 0, 0, err
    }

    // create a buffer to put the output bytes
    measurements := make([]byte, 6)
    // read the 6 output bytes
    if _, err = d.Magnetometer.connection.Read(measurements); err != nil {
        return 0, 0, 0, err
    }

    var rawXh uint8
    var rawXl uint8
    var rawZh uint8
    var rawZl uint8
    var rawYh uint8
    var rawYl uint8

    buf := bytes.NewBuffer(measurements)

    binary.Read(buf, binary.BigEndian, &rawXh)
    binary.Read(buf, binary.BigEndian, &rawXl)
    binary.Read(buf, binary.BigEndian, &rawZh)
    binary.Read(buf, binary.BigEndian, &rawZl)
    binary.Read(buf, binary.BigEndian, &rawYh)
    binary.Read(buf, binary.BigEndian, &rawYl)

    rawX := int16((uint16(rawXh) << 8) | uint16(rawXl))
    rawZ := int16((uint16(rawZh) << 8) | uint16(rawZl))
    rawY := int16((uint16(rawYh) << 8) | uint16(rawYl))

    // Gain is set to +/- 2.5 LSB/Gauss (Least Significant Byte)
    // Datasheet page 38
    // Unit convertion: gaussToMicroTesla = 100
    gainXY, gainZ := d.getGainXYZ()

    x = float32(rawX) / float32(gainXY) * float32(gaussToMicroTesla)
    z = float32(rawZ) / float32(gainZ) * float32(gaussToMicroTesla)
    y = float32(rawY) / float32(gainXY) * float32(gaussToMicroTesla)

    fmt.Printf("DEBUG rawX %016b ---> %v \t\t|\t X %v\n", rawX, rawX, x)
    fmt.Printf("DEBUG rawZ %016b ---> %v \t\t|\t Z %v\n", rawZ, rawZ, z)
    fmt.Printf("DEBUG rawY %016b ---> %v \t\t|\t Y %v\n\n", rawY, rawY, y)

    return x, z, y, nil
}

Here is the output of my little program which use this function:

...
DEBUG rawX 0000001100101011 ---> 811        |    X 121.04478
DEBUG rawZ 0000001011110111 ---> 759        |    Z 126.5
DEBUG rawY 0000001100110000 ---> 816        |    Y 121.79104

DEBUG rawX 0000001100101011 ---> 811        |    X 121.04478
DEBUG rawZ 0000001011110111 ---> 759        |    Z 126.5
DEBUG rawY 0000001100110000 ---> 816        |    Y 121.79104

DEBUG rawX 0000001100100111 ---> 807        |    X 120.44777
DEBUG rawZ 0000001011110110 ---> 758        |    Z 126.33333
DEBUG rawY 0000001100101100 ---> 812        |    Y 121.19403
...

You can see on each line the rawX(Y-Z) binary and normal representation then the definitive value in micro-tesla. In all cases those values are way off. They do not vary much even when I turn the device in every direction.

I looked closely to Adafruits C++ library for arduino and I don't see any major difference. Here is the Adafruit code for reading the magnetometer output:

 void Adafruit_LSM303_Mag_Unified::read()
 {
   // Read the magnetometer
      Wire.beginTransmission((byte)LSM303_ADDRESS_MAG);

  Wire.send(LSM303_REGISTER_MAG_OUT_X_H_M);

   Wire.endTransmission();
   Wire.requestFrom((byte)LSM303_ADDRESS_MAG, (byte)6);

  // Wait around until enough data is available
   while (Wire.available() < 6);

  // Note high before low (different than accel)
    uint8_t xhi = Wire.receive();
    uint8_t xlo = Wire.receive();
    uint8_t zhi = Wire.receive();
    uint8_t zlo = Wire.receive();
    uint8_t yhi = Wire.receive();
    uint8_t ylo = Wire.receive();

  // Shift values to create properly formed integer (low byte first)
  raw.x = (int16_t)(xlo | ((int16_t)xhi << 8));
  raw.y = (int16_t)(ylo | ((int16_t)yhi << 8));
  raw.z = (int16_t)(zlo | ((int16_t)zhi << 8));
}

Am I missing something huge? (I hope so...)

Honestly I spend a crazy amount of time on this problem and I am nowhere. I did learn a lot of interesting things about the linux kernel and i2c protocol, ioctl and more... but I am still not able to make the magnetometer work in golang with gobot.io, even if the accelerometer works...

I thanks in advance those who will spend the time to read me.

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
Jokari
  • 43
  • 3
  • One thing that might cause problems is reading the output with a single `Read()` call: it's not guaranteed it reads 6 bytes (even if no error is returned, especially with slow devices). Instead use [`io.ReadFull()`](https://golang.org/pkg/io/#ReadFull) like this: `_, err = io.ReadFull(d.Magnetometer.connection, measurements)`. Please test this and tell if it fixes your problem. – icza Nov 29 '18 at 15:09
  • I used io.ReadFull() without success. I even check the number of bytes returned by io.ReadFull(). It doesn't change the results. I still have the same behaviour. – Jokari Nov 29 '18 at 16:12
  • You're reading single bytes, there is no meaning of byte order in case of 1 byte (only with multi-byte values). – icza Nov 29 '18 at 16:34
  • The initial steps to start the reading procedure, you only write a single byte value (`lsm303RegisterMagOutXLSB`), while the supposedly equivalent C code also calls `Wire.beginTransmission((byte)LSM303_ADDRESS_MAG);`, not sure what this does, but the C code does more than just sending a single byte. The rest of your code (although far from optimal) should do what you mean it to, so either the error lies in the initial steps, or somewhere else in your code which you didn't post. – icza Nov 29 '18 at 16:42
  • The C code `Wire.beginTransmission((byte)LSM303_ADDRESS_MAG` has its equivalent in the gobot.io package. It's not visible in my code but all the steps to initiate the i2c transfert are managed by the gobot.io interface right to the [ioctl() i2c/SMBUS's call](https://github.com/hybridgroup/gobot/blob/master/sysfs/i2c_device.go) – Jokari Nov 29 '18 at 17:02
  • Are you sure that `lsm303RegisterMagOutXLSB` is the same as `LSM303_REGISTER_MAG_OUT_X_H_M` (0x03)? Have you tried swapping the high and low bytes? The #ifs in the C code seem weird to me. Maybe those where supposed to be swapped? – Peter Nov 29 '18 at 17:08
  • Yes `lsm303RegisterMagOutXLSB = 0x03`. I tried to use 0x03|0x80 adress too. Nothing change. I tried already swapping rawXh and rawXl without much success too. – Jokari Nov 29 '18 at 17:46

0 Answers0