Sample implementation for lsm303 for stm32l4: https://github.com/z49x2vmq/lsm303

It’s not test against other chips but it should work after changing include file in lsm303dlhc.h

#include "stm32l4xx_hal.h" // Change this header for other chips
...

Example

    lsm303dlhc_read_la_b(&hi2c1, recv);
    x = recv[1] << 8 | recv[0];
    y = recv[3] << 8 | recv[2];
    z = recv[5] << 8 | recv[4];

    sprintf(buf, "%5d LA: %6d %6d %6d", count++, x, y, z);

    ...

    lsm303dlhc_read_mf_b(&hi2c1, recv);
    x = recv[0] << 8 | recv[1];
    y = recv[4] << 8 | recv[5];
    z = recv[2] << 8 | recv[3];

    sprintf(buf, ", MF: %6d %6d %6d\r\n", x, y, z);

i2c Sequential Transfer

The datasheet sounds like we have to use sequential transfer. i.e. Master transmit without stop condition and restart condition with direction change for reading.

This can be achieved by following two functions in HAL:

  • HAL_I2C_Master_Sequential_Transmit_IT()
  • HAL_I2C_Master_Sequential_Receive_IT()
...
    // Below call with "I2C_FIRST_FRAME" flag will not generate stop condition when the transfer is done
    ret = HAL_I2C_Master_Sequential_Transmit_IT(i2c, LA_ADDRESS, &reg, 1, I2C_FIRST_FRAME);

    // Because above call is non-blocking we need to wait until TC flag is set.
    while (!__HAL_I2C_GET_FLAG(i2c, I2C_FLAG_TC));

    // Below call will generate restart condition and will generate stop condition 
    // because of "I2C_LAST_FRAME" flag
    ret = HAL_I2C_Master_Sequential_Receive_IT(i2c, LA_ADDRESS, buf, 6, I2C_LAST_FRAME);

    // TC flag cannot be used this time because stop condition will clear the TC flag
    // Just wait until the i2c is ready
    while(i2c->State != HAL_I2C_STATE_READY);
...

However, reading with non-sequential i2c HAL functions also work. This is much simpler to implement. Using blocking calls we don’t have to worry if transfer is done or not.

...
    ret = HAL_I2C_Master_Transmit(i2c, LA_ADDRESS, &reg, 1, 1000);

    ret = HAL_I2C_Master_Receive(i2c, LA_ADDRESS, buf, 6, 1000);
...

Endianness and Axis Ordering

I didn’t pay attention to the data sheet and assumed that the endianness and the axis order is same for both linear acceleration and magnetic field. I was wrong and I wasted few hours.

As below, linear acceleration measurements are in XYZ order and each axis data is in little endian form.

Linear Acceleration Representation
+-----------------+-----------------+-----------------+
|        X        |        Y        |        Z        |
+-----------------+-----------------+-----------------+
| buf[0] | buf[1] | buf[2] | buf[3] | buf[4] | buf[5] |
+--------+--------+--------+--------+--------+--------+
| X_LOW  | X_HIGH | Y_LOW  | Y_HIGH | Z_LOW  | Z_HIGH |
+--------+--------+--------+--------+--------+--------+

On the other hand, magnetic field measurements are in XZY order and each axis data is in big endian form.

Magnetic Field Representation
+-----------------+-----------------+-----------------+
|        X        |        Z        |        Y        |
+-----------------+-----------------+-----------------+
| buf[0] | buf[1] | buf[2] | buf[3] | buf[4] | buf[5] |
+--------+--------+--------+--------+--------+--------+
| X_HIGH | X_LOW  | Z_HIGH | Z_LOW  | Y_HIGH | Y_LOW  |
+--------+--------+--------+--------+--------+--------+

Any practical advantage for the differences? I don’t know but at least for linear acceleration, endianness can be changed by 7th bit of CTRL_REG4_A register in the lsm303 module.