Serial MODBBUS communication with Senseair LP8 CO2 Sensor

Hello folks,

I am trying to establish communication between an Arduino MKR1000 and a Senseair LP8 CO2 sensor. Unfortunately this sensor only supports serial communication with the MODBUS protocol, which I know almost nothing about.
I conencted the sensor to the Rx and Tx pins, as well as two digital pins it needs for turning off power and synchronizing communication with the master. Before each measurement the master needs to write the previous sensor state, which it read after the previous measurement, to the sensor. For the first measurement the manual wants me to set initial.

Now I tried to communicate with the sensor by simply sending the MODBUS ADU using Serial.write() after turning on the power and checking if it is ready to receive my message.

Below you can see my code until it checks if the sensor has done the measurement, which it would tell my by setting the rdy pin high. This never happens.

So now I’m asking myself if it is even possible to realize the MODBUS protocol this way and if not how would it need to be done.

int EN = 12;
int RDY = 11;
byte sensorstate[23];
byte sensor = 0x68;
byte writeData = 0x41;
byte readData = 0x44;
byte RAMaddressLow = 0x80;
byte RAMaddressHigh = 0x00;
byte byteNumWrite0 = 0x03;
byte byteNumWrite = 0x1A;
byte byteNumRead = 0x2C;
byte CalcContInit = 0x10;
byte CalcContSeq = 0x20;
byte pressLow = 10124 & 0xFF;
byte pressHigh = 10124 >> 8;

void setup() {
  pinMode(EN, OUTPUT);
  pinMode(RDY, INPUT);
  digitalWrite(EN, LOW);
  Serial.begin(9600, SERIAL_8N2);

}

// Compute the MODBUS RTU CRC
int ModRTU_CRC(byte buf[], int len) {
  int crc = 0xFFFF;

  for (int pos = 0; pos < len; pos++) {
    crc ^= (int)buf[pos];          // XOR byte into least sig. byte of crc

    for (int i = 8; i != 0; i--) {    // Loop over each bit
      if ((crc & 0x0001) != 0) {      // If the LSB is set
        crc >>= 1;                    // Shift right and XOR 0xA001
        crc ^= 0xA001;
      }
      else                            // Else LSB is not set
        crc >>= 1;                    // Just shift right
    }
  }
  // Note, this number has low and high bytes swapped, so use it accordingly (or swap bytes)
  return crc;
}


void loop() {

  while (digitalRead(RDY) == LOW) { // check if RDY pin is high for debugging
    delay(5);
  }

  digitalWrite(EN, HIGH); // enable Vcc --> power on LP8

  while (digitalRead(RDY) == HIGH) { // wait for RDY pin to go low
    delay(5);
  }
  
  // Write to sensor:
  if (millis() < 60000) { // initialize measurement on startup
    byte buf[8] = {sensor, writeData, RAMaddressHigh, RAMaddressLow, byteNumWrite0, CalcContInit, pressHigh, pressLow};
    int crc = ModRTU_CRC(buf, 8);
    byte crcLow = crc & 0xFF; // checksum low byte
    byte crcHigh = crc >> 8; // checksum high byte
    byte write0ADU[10] = {buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7], crcLow, crcHigh};
    delay(2);
    Serial.write(write0ADU, 10);
    delay(2);
  }
  else {    // after first measurement write previous sensor state
    for (int i = 6; i < 29; i++) {
      byte buf[30] = {sensor, writeData, RAMaddressHigh, RAMaddressLow, byteNumWrite, CalcContSeq};
      buf[i] = sensorstate[i];
      buf[29] = pressHigh;
      buf[30] = pressLow;
      if (i == 28) {
        int crc = ModRTU_CRC(buf, 31);
        byte crcLow = crc & 0xFF;
        byte crcHigh = crc >> 8;
        byte writeADU[33] = {buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7], buf[8], buf[9], buf[10], buf[11],buf[12],buf[13],buf[14],buf[15],buf[16],buf[17],buf[18],buf[19],buf[20],buf[21],buf[22],buf[23],buf[24],buf[25],buf[26],buf[27],buf[28],buf[29],buf[30],crcLow,crcHigh};
        delay(2);
        Serial.write(writeADU, 33);
        delay(2);
      }
    }
  }
  // read sensor's return:
  char buffer[2] = {0, 0};
  Serial.println();
  Serial.println(Serial.available());
  while (Serial.available() > 0) {
    Serial.readBytes(buffer, 2);
  }
  Serial.print("Sensor's return buffer (function code and errorcode if failure): ");
  Serial.print(buffer[0], HEX);
  Serial.print(", ");
  Serial.println(buffer[1], HEX);
  
  // wait for RDY pin to go HIGH
  while (digitalRead(RDY) == LOW) {
    delay(5);
  }

  digitalWrite(LED_BUILTIN, HIGH);
byte pressLow = 10124 & 0xFF;

Why are you mixing decimal and hexadecimal values? 0x2786 & 0xFF is 0x2700. That value will NOT fit in a byte.

I think I need to get the low and high byte of this number. byte pressLow = 10124 & 0xFF; gives me 0x8C, which I think should be the low byte.

Why not just use upperByte = highByte(10124); lowerByte = lowByte(10124);

assuming 10124 will be replaced with a variable?

Oh thank you for that advise, I didn't know of this function :) It's the pressure for correction of the CO2 concentration. 10124 is to be put if no pressure measurement is available.

    for (int i = 6; i < 29; i++) {
      byte buf[30] = {sensor, writeData, RAMaddressHigh, RAMaddressLow, byteNumWrite, CalcContSeq};
      buf[i] = sensorstate[i];
      buf[29] = pressHigh;
      buf[30] = pressLow;
      if (i == 28) {
        int crc = ModRTU_CRC(buf, 31);
        byte crcLow = crc & 0xFF;
        byte crcHigh = crc >> 8;
        byte writeADU[33] = {buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7], buf[8], buf[9], buf[10], buf[11],buf[12],buf[13],buf[14],buf[15],buf[16],buf[17],buf[18],buf[19],buf[20],buf[21],buf[22],buf[23],buf[24],buf[25],buf[26],buf[27],buf[28],buf[29],buf[30],crcLow,crcHigh};
        delay(2);
        Serial.write(writeADU, 33);
        delay(2);
      }

This probably does not what you intended. You define the buf variable in every loop again, initialising the first 6 elements and setting one element afterwards, so in the sent array only value number 28 is set correctly. Don’t loop over everything, just loop over the assignment of the array element. The the “if (i=28)” isn’t necessary anymore.

So now I’m asking myself if it is even possible to realize the MODBUS protocol this way and if not how would it need to be done.

There are libraries for the ModBus protocol but I don’t know any implementation that supports function codes 65 or 68. BTW, you never send a read command to the sensor.

Did you get any response from the sensor?

Hey everyone,

Reviving an old thread here to announce that I created a library for easy communication with the SenseAir LP8 miniature CO2 sensor:

GitLab.com: tue-umphy/co2mofetten/arduino-libraries/LP8

Have fun with it and feel free to suggest improvements!

Regards,

Yann