Capteur de CO2 Senseair S8 - code ultrasimple

Bonjour,
Ce capteur est aussi parfois appelé Senseair LP8. Le plus sur moyen de savoir que vous avez le même que moi est la référence 004-0-0053
Il est probable que mon code fonctionne avec toutes les références sensair.
Il existe de nombreuses ressources pour le sensair S8. J'ai tenté dans un premier temps d'utiliser les librairies mais elles sont souvent mal documentées, incomplétes, difficiles à porter sur un autre microcontroleur et surtout difficiles à comprendre (pour moi). Mes ressources ont été:

Je vous propose un exemple ultrasimple sans librairie portable (à mon avis) sur n'importe quel Arduino.
Les branchements:

Le code:

#include <SoftwareSerial.h>
SoftwareSerial sSerial (12, 14);//RX=12 TX=14 utiliser un port hardware si disponible
const byte CO2Command[] = {0xFE, 0X04, 0X00, 0X03, 0X00, 0X01, 0XD5, 0XC5};
byte Response[] = {0, 0, 0, 0, 0, 0, 0};
unsigned long val;

void setup() {
  sSerial.begin(9600);
  Serial.begin(9600);
  delay(100);
  Serial.print("start");
}

void loop() {
  sSerial.write(CO2Command, 8);
  sSerial.readBytes(Response, 7);
  while (sSerial.available()) sSerial.read();//vidage du buffer
  if (Response[0] == 254) {
    val = Response[3] * 256 + Response[4];
    Serial.println(val);
  }
  else Serial.println("erreur CO2");
  delay(2000);
}

Quelques remarques:
Le code est ici pour un feather huzzah. Le branchement se fait selon les ports que vous mettez en ligne 2.
Pour un Arduino UNO, on peut par exemple remplacer avec des ports disponibles:
SoftwareSerial sSerial (11, 12);
Si un port Serie Hardware est disponible, il est préférable de l'utiliser. Sur arduino mega, en utilisant le port Serial 1, le code devient:

const byte CO2Command[] = {0xFE, 0X04, 0X00, 0X03, 0X00, 0X01, 0XD5, 0XC5};
byte Response[] = {0, 0, 0, 0, 0, 0, 0};
unsigned long val;

void setup() {
  Serial1.begin(9600);//Brancher le S8 sur RX1 et TX1
  Serial.begin(9600);
  delay(100);
  Serial.print("start");
}

void loop() {
  Serial1.write(CO2Command, 8);
  Serial1.readBytes(Response, 7);
  while (Serial1.available()) Serial1.read();//vidage du buffer
  if (Response[0] == 254) {
    val = Response[3] * 256 + Response[4];
    Serial.println(val);
  }
  else Serial.println("erreur CO2");
  delay(2000);
}

Bien sur, ces exemples ne permettent que de lire la valeur en ppm du capteur. Il existent bien d'autre fonctionnalités comme l'étalonnage ou le relevé des erreurs sur ce capteur. Je compléterais mon post avec ces fonctionnalités si vous me le demandez mais le code sera un peu moins simple!

Petit complément avec la correction des erreurs. Comme prévu dans mon post précédent, le code est moins simple.
Le code vérifie si:

  • le premier octet est bien 254
  • le nombre d'octet reçu est bien 7
  • le capteur retourne une erreur

J'ai ajouté des commandes non utilisées en début de programme.
Voir dans la notice du capteur pour l'interprétation des numéros d'erreur du capteur et les autres commandes:

J'ai pas encore trouvé comment vérifier le CRC de la réponse (les deux derniers octets de vérification). Si quelqu'un a la solution, je veux bien de l'aide!

A noter: des erreurs ont lieu de temps en temps. En particulier"err rs232 Valeur CO2: nombre d octets retourné différent de 7". Le software serial est peut être limite...

#include <SoftwareSerial.h>

SoftwareSerial sSerial (12, 14);//RX=12 TX=14 utiliser un port hardware si disponible
const byte CO2Command[] = {0xFE, 0X04, 0X00, 0X03, 0X00, 0X01, 0XD5, 0XC5};
const byte MeterStatus[] = {0xFE, 0X04, 0X00, 0X00, 0X00, 0X01, 0X25, 0XC5};
const byte Acknowledgement[] = {0xFE, 0X03, 0X00, 0X00, 0X00, 0X01, 0X90, 0X05};
const byte ABCperiod[] = {0xFE, 0X03, 0X00, 0X1F, 0X00, 0X01, 0XA1, 0XC3};
byte Response[] = {0, 0, 0, 0, 0, 0, 0};
int nb;
unsigned long valCO2;
unsigned long valStatus;

void setup() {
  sSerial.begin(9600);
  Serial.begin(9600);

  delay(500);
  Serial.println("Debut");

}

void loop() {
  String s = "";
  //meter status a sensair S8
  memset(Response, 0, 7);
  sSerial.write(MeterStatus, 8);
  nb = sSerial.readBytes(Response, 7);
  while (sSerial.available()) sSerial.read();//vidage du buffer
  if (nb == 7) {
    if (Response[0] == 254) {
      valStatus = Response[3] * 256 + Response[4];
    }
    else s = "err rs232 Status: premier octet différent de 254: " + (String)Response[0];
  }
  else s = "err rs232 Status: nombre d octets retourné différent de 7: " + (String)nb;


  //demande CO2 a sensair S8
  memset(Response, 0, 7); //mettre 7 x 0 dans le tableau Response
  sSerial.write(CO2Command, 8);
  nb = sSerial.readBytes(Response, 7);
  while (sSerial.available()) sSerial.read();//vidage du buffer
  if (nb == 7) {
    if (Response[0] == 254) {
      valCO2 = Response[3] * 256 + Response[4];
    }
    else s = "err rs232 Valeur CO2: premier octet différent de 254: " + (String)Response[0];
  }
  else s = "err rs232 Valeur CO2: nombre d octets retourné différent de 7: " + (String)nb;

  //compilation des erreurs
  if (valStatus > 0) s = "erreur status: " + (String)valStatus;

  //affichage
  Serial.print("CO2:");

  if (s == "") {
    Serial.print((String)valCO2);
    Serial.print(" ppm");
  }

Il s'agit apparemment d'un CRC16 :
https://github.com/jcomas/S8_UART/blob/main/src/modbus_crc.cpp

Effectivement. Merci!

Pour prendre en compte le CRC, j'ai upgradé mon code avec l'exemple du lien de @hbachetti ci-dessus (à peine modifié pour se passer de la librairie).

On ne peut plus dire que le code est ultrasimple! J'espère que ça reste compréhensible...

#include <SoftwareSerial.h>

SoftwareSerial sSerial (12, 14);//RX=12 TX=14 utiliser un port hardware si disponible
//Commandes senseair
const byte CO2Command[] = {0xFE, 0X04, 0X00, 0X03, 0X00, 0X01, 0XD5, 0XC5};
const byte MeterStatus[] = {0xFE, 0X04, 0X00, 0X00, 0X00, 0X01, 0X25, 0XC5};
const byte Acknowledgement[] = {0xFE, 0X03, 0X00, 0X00, 0X00, 0X01, 0X90, 0X05};
const byte ABCperiod[] = {0xFE, 0X03, 0X00, 0X1F, 0X00, 0X01, 0XA1, 0XC3};

byte Response[] = {0, 0, 0, 0, 0, 0, 0};
int nb;
unsigned int valCO2;
unsigned int valStatus;
unsigned int crc, crcRead;


/* Table of CRC values for high–order byte */
static const byte auchCRCHi[] = {
  0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
  0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
  0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01,
  0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
  0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81,
  0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
  0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01,
  0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
  0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
  0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
  0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01,
  0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
  0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
  0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
  0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01,
  0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
  0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
  0x40
} ;

/* Table of CRC values for low–order byte */
static const byte auchCRCLo[] = {
  0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7, 0x05, 0xC5, 0xC4,
  0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09,
  0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD,
  0x1D, 0x1C, 0xDC, 0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,
  0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32, 0x36, 0xF6, 0xF7,
  0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A,
  0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE,
  0x2E, 0x2F, 0xEF, 0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,
  0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1, 0x63, 0xA3, 0xA2,
  0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F,
  0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB,
  0x7B, 0x7A, 0xBA, 0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,
  0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0, 0x50, 0x90, 0x91,
  0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C,
  0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88,
  0x48, 0x49, 0x89, 0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
  0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83, 0x41, 0x81, 0x80,
  0x40
};


void setup() {
  sSerial.begin(9600);
  Serial.begin(9600);

  delay(500);
  Serial.println("Debut");

}

void loop() {
  String s = "";
  //meter status a senseair S8
  memset(Response, 0, 7);
  sSerial.write(MeterStatus, 8);
  nb = sSerial.readBytes(Response, 7);
  while (sSerial.available()) sSerial.read();//vidage du buffer
  if (nb == 7) {
    if (crc == crcRead) {
      if (Response[0] == 254) {
        valStatus = Response[3] * 256 + Response[4];
      }
      else s = "err rs232 Status: premier octet différent de 254 " + (String)Response[0];
    }
    else s = "err rs232 Valeur crc";
  }
  else s = "err rs232 Status: nombre d octets retourné différent de 7: " + (String)nb;


  //demande CO2 a sensair S8
  memset(Response, 0, 7); //mettre 7 x 0 dans le tableau Response
  sSerial.write(CO2Command, 8);
  nb = sSerial.readBytes(Response, 7);
  while (sSerial.available()) sSerial.read();//vidage du buffer
  crc = modbus_CRC16(Response, 5);
  crcRead = (((unsigned int)Response[6]) << 8) + (unsigned int)Response[5];
  if (nb == 7) {
    if (crc == crcRead) {
      if (Response[0] == 254) {
        valCO2 = Response[3] * 256 + Response[4];
      }
      else s = "err rs232 Valeur CO2: premier octet différent de 254 " + (String)Response[0];
    }
    else s = "err rs232 Valeur crc";
  }
  else s = "err rs232 Valeur CO2: nombre d octets retourné différent de 7: " + (String)nb;

  //compilation des erreurs
  if (valStatus > 0) s = "erreur status: " + (String)valStatus;

  //affichage
  Serial.print("CO2:");

  if (s == "") {
    Serial.print((String)valCO2);
    Serial.print(" ppm");
  }
  else {
    Serial.print(s);

  }
  Serial.println("");//retour à la ligne
  delay(4000);
}

unsigned int modbus_CRC16 (byte * puchMsg, unsigned int usDataLen ) {
  /*
      puchMsg  -> message to calculate CRC upon
      usDataLen -> quantity of bytes in message
  */
  byte uchCRCHi = 0xFF ;             /* high byte of CRC initialized */
  byte uchCRCLo = 0xFF ;             /* low byte of CRC initialized */
  unsigned int uIndex ;                     /* will index into CRC lookup table */

  while (usDataLen--)                   /* pass through message buffer */
  {
    uIndex = uchCRCLo ^ *puchMsg++ ;  /* calculate the CRC */
    uchCRCLo = uchCRCHi ^ auchCRCHi[uIndex] ;
    uchCRCHi = auchCRCLo[uIndex] ;
  }
  return (uchCRCHi << 8 | uchCRCLo) ;
}

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.