Honeywell IH-PMC-002 I2C read checksum error

Hello everyone,

I had been trying to integrate Honeywell IH-PMC-002 sensor to ESP32 via I2C Protocol using 3.3V level shifter.

I am able to detect I2C Address of 0x08. (IH-PMC-002 on i2c scanner).

I had written below program using Datasheet . Still getting CRC checksum error and values not updating further.

#include <Wire.h>
int i=0;
#define PMS_ADDRESS 0x08
void setup() {
  // put your setup code here, to run once:
  Wire.begin();
  
  Serial.begin(115200);


   Wire.beginTransmission(PMS_ADDRESS);
      Wire.write(0xFF);
      delay(10);
      Wire.write(0x10);
      Wire.write(0x00);
      Wire.write(0x10);
      Wire.write(0x05);
      Wire.write(0x00);
      Wire.write(0xF6);
    Wire.endTransmission();

}

void loop() {
  // Send command to read data
  byte command[] = {0x10, 0x03, 0x00, 0x11};
  Wire.beginTransmission(PMS_ADDRESS);
  Wire.write(command, sizeof(command));
  Wire.endTransmission();
  
  // Request 30 bytes of data from the slave device
  Wire.requestFrom(PMS_ADDRESS, 30);

  // Check if all bytes are available
  if (Wire.available() >= 30) {
    // Read and interpret data
    byte received_data[24]; // Array to hold received data bytes
    for (int i = 0; i < 24; i++) {
      received_data[i] = Wire.read(); // Read data bytes
    }
    byte crc = Wire.read(); // Read CRC byte
    
    // Calculate CRC checksum for the received data
    byte calculated_crc = Calc_CRC8(received_data, sizeof(received_data));
    
    // Verify CRC checksum
    if (crc == calculated_crc) {
      // CRC checksum matches, data is valid
      // Interpret data and print measurements
      unsigned int pm1_concentration = (received_data[0] << 8) | received_data[1];
      unsigned int pm25_concentration = (received_data[3] << 8) | received_data[4];
      unsigned int pm4_concentration = (received_data[6] << 8) | received_data[7];
      unsigned int pm10_concentration = (received_data[9] << 8) | received_data[10];
      
      Serial.print("PM1.0 concentration: ");
      Serial.print(pm1_concentration);
      Serial.println(" mg/m^3");

      Serial.print("PM2.5 concentration: ");
      Serial.print(pm25_concentration);
      Serial.println(" mg/m^3");

      Serial.print("PM4.0 concentration: ");
      Serial.print(pm4_concentration);
      Serial.println(" mg/m^3");

      Serial.print("PM10 concentration: ");
      Serial.print(pm10_concentration);
      Serial.println(" mg/m^3");
    } else {
      // CRC checksum does not match, data is invalid
      Serial.println("CRC checksum error!");
    }
  }

  delay(1000); // Delay between readings
}

byte Calc_CRC8(byte *data, byte Num) {
  byte bit, byte, crc = 0xFF;
  for (byte = 0; byte < Num; byte++) {
    crc ^= data[byte];
    for (bit = 8; bit > 0; --bit) {
      if (crc & 0x80)
        crc = (crc << 1) ^ 0x31;
      else
        crc = (crc << 1);
    }
  }
  return crc;
}

Capture

can you provide the raw data values the checksum is being calculated on?

Getting debug data as below:

For byte[0] byte[1] = 255 and checksum byte[2] = 172.

As per datasheet PM1.0 Concentration = byte[0]*256+byte[1] = 65535 Getting right now.

i'm asking you to post the data, not explain how you get it

Here's the data.

byte command[] = {0x10, 0x03, 0x00, 0x11};
  Wire.beginTransmission(PMS_ADDRESS);
  Wire.write(command, sizeof(command));
  //Wire.endTransmission();
  
  // Request 3 bytes of data from the slave device
 Wire.requestFrom(PMS_ADDRESS,3);
received_data[0] = Wire.read(); // Read data bytes
received_data[1] = Wire.read(); // Read data bytes
received_data[2] = Wire.read(); // Read data bytes
Serial.println(received_data[0]);
Serial.println(received_data[1]);
Serial.println(received_data[2]);
unsigned int pm1_concentration = (received_data[0] << 8) | received_data[1];
Serial.println(pm1_concentration);

Serial Print output.

17:06:10.597 -> 65452
17:06:11.589 -> 255
17:06:11.589 -> 172
17:06:11.589 -> 0
17:06:11.589 -> 65452
17:06:12.593 -> 255
17:06:12.593 -> 172
17:06:12.593 -> 0
17:06:12.593 -> 65452
17:06:13.611 -> 255
17:06:13.611 -> 172
17:06:13.611 -> 0
17:06:13.611 -> 65452
17:06:14.603 -> 255
17:06:14.603 -> 172
17:06:14.603 -> 0

looks like all 1s

Just updated my code. actually i missed

Wire.requestFrom(PMS_ADDRESS,3);

Oscilloscope is reading i2C Channel with 0x11 Read command.

DSO Debug details

I2C, Time, Address, R/W, Data
1,-10.0140us,0x10,W,0x10 03 00
2,389.044us,0x10,W,0x11
3,1.60918ms,0x11,R,0xFF FF AC FF FF AC FF FF AC FF FF AC FF FF AC FF FF AC FF FF AC FF FF AC FF FF AC FF FF AC~A

reading this datasheet, I do not think you are using the right bytes to compute the CRC:

so maybe try something like this then:
(Compiles, NOT tested!)

#include <Wire.h>

#define PMS_ADDRESS 0x08

void setup() {
  // put your setup code here, to run once:
  Wire.begin();
  
  Serial.begin(115200);

  Wire.beginTransmission(PMS_ADDRESS);
  Wire.write(0xFF);
  delay(10);
  Wire.write(0x10);
  Wire.write(0x00);
  Wire.write(0x10);
  Wire.write(0x05);
  Wire.write(0x00);
  Wire.write(0xF6);
  Wire.endTransmission();

}

void loop() {
  // Send command to read data
  byte command[] = {0x10, 0x03, 0x00, 0x11};
  Wire.beginTransmission(PMS_ADDRESS);
  Wire.write(command, sizeof(command));
  Wire.endTransmission();
  
  // Request 30 bytes of data from the slave device
  Wire.requestFrom(PMS_ADDRESS, 30);

  delay(1000); // Delay between readings

  // Check if all bytes are available
  if (Wire.available()) {
    // Read and interpret data
    byte received_data[3]; // Array to hold received data bytes
    for (int j = 0; j < 4; j++) {
      for (int i = 0; i < 3; i++) {
        received_data[i] = Wire.read(); // Read data bytes
      }
      // Calculate CRC checksum for the received data
      byte calculated_crc = Calc_CRC8(received_data, 2);

      Serial.print("pm data");
      Serial.print(j+1);
      Serial.print(": ");
      for (int i = 0; i < 3; i++) {
        Serial.print(received_data[i]);
        Serial.print(", ");
      }
      Serial.print("calculated Crc: ");
      Serial.println(calculated_crc); //does is match 'received_data[2]' value?
    }

    //purge remaining bytes:
    while (Wire.available()) Wire.read();
  }
}

byte Calc_CRC8(byte *data, byte Num) {
  byte bit, byte, crc = 0xFF;
  for (byte = 0; byte < Num; byte++) {
    crc ^= data[byte];
    for (bit = 8; bit > 0; --bit) {
      if (crc & 0x80)
        crc = (crc << 1) ^ 0x31;
      else
        crc = (crc << 1);
    }
  }
  return crc;
}

hope that helps...

if this is the data, it looks repetitious. are you sure the chip is configured correctly and is being read properly

out is something like this

14:35:01.250 -> pm data4: 255, 255, 172, calculated Crc: 172
14:35:02.253 -> pm data1: 255, 255, 172, calculated Crc: 172
14:35:02.254 -> pm data2: 255, 255, 172, calculated Crc: 172
14:35:02.254 -> pm data3: 255, 255, 172, calculated Crc: 172
14:35:02.254 -> pm data4: 255, 255, 172, calculated Crc: 172
14:35:03.277 -> pm data1: 255, 255, 172, calculated Crc: 172
14:35:03.277 -> pm data2: 255, 255, 172, calculated Crc: 172
14:35:03.277 -> pm data3: 255, 255, 172, calculated Crc: 172
14:35:03.277 -> pm data4: 255, 255, 172, calculated Crc: 172
14:35:04.286 -> pm data1: 255, 255, 172, calculated Crc: 172
14:35:04.286 -> pm data2: 255, 255, 172, calculated Crc: 172
14:35:

Yes i checked basic details like Voltage levels is 4.76 and connection are proper. Sensor Pin 3 is set to 0. for I2C Selection and its working but receiving data in 0xFF.

so the CRC is correct!

just strange that all the pm data is 255(0xFF)...

did you try putting the sensor in a test environment (like blowing some smoke onto it) to see if those value change?

the IH-PMC-002 datasheet says the returned data has the following format, prefixed with 0x10 0x03 0x00 0x11
10 03 00 11 Data0_H Data0_L Data0_CRC......
i don't believe you are reading the data correctly

no it doesn't.

you SEND those to get reply is what the datasheet says (see screenshot is reply #10)

at least that's how I read it! :upside_down_face:

but yes, there could be a prefix of 0x11 though...

OP should probably simply do a trial a print out the RAW 30 (or 31) bytes to see what that gives for ONE readout....

it says you send the following to start a measurement

3.3.1 Start measurement: 10 00 10 05 00 F6

and receive the following

3.3.3 Read the data: 10 03 00 11 Data0_H Data0_L Data0_CRC......

Correct.

I had only doubt regarding after reading datasheet thoroughly.

after 0x00 - ACK - P - S .

Do we need to user Wire.endTransmission(false); with false parameter ?

Just below 3.3.3 its written.

Send this command to read the measured value. :slightly_smiling_face:

was just about to point that out! :slight_smile:

maybe try the code again from post #10 again and but comment out this line to see if that makes any difference:

byte command[] = {0x10, 0x03, 0x00}; //<------- change this command 
  Wire.beginTransmission(PMS_ADDRESS);
  Wire.write(command, sizeof(command));
  Wire.endTransmission(); // <----------------comment out this line 

if you now see the 0x11 prefix then that would match the datasheet

then would need to adjust the code one more to compute the CRC correctly (all CRC with modded post #10 code should not match it what I expect)

no harm doing a bit of trial and error! :wink: