• Welcome to ScubaBoard


  1. Welcome to ScubaBoard, the world's largest scuba diving community. Registration is not required to read the forums, but we encourage you to join. Joining has its benefits and enables you to participate in the discussions.

    Benefits of registering include

    • Ability to post and comment on topics and discussions.
    • A Free photo gallery to share your dive photos with the world.
    • You can make this box go away

    Joining is quick and easy. Login or Register now by clicking on the button

Nitrox/Trimix & CO analyzer

Discussion in 'Do It Yourself - DIY' started by Miyaru, May 19, 2020.

  1. Miyaru

    Miyaru Tec Instructor ScubaBoard Sponsor

    # of Dives: 2,500 - 4,999
    Location: Area51
    792
    820
    93
    Setup section:

    Code:
    void setup() {
      //push buttons
      pinMode(bLeft,INPUT_PULLUP);
      pinMode(bRight,INPUT_PULLUP);
      attachInterrupt(digitalPinToInterrupt(bLeft),LeftButton,CHANGE);
      attachInterrupt(digitalPinToInterrupt(bRight),RightButton,CHANGE);
     
      tft.init(240, 240, SPI_MODE3);
      tft.fillScreen(0x0000);
      tft.setRotation(2);
      tft.setTextWrap(false);                        // turn off text wrap option
    
      ads.setGain(GAIN_FOUR); // 4x gain 1 bit = 0.03125mV
      ads.begin();
      if (enableCO) {
        ads2.setGain(GAIN_TWOTHIRDS); // 2x gain 1 bit = 0.125mV
        ads2.begin();
      }
      RAhe.clear();
      RAox.clear();
      RAco.clear();
     
      heVcal = readHEsensor();
      oxVcal = readO2sensor();
    
      // read EEPROM values
      int eeAddress = 0;
      EEPROM.get(eeAddress, oxVmax);
      eeAddress += sizeof(oxVmax); //Move address to the next byte after float 'f'.
      EEPROM.get(eeAddress, heVmax );
      eeAddress += sizeof(heVmax); //Move address to the next byte after float 'f'.
      EEPROM.get(eeAddress, oxVmin );
      if (isnan(oxVmax)) {
        calibrateO2 = true;
      }
      if (isnan(heVmax)) {
        calibrateHe = true;
      }
      tft.fillRect(0,32,240,240,0x0000);
      tft.setTextColor(0xFFFF, 0x0000);
      tft.setTextSize(2);
    
      tft.setCursor(10,35);
      tft.print(ver1);
      tft.setCursor(10,55);
      tft.print(ver2);
      delay(250);
    
      //He sensor
      if (enableHe) {
        tft.setTextSize(2);
        tft.setCursor(10,80);
        tft.print(HEsensor);
        while(heVcal > 2 or heVcal < -1){ //keeps looping if sensor is cold
          heVcal = readHEsensor();
          tft.setCursor(10,100);
          tft.print(calibrate);
          tft.print(heVcal,1);
          delay(25);
        }
        tft.setCursor(10,100);
        tft.print(F("He "));
        tft.print(calibOK);
      }
       //O2 sensor
      tft.setTextSize(2);
      tft.setCursor(10,140);
      tft.print(calibrate);
      tft.setCursor(10,160);
      tft.print(O2sensor);
    
      while (oxVcal < 9 or oxVcal > 12) {
        tft.setCursor(10,180);
        if (oxVcal < 9) {
          tft.print(F("V cell too low "));
        }
        if (oxVcal > 12) {
          tft.print(F("V cell too high"));
        }
        oxVcal = readO2sensor();
        tft.setCursor(155,160);
        tft.print(oxVcal,2);
        tft.print(mV);
        delay(250);
      }
      tft.setCursor(10,180);
      tft.print(F("O2 "));
      tft.print(calibOK);
      delay(2000);
    
      tft.fillRect(0,32,240,240,0x0000);
      tft.setTextSize(3);
      tft.setCursor(0,80);
      tft.println(warn1);
      tft.print(warn2);
      tft.setCursor(0,170);
      tft.print(warn3);
      delay(3000);
    
      //end of calibration, draw screen for loop operation:
      MODcalc = 1.4;
      leftclick=0;
      tft.fillScreen(0x0000);
    }
    
     
  2. Miyaru

    Miyaru Tec Instructor ScubaBoard Sponsor

    # of Dives: 2,500 - 4,999
    Location: Area51
    792
    820
    93
    Loop section 1/2:

    Code:
    void loop() {
    // ---- timers ----------------------------------------- 
      time = millis();      // runtime
      if (millis()-menuCounter>10000 and millis()-menuCounter<10500) {
        leftclick=0;
        tft.fillRect(0,160,239,239,0x0000);
      }
    // ---- menu actions ----------------------------------------- 
      if (action==1) {
        //air calibration
        tft.fillRect(2,58,230,55,0x0000);
        tft.fillRect(0,120,239,119,0x0000);
        tft.setTextColor(0x379F, 0x0000);
        tft.setTextSize(2);
        tft.setCursor(10,58);
        tft.print(calibrate);
        tft.setCursor(10,78);
        tft.print(F("in air..."));
        // determine the O2 sensor voltage in air
        int i = 0;
        float Vavg = 0;
        RAhe.clear();
        RAox.clear();
        RAco.clear();
    
        oxVcal = readO2sensor();
        heVact = readHEsensor();
        
        tft.setCursor(10,98);
        tft.print(oxVcal);
        tft.print(mV);
        tft.print(heVact);
        tft.print(mV);
        delay(readDelay);
     
        tft.setTextSize(2);
        tft.setCursor(10,98);
        tft.print(calibOK);
        delay(2000);
        tft.fillScreen(0x0000);
        action=0;
        leftclick=0;
      } //end of calibration
      // ------100% O2 calibration-----------------
      if (action==2)
      {
        tft.fillRect(2,58,230,55,0x0000);
        tft.fillRect(0,120,239,119,0x0000);
        tft.setTextSize(2);
        tft.setCursor(10,58);
        tft.print(calibrate);
        tft.setCursor(10,78);
        tft.print(F("in 100% oxygen"));
        // determine the O2 sensor voltage in oxygen
        int i = 0;
    
        RAox.clear();
        for(i=1; i<=calibrationCount; i++) {
          oxVmax = readO2sensor();
          //heVmin = readHEsensor();
          tft.setCursor(10,98);
          tft.print(oxVact);
          tft.print(mV);
          tft.print(heVact);
          tft.print(mV);
          delay(readDelay);
        }
        tft.setTextSize(2);
        tft.setCursor(10,98);
        tft.setTextColor(0x379F, 0x0000);
        if (oxVmax > 40) {
          tft.print(calibOK);
          //write to eeprom
          int eeAddress = 0;
          EEPROM.put(eeAddress, oxVmax);
        } else {
          tft.print(F("Oxygen too low  "));
        }
        delay(2000);
        tft.fillScreen(0x0000);
        action=0;
        leftclick=0;
      } //end of calibration
    
      // ------100% He calibration-----------------
      if (action==3 and enableHe)
      {
        tft.fillRect(2,58,230,55,0x0000);
        tft.setTextSize(2);
        tft.setCursor(10,58);
        tft.print(calibrate);
        tft.setCursor(10,78);
        tft.print(F("in 100% helium"));
        // determine the O2 sensor voltage in air
        int i = 0;
        RAhe.clear();
        RAox.clear();
        for(i=1; i<=calibrationCount; i++) {
          oxVmin = readO2sensor();
          heVmax = readHEsensor();
          tft.setCursor(10,98);
          tft.print(oxVact);
          tft.print(mV);
          tft.print(heVact);
          tft.print(mV);
          delay(readDelay);
        }
        tft.setTextSize(2);
        tft.setCursor(10,98);
        tft.print(calibOK);
        //write to eeprom
        int eeAddress = sizeof(oxVmax);
        EEPROM.put(eeAddress, heVmax);
        eeAddress += sizeof(heVmax);
        EEPROM.put(eeAddress, oxVmin);
        delay(2000);
        tft.fillScreen(0x0000);
        leftclick=0;
      }
    //end of calibration----------------------------------------
    
    //---read sensors------------
      oxVact = readO2sensor();
      if (enableHe) {
        heVact = readHEsensor();
      } else {
        heVact = 0;
      }
      if (enableCO) {
        carbon = readCOsensor();
      } else {
        carbon=0;
      }
    //-- end read sensors------------
      float oxygen = 0;
      float MOD = 0;                 // Maximum Operating Depth
      int EADD = 0;                // Equivalent Air Density Depth
      float helium = 0;
      float GasDensity = 0;
      float Pdepth = 0;
      float GasWeight = 0;         // gram / liter at depth
      //correction linear drift
      oxygen = 20.9 + 79.1*(oxVact - oxVcal)/(oxVmax - oxVcal);
      if (oxygen > 100) {
        oxygen = 100;
      }
      if (oxygen < 1) {
        oxygen = 0;
      }
    
      // display CO ------------------------------------------------------
      if ((leftclick==0 or leftclick==5) and enableCO) {
        if (carbon<=1.6 and leftclick==0) {
          tft.setTextSize(2);
          tft.setCursor(80,212);
          tft.print(F("CO "));
          tft.print(carbon);
          tft.print(F("ppm "));     
        } else {
          tft.setTextSize(3);
          tft.setCursor(20,130);
          tft.print(F("CO"));
          if (carbon<10 and carbon>1.6) {
            tft.print(F("   "));
          } else if (carbon>99) {
            tft.print(space1);
          } else {
            tft.print(F("  "));
          }
          tft.print(carbon,1);
          tft.print(F("ppm  "));
          tft.setTextSize(2);
          tft.setCursor(80,212);
          tft.print(F("          "));
        }
      }
      if (leftclick==0){
        tft.setTextSize(2);
        tft.setCursor(2,210);
        tft.print(F("Menu"));
      }
      // calculate all values
      heVact = heVact - heVcal;
      //correct heVact in case of high O2 levels - empirical!
      if (oxygen>89) { heVact = heVact-17; }
      else if (oxygen>82) { heVact = heVact-16;}
      else if (oxygen>75) { heVact = heVact-15;}
      else if (oxygen>71) { heVact = heVact-14;}
      else if (oxygen>66) { heVact = heVact-13;}
      else if (oxygen>62) { heVact = heVact-12;}
      else if (oxygen>57) { heVact = heVact-11;}
      else if (oxygen>52) { heVact = heVact-10;}
      else if (oxygen>48) { heVact = heVact-9;}
    
      MOD = 10 * ( (100*MODcalc/oxygen) - 1);
      helium = 100 * heVact / heVmax; // value from eeprom
      if (helium>100) {
        helium = 100;
      }
    
     
  3. Miyaru

    Miyaru Tec Instructor ScubaBoard Sponsor

    # of Dives: 2,500 - 4,999
    Location: Area51
    792
    820
    93
    Loop section 2/2:

    Code:
    // -----screen display-----------------------------------
      if (oxVact > 1) {
        tft.setTextSize(3);
        tft.setCursor(20,58);
        tft.print(F("O2"));
        tft.setCursor(100,58);
        if (oxygen<10) {
            tft.print(space1);
        }
        if (helium<95) {
          tft.print(oxygen,1);
          tft.print(F("% "));
        }
    
        tft.setCursor(20,130);
        if (leftclick<2) {
          if (MOD<1000) {
            tft.print(F("MOD "));
            tft.print(MOD,0);
            tft.print(F("m  "));
            if (MOD < 100) {
              tft.print(space1);
            }
          }
          tft.setCursor(180,130);
          tft.print(MODcalc,1);
        }
      }
      tft.setCursor(20,90);
      tft.setTextSize(3);   
      tft.print(F("He"));
      if (helium > 2) {
        tft.setTextSize(3);
        tft.setCursor(100,90);
        tft.print(helium,1);
        tft.print(F("% "));
      }
      else {
        helium = 0;
        tft.setTextSize(3);
        tft.setCursor(100,90);
        if (oxygen<49) {
          tft.print(F("0%    "));
        } else {
          tft.print(F("     "));
        }
        if (leftclick==0) {
          tft.setTextSize(3);
          tft.setCursor(20,160); //clear EADD
          tft.print(F("        ")); //clear EADD
          tft.setTextSize(2);    //clear density
          tft.setCursor(120,190);//clear density
          tft.print(F("         "));//clear density
        }
      }
      // ----- display ----------
      tft.setTextSize(3);
      if (helium>0 and enableHe) {
          tft.setTextSize(3);
          tft.print(F("TMX "));
          if (oxygen<9.5) {
            tft.print(space1);
          }
          tft.print(oxygen,0);
          tft.print("/");
          if (helium==100) {
            tft.print("99");
          } else {
            tft.print(helium,0);
          }
          if (helium < 9.5) {
             tft.print(space1);
          }
          if (leftclick==0) { //no menu active
            tft.setTextColor(0xBE7C, 0x0000);
            tft.setTextSize(3);
            tft.setCursor(20,160);
            if ((helium<95) and (calibrateHe==false)) {
              tft.print(F("EADD "));
              GasDensity = (100-helium-oxygen)*0.012506 + helium*0.001785 + oxygen*0.014285;
              Pdepth = MOD/10+1;
              GasWeight = GasDensity * Pdepth;
              EADD = ((GasWeight/1.2881)-1)*10;
              tft.print(EADD);
              tft.print("m");
              if (EADD < 100) {
                tft.print(space1);
              }
              tft.setTextSize(2);
              tft.setCursor(120,190);
              tft.print(F("("));
              if (GasWeight > 5.9) {
                tft.setTextColor(0xF800,0x0000);
              }
              tft.print(GasWeight,2);
              tft.setTextColor(0xBE7C, 0x0000);
              tft.print(F("g/l)"));
            }
            else if ((helium>=95) and (calibrateHe==false)) { // helium > 95%, clear lower screen
              tft.setCursor(20,160);  //clear EADD
              tft.print(F("         ")); //clear EADD
              tft.setTextSize(2);     //clear density
              tft.setCursor(120,190); //clear density
              tft.print(F("         ")); //clear density
              tft.setTextSize(3);
              tft.setCursor(20,130);  //clear density
              tft.print(F("        "));  //clear MOD
              tft.setCursor(100,58);
              tft.print(F("--   "));        //clear O2
            }
            else { //100% He not calibrated, no value found in EEPROM
              tft.setTextColor(0xF800,0x0000);
              tft.setTextSize(2);     
              tft.setCursor(2,160);  //
              tft.print(calibReq);
              tft.setCursor(2,180);  //
              tft.print("with 100% Helium  ");
            }
          }
      }
      else {
        if (oxygen < 22) {
          tft.setCursor(50,13);
          tft.print("   Air  ");
        } else if ((oxygen > 22) and (oxygen < 99.5)) {
          tft.setCursor(70,13);
          tft.print("EAN ");
          tft.print(oxygen,0);
        } else {
          tft.print("OXYGEN");
        }
      }
      if (calibrateO2) {
        tft.setTextColor(0xF800,0x0000);
        tft.setTextSize(2);     
        tft.setCursor(2,160);  //
        tft.print(calibReq);
        tft.setCursor(2,180);  //
        tft.print("with 100% Oxygen  ");
      }
    
      if (leftclick==1) {
        tft.setTextSize(3);
        tft.setCursor(2,180);
        tft.print(F("     MOD     "));
        tft.setTextSize(2);
        tft.setCursor(2,216);
        tft.print(next);
        tft.setCursor(165,216);
        tft.print(F("Change"));
      }
      if (leftclick==2) {
        tft.setTextSize(3);
        tft.setCursor(2,180);
        tft.print(F("Calibrate air"));
        tft.setTextSize(2);
        tft.setCursor(2,216);
        tft.print(next);
        tft.setCursor(155,216);
        tft.print(F("Select "));
      }
      if (leftclick==3) {
        tft.setTextSize(3);
        tft.setCursor(2,180);
        tft.print(F("Calibrate O  "));
        tft.setCursor(200,190);
        tft.setTextSize(2);
        tft.print(F("2"));
        tft.setTextSize(2);
        tft.setCursor(2,216);
        tft.print(next);
        tft.setCursor(155,216);
        tft.print(select);
      }
      if (leftclick==4) {
        tft.setTextSize(3);
        tft.setCursor(2,180);
        tft.print(F("Calibrate He"));
        tft.setTextSize(2);
        tft.setCursor(2,216);
        tft.print(next);
        tft.setCursor(155,216);
        tft.print(select);
      }
    
      if (leftclick==5) {
        tft.setTextColor(0x779F, 0x000);
        tft.setTextSize(2);
        tft.setCursor(2,160);
        tft.print(F("CO sensor "));
        if (enableCO) {
          tft.print(coVact);
          tft.print(mV);
        } else {
          tft.print(F("n/a"));
        }
        tft.setTextColor(0x779F, 0x000);
        tft.setCursor(2,180);   
        tft.print(O2sensor);
        tft.print(oxVact,2);
        tft.print(mV);
    
        tft.setTextColor(0x779F, 0x000);
        tft.setCursor(2,198);   
        tft.print(HEsensor);
        if (enableHe) {
          tft.print(heVact,2);
          tft.print(mV);
        } else {
          tft.print(F("n/a"));
        }
    
        tft.setTextSize(2);
        tft.setCursor(2,216);
        tft.print(F("Exit"));
        menuCounter = millis(); //reset menu timeout
      }
    }
     
  4. Miyaru

    Miyaru Tec Instructor ScubaBoard Sponsor

    # of Dives: 2,500 - 4,999
    Location: Area51
    792
    820
    93
    Code for the buttons:

    Code:
    void LeftButton() {
      buttonStateLeft = digitalRead(bLeft);
      static unsigned long last_interrupt_time = 0;
      unsigned long interrupt_time = millis();
      if (interrupt_time - last_interrupt_time > 400)
      {
        //Serial.println("interrupt left button");
        leftclick=leftclick+1;
        if (leftclick==4 and enableHe==false) {
          leftclick=leftclick+1;
        }
        if (leftclick>5) {
          leftclick=0;
        }
      }
      last_interrupt_time = interrupt_time;
      menuCounter = millis(); //reset menu timeout
    }
    void RightButton() {
      buttonStateRight = digitalRead(bRight);
      static unsigned long last_interrupt_time = 0;
      unsigned long interrupt_time = millis();
      if (interrupt_time - last_interrupt_time > 400)
      {
        //Serial.println("interrupt right button");
        // ----menu----Change MOD----------------
        if (leftclick==1) {
          if (MODcalc >= 1.6) {
            MODcalc = 1.0;
          }
          MODcalc = MODcalc+0.1;
        }
        // ----menu----Calibrate with air----------------
        if (leftclick==2) {
          tft.setTextColor(0xFFFF, 0x000);
          tft.setTextSize(2);
          tft.setCursor(2,210);
          tft.print(F("   Calibrating air  "));
          action = 1;
        } //end of menu action
        // ----menu----Calibrate with 100% oxygen----------------
        if (leftclick==3) {
          tft.setTextColor(0xFFFF, 0x000);
          tft.setTextSize(2);
          tft.setCursor(2,210);
          tft.print(F("Calibrating 100% O2"));
          action = 2;
        } //end of menu action
        // ----menu----Calibrate with 100% helium----------------
        if (leftclick==4) {
          tft.setTextColor(0xFFFF, 0x000);
          tft.setTextSize(2);
          tft.setCursor(2,210);
          tft.print(F("Calibrating 100% He"));
          action = 3;
        } //end of menu action
      }
      last_interrupt_time = interrupt_time;
      menuCounter = millis(); //reset menu timeout
    }
    
     
    TTPaws likes this.
  5. ScubaBunga

    ScubaBunga Nassau Grouper

    # of Dives: 500 - 999
    Location: wright city, mo
    194
    128
    43
    I finally got my sensors but when connected up give me all sorts of readings, none of which are accurate! I simply have the arduino nano connected to my laptop, CO sensor pin 15 to 5v on arduino, pin 14 to ground and pin 10 to the A2 pin of the nano. The simple code to read the ZE07CO.dacReadPPM(). It starts up and reads 500ppm and then jumps all over the place - like something is not connected - in fact I get the same values if I remove the pin from the arduino. Any ideas? This should be about the most simple circuit I've put together, so not sure what's going on - I suppose it could be a bad sensor or board?
    Will check the voltage and anything else on the connections I can later...I was working on this as SpaceX landed the astronauts! Go SpaceX!
     
  6. Miyaru

    Miyaru Tec Instructor ScubaBoard Sponsor

    # of Dives: 2,500 - 4,999
    Location: Area51
    792
    820
    93
    If you test CO (e.g. with smoke), make sure you test an airflow. If the smoke/gas/air is not flowing over the sensor, the sensor will be quickly saturated and output 2V (the maximum value).
    The drawback of using the sensor directly connected to the arduino, is that it relies completely on the reference voltage. Using an internal reference is unreliable, using either 3.3V or 5V connected to the reference pin doesn't provide much more reliability, and the culprit is the arduino itself.
    If you measure the output with a digital voltmeter, you'll see quite a steady output ranging from 0.4V up to just over 2V, depending on the amount of CO in the test gas. Using an AD converter like the ADS1115 results in much more reliability: 0.4V rising to 0.48V is already a red flag for breathing gas, and the AD converter shows that difference in a reliable way.
     
  7. ScubaBunga

    ScubaBunga Nassau Grouper

    # of Dives: 500 - 999
    Location: wright city, mo
    194
    128
    43
    So this was just in air, no test gas. So it’s the 5v coming from arduino that is the issue?
     
  8. Miyaru

    Miyaru Tec Instructor ScubaBoard Sponsor

    # of Dives: 2,500 - 4,999
    Location: Area51
    792
    820
    93
    No, it's the reference voltage. Google Arduino Vref
     
  9. stepfen

    stepfen ScubaBoard Supporter ScubaBoard Supporter

    # of Dives: 200 - 499
    Location: Greece
    678
    479
    63
    Hello again,

    I just wanted to report that I've got the MD61 sensor few days ago. After initial priming (producer suggests to let it warm for up to 48h if the sensor hasn't been used for months or so), I just used it with some pure He my LDS gave to me.
    I have got 510mV of output with 100% He and it is slowly increasing (I won't let He run for too long as they gave me only a small tank for trials and I know this thing is not cheap). How does that compare to the output of MD62???

    For now I am using a striped down version of @Miyaru 's software. I have removed CO, menus, user calibration/EEPROM storage etc. I also assume linear curve (0mV for 0% He, 510mV for 100%) but I haven't tried with mixes in between.

    Once finished I plan to donate a unit to my LDS as a gift - I don't dive trimix anyway :) I just want to help them because the analyzer they have runs on the mains (AC220V) hence they can't use it on boats etc.

    With the help of SB I have saved a lot of money spend on them (maintenance, equipment, unnecessary training etc etc), hence I want to help them a bit too.

    All the best.
     
  10. Lidiano

    Lidiano Garibaldi

    # of Dives: 500 - 999
    Location: ITALY
    2
    1
    3
    Hi everyone, I'm new to the forum, I found you because I was looking for a circuit to build just like this, I wanted to ask the author @MiyaruTe of this if you could put a more detailed list of the components you use, and then in another thing, I don't I am expert, it is possible to use a bigger Touch Screen monitor so you could also insert the two buttons inside the display, another thing I order the components and I hope if I have problems you help me,
    thank you
     

Share This Page