0

UPDATE: I can't even get this calculator to reproduce the SMBus PECs illustrated in figures 8 and 9 of this datasheet!

So I'm interfacing an arduino with a Melexis temperature sensor, and it's going okay--aside from the fact that I can't seem to get the CRC check to work.

I've gotten read operations to complete successfully (although my software ignores the packet error code) but I have tried a lot of implementations of CRC8 to check the PEC byte to no avail. The code block I am using now came from OneWire:

uint8_t OneWire::crc8(const uint8_t *addr, uint8_t len)
{
    uint8_t crc = 0;

    while (len--) {
        uint8_t inbyte = *addr++;
        for (uint8_t i = 8; i; i--) {
            uint8_t mix = (crc ^ inbyte) & 0x01;
            crc >>= 1;
            if (mix) crc ^= 0x8C;
            inbyte >>= 1;
        }
    }
    return crc;
}

I rewrote it to consider just the one byte:

int smbCRC(int message) {

    uint8_t crc = 0;

  uint8_t inbyte = message & 0xFF;
  for (uint8_t i = 8; i; i--) {
    uint8_t mix = (crc ^ inbyte) & 0x01;
    crc >>= 1;
    if (mix) crc ^= 0x8C;
    inbyte >>= 1;
  }

    return crc;
}

But its CRC does not match that of the MLX datasheet (Figure 8 from here for example). When I print an int with its CRC8 like so:

int message = 0x3aD2;
lcd.print(String(message,HEX) + " " + String(smbCRC(message),HEX));

I get back "3ad2 eb", though the datasheet says the correct PEC is 0x30. Where am I going wrong? It seems like this could be caused by a bad implementation of CRC or bad assumptions on my part about the CRC input, and I'm not sure where to start troubleshooting.

Noah
  • 495
  • 2
  • 7
  • 21

4 Answers4

7

I haven't checked your CRC implementation but there is a mistake in the MLX datasheet or at least it's badly written. You have to include all the I2C frame's data for the PEC's calculation not just the replied data. For a read word command you have to include [SA_W, Command, SA_R, LSB, MSB] and for a write word command [SA_W, Command, LSB, MSB].

So, for their first example the calculation must be made on [ 0xB4, 0x07, 0xB5, 0xD2, 0x3A ] and not just on [ 0xD2, 0x3A ] and this way you get the expected 0x30.

Here is a simple C implementation of the CRC with a lookup table (non Arduino but it must be quite simple to adapt):

static const uint8_t crc_table[] = {
    0x00, 0x07, 0x0e, 0x09, 0x1c, 0x1b, 0x12, 0x15, 0x38, 0x3f, 0x36, 0x31,
    0x24, 0x23, 0x2a, 0x2d, 0x70, 0x77, 0x7e, 0x79, 0x6c, 0x6b, 0x62, 0x65,
    0x48, 0x4f, 0x46, 0x41, 0x54, 0x53, 0x5a, 0x5d, 0xe0, 0xe7, 0xee, 0xe9,
    0xfc, 0xfb, 0xf2, 0xf5, 0xd8, 0xdf, 0xd6, 0xd1, 0xc4, 0xc3, 0xca, 0xcd,
    0x90, 0x97, 0x9e, 0x99, 0x8c, 0x8b, 0x82, 0x85, 0xa8, 0xaf, 0xa6, 0xa1,
    0xb4, 0xb3, 0xba, 0xbd, 0xc7, 0xc0, 0xc9, 0xce, 0xdb, 0xdc, 0xd5, 0xd2,
    0xff, 0xf8, 0xf1, 0xf6, 0xe3, 0xe4, 0xed, 0xea, 0xb7, 0xb0, 0xb9, 0xbe,
    0xab, 0xac, 0xa5, 0xa2, 0x8f, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9d, 0x9a,
    0x27, 0x20, 0x29, 0x2e, 0x3b, 0x3c, 0x35, 0x32, 0x1f, 0x18, 0x11, 0x16,
    0x03, 0x04, 0x0d, 0x0a, 0x57, 0x50, 0x59, 0x5e, 0x4b, 0x4c, 0x45, 0x42,
    0x6f, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7d, 0x7a, 0x89, 0x8e, 0x87, 0x80,
    0x95, 0x92, 0x9b, 0x9c, 0xb1, 0xb6, 0xbf, 0xb8, 0xad, 0xaa, 0xa3, 0xa4,
    0xf9, 0xfe, 0xf7, 0xf0, 0xe5, 0xe2, 0xeb, 0xec, 0xc1, 0xc6, 0xcf, 0xc8,
    0xdd, 0xda, 0xd3, 0xd4, 0x69, 0x6e, 0x67, 0x60, 0x75, 0x72, 0x7b, 0x7c,
    0x51, 0x56, 0x5f, 0x58, 0x4d, 0x4a, 0x43, 0x44, 0x19, 0x1e, 0x17, 0x10,
    0x05, 0x02, 0x0b, 0x0c, 0x21, 0x26, 0x2f, 0x28, 0x3d, 0x3a, 0x33, 0x34,
    0x4e, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5c, 0x5b, 0x76, 0x71, 0x78, 0x7f,
    0x6a, 0x6d, 0x64, 0x63, 0x3e, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2c, 0x2b,
    0x06, 0x01, 0x08, 0x0f, 0x1a, 0x1d, 0x14, 0x13, 0xae, 0xa9, 0xa0, 0xa7,
    0xb2, 0xb5, 0xbc, 0xbb, 0x96, 0x91, 0x98, 0x9f, 0x8a, 0x8d, 0x84, 0x83,
    0xde, 0xd9, 0xd0, 0xd7, 0xc2, 0xc5, 0xcc, 0xcb, 0xe6, 0xe1, 0xe8, 0xef,
    0xfa, 0xfd, 0xf4, 0xf3
};

uint8_t
crc8(uint8_t *p, uint8_t len)
{
        uint16_t i;
        uint16_t crc = 0x0;

        while (len--) {
                i = (crc ^ *p++) & 0xFF;
                crc = (crc_table[i] ^ (crc << 8)) & 0xFF;
        }

        return crc & 0xFF;
}
thb
  • 86
  • 1
  • 2
  • You're absolutely right! I used a calculator to figure out exactly what was being considered by the CRC examples in the figures in the datasheet, and discovered that the command, address, AND data were all part of the calculation. So the main problem was that I was not supplying enough data to the algorithm. – Noah Feb 14 '14 at 16:23
  • what polynomial representation did you use to create this lookup table? – Jon V Jul 06 '15 at 13:24
2

It is possible to get the compiler to figure out the lookup table for you, as follows:

#include <stdio.h>
#include <stdint.h>

/* * *
 * Just change this define to whatever polynomial is in use
 */
#define  CRC1B(b)       ( (uint8_t)((b)<<1) ^ ((b)&0x80? 0x07 : 0) ) // MS first

/* * *
 * 8+1 entry enum lookup table define
 */
#define  CRC(b)         CRC_##b          // or CRC8B(b)

enum {
    CRC(0x01) = CRC1B(0x80),
    CRC(0x02) = CRC1B(CRC(0x01)),
    CRC(0x04) = CRC1B(CRC(0x02)),
    CRC(0x08) = CRC1B(CRC(0x04)),
    CRC(0x10) = CRC1B(CRC(0x08)),
    CRC(0x20) = CRC1B(CRC(0x10)),
    CRC(0x40) = CRC1B(CRC(0x20)),
    CRC(0x80) = CRC1B(CRC(0x40)),
    // Add 0x03 to optimise in CRCTAB1
    CRC(0x03) = CRC(0x02)^CRC(0x01)
};

/* * *
 * Build a 256 byte CRC constant lookup table, built from from a reduced constant
 * lookup table, namely CRC of each bit, 0x00 to 0x80. These will be defined as
 * enumerations to take it easy on the compiler. This depends on the relation:
 *   CRC(a^b) = CRC(a)^CRC(b)
 * In other words, we can build up each byte CRC as the xor of the CRC of each bit.
 * So CRC(0x05) = CRC(0x04)^CRC(0x01). We include the CRC of 0x03 for a little more
 * optimisation, since CRCTAB1 can use it instead of CRC(0x01)^CRC(0x02), again a
 * little easier on the compiler.
 */

#define  CRCTAB1(ex)    CRC(0x01)ex, CRC(0x02)ex,  CRC(0x03)ex,
#define  CRCTAB2(ex)    CRCTAB1(ex)  CRC(0x04)ex,  CRCTAB1(^CRC(0x04)ex)
#define  CRCTAB3(ex)    CRCTAB2(ex)  CRC(0x08)ex,  CRCTAB2(^CRC(0x08)ex)
#define  CRCTAB4(ex)    CRCTAB3(ex)  CRC(0x10)ex,  CRCTAB3(^CRC(0x10)ex)
#define  CRCTAB5(ex)    CRCTAB4(ex)  CRC(0x20)ex,  CRCTAB4(^CRC(0x20)ex)
#define  CRCTAB6(ex)    CRCTAB5(ex)  CRC(0x40)ex,  CRCTAB5(^CRC(0x40)ex)

/* * *
 * This is the final lookup table. It is rough on the compiler, but generates the
 * required lookup table automagically at compile time.
 */
static const uint8_t crc_table[256] = { 0, CRCTAB6() CRC(0x80), CRCTAB6(^CRC(0x80)) };

uint8_t crc8(uint8_t *p, uint8_t len)
{
    uint8_t crc = 0x0;

    while (len--) {
        crc = crc_table[crc ^ *p++];
    }

    return crc;
}

void main( void )
{
    int i, j;

    printf("static const uint8_t crc_table[] = {");
    for (i = 0; i < 0x10; i++)
    {
        printf("\n   ");
        for (j = 0; j < 0x10; j++)
        {
            printf( " 0x%02x,", crc_table[i*0x10+j] );
        }
    }
    printf("\n};\n\n");
}
ilgitano
  • 171
  • 2
  • 3
0

See reference on following links:

Function code for Arduino:

byte c8( byte x ){
  for( byte i = 8; i--; ) {
    x = ( x << 1 ) ^ ( x & 128 ? 7 : 0 );
  }
  return x;
}

void setup() {
  Serial.begin( 9600 );
  int msg = 0x3AD2;
  Serial.print( '0x' );
  Serial.print( c8( msg ), HEX );
  // '0x30' is displayed on Serial Monitor
}
void loop() {}

Since argument x is strong-typed as byte (uint8_t), word-size data (such 0x3AD2) will be truncated to byte-size (which is 0xD2 in case of 0x3AD2).

vcc2gnd
  • 137
  • 1
  • 5
  • This works for the data in my question, but the next example in the datasheet is 0xC807 -> 0x48, while this function returns 0x15. – Noah Dec 16 '13 at 15:53
-1

Stripped code from linux kernel code, i am using it for smbus pec calculation..

#include<stdio.h>

#define POLY    (0x1070U << 3)
#define DST_SLAVE_ADDRESS 0x1d //7-bit Slave Address

static unsigned char crc8(short int data)
{
    int i;

    for (i = 0; i < 8; i++) {
        if (data & 0x8000)
            data = data ^ POLY;
        data = data << 1;
    }
    return (unsigned char)(data >> 8);
}

unsigned char i2c_smbus_pec(unsigned char crc, unsigned char *p, int count)
{
    int i;

    for (i = 0; i < count; i++)
        crc = crc8((crc ^ p[i]) << 8);
    return crc;
}

/* Assume a 7-bit address, which is reasonable for SMBus */
static unsigned char i2c_smbus_msg_pec(unsigned char slave_addr, char *msg, int len)
{
    /* The address will be sent first */
    unsigned char addr = slave_addr << 1;
    int pec;
 
    pec = i2c_smbus_pec(0, &addr, 1);

    /* The data buffer follows */
    return i2c_smbus_pec(pec, msg, len);
}



main()
{
    //SMBUS PEC include everthing include dst slave address
    //dst i2c address here is 0x1d (7bit) , 0x3a (8bit)
    char msg[] = {0x0F,0x19,0x21,0x01,0x00,0x00,0xEB,0x84,0x08,0x00,0x00,0x01,0x00,
                  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xAA,0xEF,0x81,0xB4};


    //dst i2c address here is 0x10 (7bit) , 0x20 (8bit)
    //char msg[] = { 0x0F,0x19,0x3B,0x01,0x00,0x00,0xD3,0x84,0x88,0x00,0x00,0x00,
    //               0x00,0x00,0x00,0x30,0xFF,0xEF,0x00,0x00,0x00,0x00,0x00,0x21,0xE5,0xD5,0xA8};
    
    int len = sizeof(msg);
    int calc_pec =  i2c_smbus_msg_pec(DST_SLAVE_ADDRESS, msg , len);

    printf("pec = 0x%x\n", calc_pec);
    
}

Alok Prasad
  • 622
  • 7
  • 12