Manually calibrating the S8 CO2 sensor

The Senseair S8 sensor has an automatic baseline calibration (ABC). This means that it looks for the lowest value of the CO2 in the last 7-14 days and resets itself to this value. So it assumes that the room that you have the sensor in gets outside CO2 values every week or so.

Sometimes, especially in newly build sensors, the CO2 value can be totally off. This will fix by itself by waiting for 1-2 weeks until the baseline calibration kicks in. However if you want to manually force a calibration you can do so as well by following these steps:

  1. Power on the DIY air quality sensor
  2. Make sure you have outside CO2 levels around the sensor. Best you do this calibration outside. Also make sure the sensor was surrounded by outside CO2 levels for at least 15 minutes.
  3. Use a jumper cable (male to male) and connect the bCAL_in pin of the Senseair S8 to the Ground (e.g. the GND pin on the Wemos)
  4. Connect them for more than 4 seconds but less than 8 seconds. It is very important that you exactly hit this range. So I recommend using a stopwatch and time 6 seconds.

By the way, if you hold the connection for more than 16 seconds it will calibrate to a Zero baseline. So make sure you not holding it too long.

Sometimes it takes a few tries until the calibration is successful.


Well I tried resetting my SenseAir and all I get now is a starting value which starts low around 300ish, then jumps up to 65342, which should not be the case. I tried resetting multiple times, and even tried to reset to 0 by connecting the 2 pins for over 16 seconds.
Pins are all soldered properly, and was previously getting values of around 1200 which I believe were normal.

Any suggestions?

It seems the S8 manual calibration needs quite a few tries sometimes. I would make sure you do not go longer than 8 seconds because otherwise you calibrate it to 0ppm but you want to calibrate to 400ppm which is the CO2 value outside.

My Senseair somehow managed to get miscalibrated - values when taken outside were hovering around 1400 and inside were in the 1700s. I didn’t have much luck with the manual calibration using the Ground pin, what did work is the manual calibration sketch example from the S8_UART library. (The RX and TX pin for the AirGradient are 2,0).

   To do a manual calibration 

#include <Arduino.h>
#include "s8_uart.h"   

#define DEBUG_BAUDRATE 115200

#if (defined USE_SOFTWARE_SERIAL || defined ARDUINO_ARCH_RP2040)
  #define S8_RX_PIN 2        // Rx pin which the S8 Tx pin is attached to (change if it is needed)
  #define S8_TX_PIN 0         // Tx pin which the S8 Rx pin is attached to (change if it is needed)
  #define S8_UART_PORT  1     // Change UART port if it is needed

#define COUNTDOWN (6*60)      // Time in seconds to wait outside before a manual calibration starts

  SoftwareSerial S8_serial(S8_RX_PIN, S8_TX_PIN);
  #if defined(ARDUINO_ARCH_RP2040)
    REDIRECT_STDOUT_TO(Serial)    // to use printf (Serial.printf not supported)
    UART S8_serial(S8_TX_PIN, S8_RX_PIN, NC, NC);
    HardwareSerial S8_serial(S8_UART_PORT);   

S8_UART *sensor_S8;
S8_sensor sensor;

void setup() {

  // Configure serial port, we need it for debug

  // Wait port is open or timeout
  int i = 0;
  while (!Serial && i < 50) {
  // First message, we are alive

  // Initialize S8 sensor
  sensor_S8 = new S8_UART(S8_serial);

  // Check if S8 is available
  int len = strlen(sensor.firm_version);
  if (len == 0) {
      Serial.println("SenseAir S8 CO2 sensor not found!");
      while (1) { delay(1); };

  // Show basic S8 sensor info
  Serial.println(">>> SenseAir S8 NDIR CO2 sensor <<<");
  printf("Firmware version: %s\n", sensor.firm_version);
  sensor.sensor_id = sensor_S8->get_sensor_ID();
  Serial.print("Sensor ID: 0x"); printIntToHex(sensor.sensor_id, 4); Serial.println("");

  // Countdown waiting outside
  Serial.println("Now, you put the sensor outside and wait.");
  Serial.println("Countdown begins...");
  unsigned int seconds = COUNTDOWN;
  while (seconds > 0) {
    printf("Time remaining: %d minutes %d seconds\n", seconds / 60, seconds % 60);
  Serial.println("Time reamining: 0 minutes 0 seconds");

  // Start manual calibration
  Serial.println("Starting manual calibration...");
  if (!sensor_S8->manual_calibration()) {
    Serial.println("Error setting manual calibration!");
    while (1) { delay(10); }


void loop() {

  static unsigned int elapsed = 0;

  // Wait 2 sec lamp cycle
  elapsed += 2;

  // Check if background calibration is finished
  sensor.ack = sensor_S8->get_acknowledgement();
  if (sensor.ack & S8_MASK_CO2_BACKGROUND_CALIBRATION) {
    printf("Manual calibration is finished. Elapsed: %u seconds\n", elapsed);
    while (1) { delay(10); }
  } else {
    Serial.println("Doing manual calibration...");


I plugged the USB into a laptop and took the sensor outside, the serial showed a 6 minute countdown at the end of which the sensor calibrated.

1 Like

could this be the reason why one of my SensAir S8 shows me 495ppm and another 385 ppm and they are placed next to each other? I finished the installation 2 days ago, so it hasn’t gone 2 week.

Yes, that is likely a calibration issue and overtime they should come to the same level with the Automatic Baseline Calibration.