About the Grove O₂ gas sensor

For less than 50€, you can get your hands on this tiny and easy to use sensor to monitor O₂ concentration up to 25%.

https://www.seeedstudio.com/Grove-Oxygen-Sensor-ME2-O2-2-p-1541.html

The problem is, the documentation is really crappy.
After hours of searching and testing to find the actual working example code for this sensor, I decided to get calibrated values for this sensor and interpolate to get the correct formula.

Compatibility

This sensor is compatible with 5V and 3.3V boards.

Measurement range

This sensor will provide reliable measurements for an O₂ concentration between 0% and 25%.

This sensor is an electrochemical cell, thus its lifespan will be finite. The datasheet says about 2 years but that highly depends on your environment and the concentration you're trying to detect.

Preheating time

Some pages will say that you need 48 Hrs before reading data. This is not true. Generally, in normal conditions, say, between 10°C and 30°C, the sensor will give a very reasonable output without preheating time.

That being said, it is a good thing to let it heat for a few minutes before actually reading data, so the output is more stable. Give it 20 minutes max.

Example codes

There is a documentation page available at https://seeeddoc.github.io/Grove-Gas_Sensor-O2/

The code is completely wrong, and will give you wrong values. Just by looking at the expected values for Vout (186 V ? Hmmm), you can tell that it's clearly not adapted. The values are in mA, and are related to the current output of the actual sensor (the ME2-O2-Ф20 on the board). Somehow the guys at Seeed studio messed up their example code.

So forget this code.

There is also some documentation at this page : http://wiki.seeedstudio.com/Grove-Gas_Sensor-O2/ where they provide an alternate example code.

Forget it also.

The calculation they make is as follow:

Concentration_O₂ = (Vout * 0.21 / 2) * 100;

While they do not explain why this would work, it actually give wrong values most of the time.

(FYI : the 0.21 comes from the actual amplifier that has a ratio of 210, but that's it)

A last example I found in a forum is a zip file (containing C code for Arduino) discussed in the related comment on the Robotshop forums : https://www.robotshop.com/community/forum/t/grove-o2-gas-sensor/24167/16

What this code does is basically assume that you will calibrate it in an open-air environment (20.8% O₂), and that 0V = 0ppm. This is not really accurate, see below.

Linearity

While electrochemical cell sensors are supposed to be strictly linear, there is always some differences in real life. This sensor is no exception; and if you assumes its linearity and that 0V = 0ppm, you might end up with values that are quite different when they are far from your calibration point (that will likely be ambient air, so 20.8% or so).

If you plan to use it in the full range of its capabilities, you have to calibrate it fully. That's what we'll try to do next.

Calibration

It's not really a calibration per se, but we're going to find data points where we know the dioxygen concentration for sure, and plot the whole thing, and hopefully find an accurate-enough linear approximation of the formula that we can use in real-world situations.

For this, I used a MAP Mix Provectus from Dansensor that can output a gas that has a calibrated concentration. With a N₂ and a O₂ bottle, I could expose the sensor to different concentrated gas from 2% to 25% of O₂.

Here are the data points I could get :

diagram

As we can see, it's not perfectly linear : there's a little offset (about 0.5%, still significant).

The calculated linear regression give a R² of 0.9986 which is not bad. The slope is 14.581.

So the formula would be :

// For Vout in volts
Concentration_O₂ = Vout * 14.581 + 0.5483; // in percent

You could also use the multimap function proposed in one of the examples with updated values :

float VoutArray[] = { 0, 0.13, 0.19, 0.24, 0.30, 0.35, 0.43, 0.49, 0.57, 0.64, 1.02, 1.31, 1.42, 1.68 };
float O2ConArray[] = { 0.5, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 21, 25 };
unsigned int ConArraySize = 14;

// This code uses MultiMap implementation from http://playground.arduino.cc/Main/MultiMap
float FmultiMap(float val, float * _in, float * _out, uint8_t size)
{
    // Take care the value is within range
    if (val <= _in[0]) return _out[0];
    if (val >= _in[size-1]) return _out[size-1];

    // Search right interval
    uint8_t pos = 1;  // _in[0] already tested
    while(val > _in[pos]) pos++;

    // This will handle all exact "points" in the _in array
    if (val == _in[pos]) return _out[pos];

    // Interpolate in the right segment for the rest
    return (val - _in[pos-1]) * (_out[pos] - _out[pos-1]) / (_in[pos] - _in[pos-1]) + _out[pos-1];
  }

// For Vout in volts
Concentration_O₂ = FmultiMap(Vout, VoutArray, O2ConArray, ConArraySize);

All in all, a complete solution would be along the lines of that (Vref = 5V or 3.3V depending on your board) :

// Read Vout on average
unsigned long sum = 0;
for (int i=0; i<32; i++) {
    sum += analogRead(O2_SENSOR_PIN);
    delay(10); // So we sample on a larger time interval
}

// Measured Vout (>> 5 = quick division by 2^5 = 32)
float Vout = (sum >> 5) * (VRef / 1023.0);
// Either one of those two - your preference :
o2_concentration_in_percent = Vout * 14.581 + 0.5483;
o2_concentration_in_percent = FmultiMap(Vout, VoutArray, O2ConArray, ConArraySize);

And 🎉! It should give you something quite accurate (for a sensor this unexpensive).

Disclaimer

On some forums, some users claim that the on-board circuitry for this gas sensor has changed over time, and that different versions exist under the same denomination. That can be true, and in this case, the calibration that I have done above may be off with your hardware, so be advised ;)