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 :

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 ;)