[Solved] I2C-like communication

Dear Community,

I have a device which uses I2C-like communication. All timing and behavior is the same, except for two things. Firstly, the device doesn't send and ACK after receiving the 7-bit address+R/W byte. Secondly, the device doesn't send ACK between each data word in a write command. Why the makers of the device would deviate from the I2C protocol is beyond me, but I would like to solve the issue.

Is there a way to edit the Wire library to support this device? As far as I understand the Wire library makes use of the I2C capabilities of the Atmega chip, but I can't really understand what's going on inside the library. Too bad I'm just a Arduino and c++ beginner..

I've looking for some alternative libraries also, and found one. http://dsscircuits.com/articles/arduino-i2c-master-library.html. While this library seems easier to understand, it doesn't support this device and I'm not sure what to edit to make it work.

If I can't edit either library, I guess I would be stuck just generating the necessary waveforms? Can I get the required speed with standart digitalOutput/digitalInput commands? Or would I have to write directly to the Atmega chip then?

Thanks for your help

Another possibility is to use a software-only library GitHub - todbot/SoftI2CMaster: Software I2C / TWI library for Arduino allows any two pins to be SDA & SCL

In any case you have to change the library to ignore the ACK status after a write. It depends if the rest of the states are correct as in the I2C specification if the hardware will work or if you have to use the software library.

Can you post a link to the datasheet of your device?

I would like to share the datasheet, but I'm not sure if it's under the confidentiality agreement, I will check with my supervisor asap. But as far I can see, the communication minus the two cases I've stated, is the same.

If I understand correctly, the software-only library only uses 'easy' read/write functionality of the pins. I guess that would make it easier to understand for a newb like me.

Actually, I seem to get some (weird) response from the device. This weird behavioral is probably because the expected ACK bit is actually part of the data word send by the device. If I get back on my work I will try and get into more detail on what happens. However, this seems to suggest there is at least some I2C-like communication going on.

Too bad, I can't reproduce the weird response I got from the device anymore.. I can share the datasheet. Dropbox - Error - Simplify your life

Why the makers of the device would deviate from the I2C protocol is beyond me,

To avoid licensing payments?

but I would like to solve the issue.

There is really no issue to solve: you just pretend it is an i2c-compliant device but disregard the NACK signal.

I can share the datasheet.

Which clearly shows that it is a i2c compliant device, with ACK / NACK signal.

dhenry:

Why the makers of the device would deviate from the I2C protocol is beyond me,

To avoid licensing payments?

but I would like to solve the issue.

There is really no issue to solve: you just pretend it is an i2c-compliant device but disregard the NACK signal.

dhenry:

I can share the datasheet.

Which clearly shows that it is a i2c compliant device, with ACK / NACK signal.

As far as I know every device is free to use the I2C protocol, but that might be beyond the scope of my post. It is not a standard I2C, take a look at the sequence for reading. Standard I2C is 'device address + R/W' 'ACK' 'Byte' 'ACK' 'byte' 'NACK'. While this device is 'device address + R/W' 'Byte' 'ACK' 'byte' 'NACK'. This means all bits shift one bit to the left. As far as I understand that means I can't use the Atmega hardware I2C capabilities.

I'm currently looking into the software library pylon brought to my attention. It seems that I can make this library work for my device with only minor editing.

This means all bits shift one bit to the left.

You probably want to read up on the i2c protocol.

dhenry:

This means all bits shift one bit to the left.

You probably want to read up on the i2c protocol.

I really don't see your point. From the i2c-bus specification http://www.nxp.com/documents/user_manual/UM10204.pdf chapter 3.1.6:

The acknowledge takes place after every byte. The acknowledge bit allows the receiver to
signal the transmitter that the byte was successfully received and another byte may be
sent. The master generates all clock pulses, including the acknowledge ninth clock pulse.

In this device, ACK doesn't take place after every byte, so it's not i2c. Instead of sending a '0' to the master as acknowledge, the device starts sending data immediately. This means the first bit of the actual data gets interpreted as the ACK bit, leaving 7 bits in the data word instead of 8. This makes the bus hang, because the master expects 8 bits from the device.

Maybe you can make your point clearer?

I see your point. Judging by the datasheet, when you write to the device you are expected to send the data without waiting for an ACK (why, I don't know).

Reading looks a bit more standard, on page 12 they mention an ACK between each byte (except after the address, grrr). Is it possible the datasheet is wrong? Do you have a logic analyzer you can apply to the problem?

More info and screenshots of what to expect:

You could do it in software without an enormous amount of effort. Probably someone has a bit-banged I2C library somewhere you could adapt.

According to the Atmega328 datasheet:

All data packets transmitted on the TWI bus are nine bits long, consisting of one data byte and an acknowledge bit.

There does not seem to be any provision for the longer bit length your device is dealing in.

dhenry:
You probably want to read up on the i2c protocol.

If you read the datasheet you'll see what he is saying. It does not show an ACK between the address and the data, and thus all bits will be shifted one to the left, like he said.

Maybe you can make your point clearer?

You have a choice: either you think a non-i2c compliant device is being used with i2c; or the device datasheet made a mistake.

You can invest lots of time / efforts to write a i2c-like protocol to communicate with the device, if you pick the 1st choice;

Or you can try a i2c-compliant code on this device and see if it works.

I hope that's sufficiently clear.

I've tried different I2C master devices (Arduino, Aardvark) and they didn't work. I used a blog post used by the softwareI2Clib pylon pointed me to (Coding Laboratory: I2C on an AVR using bit banging) to create my own bit-banging application. For those interested, here is it:

// Port for the I2C
#define I2C_DDR DDRD
#define I2C_PIN PIND
#define I2C_PORT PORTD

// Pins to be used in the bit banging
#define I2C_CLK 0
#define I2C_DAT 1

#define I2C_DATA_HI() \
        I2C_DDR &= ~( 1 << I2C_DAT );\
        I2C_PORT |= ( 1 << I2C_DAT );
#define I2C_DATA_LO() \
        I2C_DDR |= ( 1 << I2C_DAT );\
        I2C_PORT  &=~ ( 1 << I2C_DAT );

#define I2C_CLOCK_HI() \
        I2C_DDR &= ~( 1 << I2C_CLK );\
        I2C_PORT |= ( 1 << I2C_CLK );
#define I2C_CLOCK_LO() \
        I2C_DDR |= ( 1 << I2C_CLK );\
        I2C_PORT  &=~ ( 1 << I2C_CLK );

int i = 0;
int g = 0;

void setup()
{
  I2C_Init();
  Serial.begin(9600);
}

void loop()
{
  
  while(i == 0)
  {
  I2C_Start();
  I2C_Write(0x01);
  g = I2C_Read(1);
  I2C_Read(1);
  I2C_Read(1);
  I2C_Read(1);
  I2C_Read(0);
  I2C_Stop();
  i++;
  }
  Serial.print(g);
  delay(500);
  
}

void I2C_WriteBit( unsigned char c )
{
if ( c > 0 )
{
I2C_DATA_HI();
}
else
{
I2C_DATA_LO();
}

I2C_CLOCK_HI();
delay(1);

I2C_CLOCK_LO();
delay(1);

if ( c > 0 )
{
I2C_DATA_LO();
}

delay(1);

}

unsigned char I2C_ReadBit()
{
I2C_DATA_HI();

I2C_CLOCK_HI();
delay(1);

unsigned char c = I2C_PIN;

I2C_CLOCK_LO();
delay(1);

return ( c >> I2C_DAT ) & 1;
}

// Inits bitbanging port, must be called before using the functions below
//
void I2C_Init()
{
I2C_PORT &= ~( ( 1 << I2C_DAT ) | ( 1 << I2C_CLK ) );

I2C_CLOCK_HI();
I2C_DATA_HI();

delay(1);
}

// Send a START Condition
//
void I2C_Start()
{
// set both to high at the same time
I2C_DDR &= ~( ( 1 << I2C_DAT ) | ( 1 << I2C_CLK ) );
delay(1);

I2C_DATA_LO();
delay(1);

I2C_CLOCK_LO();
delay(1);
}

// Send a STOP Condition
//
void I2C_Stop()
{
I2C_CLOCK_HI();
delay(1);

I2C_DATA_HI();
delay(1);
}

// write a byte to the I2C slave device
//
unsigned char I2C_Write( unsigned char c )
{
for ( char i=0;i<8;i++)
{
I2C_WriteBit( c & 128 );

c<<=1;
}

//return I2C_ReadBit();
return 0;
}


// read a byte from the I2C slave device
//
unsigned char I2C_Read( unsigned char ack )
{
unsigned char res = 0;

for ( char i=0;i<8;i++)
{
res <<= 1;
res |= I2C_ReadBit();
}

if ( ack > 0)
{
I2C_WriteBit( 0 );
}
else
{
I2C_WriteBit( 1 );
}

delay(1);

return res;
}

For those who want to use this on normal I2C devices, switch the commented lines in the I2C_Write function. I've tested it with an normal I2C slave and the library works for that. This code uses the internal pullups for both the SCL and SDA line. WARNING: don't use port 0 and 1 on non-Leonardo Arduinos. Those are used for serial communication.

With this code I can send the right waveforms (checked with an oscilloscope). However, I can't get a response from the device yet. I begin to suspect the device is broken.. But I suppose my programming question is solved now. Thanks for the help.