Having Difficulty With Accurate Readings Below 20 Celsius with MS5607 Altimeter

Hello, this is my first post. I have read and read and read all over trying to find a fix for this, but I am unable to. My setup is an Arduino Mega 2560 and an Altimeter (MS5607). The code works absolutely fine whenever it is above 20 Celsius. However, when it reaches below 20 Celsius, My readings get extremely whacky. I was wondering if someone could have a look at my code and point me in the correct direction. Very much appreciated, thank you.

Using i2c. Also using IntersemaBaro.h

#include <Wire.h>
#define ADDRESS 0x76 //0x77
#include "IntersemaBaro.h"
Intersema::BaroPressure_MS5607B baro(true);

uint32_t D1 = 0;
uint32_t D2 = 0;
int64_t dT = 0;
int32_t TEMP = 0;
int32_t t_mod;
int64_t OFF = 0; 
int64_t SENS = 0; 
uint16_t C[7];

float Temperature;

void setup() {
   baro.init();
// Disable internal pullups, 10Kohms are on the breakout
 PORTC |= (1 << 4);
 PORTC |= (1 << 5);

  Wire.begin();
  Serial.begin(9600); //9600 changed 'cos of timing?
  delay(100);
  initial(ADDRESS);

}

void loop()
{
  
  D2 = getVal(ADDRESS, 0x58);// Temperature raw

  dT   = D2 - ((uint32_t)C[5] << 8);    
  OFF  = ((int64_t)C[2] << 16) + ((dT * C[4]) >> 7);
  SENS = ((int32_t)C[1] << 15) + ((dT * C[3]) >> 8);
  TEMP = (int64_t)dT * (int64_t)C[6] / 8388608 + 2000;


    
  if(TEMP < 2000) // if temperature lower than 20 Celsius 
  {
    int32_t T1    = 0;
    int64_t OFF1  = 0;
    int64_t SENS1 = 0;
    
    t_mod = (TEMP - 2000)*(TEMP - 2000);
    T1 = (dT*dT)>>31;
    OFF1=(61*t_mod)>>4;
    SENS1=2*t_mod;
    
  }

  Temperature = (float)TEMP / 100; 
  Serial.print("Actual TEMP= ");
  Serial.print(Temperature);
  Serial.println();
  
  int alt = baro.getHeightCentiMeters();
  Serial.print("Feet: ");
  Serial.println((float)(alt) / 30.48);
  Serial.print(" \n");
  delay(1000);
}

long getVal(int address, byte code)
{
  unsigned long ret = 0;
  Wire.beginTransmission(address);
  Wire.write(code);
  Wire.endTransmission();
  delay(10);
  
  // start read sequence
  Wire.beginTransmission(address);
  Wire.write((byte) 0x00);
  Wire.endTransmission();
  Wire.beginTransmission(address);
  Wire.requestFrom(address, (int)3);
  if (Wire.available() >= 3)
  {
    ret = Wire.read() * (unsigned long)65536 + Wire.read() * (unsigned long)256 + Wire.read();
  }
  else {
    ret = -1;
  }
  Wire.endTransmission();
  return ret;
}

void initial(uint8_t address)
{

  Serial.println();
  Wire.beginTransmission(address);
  Wire.endTransmission();
  delay(10);


  for (int i=0; i<6  ; i++) {

    Wire.beginTransmission(address);
    Wire.write(0xA2 + (i * 2));
    Wire.endTransmission();

    Wire.beginTransmission(address);
    Wire.requestFrom(address, (uint8_t) 6);
    delay(1);
    if(Wire.available())
    {
       C[i+1] = Wire.read() << 8 | Wire.read();
    }
    else {
      Serial.println("Error reading PROM 1"); // error reading the PROM or communicating with the device
    }
    Serial.println(C[i+1]);
  }
  Serial.println();
// Disable internal pullups, 10Kohms are on the breakout
 PORTC |= (1 << 4);
 PORTC |= (1 << 5);

You got something against a portable, one time call to digitalWrite()? OK, two times.

  delay(100);

Give me a break. delay() and direct port manipulation do not go in the same sketch.

Is it the raw ready that is “whacky”, or the computed temperature?

  TEMP = (int64_t)dT * (int64_t)C[6] / 8388608 + 2000;

The 8388608 is not an int value. It needs L or UL on the end to be interpreted correctly.

PaulS, thank you for your response.

Here is the data whenever it nears 20 Celsius. Actual TEMP= 24.20 Feet: 926.15

Actual TEMP= 23.61 Feet: 933.07

Actual TEMP= 23.17 Feet: 942.06

Actual TEMP= 21.79 Feet: 955.28

Actual TEMP= 20.30 Feet: 967.32

Actual TEMP= 141218.20

Feet: 978.12

Here is the temp in just a normal room with nothing going on...

Actual TEMP= 23.25 Feet: 927.95

Actual TEMP= 23.25 Feet: 928.25

Actual TEMP= 23.25 Feet: 927.95

Actual TEMP= 23.25

Feet: 928.25

"The 8388608 is not an int value. It needs L or UL on the end to be interpreted correctly." So 8388608 needs to be assigned as the following:

TEMP = (int64_t)dT * (int64_t)C[6] / 8388608L + 2000;

Also.. just deleted the delay. Yeah, that was pretty bad..

I'm thinking that it's my if statement that's messed up. What are your thoughts? I don't need to worry about the -15 Celsius. This is simply a balloon that will go up to 86000 feet, then drop back down. Temperatures this weekend didn't reach near that. They, however, did reach below 20 Celsius and we ran into BIG problems with temp and altitude problems.

You didn't answer the fundamental question. Is it the raw value that goes haywire, or the value that you create from the raw value?

The fact that 2000 shows up so often in your manipulation of the raw value suggests to me that there is a problem with your manipulation of the raw value.

It is the value that I created that goes haywire. In other words, faulty manipulation of raw value. I have messed up somewhere within my calculations to get accurate temperature when it is below 20 degrees Celsius. The Altimeter is in perfect working condition.

 if(TEMP < 2000) // if temperature lower than 20 Celsius 
  {
    int32_t T1    = 0;
    int64_t OFF1  = 0;
    int64_t SENS1 = 0;
    
    t_mod = (TEMP - 2000)*(TEMP - 2000);
    T1 = (dT*dT)>>31;
    OFF1=(61*t_mod)>>4;
    SENS1=2*t_mod;  
  }

By declaring the variables inside the code block, you are creating new instances of them, which are discarded when the block is done. That is almost certainly NOT what you want (since you don’t actually do anything with the values you calculate. In fact, gcc probably optimizes away the calculations and does nothing except setting t_mod.)

Lose the declarations…

Also, doesn’t setting the bits on the output port of an input ENABLE the pull-ups?

// Disable internal pullups, 10Kohms are on the breakout
 PORTC |= (1 << 4);
 PORTC |= (1 << 5);

Okay, so here is what I have since last posting. Still not getting the right answer but I feel much closer to it. I followed the schematic from here: http://www.farnell.com/datasheets/1756127.pdf

Pages 7 and 8 are what I used for my coding. My numbers for the while statements are as follows… 99,999 for above 20 Celsius 10,000 for below 20 Celsius. Reason being that TEMP shoots up to a RIDICULOUSLY high number WHEN it gets BELOW 2000.

This is my code. Should be much easier to follow now.

#include <Wire.h>
#define ADDRESS 0x76 //0x77
#include "IntersemaBaro.h"
Intersema::BaroPressure_MS5607B baro(true);
uint32_t D1 = 0;
uint32_t D2 = 0;
int64_t dT = 0;
int32_t TEMP = 0;
int64_t OFF = 0;
int64_t SENS = 0;
int32_t P = 0;

uint16_t C[7];

float Temperature;
float Pressure;


void setup() {
  
  baro.init();
// Disable internal pullups, 10Kohms are on the breakout
 PORTC |= (1 << 4);

  Wire.begin();
  Serial.begin(9600); //9600 changed 'cos of timing?
  delay(100);
  initial(ADDRESS);

}

void loop()
{

  D2 = getVal(ADDRESS, 0x58);// Temperature raw

  //dT and Temp will calculate Temp
  dT   = D2 - ((uint32_t)C[5] *(2*2*2*2*2*2*2*2));
  
  //off and sens will calculate temperature compensated pressure  
  OFF  = ( (int64_t)C[2]*131072 ) + ( (dT * C[4]) / (64));  //CHANGED
  SENS = ( (int64_t)C[1]*65536  ) + ( (dT * C[3]) / (128)); //CHANGED


  TEMP = 2000 + (dT * (int32_t)C[6] / (8388608)); //CHANGED
  
      int32_t T2;
      int64_t OFF2;
      int64_t SENS2;
  
  while(TEMP >10000) // if temperature lower than 20 Celsius
  {
      
       T2=(dT*dT)/(2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2);
       OFF2= (61*(TEMP-2000)*(TEMP-2000))/(16);
       SENS2 = 2*(TEMP-2000)*(TEMP-2000);
       break;      
  }

while(TEMP<9999)
  {
        
         T2=0;
         OFF2=0;
         SENS2 =0;
         break;
    }    

         TEMP = TEMP - T2;
         OFF = OFF - OFF2;
         SENS = SENS - SENS2;
        
         Serial.print("     Actual TEMP= ");
         Serial.print(TEMP);
         Serial.println();
         delay(1000);

}



long getVal(int address, byte code)
{
  unsigned long ret = 0;
  Wire.beginTransmission(address);
  Wire.write(code);
  Wire.endTransmission();
  delay(10);
  // start read sequence
  Wire.beginTransmission(address);
  Wire.write((byte) 0x00);
  Wire.endTransmission();
  Wire.beginTransmission(address);
  Wire.requestFrom(address, (int)3);
  if (Wire.available() >= 3)
  {
    ret = Wire.read() * (unsigned long)65536 + Wire.read() * (unsigned long)256 + Wire.read();
  }
  else {
    ret = -1;
  }
  Wire.endTransmission();
  return ret;
}

void initial(uint8_t address)
{

  Serial.println();
  Serial.println("PROM COEFFICIENTS ivan");

  Wire.beginTransmission(address);
  Wire.write(0x1E); // reset
  Wire.endTransmission();
  delay(10);


  for (int i=0; i<6  ; i++) {

    Wire.beginTransmission(address);
    Wire.write(0xA2 + (i * 2));
    Wire.endTransmission();

    Wire.beginTransmission(address);
    Wire.requestFrom(address, (uint8_t) 6);
    delay(1);
    if(Wire.available())
    {
       C[i+1] = Wire.read() << 8 | Wire.read();
    }
    else {
      Serial.println("Error reading PROM 1"); // error reading the PROM or communicating with the device
    }
    Serial.println(C[i+1]);
  }
  Serial.println();
}

You are not paying attention to the responses. For example, these almost certainly won't work the way you intend.

TEMP = 2000 + (dT * (int32_t)C[6] / (8388608)); //CHANGED
...
       OFF2= (61*(TEMP-2000)*(TEMP-2000))/(16);

How is anyone supposed to make sense out of a statement and comment like this?

while(TEMP >10000) // if temperature lower than 20 Celsius

cbf0005: This is simply a balloon that will go up to 86000 feet, then drop back down. Temperatures this weekend didn't reach near that.

Just curious... did you read the datasheet? I'm no expert but it looks like the max altitude for the MS5607 (10 millibars) is 84998.2 feet.

Good thing you're not going to exceed that eh?

(I'm just disappointed because I'm looking for a good altimeter for HAB use)

Regards,

Brad KF7FER

EDIT: Added units to the altitude

       T2=(dT*dT)/(2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2);

Oh, don’t do that!
C has this rule about doing math with “int” sized things, unless some element of the expression is larger than that.
Now, “dT” is a 64bit int, so that MIGHT cause the 2^31 (?) to be calculated using 64bit numbers. But the 2s are also inside of parenthesis, which leaves me “uncertain” as to what the compiler will do. You should NEVER be uncertain if you can avoid it.
You can fix this by making the constant expression also use 64bit numbers, like:
(((in64_t)2)222222222222222222222222222222)
or perhaps
(1LL<<31)

avr-gcc is known to be pretty slow at 64bit math; it might be a good idea to restrict the 64bit calculations to exactly those expressions where they are actually needed.

Okay, I’ve got that fixed. Thanks for the help. Still spitting out weird numbers. It has to be something else I’ve done within that while statement for below 20 Celsius. I just have no clue what it could be at this point.

Here’s my code to this point.

#include <Wire.h>
#define ADDRESS 0x76 //0x77
#include "IntersemaBaro.h"
Intersema::BaroPressure_MS5607B baro(true);
uint32_t D1 = 0;
uint32_t D2 = 0;
int64_t dT = 0;
int32_t TEMP = 0;
int64_t OFF = 0; 
int64_t SENS = 0; 
int32_t P = 0;

uint16_t C[1];
//uint16_t C[2];
//uint16_t C[3];
//uint16_t C[4];
//uint16_t C[5];
//uint16_t C[6];
//uint16_t C[7];




float Temperature;
float Pressure;


void setup() {
  
  baro.init();
// Disable internal pullups, 10Kohms are on the breakout
 PORTC |= (1 << 4);

  Wire.begin();
  Serial.begin(9600); //9600 changed 'cos of timing?
  delay(100);
  initial(ADDRESS);

}

void loop()
{

      int32_t T2;  //Unsigned int 32 bit for T2 (used for Temperature compensation)
      int64_t OFF2; //Unsigned int 64 bit for OFF2 (used for Temperature compesation [see flow chart])
      int64_t SENS2; //Unsigned int 64 bit for SENS2 (used for Temperature compensation [see flow chart])
  
  D2 = getVal(ADDRESS, 0x58);// raw temperature value

  //dT and Temp will calculate temperature
  dT   = D2 - ((uint32_t)C[5] *(2*2*2*2*2*2*2*2));
  
  //off and sens will calculate temperature compensated pressure  
  OFF  = ( (int64_t)C[2]*131072 ) + ( (dT * C[4]) / (64));  
  SENS = ( (int64_t)C[1]*65536  ) + ( (dT * C[3]) / (128)); 
  TEMP = 2000 + (dT * (int32_t)C[6] / (8388608) ); //base temperature formula
  
  while(TEMP <2000) // FOR WHEN THE TEMPERATURE IS BELOW 20 CELSIUS
  {
       
    Serial.print("Made pass through low temp\n");   
       T2=((dT*dT)/(1LL<<31)); 
       OFF2= -(61)*((TEMP-2000)*(TEMP-2000))/(16);    
       SENS2 = 2*(TEMP-2000)*(TEMP-2000);
       break;      
  }

while(TEMP>2000) //FOR WHEN THE TEMPERATURE IS ABOVE 20 CELSIUS
  {
         
         T2=0;
         OFF2=0;
         SENS2 =0;
         break;
    }    

         TEMP = TEMP - T2;
         OFF = OFF - OFF2;
         SENS = SENS - SENS2;
         
         Serial.print("     Actual TEMP= ");
         Serial.print(TEMP);
         Serial.print("Temp two");
         Serial.print(T2);         
         Serial.println();
         delay(1000);

}



long getVal(int address, byte code)
{
  unsigned long ret = 0;
  Wire.beginTransmission(address);
  Wire.write(code);
  Wire.endTransmission();
  delay(10);
  // start read sequence
  Wire.beginTransmission(address);
  Wire.write((byte) 0x00);
  Wire.endTransmission();
  Wire.beginTransmission(address);
  Wire.requestFrom(address, (int)3);
  if (Wire.available() >= 3)
  {
    ret = Wire.read() * (unsigned long)65536 + Wire.read() * (unsigned long)256 + Wire.read();
  }
  else {
    ret = -1;
  }
  Wire.endTransmission();
  return ret;
}

void initial(uint8_t address)
{

  Serial.println();
  Serial.println("PROM COEFFICIENTS ivan");

  Wire.beginTransmission(address);
  Wire.write(0x1E); // reset
  Wire.endTransmission();
  delay(10);


  for (int i=0; i<6  ; i++) {

    Wire.beginTransmission(address);
    Wire.write(0xA2 + (i * 2));
    Wire.endTransmission();

    Wire.beginTransmission(address);
    Wire.requestFrom(address, (uint8_t) 6);
    delay(1);
    if(Wire.available())
    {
       C[i+1] = Wire.read() << 8 | Wire.read();
    }
    else {
      Serial.println("Error reading PROM 1"); // error reading the PROM or communicating with the device
    }
    Serial.println(C[i+1]);
  }
  Serial.println();
}

Get rid of the mixed-type math. If you use 64-bit in a calculation then make it all 64-bit, not "whatever". Learn to shift bits instead of x2x2x2x2.

Just for fun, set up a test sketch with the equations you have now and print results for different inputs. Then break the ones that go silly down and find out where they went silly. You will learn more that simply "don't do it that way". You might even learn to avoid things you haven't done yet.

Mixed bits.. Gotcha. I'll let you know how it goes. Thanks.

EDIT: Worked. Thank God. Thank YOU! I really appreciate it!

uint16_t C[1];
:
OFF = ( (int64_t)C[2]*131072 ) + ( (dT * C[4]) / (64));
SENS = ( (int64_t)C[1]*65536 ) + ( (dT * C[3]) / (128));

Well, THAT’S not likely to work very well…

Personally, I don’t think “we” recommend subroutines nearly often enough. Why don’t you do it just like in the datasheet:

void setup() {
  read_calibration();
}

void loop() {
  read_data();
  calculate_temp();
  adjust_lowtemp();  // for especially low temperatures
  calculate_pres();
}

I’m not liking the datasheet very much. When they say something like “if temp < 20C then do something with (2000-temp)”, they’re really mixing up their real temperatures vs their scaled temperatures. And why can’t we have a real equation for getting temp/pressure instead of this mishmash of fixed and floating point…

cbf0005: Mixed bits.. Gotcha. I'll let you know how it goes. Thanks.

EDIT: Worked. Thank God. Thank YOU! I really appreciate it!

I don't know if you went over it all but it probably wouldn't hurt to double-check even down to pieces. But that depends on how important the results are going to be.

Get rid of that piece of junk. There are MUCH better pressure sensor/altimeters, with data sheets that actually make sense. This one works very well and even has alarm settings: http://www.pololu.com/product/2126