Search code examples
cfloating-pointfixed-point

Convert Q18.2 to float


I'm trying to read the pressure value from MPL3115. At chapter 14.3 the ds says:

The pressure data is stored as a 20-bit unsigned integer with a fractional part. The OUT_P_MSB (01h), OUT_P_CSB (02h) and bits 7 to 6 of the OUT_P_LSB (03h) registers contain the integer part in Pascals. Bits 5 to 4 of OUT_P_LSB contain the fractional component. This value is representative as a Q18.2 fixed point format where there are 18 integer bits and two fractional bits.

Here my (testing) code for Microchip XMEGA (GCC):

#define MPL3115_ADDRESS                 0x60
#define MPL3115_REG_PRESSURE_DATA       0x01

bool _ReadRegister(uint8_t address, uint8_t *readBuf, uint8_t readCount)
{
    TWI_MasterWriteRead(&twiMaster, MPL3115_ADDRESS, &address, 1, readCount);
    while (twiMaster.status != TWIM_STATUS_READY);
    memcpy(readBuf, (void *) twiMaster.readData, readCount);
    return true;
}

bool MPL3115_ReadPressure(float *value) {
    uint8_t raw[3];

    _ReadRegister(MPL3115_REG_PRESSURE_DATA, raw, 3);
    uint32_t data = (((uint32_t) raw[0]) << 16) | (((uint32_t) raw[1]) << 8) | raw[2];
    uint32_t q18n2 = data >> 4;

    *value = (double) q18n2 / 4;
    return true;
}

I'm pretty sure the i2c line is working because I can successfully read the temperature from the same chip. I configured it in barometer mode and 128 oversampling (CTRL_REG1 = 0x38). In debug mode I read the following values:

  • raw0 = 0x18
  • raw1 = 0x25
  • raw2 = 0x70
  • data = 1582448

to obtain the pressure in Pascal I have to right shift data of 4 bits:

  • q18n2 = 98908

now to convert the Q18.2 to float I should multiply for 2^-n or divide for 4:

  • value = 24727 Pa

This should be in Pascal, so divide for 100 and get mBar = 247.27 mBar... it's unlikely I have such a pressure here! By the way now should be around 1008 mBar.

Are there any mistakes in my thoughts?


Solution

  • you must to right shift data of 6 bits and then add fractional part (2 bits * 0.25).

    *value = (raw0 << 10) | (raw1 << 2) | (raw2 >> 6);
    *value += 0.25 * ((raw2 >> 4) & 0x03);