Hello!
I have an MPL3115A2 sensor on a PCB and I need help because I find the documentation is unclear and a bit misleading.
The sensor is hooked-up to a PIC24 MCU and I need to use it in altimeter mode. At the moment, just sitting on my desk, it gives me rougly 302m. My location is approximately 45.479056, -75.793359 (+/- 800ft of this location... in that neighborhood). My real location based on Google Earth, is 106m which is about 347ft. Installed a barometer application on my iPhone 6S and it says I'm at 286ft. So both are somewhat close (I'm in my basement and it's 24C). Let's use 100m averaged between both (328ft).
I can talk to the sensor no problem, communication works. But now here's where the confusion lies.
Take these readings that the sensor returns per second (here's after 12 seconds):
MSB: FF -- CSB: 2D -- LSB: B0 -- Calculated: 301.68m
MSB: FF -- CSB: 2D -- LSB: F0 -- Calculated: 301.93m
MSB: FF -- CSB: 2D -- LSB: F0 -- Calculated: 301.93m
MSB: FF -- CSB: 2E -- LSB: 30 -- Calculated: 302.18m
MSB: FF -- CSB: 2E -- LSB: 10 -- Calculated: 302.06m
MSB: FF -- CSB: 2E -- LSB: 40 -- Calculated: 302.25m
MSB: FF -- CSB: 2E -- LSB: A0 -- Calculated: 302.62m
MSB: FF -- CSB: 2E -- LSB: B0 -- Calculated: 302.68m
MSB: FF -- CSB: 2E -- LSB: A0 -- Calculated: 302.62m
MSB: FF -- CSB: 2E -- LSB: E0 -- Calculated: 302.87m
MSB: FF -- CSB: 2F -- LSB: 00 -- Calculated: 303.00m
MSB: FF -- CSB: 2E -- LSB: 60 -- Calculated: 302.37m
Moving the PCB to the max limits my USB cable can reach, returning to center point on the table still doesn't make much sense. All value are always just random swinging like 2m or more. I don't have more than around 1m slack.
In the NXP datasheet <Xtrinsic MPL3115A2 I2C Precision Altimeter> section 7.1.3 page 21, it states the following:
The Altitude data is stored as a signed fractional 20-bit value in meters in Q16.4 format. The OUT_P_MSB and OUT_P_CSB registers contain the integer part in meters and the OUT_P_LSB register contains the fractional part. Left shifting the OUT_P_MSB byte by 24 bits into a 32 variable and doing a logical OR with the OUT_P_CSB byte left shifted 16 bits and a logical OR with the OUT_T_LSB byte left shifted 8 bits gives the altitude in meters times 65536. (note: this is a copy-paste from the original document but there is a typo with the register names in the 'shifting' section using OUT_T_xSB rather than OUT_P_xSB).
So basically, it says to do the following:
Altitude = AltitudeIntegerMSB << 24 | AltitudeIntegerCSB << 16 | AltitudeIntegerLSB << 8;
This will result in 0xFF2DB000 or 4,281,184,256 decimal. This value divided in 65536 = 65,325.6875 which clearly doesn't make any sense.
Alternatively, since the LSB only uses the 4 high bits, right-shifting 0xFF2DB0000 by 4 bits is now 0xFF2DB0 (16,723,376) which, divided in 65536, is 255.17. Much more sense. But yet again, first, the value is in meters therefore says I'm at 255.17m although my phone and Google Earth say I'm roughly 100m from sea level and second, how will this calculation return a negative value if I go lower than sea level?
Then I found application note AN4519 <Data Manipulation and Basic Settings of the MPL3115A2 Command Line Interface Driver Code> and on page 9, it states the following:
5.1 Converting a 20-bit Altitude reading to a signed decimal number
The 20-bit measurement in meters is comprised of a signed integer component and a fractional component. The signed 16-bit integer component located in OUT_P_MSB and OUT_P_CSB. The fraction component is located in bits 7-4 of OUT_P_LSB. Bits 3-0 of OUT_P_LSB are not used. The sign of the result is easy to determine by simply checking if the high byte of the value is greater than 0x7F. If so, then the value is a negative number and needs to be transformed by performing a 2’s complement conversion. This involves executing a 1’s complement (i.e., switch all 1’s to 0’s and all 0’s to 1’s) and followed by adding 1 to the result. To convert the fractional component in OUT_P_LSB, we use the upper byte of the value. Since the resolution is in 0.0625 meters, we can compute the fractional value by using the following table. (table 2). We can then add these values together for each bit that is a 1 and we get the fractional component of the altitude value in fractions of a meter. For example, the value of 0.75 meters will be represented by the following 4-bit value: 1100. So using the table above, you would add 0.5 to 0.25 to get 0.75 meters.
If I apply this method using the same values as in the previous example:
MSB: 0xFF, CSB: 0x2D, LSB: 0xB0 = 0xFF2DB0
This is the result: MSB = 0xFF therefore higher than 0x7F so applying the 2's complement idea explained in the doc:
0xFF all bits reversed = 0x00 + 1 = 0x01 so I now end-up with the following:
MSB: 0x01, CSB: 0x2D, LSB: 0xB0= 0x012DB0
Then what? I bit-wise OR these values according to the Q16.4 format stated in my first quote from the datasheet? Since 0xB0 is the decimal and uses only the 4 high bits, then the integer part is 0x012D which is 301 decimal and the fractional value 0xB0 which is really just 0xB = 0.1875 (based off Table 2) so the end-result is 301.1875. Given that all of these are in meters, it's reading 301.1875 m. This is off again! And again, how will this return some negative altitude if I go below sea level?
Also in this same document at the top of page 5 part of section 3.2 <Active altimeter mode>, the note says the following:
In Altimeter mode, the 20-bit value stored in OUT_P_MSB, OUT_P_CSB and OUT_P_LSB is in meters represented as a signed 16-bit value in OUT_P_MSB and OUT_P_CSB and unsigned fractional value in bits 7-4 of OUT_P_LSB. Bits 3-0 of OUT_P_LSB are unused in this mode.
Given my returned values MSB: 0xFF, CSB: 0x2D, LSB: 0xB00, what is the proper calculation I should be using for this elevation so that I get something close to my actual real altitude of, supposedly, something close to 100m above sea level?
Pseudo code:
// Set altitude mode
Reg_Value = Read 0x26 register
Reg_Value |= 0x80
Write new Reg_Value to 0x26 register
//Set oversampling rate
Reg_Value = Read 0x26 register
Reg_Value |= 0x38
Write new Reg_Value to 0x26 register
// Enable event flags
Write 0x07 to 0x13
// Clear OST bit
Reg_Value = Read 0x26 register
Reg_Value &= 0xFD
Write new Reg_Value to 0x26 register
// Set OST bit
Reg_Value = Read 0x26 register
Reg_Value |= 0x02
Write new Reg_Value to 0x26 register
// Check PDR flag
Read 0x00 register & 0x04 until == 0x00
// Read altitude registers
Read MSB register 0x01 --> Returns 0xFF
Read CSB register 0x02 --> Returns 0x2D
Read LSB register 0x03 --> Returns 0xB0
Been on this problem for 2 days, searching the web and cannot find something giving me an "ah-ha!" moment... please help!
Thanks!
Ben
Hi Thomas,
I'm having issues with the message forum. Let me know if you got my last response ending with <Meanwhile I will re-read the documentation I have on-hand.>
Ben
Hi again Tom,
So I think I finally 'got' it!
I took the device specs for understanding the calculations as these are given numbers:
Minimum: -698m
Maximum: 11775m
Minimum calculation with negative sign removed is 698 then you have to calculate the 2's complement backwards:
698 * 65536 = 0x02BA0000 - 1 = 02B9 FFFF != FD46 0000
Therefore, the left-most byte is FD which, based on AN4519 page 9 section 5.1 Converting a 20-bit Altitude reading to a signed decimal number:
The sign of the result is easy to determine by simply checking if the high byte of the value is greater than
0x7F. If so, then the value is a negative number and needs to be transformed by performing a 2’s
complement conversion. This involves executing a 1’s complement (i.e., switch all 1’s to 0’s and all 0’s to
1’s) and followed by adding 1 to the result.
So if the sensor was to output the following values:
MSB: 0xFD
CSB: 0x46
LSB: 0x00
Then this is the first step: Value = MSB << 24 | CSB << 16 | LSB << 8;
Second step is to evaluate MSB:
if( MSB > 0x7F )
{
Altitude = (!MSB + 1)/65536;
}
Therefore, !FD460000 = 0x02B9FFFF + 1 = 0x02BA0000 / 65536 = 698 and because the MSB value indicates this is a negative altitude (> 0x7F), then add '-' to the value.
Maximum: 11775 * 65536 = 2DFF 0000
With these calculations, now it makes much more sense. If I start the circuit on my basement floor to be 0ft offset, then I walk the circuit on the floor on floor 1, I get close to 10ft then proceed to the floor on the second floor and I get close to 20ft. Having close to 1ft between each floor, then this seems to make perfect sense.
For the 0 offset, when the device starts, whatever altitude its at, I give it a command which grabs that altitude and substracts it going forward with the next values. I'm not interested in my altitude from sea level, I'm interested in a difference in altitude between my current point which is set as 0-ft and a given point a few minutes later.
If you want to add to this, then please go ahead.
Thanks again for your time and effort.
Ben
Hello Ben,
Yes, I think you properly understood it.
Note that the calculated altitude is negative if the measured pressure is higher than 1013.26 hPa, which is the default pressure reference at sea level stored in the BAR_IN_MSB (0x14) and BAR_IN_LSB (0x15) registers.
To convert the raw 20-bit data to a signed decimal number in meters, you need to first check if the number is negative or positive by looking at the sign bit (MSB of OUT_P_MSB). If it is positive, simply convert it to decimal. If it is negative, perform a 2’s complement to decimal conversion.
Example 1:
MSB: 0xFF
CSB: 0xFF
LSB: 0xF0
16-bit integer component = 0xFFFF = 0b1111 1111 1111 1111
1’s complement (inverting the bits) = 0x0000 = 0b0000 0000 0000 0000
Adding one = 0x0001 = 0b0000 0000 0000 0001 = 1
Since the original number was negative (MSB of OUT_P_MSB = 1), the final 16-bit integer component = -1
4-bit fractional component remains unchanged = 0x0F = 0.9375
The final decimal number in meters is -1 + 0.9375 = -0.0625m
Example 2:
MSB: 0xFD
CSB: 0x46
LSB: 0x00
16-bit integer component = 0xFD46 = 0b1111 1101 0100 0110
1’s complement (inverting the bits) = 0x02B9 = 0b0000 0010 1011 1001
Adding one = 0x02BA = 0b0000 0010 1011 1010 = 698
Since the original number was negative (MSB of OUT_P_MSB = 1), the final 16-bit integer component = - 698
Since the 4-bit fractional component is 0, the final decimal number in meters is - 698 as you correctly calculated.
If you are interested in relative measurements (difference in altitude) and not the absolute ones, the MPL3115A2 is a very good candidate for your application. It is intended more for the scenario of going between two floors or from one point to another and measuring this delta, than to be giving exact absolute altitude.
Best regards,
Tomas
Hi Thomas,
Thanks again for your reply.
This brings me to my next question then since you brought it up!
You said "If you are interested in relative measurements (difference in altitude) and not the absolute ones, the MPL3115A2 is a very good candidate for your application. It is intended more for the scenario of going between two floors or from one point to another and measuring this delta, than to be giving exact absolute altitude."
So what approach do you suggest?
Also, is it normal that if I sample, let's say, once a second, I get a good swing between readings like this:
Absolute altitude: 0.0ft (0.00m)
Absolute altitude: 0.8ft (0.25m)
Absolute altitude: -0.4ft (-0.12m)
Absolute altitude: -1.2ft (-0.37m)
Absolute altitude: -1.0ft (-0.31m)
Absolute altitude: -0.4ft (-0.12m)
Absolute altitude: 0.4ft (0.12m)
Absolute altitude: -1.0ft (-0.31m)
Absolute altitude: -0.6ft (-0.18m)
Absolute altitude: -2.2ft (-0.68m)
Absolute altitude: 0.0ft (0.00m)
Absolute altitude: 0.2ft (0.06m)
Absolute altitude: -0.4ft (-0.12m)
Absolute altitude: 0.0ft (0.00m)
The circuit is just sitting there on a table. I'm in my house (basement), ambient temperature is probably around 23C. All doors and windows are closed because it's below freezing outside. What can cause these swings and what approach should I use in order to mitigate this effect and have a more constant reading?
Thanks again!
Benoit
Hi Ben,
Such issue with accuracy or drifting measurements that do not correlate with the weather or ambient pressure changes can be related to a little-known fact about light sensitivity. The sensor die is light sensitive and direct light exposure through the port hole can lead to varied accuracy of pressure measurement. My recommendation is to avoid such exposure to the port during normal operation. For example you can put a small piece of foam over the port to block out light, but still allow the air to reach the sensor. Using this and maximum oversampling (128x – OS[2:0] = 0b111]), you should be able to achieve less varying values.
Best regards,
Tomas
Hello Thomas,
Thanks for the reply! Very appreciated!
Yes I do remember your messages and I did re-read them but I think there's still a few things I'm just not grasping. The equation in your response is also what I use to convert raw values to meters in altimeter mode but your overall response still doesn't quite answer all my questioning unfortunately. Sorry, I'm just trying to make sense of what I am reading through different documents and correlate that with what I am seeing as the output from my sensor. Maybe what I am getting is fine and I'm just not interpreting it correctly because the documentation is confusing me.
As for the 0 offset, I will deal with that later. I think the root problem is the understanding on how to interpret and convert the returned values correctly. I seem to be mis-understanding / mis-interpreting different explanations between documents.
I'll go back to basics. Here's my code:
//----- Initialization()
// Altimeter set to ALTITUDE mode (Register 0x26h, bit 7)
Register = Read_I2C( 0xC0, 0x26 );
Register = Register | 0x80; // Force to altitude mode on bit 7
Write_I2C( 0xC0, 0x26, Register );
delay_ms( 10 );
Write_I2C( 0xC0, 0x13, 0x07 );
delay_ms( 10 );
Write_I2C( 0xC0, 0x26, 0xB9 );
delay_ms( 10 );
//----- ReadAltitude()
// Read altimeter 'busy' flag
while(( Read_I2C( 0xC0, 0x00 ) & 0x08 ) != 0x08 );
// Read registers
MSB = Read_I2C( 0xC0, 0x01 );
delay_ms( 10 );
CSB = Read_I2C( 0xC0, 0x02 );
delay_ms( 10 );
LSB = Read_I2C( 0xC0, 0x03 );
delay_ms( 10 );
printf( "\n\rMSB: %02X, CSB: %02X, LSB: %02X (Decimal: %0.3f)", MSB, CSB, LSB, ((float) (LSB >> 4) * 0.0625 ));
Here's the output for 30 seconds at 1 sample per second:
MSB: FF, CSB: 84, LSB: 80 (Decimal: 0.500)
MSB: FF, CSB: 84, LSB: 50 (Decimal: 0.312)
MSB: FF, CSB: 84, LSB: D0 (Decimal: 0.812)
MSB: FF, CSB: 84, LSB: F0 (Decimal: 0.937)
MSB: FF, CSB: 84, LSB: 90 (Decimal: 0.562)
MSB: FF, CSB: 84, LSB: E0 (Decimal: 0.875)
MSB: FF, CSB: 85, LSB: 40 (Decimal: 0.250)
MSB: FF, CSB: 85, LSB: 30 (Decimal: 0.187)
MSB: FF, CSB: 84, LSB: 90 (Decimal: 0.562)
MSB: FF, CSB: 84, LSB: E0 (Decimal: 0.875)
MSB: FF, CSB: 84, LSB: E0 (Decimal: 0.875)
MSB: FF, CSB: 84, LSB: B0 (Decimal: 0.687)
MSB: FF, CSB: 84, LSB: B0 (Decimal: 0.687)
MSB: FF, CSB: 84, LSB: F0 (Decimal: 0.937)
MSB: FF, CSB: 84, LSB: B0 (Decimal: 0.687)
MSB: FF, CSB: 84, LSB: 60 (Decimal: 0.375)
MSB: FF, CSB: 83, LSB: 20 (Decimal: 0.125)
MSB: FF, CSB: 83, LSB: 70 (Decimal: 0.437)
MSB: FF, CSB: 83, LSB: 20 (Decimal: 0.125)
MSB: FF, CSB: 83, LSB: 50 (Decimal: 0.312)
MSB: FF, CSB: 83, LSB: 80 (Decimal: 0.500)
MSB: FF, CSB: 83, LSB: 60 (Decimal: 0.375)
MSB: FF, CSB: 83, LSB: 50 (Decimal: 0.312)
MSB: FF, CSB: 83, LSB: 40 (Decimal: 0.250)
MSB: FF, CSB: 83, LSB: 40 (Decimal: 0.250)
MSB: FF, CSB: 84, LSB: 40 (Decimal: 0.250)
MSB: FF, CSB: 84, LSB: 00 (Decimal: 0.000)
MSB: FF, CSB: 83, LSB: B0 (Decimal: 0.687)
MSB: FF, CSB: 84, LSB: 10 (Decimal: 0.062)
MSB: FF, CSB: 83, LSB: 70 (Decimal: 0.437)
The decimal portion seems to make sense. The problem I am having a hard time figuring-out is the integer part. I looked at a few NXP documents and I am understanding explanations differently between documents and I think this is what is confusing me.
In the main MPL3115 NXP datasheet page 14 section 6.2, it says "altitude is stored as 20-bit 2’s complement value in meters and fractions of a meter". Then on page 21 section 7.1.3 in the same document, it says "The Altitude data is stored as a signed fractional 20-bit value in meters in Q16.4 format. The OUT_P_MSB and OUT_P_CSB registers contain the integer part in meters and the OUT_P_LSB register contains the fractional part. Left shifting the OUT_P_MSB byte by 24 bits into a 32 variable and doing a logical OR with the OUT_P_CSB byte left shifted 16 bits and a logical OR with the OUT_T_LSB byte left shifted 8 bits gives the altitude in meters times 65536."
Then if you read AN4519 from NXP, page 9 section5.1 Converting a 20-bit Altitude reading to a signed decimal number:
The 20-bit measurement in meters is comprised of a signed integer component and a fractional component. The signed 16-bit integer component located in OUT_P_MSB and OUT_P_CSB. The fraction component is located in bits 7-4 of OUT_P_LSB. Bits 3-0 of OUT_P_LSB are not used. The sign of the result is easy to determine by simply checking if the high byte of the value is greater than 0x7F. If so, then the value is a negative number and needs to be transformed by performing a 2’s complement conversion. This involves executing a 1’s complement (i.e., switch all 1’s to 0’s and all 0’s to 1’s) and followed by adding 1 to the result.
In this NXP document, it now says that if the high-byte value is greater than 0x7F, it's because the value is negative but that is not explicitely explained in the main datasheet or it's a misunderstanding on my part.
So now: how/where do I apply the 2's complement? To the high byte only? Or the entire 16-bit integer portion only? Or the full 24-bit value?
Again using MSB: FF, CSB: 83, LSB: 70:
Question 1) To you, do they make any sense at all when interpreted as individual bytes?
Question 2) If you were to use that last sample from my example, what altitude do you get from this sample and how do you do the calculation to get to that value (e.g. please provide exact steps)?
Does it make more sense as to what I am not understanding?
Thanks again, your time and help is very appreciated!
Sorry for the length and sorry for the confusion. Meanwhile I will re-read the documentation I have on-hand.
Ben
Hello Benoit,
I remember we have already discussed about this topic almost two years ago:
https://community.nxp.com/t5/Sensors/MPL3115A2-help-needed-please/m-p/880795
To convert raw values to meters in altimeter mode I use something like this:
Altitude = (float) ((short) ((OUT_P_MSB << 8 | OUT_P_CSB)) + (float) (OUT_P_LSB >> 4) * 0.0625;
As I mentioned earlier, the MPL3115A2 computes altitude as a function of pressure and temperature using the NASA Standard Atmospheric Model of 1976 as shown in the datasheet, section 9.1.3 and below.
BAR_IN_MSB, BAR_IN_LSB registers allow to modify the default pressure value (101,326 Pa) that internally is used in this equation:
Best regards,
Tomas