I have recently been developing a library for the Measurement Specialties MS5803 line of digital pressure sensors. These sensors are available in several different pressure ranges from 1 to 30 bar, they are submersible if installed in a proper housing, they communicate via I2C or SPI, and they cost around US$35. It’s fairly straightforward to interface the MS5803 with a microcontroller like the Arduino that can also communicate via the I2C or SPI protocols. They are a surface-mount style chip, meant to solder onto a 1.27mm pitch SOIC-8 layout (like the green board I used in the picture below).
However, usage with an 8-bit microcontroller like the Arduino is complicated by the fact that the temperature compensation and pressure calculation necessary for an accurate reading requires some care. As the MS5803 datasheet outlines, several of the intermediate calcuations necessitate the use of large 64-bit integers (a ‘long long’ in Arduino parlance), rather than the standard int or float values typically used for most operations. There are libraries floating around for the MS5803 that carry out all of the calculations as float values, but this causes strange pressure readings due to overflows. This thread on the Arduino forums discusses the trials and tribulations of getting the math right for these sensors.
My libraries, available on GitHub, attempt to do all of the pressure compensation calculations using the correct integer math. The libraries are listed below, and please note that you must use the library specific to your model of pressure sensor, since the internal math differs between models. These libraries only work via I2C communication (using the built-in Arduino Wire library). Place the collection of files in your Arduino/libraries directory. When you open a new Arduino session, you should find an example sketch in the File>Examples>MS5803 menu.
https://github.com/millerlp/MS5803_01
https://github.com/millerlp/MS5803_02
https://github.com/millerlp/MS5803_05
https://github.com/millerlp/MS5803_14
https://github.com/millerlp/MS5803_30
To illustrate the issues with variable rollover, I wrote a Arduino sketch that implements two methods for calculating the pressure from a MS5803, one using float variables, the other using 64-bit integer math. When you query the MS5803 to make a temperature pressure reading, it returns two 32-bit values, D1 and D2. D2 represents the temperature, while D1 represents a raw pressure value that must be temperature-compensated in your program (or using the library I wrote). As the temperature varies, there are values of D2 where a float-based math process will rollover values (for a float, suddenly switching from 3.4028235e38 to -3.4028235e38), and this causes a sudden change in the pressure value. It also subtly changes the final temperature value (not shown), but this effect is small, on the order of 0.01 to 0.04 Celsius across the normal temperature range of 0 to 30C. The pressure changes at the rollover points are large though, around 100mbar, which is equivalent to a roughly 800 meter change in elevation in air. Also note that the baseline pressure diverges between the float-based and integer-based solutions as you move away from the 20C range, so that the pressure error will grow as you move to warmer or colder climates.
The second image is a closeup of the first spike to the left of the 20C temperature range. Again, around 20C the two methods should be equivalent, but once the temperature shifts far enough away from 20C, you hit one of the overflow points at which the adjusted pressure is shifted from the “correct” reading produced with 64-bit integer math. At 17C and 998mbar you can see that the offset is only around 3mbar, while at 5C and 998mbar that difference increases to ~9mbar with the floating point errors.
The third image is a 3D representation of the same data, showing that the rollover issues are persistent across a range of D1 values (changing raw pressure), and that they increase at higher pressures (the blue and orange planes are a bit farther apart at higher values of D1, raw pressure). The orange/red plane with the ridges is the float-based math solution, while the blue plane is the integer-based math solution. If you are under higher pressure underwater, let’s say 1300mbar absolute (roughly 3m/9.8ft depth), the deviation increases so that at 5C + 1300mbar the deviation is 20mbar (~20cm freshwater).
The code below is my Arduino sketch to compare the float vs. integer math methods. The results from this were used to generate the data for the figures above.
[code lang="C"] /* This sketch compares two methods of calculating pressure and temperature from a MS5803_14BA pressure sensor. The Float method uses float variables to make calculations, while the Integer method uses 64-bit integer math wherever possible to better follow the method outlined in the MS5803 datasheet. This version outputs comma-separated values to the terminal that could be captured and analyzed afterwards. It steps through values of D2 to see where overflows happen. */ // Float method variables unsigned long D1 = 4311550; unsigned long D2 = 8387300; const unsigned int C1 = 46546; const unsigned int C2 = 42845; const unsigned int C3 = 29751; const unsigned int C4 = 29457; const unsigned int C5 = 32745; const unsigned int C6 = 29059; static long dT = 0; static float TEMP = 0; static float Offset = 0; static float Sensitivity = 0; static float T2 = 0; static float OFF2 = 0; static float Sens2 = 0; float mbar = 0; // Integer method variables uint32_t D1n = 4311550; uint32_t D2n = 8387300; const uint16_t C1n = 46546; const uint16_t C2n = 42845; const uint16_t C3n = 29751; const uint16_t C4n = 29457; const uint16_t C5n = 32745; const uint16_t C6n = 29059; static int32_t dTn = 0; static int32_t TEMPn = 0; static int64_t Offsetn = 0; static int64_t Sensitivityn = 0; static int64_t T2n = 0; static int64_t OFF2n = 0; static int64_t Sens2n = 0; int32_t mbarInt = 0; float mbarn = 0; void setup(void){ Serial.begin(115200); Serial.println("Starting up..."); delay(400); // Output a header row Serial.print("D1,D2,Orig.dT,New.dT,Float.TEMP,Int.TEMP,"); Serial.print("Float.T2,Int.T2,Float.OFF2,Int.OFF2,Float.Sens2,Int.Sens2,"); Serial.print("Float.Raw.Offset,Int.Raw.Offset,Float.Raw.Sens,Int.Raw.Sens,"); Serial.print("Float.Adj.Temp,Int.Adj.TEMP,Float.Adj.Offset,Int.Adj.Offset,"); Serial.print("Float.Adj.Sens,Int.Adj.Sens,"); Serial.println("Float.Adj.Press,Int.Adj.Press,Float.Adj.Temp,Int.Adj.Temp"); // You may want to remove this outer j loop, as it takes a long time // to get through all the various D1 values for (uint32_t j = 4300000; j < 4500000; j = j + 1000){ D1 = j; D1n = j; //-------------------------------------------------------- // Step through D2 values. A value of 7805000 is ~-1C, // a value of 8385000 is ~20C, and a value of 8670000 is // ~30C. The changing temperature will trigger variable // overflows at certain points. for (uint32_t i = 7805000; i < 8670000;i = i + 1000){ D2 = i; D2n = i; Serial.print(D1); Serial.print(","); Serial.print(D2); Serial.print(","); // Float version dT = D2 - (C5 * pow(2,8)); Serial.print(dT); // original method dT Serial.print(","); // Integer version dTn = (int32_t)D2n - ((int32_t)C5n * 256); Serial.print(dTn); // new method dTn Serial.print(","); //-------------------- // Float version float TEMP = 2000 + ((float)dT * C6) / pow(2,23); Serial.print(TEMP); Serial.print(","); // Integer version TEMPn = 2000 + ((int64_t)dTn * C6n) / 8388608LL; TEMPn = (int32_t)TEMPn; // recast as int32_t just in case Serial.print(TEMPn); Serial.print(","); //------------------------------------------ // Float version if (TEMP < 2000.0) { T2 = 3 * pow((float)dT,2) / pow(2,33); OFF2 = 3 * pow((TEMP - 2000),2) / 2; Sens2 = 5 * pow((TEMP-2000),2) / pow(2,3); } else { T2 = 7 * pow((float)dT,2) / pow(2,37); OFF2 = 1 * pow((TEMP-2000),2) / pow(2,4); Sens2 = 0; } if (TEMP < -1500){ OFF2 = OFF2 + 7 * pow(TEMP + 1500,2); Sens2 = Sens2 + 4 * pow(TEMP+1500,2); } // Integer version if (TEMP < 2000){ T2n = 3 * ((uint64_t)dTn * dTn) / 8589934592ULL; T2n = (int32_t)T2n; OFF2n = 3 * ((TEMPn-2000) * (TEMPn-2000)) / 2; Sens2n = 5 * ((TEMPn-2000) * (TEMPn - 2000)) / 8; } else { T2n = 7 * (((uint64_t)dTn * dTn) / (uint64_t)1<<37); T2n = (int32_t)T2n; OFF2n = 1 * ((TEMPn-2000) * (TEMPn-2000)) / 16; Sens2n = 0; } if (TEMPn < -1500) { OFF2n = OFF2n + 7 * ((TEMPn+1500)*(TEMPn+1500)); Sens2n = Sens2n + 4 * ((TEMPn+1500)*(TEMPn+1500)); } Serial.print(T2); // Float method T2 Serial.print(","); Serial.print((int32_t)T2n); // Integer method T2n Serial.print(","); Serial.print(OFF2); // Float method OFF2 Serial.print(","); char buffer[64]; // OFF2n and Sens2n are 64-bit values and need to be // printed specially sprintf(buffer, "%0ld", OFF2n/1000000L); // Integer method OFF2n Serial.print(buffer); sprintf(buffer, "%0ld", OFF2n%1000000L); Serial.print(buffer); Serial.print(","); Serial.print(Sens2); // Float method Sens2 Serial.print(","); // Serial.print("new Sens2 = "); sprintf(buffer, "%0ld", Sens2n/1000000L); // Integer method Sens2n Serial.print(buffer); sprintf(buffer, "%0ld", Sens2n%1000000L); Serial.print(buffer); Serial.print(","); //----------------------------------------- // Initial Offset and Sensitivity // Float method Offset = C2 * pow(2,16) + (C4 * dT) / pow(2,7); Sensitivity = C1 * pow(2,15) + (C3 * dT) / pow(2,8); // Integer method Offsetn = (int64_t)C2n * 65536 + (C4n * (int64_t)dTn)/128; Sensitivityn = (int64_t)C1n * 32768 + (C3n * (int64_t)dTn)/256; Serial.print(Offset); // Float method Offset Serial.print(","); sprintf(buffer, "%0ld", Offsetn/1000000L); // Integer method Offsetn Serial.print(buffer); sprintf(buffer, "%0ld", Offsetn%1000000L); Serial.print(buffer); Serial.print(","); Serial.print(Sensitivity); // Float method Sensitivity Serial.print(","); sprintf(buffer, "%0ld", Sensitivityn/1000000L); // Integer method Sensitivityn Serial.print(buffer); sprintf(buffer, "%0ld", Sensitivityn%1000000L); Serial.print(buffer); Serial.print(","); //----------------------------- // Adjust TEMP, Offset, Sensitivity // Float method TEMP = TEMP - T2; Offset = Offset - OFF2; Sensitivity = Sensitivity - Sens2; // Integer method TEMPn = TEMPn - T2n; Offsetn = Offsetn - OFF2n; Sensitivityn = Sensitivityn - Sens2n; Serial.print(TEMP); // Float method TEMP Serial.print(","); Serial.print(TEMPn); // Integer method TEMP Serial.print(","); Serial.print(Offset); // Float method Offset Serial.print(","); sprintf(buffer, "%0ld", Offsetn/1000000L); // Integer method Offsetn Serial.print(buffer); sprintf(buffer, "%0ld", Offsetn%1000000L); Serial.print(buffer); Serial.print(","); // Serial.print("Adjusted new Sensitivity = "); Serial.print(Sensitivity); // Float method Sensitivity Serial.print(","); sprintf(buffer, "%0ld", Sensitivityn/1000000L); // Integer method Sensitivityn Serial.print(buffer); sprintf(buffer, "%0ld", Sensitivityn%1000000L); Serial.print(buffer); Serial.print(","); //---------------------------------------- // Final pressure calculation // Float method mbar = ((D1 * Sensitivity) / pow(2,21) - Offset) / pow(2,15) / 10; Serial.print(mbar); Serial.print(","); // Integer method mbarInt = ((D1n * Sensitivityn) / 2097152 - Offsetn) / 32768; mbarn = (float)mbarInt / 10; Serial.print(mbarn); // Integer method pressure (mbar) Serial.print(","); Serial.print(TEMP/100); // Float method final temperature Serial.print(","); Serial.print((float)TEMPn/100); // integer method final temperature Serial.println(); } } } void loop(void){} [/code]