Arduino code for MS5803 pressure sensors

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).

The MS5803 pressure sensor is the little white circle on the green circuit board, just below the writing "SOIC 8PINS".

The MS5803 pressure sensor is the little white circle on the green circuit board, just below the writing “SOIC 8PINS”.

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.

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 in 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 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. 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.

A comparison of calculated pressure values using float-based math or integer-based math with large 64-bit integers. The blue line (integer-based math) is the correct response.

A comparison of calculated pressure values using float-based math or integer-based math with large 64-bit integers. The blue line (integer-based math) is the correct response.

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.

A closeup of one of the shifts in temperature-adjusted pressure that occurs with float-based math. The blue line represents the correct integer-based math solution.

A closeup of one of the shifts in temperature-adjusted pressure that occurs with float-based math. The blue line represents the correct integer-based math solution.

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).

Here the Temperature value and D1 (raw pressure) value are varied simultaneously, showing the presence of spikes in the compensated pressure where variables overflow when using floating-point math on the Arduino (orange/red plane). The blue plane represents the results from integer-based math which avoids rollovers.

Here the Temperature value and D1 (raw pressure) value are varied simultaneously, showing the presence of spikes in the compensated pressure where variables overflow when using floating-point math on the Arduino (orange/red plane). The blue plane represents the results from integer-based math which avoids rollovers.

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.

/* 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){}