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
- Using Adafruit's library and Arduino platform: OK - output is normal
- Using Raspberry Pi 2 + python example: OK - output is normal
- Using Raspberry Pi 2 + gobot.io + lsm303DLHC driver: Error
- 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.