The output from 968-006 SO2 sensor fluctuates

Hello I have a problem with the 968-006 SO2 sensor. Sometimes when I run the arduino code from the ULPSM github example, the recheck settings shows up even though I haven’t changed anything. It works mostly but sometimes it doesn’t I don’t understand why. But when it does work, the ppm values fluctuates by 0-2 ppm in clean environment. What I’ve seen is that the safe levels are within 0.15 ppm. I’m using MCP6002 opamp for it and this is the diagram.

My setup looks like this(It’s messy lol):

The data I’ve logged:

output_2026-02-20_13-58-28.log (40.0 KB)

output_2026-02-14_17-54-28.txt (38.9 KB)

The graph for the data:
(Feb 14)

I can’t post the Feb 20 one because of 3 media limit.

Hi @peanutzy25 ,

The first thing to try is to add by-pass capacitors to near each op-amp’s supply lines. Say 100nF cap from VDD to VSS. They are pretty essential.

Cheers ,heke

3 Likes

Hello @peanutzy25,

That first discontinuity from 95 to 0 at index 3000 suggests an integer overflow.

As a simple example, consider a uint8_t:

Actual Perceived
254 254
255 255
256 0
257 1

This could be an actual overrun or an error in math e.g. division. Watch out for implicit cast operations. IMO, it’s better to explicitly cast your numbers to prevent this type of hazard.

Sincerely,

APDahlen

P.S. There is more going on than just the classic wrap, as the number continued negative past index 10,000 instead of jumping back up. Others may have more to say about this behavior.

3 Likes

Hello @heke Thanks for the reply! I have added the 100nF to both the mcp6002 ic. The signals look much more cleaner but it drifts away after some time.

2_100nF_setup_NOTCHARGING.log (29.3 KB)

On the first few minutes the data looks like it is very stable but after that it drifts up and down significantly but it seems like the high-frequency noise has been eliminated.

Hi @APDahlen It’s actually a command I sent on the serial monitor where if it receives the letter ‘Z’, it will make the latest current reading as the baseline for clean air or 0 ppm. It’s used to calibrate the sensor.

Do some investigation as to where the reference voltage for the ADC conversion is coming from.

If (for example) it’s the the USB power supply voltage, any variation in such will be directly reflected in the conversion result when measuring an input that is not also proportional to that voltage.

Use of a suitable voltage reference IC is needful for such cases. Considering that the nominal output of the sensor here appears to span only 60mV while the spec for USB power can vary by ±250mV and still be within spec, it’s a case that would seem to qualify.

1 Like

I forgot that I updated my diagram. The AREF pin on the arduino is connected to the 3.3v output. Here is my updated diagram

Here is my code for reference

/*#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>*/
#include "Arduino.h"
#include "ULP.h"

//#define SCREEN_WIDTH 128
//#define SCREEN_HEIGHT 64


const int C1 = A0;
const int T1 = A3;

const int n = 5; //gas sensor seconds
const int m=1; //temperature sensor seconds
const int s=10; //seconds to read all sensor

const float Sf1 = 42.64;

unsigned long etime;

SO2 sensor1(C1, T1, Sf1);
//Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);

void setup() {
  Serial.flush();
  Serial.begin(9600);
  /*if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // 0x3C is common address
    Serial.println("test");
    for(;;);
  }

  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.setCursor(0,0);*/

  // put your setup code here, to run once:
  analogReference(EXTERNAL);
  sensor1.pVcc = 3.31;
  //sensor1.pVcc = 5.04;  //analogRead Reference Voltage, maybe measure Aref??
  sensor1.pVsup = 3.31;  //voltage supplied to V+ of ULP, default is 3.3 Volts, probably should measure this as well.
    // initialize serial communications at 9600 bps:

  Serial.println();
  Serial.println("Setting Up.");
  Serial.println(analogRead(A0)); // VGAS
  Serial.println(analogRead(A3)); // VTEMP
  /*display.println("Setting Up.");
  display.println(analogRead(A0));
  display.println(analogRead(A3));
  display.display();
  delay(1000);
  display.clearDisplay();*/

  Serial.print("Vsup for all sensors = ");
  Serial.println(sensor1.pVsup);
  Serial.print("Vcc for all sensors = ");
  Serial.println(sensor1.pVcc);
  Serial.print("Vref for sensor 1 = ");
  Serial.println(sensor1.pVref);
  /*display.print("Vsup for all sensors = ");
  display.println(sensor1.pVsup);
  display.print("Vcc for all sensors = ");
  display.println(sensor1.pVcc);
  display.print("Vref for sensor 1 = ");
  display.println(sensor1.pVref);
  display.display();
  delay(1000);
  display.clearDisplay();*/

  Serial.println("Remove Sensor.");
  /*display.println("Remove Sensor.");
  display.display();
  delay(1000);*/
  if (sensor1.OCzero(n)) {
    Serial.print("Vref new = ");
    Serial.println(sensor1.pVref_set);
  } else {
    Serial.println("Recheck Settings, Zero out of range");
    while (1) {
      Serial.println(analogRead(A0));
      delay(1000);
      }
  }


  Serial.println("Finished Setting Up, Replace Sensor Now.");
  Serial.println("T1, mV, nA, C1");
  etime = millis();

}

void loop() {
  // put your main code here, to run repeatedly:

  while (Serial.available()) {
    if (Serial.read() == 'Z') {
      Serial.println("Zeroing");
      sensor1.zero(); //Uses last values read of Izero and Tzero
      Serial.print("Izero, Tzero: ");
      Serial.print(sensor1.pIzero);
      Serial.print(", ");
      Serial.println(sensor1.pTzero);
    }
  }

  if (millis() - etime > (s * 1000)) {//also handles case where millis rolls over, but may cause timing issue.
    etime = millis();

    sensor1.getIgas(n);
    sensor1.getTemp(m);
    sensor1.getConc(sensor1.pT);

    Serial.print(sensor1.convertT('C')); //use 'C' or 'F' for units
    Serial.print("C, ");
    Serial.print(sensor1.pVgas);
    Serial.print("mV, ");
    Serial.print(sensor1.pInA);
    Serial.print("nA, ");
    Serial.print(sensor1.convertX('M')); //use 'M' or 'B' for units or Serial.println(sensor1.pX); to just print ppb
    Serial.print("ppm");
  } 

}

@peanutzy25

Were you able to also add a voltage reference for the Op amps like Rick had suggested?

As of now I don’t have a voltage reference IC. How should the connection be to the op amps if using a voltage reference IC?

@peanutzy25

Example item 296-REF4132B50DBVRCT-ND.
This would be in series between the 5v of the Arduino and the V+ of an Opamp.
https://www.digikey.com/en/products/detail/texas-instruments/REF4132B50DBVR/12642173


To work with your setup you would need a PA0086-ND and solder the chip to that adapter.
https://www.digikey.com/en/products/detail/chip-quik-inc/PA0086/5014711

If you have other voltage sources you could use options from the below link.
https://www.digikey.com/en/products/filter/power-management-pmic/voltage-reference/693?s=N4IgjCBcoGwJxVAYygMwIYBsDOBTANCAPZQDaIALGGABxwDsIAuoQA4AuUIAyuwE4BLAHYBzEAF9CYevQTQQKSBhwFiZEACYKcaRBYgOXXoNESpcOogVoseQiUjkADM3FugA

Two things I note.

First, from the datasheet for the 968-006, it states that the "Power-On Stabilization Time is 60 minutes, which means it should be powered on for 60 minutes before one can consider the measurements taken as valid. Have you taken that into account?

Second, based on my calculations, using the full 10-bit resolution of the ATmega328 of the Arduino Uno using a 3.3V reference voltage, one bit is equivalent to about 3.2mV, which, according to the datasheet for the sensor board, is slightly more than the typical output change of 1ppm when using a 3.0V reference.

So, basically one LSB is about equivalent to 1ppm, and therefore 20 LSB would be the full range of the sensor’s 20ppm output. This is only 1.2% of the full range of the ADC. It’s very easy for the ADC to misread any given sample by an LSB or two, which means one or two ppm in this application.

I haven’t looked into the include files in your code above, but one should make sure that you’re giving the Arduino the best chance to get an accurate reading by slowing the ADC clock down as much as possible (this gives a longer sample time) and by taking multiple samples and averaging them (this helps to filter out noise).