Nitrox/Trimix & CO analyzer

Please register or login

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. Log in or Register now!

OP
Miyaru

Miyaru

Technical instructor
ScubaBoard Sponsor
Scuba Instructor
Messages
1,517
Reaction score
1,679
Location
Malta
# of dives
5000 - ∞
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);
}
 
OP
Miyaru

Miyaru

Technical instructor
ScubaBoard Sponsor
Scuba Instructor
Messages
1,517
Reaction score
1,679
Location
Malta
# of dives
5000 - ∞
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;
  }
 
OP
Miyaru

Miyaru

Technical instructor
ScubaBoard Sponsor
Scuba Instructor
Messages
1,517
Reaction score
1,679
Location
Malta
# of dives
5000 - ∞
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
  }
}
 
OP
Miyaru

Miyaru

Technical instructor
ScubaBoard Sponsor
Scuba Instructor
Messages
1,517
Reaction score
1,679
Location
Malta
# of dives
5000 - ∞
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
}
 

ScubaBunga

Contributor
Messages
233
Reaction score
168
Location
wright city, mo
# of dives
500 - 999
I just ordered two of these. The stats and ease of use look better than the SPEC sensor I originally used. Shipping will be another month it seems with continued slowness due to covid.. but hopefully not too long.

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!
 
OP
Miyaru

Miyaru

Technical instructor
ScubaBoard Sponsor
Scuba Instructor
Messages
1,517
Reaction score
1,679
Location
Malta
# of dives
5000 - ∞
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!
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.
 

ScubaBunga

Contributor
Messages
233
Reaction score
168
Location
wright city, mo
# of dives
500 - 999
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.
So this was just in air, no test gas. So it’s the 5v coming from arduino that is the issue?
 

stepfen

ScubaBoard Supporter
ScubaBoard Supporter
Messages
831
Reaction score
651
Location
Greece
# of dives
200 - 499
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.
 

Lidiano

Banned
Messages
3
Reaction score
1
Location
ITALY
# of dives
500 - 999
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
 
https://www.shearwater.com/products/peregrine/

Top Bottom