Arduino Due I2C bus lock up

Hi everybody,

as the title of this topic says, I'm having problems with my Due using I2C. More precisely I'm having random (several times a day or none at all) the situation were the data line of my I2C communication is stuck low. I assume that the communication cycle is somehow disturbed and one of the slaves is holding the bus low.

The Due is configured as master and is connected to several slaves, PWM chips, temperature sensors, EEPROM. The clock speed is set to 75kHz. Attached you can find a picture from the logic analyser capturing the moment of the bus lock up and an oscilloscope picture showing the signal quality. Please be aware, that the different clock pulse length on the logic analyser picture came from the low sampling rate of 200kHz. It was necessary to set it that low so I can capture a longer period of time. With a higher sampling rate the clock is perfectly fine.

Although the root of this problem might be hardware I ask you to discuss software based solutions here. The hardware is pretty big and is hard to explain in this topic. And that being said I work full time on investigation hardware related problems but couldn't find any obvious problems yet.

That's why I ask you if someone has an idea to tackle this problem from the software side. I was carefully reading the NXP (Phillips) document for the I2C bus specification and in section 3.1.16 Bus Clear you can find the paragraph:

"If the data line (SDA) is stuck LOW, the master should send nine clock pulses. The device
that held the bus LOW should release it sometime within those nine clocks. If not, then
use the HW reset or cycle power to clear the bus."

Unfortunately I can not make a HW reset nor can I make a power cycle cause everything is combined in a moving system and would be damaged/not working properly if doing that.

I was looking into the wire library of Arduino and correct me if I'm wrong but unfortunately I couldn't find any error handling that can handle a situation like this.

So my question is somebody having or already solved a similar problem or can you give me a hint in any direction. I will also try to make a minimal piece of firmware that includes only the communication with some of the I2C components. This will help test and discuss several approaches

This is

I2C_signal.PNG

Some general thoughts about Sam3x I2C:

The DUE I2C interface (Sam3x8e) is somehow different from the one of an AVR chip. It expects you to set the stop bit in the control register before you send the next to last byte if you read several bytes, or at the same time you send the Start if you read only one byte. If you don't set the stop bit properly, the DUE will send an ACK instead of NACK on the last read.

You can try to write entirely the program sequence needed without any blocking code using TWI registers. For safety, you can consider having a reset line from the Master (DUE) to the sensor in case a reading can't be performed after a certain timeout.

I have only a short experience with this interface but I found it extremely sensitive to EMI, e.g. I did tests between two DUE (One in Master mode, the other one in Slave mode) and I had to use a very short jumper between grounds to make this work (no builtin glitch filtering). It seems the answer is to use a fast-mode compliant isolator like the PCA9517A. The isolator filters 50ns glitches and increases fall time.

Furthermore, arbitration of the bus is performed internally by hardware and puts the TWI in slave mode automatically if the bus arbitration is lost. I wonder if this could be an issue too in case arbitration sets itself even though you are not using multi master mode.

Another issue is the recurring question of pull ups, SDA/SCL have builtin pull ups of 1K, and SDA1/SCL1 don't have any pull ups. You may want to use SDA1/SCL1 with 2K2 pull ups instead of SDA/SCL. Note that TWI0 is for SDA1/SCL1 and TWI1 for SDA/SCL.

In Sam S70 datasheet, in case an I2C slave is locked, they suggest this to perform a bus reset (Errata section, page 1581):
Reconfigure the SCL line in GPIO output and generate nine clock pulses via software to unlock the I2C device. Once done, the SCL line can be reconfigured as a peripheral line.

We have made a trouble-free operation of a DUE-based TWI Bus System (Fig-1) consisting of two TWI slave devices -- Temperature Sensor (BMP180) and EEPROM (24C512). The schematic and the attached program may help you to trouble-shoot your intermittently-working system. (Theory of TWI Bus Technology).


Figure-1: TWI Bus operation using DUE and two TWI slaves


Figure-2A: TWI Bus timing diagram showing BusEvents of ATmega328 (shown as reference)

TWI Bus Programming/Operation of Fig-1
(1) Check that the Bus is Free. It can be known by asserting START command and then checking the generation of known Status word which is automatically generated within Master TWI Logic if the process is successful. This is 0x08 for ATmega328 (how much is it for SAM3X8E?) Can we say that this is a Transmission Process?

(2) Assert the 'SLA + W (Slave Address + Write-bit)' control byte on the bus to check the functioning of the bus and the presence of the target slave (S1) in the system. It can be known by checking the known status word (it is 0x18 for ATmega328). This is generated within Master TWI Logic once the Slave-1 accepts its address (the device address) and then creates the ACK-bit by pulling down the SDA Line (Fig-2A). This is the ACK (or NACK) signal which, in fact, triggers the Twi Logic of the Master to genertae the status word.

Is this a Transmission Process. Does correct value (expected value) of the status word indicate that the current Transmission Process is a successful transmission?

Mechanism of ACK Generation by Slave: A Transmission Process is a process in which the Master automatically generates required number of SCL (serial clock) pulses when data is written in TWI's Data Register. There are 8 clock pulses in the first phase which shifts-in (in write mode, W=0) the data byte into the slave. During this time, the Processor keeps polling some flag (in Atmega328, it is TWINT) to see the 'end of the process.' At the end of of 8th pulse (Fig-2A), the Master releases the SDA line; because there is a pull-up, the SDA line immediately assumes H-state. Now comes the 2nd phase of clock pulse generation, when, the Master generates 9th clock pulse (Fig-2A) during which the Slave pulls down SDA Line (if Slave accepts the data). This change in the SDA line appears as ACK bit to the Master's TWI Logic. As a result, a known value of status word is generated within TWI Logic of the Master. At the end of 9th SCL pulse, the Slave releases the SDA lines; the SDA line again assumes H-state, and makes it ready for next transmission.

TWI Bus is under the Control of Master; because, SCL line is at L-state and SDA line is at H-state. The bus is not in the locked state. Another bus Master (in a multi-master/multi-slave system) will be successful in seizing the bus if the bus lines (SCL and SDA) are at H-states. This H-states of the bus lines can be established in two ways: (a) the Current bus Master must execute a STOP command and (b) the competing Master must reset the TWI Bus by asserting 9 SCL pulses which can be generated by the requesting Master through the execution of the following codes:

{
     Wire.beginTransmission(dviceaddress);  //address of the target slave  SLA
     Wire.write(0x00);                                //SLA + W
     Wire.endTransmission();                      
}

....to be continued

bmp85TempEE512.ino (5.62 KB)

I've been suffering from the Wire1 I2C bus on the Arduino hanging when subjected to electromagnetic interference. The symptom is the clock line held high and the data line held low. My solution is to first trap the error with a modified EndTransmission function, then disable the I2C bus, bit bang 9 pulse on the clock line to reset the slave devices, then restart the I2C bus.

int Wire1_EndTransmission(int Address){
int stop_msg = Wire1.endTransmission();
if(stop_msg !=0){
Serial.print("I2C error:");
Serial.print(Address);
Serial.print(" ");
Serial.println(stop_msg);
uint32_t I2C_Status = TWI_GetStatus(WIRE1_INTERFACE);
Serial.print("I2C status: ");
Serial.println(I2C_Status);
//disable I2C bus
TWI_Disable(WIRE1_INTERFACE);
//send 9 clock pulses to reset slaves
pinMode(71, OUTPUT);
for(int i = 0; i < 9; i++){
digitalWrite(71, HIGH);
delayMicroseconds(5);
digitalWrite(71, LOW);
}
//Restart bus
Wire1.begin();
}
return stop_msg;
}

Reseting the I2C bus as you did is certainly a good solution :slight_smile: .

To avoid EMI issues on the bus, you can add a 10 to 20 Ohms resistor in serie either on SDA or SCL lines(or SDA1/SCL1).

In this thread, there is a MasterResetI2CBus() function that you can use after a timeout:

https://forum.arduino.cc/index.php?topic=560415.0

Interesting. The code is for TWI0.
Do you think the following code should work as reset for TWI1?
Untested

		TWI1->TWI_CR = TWI_CR_SVDIS | TWI_CR_MSDIS;
		PMC->PMC_PCER0 = PMC_PCER0_PID12;  // PIOB power ON


		  PIOB->PIO_PER |= PIO_PER_P12;      // TWCK1 pin (SCL) back to GPIO
		  PIOB->PIO_OER |= PIO_OER_P12;
		  PIOB->PIO_OWER |= PIO_OWER_P12;

		  PIOB->PIO_PER |= PIO_PER_P13;      // TWD1 pin (SDA)  back to GPIO
		  PIOB->PIO_OER |= PIO_OER_P13;
		  PIOB->PIO_OWER |= PIO_OWER_P13;

		  // Generate 9 clock pulses
		  for (uint8_t i = 0; i < 10; i++)
		  {
		    digitalWrite(SCL, HIGH);
		    delay(10);
		    digitalWrite(SCL, LOW);
		    delay(10);
		  }

		  // Send a STOP
		  digitalWrite(SCL, LOW);
		  digitalWrite(SDA, LOW);
		  delay(20);
		  digitalWrite(SCL, HIGH);
		  digitalWrite(SDA, HIGH);

		  // Back to TWI1
		  //PMC->PMC_PCDR0 = PMC_PCDR0_PID12;     // PIOB power OFF
		  PIOB->PIO_PDR |= PIO_PDR_P12            // Enable peripheral control
		                   | PIO_PDR_P13;
		  PIOB->PIO_ABSR &= ~(PIO_PB12A_TWD1      // TWD1 & TWCK1 Peripherals
		                        | PIO_PB13A_TWCK1);
		  TWI1->TWI_CR = TWI_CR_MSEN;
		  delay(30);

In a sketch within TW1 (SDA/SCL) is the Master, that should work if you define SCL (21) and SDA (20).

To test it is easy, e.g. reverse Master and Slave roles in the sketch I provided in:

https://forum.arduino.cc/index.php?topic=560415.0