MS5607 super basic Arduino Altimeter

The Rocketry Forum

Help Support The Rocketry Forum:

This site may earn a commission from merchant affiliate links, including eBay, Amazon, and others.

MrBill2u

Well-Known Member
Joined
May 15, 2014
Messages
89
Reaction score
0
I know there are Arduino altimeters all over the place, but I wanted to try and scratch build one and code it up myself. I had a tough time finding code that actually worked with the MS5607 on Arduino 1.x.

The wiring was silly simple for the Arduino pro mini 5v. I used the mini shield to make it a tad easier, but it is just connecting vcc to vcc, gnd to gnd, sda to sda, scl to scl and grabbing a pair of resistors in the mid 10ks out of the resistor bin to pull up sda and scl to vcc. After everything was connected up I slid it into a 29mm tube. It fits snugly.... it would not fit in a 29mm coupler.

I soldered the FTDI header on and got a FTDI board to make programming easy.

This is a very bare bones altimeter program. It took me a lot of nail biting and head-smashing-on-desk to get the initial communications and math working so I figured I would drop the code out here just in case somebody somewhere decided they wanted to build a similar setup. The only bits implemented are communication with MS5607, basic altitude in feet using the simple version of temperature controlled pressure, launch detect, apogee detect and post apogee LED flashing of peak AGL altitude in feet. The only testing I have done is hitting the reset button and tossing it in the air with launch detect set to 3 feet. Yay for those 6 foot apogees :)



altimeter.ino
Code:
#include "intersemabaro.h"
#include <Wire.h>

/*

I wrote this to convince an MS5607 to play nice with the arduino pro mini 5v.

This is a very basic altimeter.  It doesn't log or do anything fancy.
It is only intended as a starting place to work through the initial conversation with the altimeter.

Ground level is determined on reset.
Launch detect should fire when the altitude AGL is above the AGL_for_launch_detect variable.
After Apogee detect it will begin flashing the altitude in feet.

5 fast blips = beginning number
2 medium blips = digit separator
slow blips = count them to find the digit  :)

The intersemabaro.h handles smoothing of results and temperature corrected pressure to altitude results.
To make it refresh faster you can set uint8_t NUM_SAMP_FOR_AVG lower in intersemabaro.h
To make the results smoother set it higher.  I get roughly 9 samples per second with 5 result averaging.

I don't own this code and I make no promises whatsoever about it's functionality. 
I tested it by tossing it in the air... it has never even seen a rocket  :)

Bill Garrett - 2014 - [email protected]

*/

int led = 13;
unsigned long time;
int current_altitude = 0;
int peak_altitude_AGL = 0;
int ground_altitude = 0;
boolean apogee_detected = false;
boolean launch_detected = false;
int AGL_for_launch_detect = 100;
int apogee_countdown = 3;

Intersema::BaroPressure_MS5607 baro;

// the setup routine runs once when you press reset:
void setup() {                
  // initialize the digital pin as an output.
  pinMode(led, OUTPUT);    
  Serial.begin(9600);           // set up Serial library at 9600 bps
  baro.init();
  ground_altitude = static_cast<uint32_t>(baro.getHeightCentiMeters()/30.48); 
  Serial.println("Setup complete.");  
}

// the loop routine runs over and over again forever:
void loop() {
  current_altitude = static_cast<uint32_t>(baro.getHeightCentiMeters()/30.48); 

  if((current_altitude-ground_altitude) > peak_altitude_AGL ){
      peak_altitude_AGL = current_altitude-ground_altitude;
  }
  else if(launch_detected & !apogee_detected){
    apogee_countdown--;
    if(apogee_countdown==0){
      apogee_detected=true;
      Serial.println("Apogee!!");
    }
  }
  
  if (!launch_detected & (peak_altitude_AGL > 2)){
    launch_detected=true;
  }

  if(apogee_detected){
    blinkOut(peak_altitude_AGL);
  }
}

void blinkOut(uint32_t in){
  Serial.println(log10(in));
  blinkLEDBegin();
  for(int d=log10(in); d >=0; d--){
    const uint32_t currentDigit=static_cast<uint32_t>(in/pow(10,d))%10;
    for(uint32_t i=currentDigit; i>0; i--){
      blinkLED();    
    }
    blinkLEDBreak();
  }
}

void blinkLED(){
  digitalWrite(led, HIGH);
  delay(125);
  digitalWrite(led, LOW);
  delay(125);  
}

void blinkLEDBreak(){
  delay(500);
  digitalWrite(led, HIGH);
  delay(50); 
  digitalWrite(led, LOW);
  delay(50);  
  digitalWrite(led, HIGH);
  delay(50);
  digitalWrite(led, LOW);
  delay(500);  
}
void blinkLEDBegin(){
  for(int i=10;i>0;i--){
    digitalWrite(led, HIGH);
    delay(25);
    digitalWrite(led, LOW);
    delay(25);
  }
  delay(500);  
}


intersemabaro.h
Code:
#ifndef INTERSEMA_BARO_H
#define INTERSEMA_BARO_H

/*

I have found variations of this code all over.  None of them worked for me with arduino 1.x.  

I don't own this code and I make no promises whatsoever about it's functionality.  It is working for me  :)

Bill Garrett - 2014 - [email protected]

*/


#include <Wire.h>
#include "Arduino.h"

namespace Intersema
{
  
class BaroPressure
{
public:
    virtual void init() = 0;   
   
    int32_t getHeightCentiMeters(void)
    {
        return AcquireAveragedSampleCm(NUM_SAMP_FOR_AVG);
    }   
    
protected:
    virtual int32_t AcquireAveragedSampleCm(const uint8_t nSamples) = 0;
    virtual uint32_t ConvertPressureTemperature(uint32_t pressure, uint32_t temperature) = 0;
    
    int32_t PascalToCentimeter(const int32_t pressurePa)
    {
        // Lookup table converting pressure in Pa to altitude in cm.
        // Each LUT entry is the altitude in cm corresponding to an implicit
        // pressure value, calculated as [PA_INIT - 1024*index] in Pa.
        // The table is calculated for a nominal sea-level pressure  = 101325 Pa.
        static const int32_t PZLUT_ENTRIES = 77;
        static const int32_t PA_INIT       = 104908;
        static const int32_t PA_DELTA      = 1024;

        static const int32_t lookupTable[PZLUT_ENTRIES] = {
       -29408, -21087, -12700,  -4244,   4279,
        12874,  21541,  30281,  39095,  47986,
        56953,  66000,  75126,  84335,  93628,
       103006, 112472, 122026, 131672, 141410,
       151244, 161174, 171204, 181335, 191570,
       201911, 212361, 222922, 233597, 244388,
       255300, 266334, 277494, 288782, 300204,
       311761, 323457, 335297, 347285, 359424,
       371719, 384174, 396795, 409586, 422552,
       435700, 449033, 462560, 476285, 490216,
       504360, 518724, 533316, 548144, 563216,
       578543, 594134, 609999, 626149, 642595,
       659352, 676431, 693847, 711615, 729752,
       748275, 767202, 786555, 806356, 826627,
       847395, 868688, 890537, 912974, 936037,
       959766, 984206};

        

        if(pressurePa > PA_INIT) 
             return lookupTable[0];
        else 
        {
           const int32_t inx = (PA_INIT - pressurePa) >> 10;      
           if(inx >= PZLUT_ENTRIES - 1) 
               return lookupTable[PZLUT_ENTRIES - 1];
           else 
           {
                const int32_t pa1 = PA_INIT - (inx << 10);
                const int32_t z1 = lookupTable[inx];
                const int32_t z2 = lookupTable[inx+1];
                return (z1 + (((pa1 - pressurePa) * (z2 - z1)) >> 10));
            }
        }
    }
    
    static const uint8_t NUM_SAMP_FOR_AVG = 5;

    unsigned int coefficients_[6];
};

class BaroPressure_MS5607 : public BaroPressure
{
public:
    BaroPressure_MS5607() { 
    }
    
    void init()
    {    
       ResetSensor();
       ReadCoefficients();
    }
    
private:

    static const uint8_t i2cAddr_ = 0x76;
    static const uint8_t cmdReset_   = 0x1E;
    static const uint8_t cmdAdcRead_ = 0x00;
    static const uint8_t cmdAdcConv_ = 0x40;
    static const uint8_t cmdAdcD1_   = 0x00;
    static const uint8_t cmdAdcD2_   = 0x10;
    static const uint8_t cmdAdc256_  = 0x00;
    static const uint8_t cmdAdc512_  = 0x02;
    static const uint8_t cmdAdc1024_ = 0x04;
    static const uint8_t cmdAdc2048_ = 0x06;
    static const uint8_t cmdAdc4096_ = 0x08;
    static const uint8_t cmdPromRd_  = 0xA0;

    void ResetSensor()
    {
        Wire.begin();
        Wire.beginTransmission(i2cAddr_);
        Wire.write(cmdReset_);   
        Wire.endTransmission(); 
        delay(3);
        Serial.println("Sensor Reset Sent");
    }

    void ReadCoefficients(void)
    {
        for(uint8_t i=0; i<6; ++i)
            coefficients_[i] = ReadCoefficient(i + 1);  
            

   for(uint8_t i=0; i<6; ++i)
        {
            Serial.print("Coefficient ");
            Serial.print(i + 1, DEC);
            Serial.print(" : ");
            Serial.println(coefficients_[i], DEC);
        }
    }

    uint16_t ReadCoefficient(const uint8_t coefNum)
    {
        uint16_t rC=0;
    
        Wire.beginTransmission(i2cAddr_);
        Wire.write(cmdPromRd_ + coefNum * 2); // send PROM READ command
        Wire.endTransmission(); 
    
        Wire.requestFrom(i2cAddr_, static_cast<uint8_t>(2));

        if(Wire.available() >= 2)
        {
            uint16_t ret = Wire.read();   // read MSB and acknowledge
            uint16_t rC  = 256 * ret;
            ret = Wire.read();        // read LSB and not acknowledge
            rC  = rC + ret;
            return rC;
        }
        else
        {
       Serial.println("No data available in ReadCoefficient()");
        }
    
        return 0;
    }

    virtual int32_t AcquireAveragedSampleCm(const uint8_t nSamples)
    {
        int64_t pressAccum = 0;

        for(size_t n = nSamples; n; n--) 
        {
            const uint32_t temperature = ReadAdc(cmdAdcD2_ | cmdAdc4096_); // digital temperature value : typical 8077636
            const uint32_t pressure    = ReadAdc(cmdAdcD1_ | cmdAdc4096_); // digital pressure value : typical 6465444  
            const uint32_t pressConv   = ConvertPressureTemperature(pressure, temperature);                 
            pressAccum += pressConv;
        }

        const int32_t pressAvg = pressAccum / nSamples;        
        const int32_t AltCm = PascalToCentimeter(pressAvg);

        return AltCm;   
    }
    
    int32_t ReadAdc(const uint8_t cmd)
    {             
        Wire.beginTransmission(i2cAddr_);
        Wire.write(cmdAdcConv_ | cmd); // send conversion command
        Wire.endTransmission(); 

        // wait necessary conversion time
        switch(cmd & 0x0f) 
        {
        case cmdAdc256_: 
            delay(1);
            break;
        case cmdAdc512_: 
            delay(3);
            break;
        case cmdAdc1024_: 
            delay(4);
            break;
        case cmdAdc2048_: 
            delay(6);
            break;
        case cmdAdc4096_: 
            delay(10); 
            break;
        }

        Wire.beginTransmission(i2cAddr_);
        Wire.write(cmdAdcRead_);
        Wire.endTransmission();
    
        Wire.requestFrom(i2cAddr_, static_cast<uint8_t>(3));

        if(Wire.available() >= 3)
        {
            uint16_t ret  = Wire.read(); // read MSB and acknowledge
            uint32_t temp = 65536 * ret;
            ret  = Wire.read();      // read byte and acknowledge
            temp = temp + 256 * ret;
            ret  = Wire.read();  // read LSB and not acknowledge
            temp = temp + ret;
                
            return temp;
        }

        else
        {
           Serial.println(Wire.available());
        }

        return 0;
    }

    uint32_t ConvertPressureTemperature(uint32_t pressure, uint32_t temperature)
    {      
        const uint64_t C1 = static_cast<uint64_t>(coefficients_[0]);
        const uint64_t C2 = static_cast<uint64_t>(coefficients_[1]);
        const uint64_t C3 = static_cast<uint64_t>(coefficients_[2]);
        const uint64_t C4 = static_cast<uint64_t>(coefficients_[3]);
        const uint64_t C5 = static_cast<uint64_t>(coefficients_[4]);
        const uint64_t C6 = static_cast<uint64_t>(coefficients_[5]);
        
        // calcualte 1st order pressure and temperature (MS5607 1st order algorithm)
        const int32_t dT    = temperature - C5 * 256;                     // difference between actual and reference temperature
        const int32_t temp  = 2000 + (dT * C6) / pow(2, 23) ; //        // actual temperature
     
        const int64_t OFF   = (C2 * pow(2, 17)) + ((C4 * dT) / pow(2, 6)); // offset at actual temperature
        const int64_t SENS  = (C1 * pow(2, 16)) + ((C3 * dT) / pow(2, 7)); // sensitivity at actual temperature
        const int32_t press = ((pressure * SENS / pow(2, 21) - OFF) / pow(2, 15)); // / 100;      // temperature compensated pressure

        return press; 
    }

};

} // namespace Intersema
#endif
 
Last edited:
No syntax highlighting but the smilies are displayed nicely :)
 
Doh! I thought the code tags would take care of killing the smilies. Once you drop it into the arduino source editor it will highlight up. Only thing you have to be aware of for the pro mini 5v is that the board will not autodetect through FTDI so you have to manually select it.
 
Glad to see that other are playing with Arduino board
You could now add some digital filtring to make it more reliable.
The only downside is that the MS5607 is 10 rime more expensive than a BMP180
However it would be interresting to do some compareason with the 2 sensors
I have also published my altimeter code with is for the BMP085 and BMP180
https://rocket.payload.free.fr/index.php?option=com_content&view=article&id=2&Itemid=2&lang=en
feel free to re-used any of it
 
Nice work! This looks very similar to the MicroPeak altimeter I designed using the MS5607 sensor and ATTiny85 microcontroller. We publish complete schematics, circuit board artwork and source code for all of our stuff.

https://altusmetrum.org/MicroPeak/

Prices for the MS5607 have dropped quite a bit in the last couple of years, but it's still hard to get them in small quantities.
 
Thanks! I have to make my brain bigger before I "Get" double exponential filtering. I am not even doing a running average, more like a skipping average :D
 
I really enjoyed writing the blinkOut procedure. Funny, I thought the altimeter portion would be more up my alley, but communicating information in human readable form with only a single bit output device was a fun little mental exercise.

@bdureau I am kind of backwards. I don't like implementing math functions I cannot grasp. I am sure I will grasp the more advanced smoothing functions someday :)
 
I need to ask for some opinions. If an altimeter is used in a thermal cutter chute reefing device attached directly to a chute, would the wide and erratic variation of sensed pressures be too problematic or could filtering reduce it adequately?
 
I am a n00b here and have never actually launched a DD rocket. In my imagination you would want to put the altimeter in a tube and drill a few very small holes to even out the pressure readings. A small tube like a nuun container would probably work pretty well for such a task.
 
Back
Top