I2C read returning the write command value

Hello all,

I am quite new to using I2C with arduino and I am trying to interface schurter CDS1 Touch Display Switch with arduino nano. When I am trying to read the register 0xFC containing device serial number, the value returned for the first byte in FC. Similarly, when I read the 'Who am I' register 0x00, the value returned is 0. The read command is simply returning the value written earlier. What am I doing wrong?

Here's the code:

#include <Wire.h>

byte error=1, CDS1_found, lowByte=0, byte0=0;
int address = 0x3F;
int numbytes =0;

void setup()
{
  delay(9000);              //Initial delay to set up CDS1 switch.
  Wire.begin();
  Serial.begin(9600);
  TWBR = 72;
  
  pinMode(13, OUTPUT);
  
  Serial.println("Scanning I2C bus...");
  
  Wire.beginTransmission(address);
  digitalWrite(13, HIGH);
  error = Wire.endTransmission();
  Serial.println(error,HEX);
  if (error == 0)
  {
    Serial.println("I2C device found at 0x3F");
    Serial.println("Send read request to read who am I register...");
    Wire.beginTransmission(address);
    Wire.write(0xFC);               //Serial number
    error = Wire.endTransmission();
    Serial.println(error,HEX);
    numbytes = Wire.requestFrom(address, 1);
    Serial.println(numbytes,HEX);
    while(Wire.available()<1);
    byte0 = Wire.read();
    Serial.println(byte0, HEX);
    delay(300);
    digitalWrite(13, LOW);
  }
}

void loop()
{

}

And here's the result from serial monitor:

Scanning I2C bus...
0
I2C device found at 0x3F
Send read request to read who am I register...
0
1
FC

Please can someone point out what I am doing wrong and help me solve this issue? Thanks in advance.

CDS1 User Manual Rev 1.2.pdf (1.14 MB)

I don't think you need to use Wire.available(). This thread and this discussion, leading to this tutorial might be helpful.

A Arduino Nano is a 5V board, the sensor is a 3.3V sensor.
It will probably work, but you might need a I2C level shifter.
Do you use pullup resistors at SDA and SCL ? What is the value ? Are the pullup resistors to 3.3V ?

Please remove this line:

TWBR = 72 ;

If you need to change the clock speed, use Wire.setClock(). That is for later.

Please remove this line:

while(Wire.available()<1) ;

That is a common mistake.

Did you read the datasheet ?

Instead of just writing the register address, also the number of bytes to read should be send.
I think you also have to write and read the CRC byte.
This sentence is confusing: "Between the read command from the master and the start of clocking out the values from the slave a delay of at least 10ms must be inserted"
The pictures show what they mean, it is a delay of 10 ms between writing the register address and reading data.

To be able to communicate with it, you need to calculate the CRC.

The ID at register address 0 is the most simple data. Start with that.

byte length = 1;   // data length
byte register_address = 0x00;   // ID register
byte crc = ? ? ? ?
Wire.beginTransmission( address);
Wire.write( length);
Wire.write( register_address);
Wire.write( crc);
Wire.endTransmission();

delay( 10);  // required minimal delay

Wire.requestFrom( address, 2);  // one databyte plus crc
int id = Wire.read();
int crc = Wire.read();
Serial.println( id, HEX);   // hoping for 0x53

The CRC is a CRC-8-CCITT with polynomial of 0x07. I can not find a good example. I hope someone else can give a working function.

Hello,

Thanks a lot for your prompt responses Koepel and jremington :slight_smile: !!

Yes, I have a 3.3V supply for the sensor. Also, I have 3KOhm pull up resistors connected to 3.3V.

I have removed TWBR=72; and while(Wire.available()<1) ;.

Following is the updated code:

#include <Wire.h>

byte error=1, CDS1_found, lowByte=0, byte0=0;
byte address = 0x3F, crc_received = 0;
int numbytes =0;
byte length = 1;   // data length
byte register_address = 0x00;  // ID register
byte crc_calc = 0;
byte crc_array[3] = {0x3F, 0x01, 0x00}; 

void setup()
{
  delay(9000);              //Initial delay to set up CDS1 switch.
  Wire.begin();
  Serial.begin(9600);
  pinMode(13, OUTPUT);
  
  Serial.println("Scanning I2C bus...");
  
  Wire.beginTransmission(address);
  digitalWrite(13, HIGH);
  error = Wire.endTransmission();
  Serial.println(error,HEX);
  if (error == 0)
  {
    Serial.println("I2C device found at 0x3F");
    Serial.println("Send read request to read who am I register...");
    
    Wire.beginTransmission(address);
    Wire.write(length);
    Wire.write(register_address);
    crc_calc = CRC8(crc_array, 3);
    Wire.write(crc_calc);
    error = Wire.endTransmission();
    Serial.println(error,HEX);
    
    delay( 10);  // required minimal delay
    
    numbytes = Wire.requestFrom(address, 2);
    Serial.println(numbytes,HEX);
    byte0 = Wire.read();
    crc_received = Wire.read();
    Serial.println(crc_calc,HEX);
    Serial.println( byte0, HEX);   // hoping for 0x53
    Serial.println( crc_received, HEX);
    delay(300);
    digitalWrite(13, LOW);
  }
}

void loop()
{

}


byte CRC8(const byte *data, byte len) {
  byte crc = 0x00;
  while (len--) {
    byte extract = *data++;
    for (byte tempI = 8; tempI; tempI--) {
      byte sum = (crc ^ extract) & 0x01;
      crc >>= 1;
      if (sum) {
        crc ^= 0x8C;
      }
      extract >>= 1;
    }
  }
  return crc;
}

Serial monitor results:

Scanning I2C bus...
0
I2C device found at 0x3F
Send read request to read who am I register...
0
2
45
1
0

Unfortunately, the data returned is not 0x53. I am not sure what is going wrong. Please can you help?

Thanks!

I suspect that the CRC8 you are using is not the correct algorithm. The polynomial required is 0x07. There are many variants, and this one, commonly used with Arduino, uses 0x8C as the polynomial. You could try replacing the 0x8C below with 0x07.

The only way to tell for sure is to get some examples of valid messages, with the correct CRC.

//CRC-8 - based on the CRC8 formulas by Dallas/Maxim
//code released under the therms of the GNU GPL 3.0 license
byte CRC8(const byte *data, byte len) {
  byte crc = 0x00;
  while (len--) {
    byte extract = *data++;
    for (byte tempI = 8; tempI; tempI--) {
      byte sum = (crc ^ extract) & 0x01;
      crc >>= 1;
      if (sum) {
        crc ^= 0x8C;
      }
      extract >>= 1;
    }
  }
  return crc;
}

How did you come up with the contents of crc_array in the following?

  Wire.write(length);
    Wire.write(register_address);
    crc_calc = CRC8(crc_array, 3);
    Wire.write(crc_calc);

Study these instructions from the user guide carefully. I don't understand them completely, so you may have to experiment.

I think you would have an easier time using "RS232" communications.

I2C write cycle:The machine control unit sends the start condition and afterwards the write command consisting of the I2C slave address of the CDS1 and the R/W_n bit set for write, followed by a data byte which defines the number of bytes to be written. Then the address of the first instruction register needs to be sent followed by the data bytes. Each single byte will be acknowledged by an acknowledge bit. In addition, the register address is automatically incremented after each data byte and its acknowledge bit. Once the last data byte was sent, the machine control unit has to send a CRC-8 check sum to the CSD1 which was calculated about the whole package. The CDS1 compares the preserved check sum to the calculated one. If a CRC error is detected, the CDS1 generates an interrupt. When the write sequence is finished, the machine control unit sends the I2C stop condition.
I2C

As far as I know, the I2C address (with Read/Write bit) is not a part of the data for a CRC.
Is that specified in the datasheet somewhere ? A CRC is always used with data bytes.

You are getting closer, but this CRC is nasty.

@OP

In the light of your original post and Post#4, the following program is prepared. You may try it:

I2C write cycle:The machine control unit sends the start condition and afterwards the write command consisting of the I2C slave address of the CDS1 and the R/W_n bit set for write, followed by a data byte which defines the number of bytes to be written. Then the address of the first instruction register needs to be sent followed by the data bytes. Each single byte will be acknowledged by an acknowledge bit. In addition, the register address is automatically incremented after each data byte and its acknowledge bit. Once the last data byte was sent, the machine control unit has to send a CRC-8 check sum to the CSD1 which was calculated about the whole package. The CDS1 compares the preserved check sum to the calculated one. If a CRC error is detected, the CDS1 generates an interrupt. When the write sequence is finished, the machine control unit sends the I2C stop condition.

#include <Wire.h>
#define slaveAddress 0x3F
#define bytesToSend 0x02   //number of data bytes for two register 
#define addressOfFirstReg 0xFC
#define firtDataByte 0x53
#define secondDataByte 0x17

byte data [5] = {slaveAddress, bytesToSend, addressOfFirstReg, firtDataByte, secondDataByte}; 
byte crc;

void setup()
{
  // delay(9000);              //Initial delay to set up CDS1 switch.
  Wire.begin();
  Serial.begin(9600);

  Wire.beginTransmission(slaveAddress);  //0x3F
  Wire.write(bytesToSend); //0x02
  Wire.write(addressOfFirstReg); //0xFC
  Wire.write(firtDataByte); //0x53 for the First Register
  Wire.write(secondDataByte); //0x17 for the next register
  crc = CRC8(data, 5);
  Wire.write(crc); //for above 5-byte; polynomial must match with slave's polynomial
  Wire.endTransmission();
  //  Serial.print(crc, HEX);  //prints: E6

  //------------------------------------------------------------------
  Wire.beginTransmission(slaveAddress);  //0x3F
  Wire.write(addressOfFirstReg); //0xFC          pointing to firtsRegister of Slave
  Wire.endTransmission();

  Wire.requestFrom(slaveAddress, 2); //2-byte must come from slave register
  byte x = Wire.read();
  Serial.println(x, HEX); // Serial Monitor should show: 53.
  byte y = Wire.read();
  Serial.println(y, HEX); // Serial Monitor should show: 17
  //------------------------------------------------------------------
}

void loop()
{

}

//due to @jremington
//CRC-8 - based on the CRC8 formulas by Dallas/Maxim
//code released under the therms of the GNU GPL 3.0 license
byte CRC8(const byte *data, byte len)
{
  byte crc = 0x00;
  while (len--)
  {
    byte extract = *data++;
    for (byte tempI = 8; tempI; tempI--)
    {
      byte sum = (crc ^ extract) & 0x01;
      crc >>= 1;
      if (sum)
      {
        crc ^= 0x8C;
      }
      extract >>= 1;
    }
  }
  return crc;
}

The datasheet (User Manual) has not enough information. You should contact Schurter.
This is Schurter's page about the CDS1 : https://www.schurter.com/en/Landing-Page/Products-and-Technologies/CDS1.
I have just sent them a mail with a link to this topic and I ask them for an example for I2C.

Koepel:
I have just sent them a mail with a link to this topic and I ask them for an example for I2C.

Thanks @Koepel. When you get the information, please post it here. I will be able to check the validity of my codes of Post#6.

@jremington, GolamMostafa and koepel : Thank you very much for your advice and help.

jremington:
...I think you would have an easier time using "RS232" communications.

How would RS232 be easier? Doesn't it use CRC checks as well?

@GolamMostafa : I will try the code today and let you know the results.

@Koepel: Thank you for the enquiry. I have requested sample code as well. I will share it with everyone once I get it from them.

How would RS232 be easier?

Much simpler data transmission and reception.

jremington:
Much simpler data transmission and reception.

But, at the cost of reliability? To make async serial protocol more reliable, we need to create the handshakings artificially by software instructions. The error checking is a simile parity bit.

The I2C has built-in handshaking in the protocol itself. It thus provides far greater reliability than the RS232 protocol. The error checking can be done using much better CRC scheme.

@GolamMostafa: I tried your code now and this is the result:

FF
FF

I am really beginning to think whether I should try SPI or RS232 for a change because I tried I2C comms with MPU6050 which is another I2C device and it works fine !

Could there be a problem with I2C in this display?

The CRC checksum is not right yet, in my opinion. If I had such a display myself, then I would write a sketch that tests it all, with all checksums and all possibilities, until I got the right answer. With the right answer, perhaps I could reverse-engineer the CRC function. There are only 256 possible checksum values and only a small number of possible ways to calculate it.

There was a firmware update for I2C. That update is for the touch function. Even without the update, the I2C should work.

For serial, there is also little information. There is a thread on the arduino forum about it: CDS1 Display Switch (Relais Schalten) Aurduino Mega - Deutsch - Arduino Forum

But, at the cost of reliability?

No. I2C as implemented on Arduino is unreliable, with no error checking.

Since I2C data is transferred most significant bit first, it is possible that the CRC calculation is done MSB first as well. The code in msg #4 calculates the CRC starting at the low order bit.

The code below starts with the most significant bit. Give it a try.

byte CRC8_rev(const byte *data, byte len) {
  byte crc = 0x00;
  while (len--) {
    byte extract = *data++;
    for (byte tempI = 8; tempI; tempI--) {
      byte sum = (crc ^ extract) & 0x80;
      crc <<= 1;
      if (sum) {
        crc ^= 0x07;
      }
      extract <<= 1;
    }
  }
  return crc;
}

Pete

There is a thread on the arduino forum about it: CDS1 Display Switch (Relais Schalten) Aurduino Mega - Deutsch - Arduino Forum

Great find, Koepel!

The code in the first post of that thread appears to be exactly what you need. It communicates with the CDS1 display using RS232, evidently using the correct checksum calculation. The author claims that the display works perfectly but there are problems with relays attached to the system.

While there is obviously a lot of stuff you don't need, the framework is there for you to build your own application. Note that the code reads the CDS1 interrupt output.

Here is the checksum calculation:

// CRC table
byte crcTable[256];
...
// Initialise CRC Table
  CRC(0x07); 
...
// *** CRC Calculation ***
byte AppendChecksum(byte command[], byte size)
{
  byte length = size - 1;
  byte CRC = CalculateCRC8(command, length);
  return CRC;
}

// Create CRC Table with given polynom
void CRC( byte polynome)
{
  int temp;
  int i;
  int j;

  for (i = 0; i < 256; ++i)
  {

    temp = i;

    for (j = 0; j < 8; ++j)
    {

      if ((temp & 0x80) != 0)
      {
        temp = (temp << 1) ^ polynome;
      }
      else
      {
        temp <<= 1;
      }
    }
    crcTable[i] = (byte)temp;
  }
}

byte CalculateCRC8(byte data[], byte size)
{
  byte crc = 0;
  byte i = 0;

  if (size <= 0)
  {
    return 0;
  }

  for (i = 0; i < size; i++)
  {
    crc = crcTable[crc ^ data[i]];
  }

  return crc;
}

An example command sent to the CDS1:

void enableAllFeatures()
{
  DEBUG_PRINTLN("Enable All Features: 0x81 / 0x10 / 0x3F / 0x8A");
  byte message[4] = {0x81, 0x10, 0x3F, 0x00};
  message[sizeof(message) - 1] = AppendChecksum(message, sizeof(message));
  Serial1.write(message, sizeof(message));
...

UGH (not to be imitated):   goto loop;

As I surmised, the CRC calculation is done most significant bit first. If you use the code I posted in #15, the I2C communication should work.

Pete

This Arduino code calculates "8A" for a CDS1 message checksum, as the DEBUG message in the code suggests.

//Test CDS1 CRC calculation

// CRC table
byte crcTable[256];

// *** CRC Calculation ***
byte AppendChecksum(byte command[], byte size)
{
  byte length = size - 1;
  byte CRC = CalculateCRC8(command, length);
  return CRC;
}

// Create CRC Table with given polynom
void CRC( byte polynome)
{
  int temp;
  int i;
  int j;

  for (i = 0; i < 256; ++i)
  {

    temp = i;

    for (j = 0; j < 8; ++j)
    {

      if ((temp & 0x80) != 0)
      {
        temp = (temp << 1) ^ polynome;
      }
      else
      {
        temp <<= 1;
      }
    }
    crcTable[i] = (byte)temp;
  }
}

byte CalculateCRC8(byte data[], byte size)
{
  byte crc = 0;
  byte i = 0;

  if (size <= 0)
  {
    return 0;
  }

  for (i = 0; i < size; i++)
  {
    crc = crcTable[crc ^ data[i]];
  }

  return crc;
}
void setup() {
  // Initialize CRC Table
  CRC(0x07);
  Serial.begin(9600);
  Serial.println("Enable All Features: 0x81 / 0x10 / 0x3F / 0x8A");
  byte message[4] = {0x81, 0x10, 0x3F, 0x00};
  Serial.print("CRC = ");
  Serial.println(AppendChecksum(message, sizeof(message)), HEX); //prints 8A
}

void loop() {}

Yep, my code in #15 generates 0x8A for that message.

Pete