I2C (TWI): Hold SDA-line low by Slave when line has resistance between Master and Slave

i2c

Greetings to everyone!

The schema in brief:

Located at device 1                                   Located at device 2
Master SDA --------------- <physical connector> --------------- SDA Slave

According to I2C specifications: The Slave device must provide ACK signal holding SDA line on logical zero on success or do nothing on error, leaving I2C line high (NACK, logical one). This works fine when the resistance between Master is Slave is very low, for example, 5 milli-Ohm. But when the resistance rises to 100 or even 1000 milli-Ohm, the Slave can't hold line at appropriate logical zero. Such resistance can appear when a physical connector exists between Master and Slave.

Schematically, the SDA-signal at line with 100-1000 milli-Ohm resistance behaves like this:

Master sends some control sequence (7 bits + 1 R/W bit) to the I2C bus and the Slave responses with 1 bit ACK (logical zero).
Response with logical zero means that Slave holds line low, when Master holds line high.

                   Dev address            W     ACK
     1    0    1    0    1     1    0     0      ?
5V -----     -----     ----- -----
   |   |     |   |     |         |             ----- ~2.5V
0V |   |_____|   |_____|         |_____ _____

Here the ACK signal is at level between 2-3 volts, so it is obviously not a logical zero.

How to "help" Slave keep SDA-line on zero (below 0.3V)? Are there are any common practices for solving this?

Thanks in advance.

==========================================================

UPDATED POST WITH DETAILS:

Double checked the I2C manual: https://www.nxp.com/docs/en/user-guide/UM10204.pdf
Page 10, section 3.1.6

The Acknowledge signal is defined as follows: the transmitter releases the SDA line
during the acknowledge clock pulse so the receiver can pull the SDA line LOW and it
remains stable LOW during the HIGH period of this clock pulse

Thus, Master must release the line by switching to high impedance mode. But according to the details posted below Master stays at high level, preventing the Slave to hold SDA line low.

Tested at Proteus 8.10
Master: MPU ATmega328P
Slave: EEPROM 24C01C

======================================

With resistor on the BUS:

======================================

enter image description here

enter image description here

enter image description here

======================================

Without resistor on the BUS:

======================================

enter image description here

enter image description here

enter image description here

======================================

I2C debugger logs compare:

======================================

enter image description here

======================================

C-code:

======================================

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>

int main(void)
{     
    // Set bit rate to 400 KHz
    TWBR = (8000000LU / 400000LU - 16) / 2;
    
    // Send START
    TWCR = (1 << TWINT) | (1 << TWSTA) | (1 << TWEN);
    
    // Wait till operation is complete: Interrupt Flag is set
    while ( !(TWCR & (1 << TWINT)) );
    
    // Exit if status not: START has been transmitted
    if (TWSR != 0x08) {
        return 1;
    }
    
    // Load data register with: 0b_1010_000_0 (EEPROM addres + write)
    TWDR = 0xA0;
    
    // Transmit data    
    TWCR = (1 << TWINT) | (1 << TWEN);
    
    // Wait till operation is complete: Interrupt Flag is set
    while ( !(TWCR & (1 << TWINT)) );

    // Exit if status not: SLA+W has been transmitted and ACK received
    if (TWSR != 0x18) { 
        return 1; 
    }

    // ... rest of code skipped
    
    // Send STOP
    TWCR = (1 << TWINT) | (1 << TWSTO) | (1 << TWEN);
        
    return 0;
}

Compile log:

avr-gcc.exe -Wall -gdwarf-2 -fsigned-char -MD -MP -DF_CPU=1000000 -O1 -mmcu=atmega328p  -o "main.o" -c "../main.c"
avr-gcc.exe -mmcu=atmega328p  -o "./Debug.elf" "main.o"
avr-objcopy -O ihex -R .eeprom "./Debug.elf" "./Debug.hex"
avr-objcopy -j .eeprom --set-section-flags=.eeprom="alloc,load" --change-section-lma .eeprom=0 --no-change-warnings -O ihex "./Debug.elf" "./Debug.eep" || exit 0 
Compiled successfully.

Best Answer

That will not be a problem with connector of 1 ohms.

You will be in specs even with several tens of ohms of resistance in series, but obviously the actual value depends on supply voltage and pull-up resistance value.

If you are seeing that a chip can't pull low during ACK, the problem is not the series resistance, it is something else that is supposed to not happen, most likely MCU IO pins are not used in open drain mode or something similar.

Edit: The problem is that Proteus is wrong and fails to simulate I2C peripheral usage. In real life the AVR would make the pins go into open drain mode as soon as TWEN is set.