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:
======================================
======================================
Without resistor on the BUS:
======================================
======================================
I2C debugger logs compare:
======================================
======================================
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.