Go Down

Topic: The transfer could not be performed due to a clock stretch timeout (-2147024775) (Read 817 times) previous topic - next topic

manaldaest2016

I am setting an I2C system between a Raspberry Pi 3 (running a UWP program in Windows 10 IoT Core) as Master connecting to an Arduino DUE as Slave (with Arduino 1.6/1.8 ). Right after the Pi 3 writes to the Arduino DUE, it fails with the following error:

"The transfer could not be performed due to a clock stretch timeout. Make sure the clock line is not being held low by the slave device."

I had no error messages connecting the Pi 3 to the Arduino UNO. I thought the Pi 3 would be more compatible with Arduino DUE and had less issues since both boards run on 3.3V.

I have done the following wiring connections:

1) Connected to SDA(20)/SCL(21) directly.
2) Connected to SDA(20)/SCL(21) with a resistor of 1k ohm and 4.6k ohm to 3.3V.
3) Connected to SDA(20)/SCL(21) with a resistor of 1k ohm and 4.6k ohm to 5V.
4) Connected to SDA1(70)/SCL1(71) directly.
5) Connected to SDA1(70)/SCL1(71) with a resistor of 1k ohm and 4.6k ohm to 3.3V.
6) On all connections both are Grounded directly.
7) Even connected one time the Pi 3 3.3V to the Due 3.3V directly (and disconnected right after testing).

This is the Pi 3 Master code (only the relevant part that deals with Arduino):

Code: [Select]

public static async void InitCommArduino()
{
    I2cConnectionSettings settings = new I2cConnectionSettings(0x40); // Arduino address
    settings.BusSpeed =I2cBusSpeed.FastMode; //.StandardMode;

    /*
    // Get a selector string that will return all I2C controllers on the system
    string aqs = I2cDevice.GetDeviceSelector("I2C1");
    // Find the I2C bus controller devices with our selector string
    DeviceInformationCollection dis = await DeviceInformation.FindAllAsync(aqs);
    // Create an I2cArduinoDevice with our selected bus controller and I2C settings
    I2cToArduino = await I2cDevice.FromIdAsync(dis[0].Id, settings);
    */

    I2cController controller = await Windows.Devices.I2c.I2cController.GetDefaultAsync();
    //Create an I2cDevice with our selected bus controller and I2C settings
    I2cToArduino = controller.GetDevice(settings); 
    SignalArduinoToggleLights(new byte[] { 1 }, 1);
}

public static void SignalArduinoToggleLights(byte[] LightBuf, int IntBuf)
{
    /* Write the register settings */
    // When WriteBuf is
    // 0 = Red
    // 1 = Green
    try
    {
        I2cToArduino.Write(LightBuf);
    }
    /* If the write fails display the error and stop running */
    catch (Exception ex)
    {
        string some = ex.Message;
        //CommToArduino.Text = "Failed to communicate with device: " + ex.Message;
        return;
    }
}


This is the Arduino Due Slave code:

Code: [Select]

#include <Wire.h>
#define Wire Wire1

void setup() {
   Serial.begin(115200);
   InitiateWire();
   ResetLights();
}

void InitiateWire() {
   Wire1.begin(0x40);
   Wire1.setClock(400000L);
   Wire1.onReceive(i2cReceiveFromPi3);
   Serial.println("I2C init: done.");
}

void loop() {
   // some code here...
}

void i2cReceiveFromPi3(int byteCount) { //byte i2cHandleRx(byte command)
   while (Wire1.available()) { // slave may send less than requested
int x = Wire1.read(); // receive a byte as character
Serial.print("x value from Pi3: ");
Serial.println(x);

if (x == 1) { // checks to see byte is an e
TurnOnOffLight(1); // a Zero turns light ON
}
else if (x == 0) {
TurnOnOffLight(0);
}
   }
}

void TurnOnOffLight(int DoAction) {
   if (DoAction == 1) { // GREEN
digitalWrite(Relay_Red_Light, 0);   // Turns Off RED Light
digitalWrite(Relay_Green_Light, 1);   // Turns On GREEN Light
   }
   else if (DoAction == 0) {
digitalWrite(Relay_Green_Light, 0);   // Turns Off GREEN Light
digitalWrite(Relay_Red_Light, 1);   // Turns On RED Light
   }
}

void ResetLights() {
   pinMode(Relay_Green_Light, OUTPUT);
   digitalWrite(Relay_Green_Light, 0);   // Turns OFF Relay to start
   pinMode(Relay_Red_Light, OUTPUT);
   digitalWrite(Relay_Red_Light, 0);   // Turns OFF Relay to start
}


Some say the Arduino Wire library is not for the DUE model; however, others argue that DUE has its own Wire library and the IDE correctly integrates it when compiling and deploying/uploading to the DUE board.

Questions:

1) What code am I missing on Pi 3 and/or Arduino DUE?
2) Is there any wiring in particular that I should follow explicitly using the Arduino DUE (since I have seen many using the Arduino UNO and that doesn't help with my case)? If you can provide images or fritzing sketches I will appreciate a lot!!!
3) Is there any board I could setup between the Pi 3 and Arduino DUE that could resolve this communication problem?

I am relatively new to Pi 3 and Arduino (and boards in general). So I will appreciate if you provide as much details as possible.

Thanks in advance for your response!

ard_newbie

SCL / SDA did have pullups build in but not SCL1 / SDA1. On SCL1/ SDA1 you need Pullups about 2k2 Ohm. The outputs of the Due is about 4 mA, the others Arduino about 20 mA... for best performance the I2C Pull ups should be 2k2 ohms for a 1.5 mA bus current.

Whenever you are using SDA1/SCL1 and the Wire library, replace everywhere wire by wire1. Therefore at the beginning of your sketch, you will have :

#include <Wire.h>
#define Wire Wire1

Some thoughts:

The I2C protocol is a very raw protocol that is inexpensive to implement, but is very susceptible to noise on the bus. The AVRs input stages contain a spike suppression unit removing spikes shorter than 50ns, however ATMEL ARMs Sam3 (and Sam4) have nothing. Occasionally EMI spikes last too long and are then read as data or as clock pulses. When this happens, the master and the slave get out of sync, often resulting in the slave holding SDA low while awaiting a clock pulse, while the master is waiting for the slave to release SDA so it can send a final NAK.

I2C has no concept of a timeout, so this state will last forever unless you detect it and force a resolution.

When a Master sees the invalid data, you can try to recover by temporarily taking control of the SCL and SDA pins yourself and toggling them as follows:
• Pulse clock 9 times to force any slave that was sending data to finish sending the current byte, at which point it will release the SDA line. When SDA goes high, the slave will see the next clock pulse as a NAK, so it will stop sending any more bytes. Any leftover clock pulses won't have any START marker, so will be ignored by the slaves.
• Then send a STOP condition (take CLK low, then take SDA low, then take CLK high, and finally allow SDA to transition low-> high while CLK is high). All masters and slaves on the bus should now be OK again.

There is a failure mode in which the master thinks another master has control of the bus (multi-master mode is supported by Sam3x). To recover from that state, first issue the pulses as above, then stop and restart Wire, and then stop and restart any frozen sensors.

It would be nice if all this functionality were in the Wire library, but as of today that's not the case so you'll have to do it yourself.

So if you have a long I2C bus, you have ringing on the falling edges, and the clocks coming into your board will have glitches on the order of 50ns, and your clocks leaving your board will generate these glitches as well. This leads to double clock counts and devices falling out of sync, pulling SDA low to ACK at the wrong times, eventually colliding with another device trying to let SDA go high and the bus will lock up with SDA held low.

A first workaround is to consider 10 or 20 ohm series resistors between the Due and the bus, this will attenuate the glitches leaving your Due. Another option seems to use a fast-mode compliant isolator like the PCA9517A. The isolator filters 50ns glitches and increases fall time.

I don't use the Wire library because I had too much issues with EMI and I wanted to make the I2C protocol work with the DUE. Here is an example sketch to test I2C with a single board (note that TWI0 is for SDA1/SCL1 whereas TWI1 is for SDA/SCL). You will note a dedicated function to reset the bus, usefull at the beginning and to recover from a trapped situation if you implement some timeout. I also did some tests using I2C DMA with two DUE boards and surprisingly results were good even with long wires (> 1 m, much less sensitive to EMI).

To be continued....

ard_newbie

Following my answer (more than 9000 characters !!):

Code: [Select]

/******************************************************************************/
/************    Master TWI0, Slave TWI1, temperature reading     *************/
/*          Jumper between SDA/SDA1 and another one between SCL/SCL1          */
/******************************************************************************/
#define read  1
#define write 0
#define TemperatureAddress      (0b1001000)  // 7-bit address

/*********************     Init TWIn      ************************/

void I2c_Init(Twi* pTWI, boolean Master) {

  if (pTWI == TWI0) {
    PMC->PMC_PCER0 |= PMC_PCER0_PID22;      // TWI0 power ON
    PIOA->PIO_PDR |= PIO_PDR_P17            // Enable peripheral control
                     | PIO_PDR_P18;
    PIOA->PIO_ABSR &= ~(PIO_PA17A_TWD0      // TWD0 & TWCK0 Peripherals type A
                        | PIO_PA18A_TWCK0);
  }
  else {
    PMC->PMC_PCER0 |= PMC_PCER0_PID23;      // TWI1 power ON
    PIOB->PIO_PDR |= PIO_PDR_P13            // Enable peripheral control
                     | PIO_PDR_P12;
    PIOB->PIO_ABSR &= ~(PIO_PB12A_TWD1      // TWD1 & TWCK1 Peripherals type A
                        | PIO_PB13A_TWCK1);
  }
  // I2C lines are Open drain by hardware, no need to program PIO_ODER

  pTWI->TWI_CR = TWI_CR_SWRST;  // TWIn software reset
  pTWI->TWI_RHR;                // Flush reception buffer

  pTWI->TWI_CR = TWI_CR_SVDIS | TWI_CR_MSDIS; // Disable Master and Slave modes

  //Enable master mode
  if (Master == 1) {
    //enter slave address
    pTWI->TWI_MMR |= TWI_MMR_DADR(TemperatureAddress);

    pTWI->TWI_CR = TWI_CR_MSEN;  // Master mode enable
    //clockwave from 100khz to 400khz
    SetClock(pTWI, 400000);
    MasterResetI2CBUS();
  }
  else { // Enable Slave mode
    pTWI->TWI_SMR = TWI_SMR_SADR(TemperatureAddress);
    pTWI->TWI_CR = TWI_CR_SVEN;     // Slave mode enable
  }
}

/******************     Master Reset TWI0 BUS     ********************/

void MasterResetI2CBUS() {

  TWI0->TWI_CR = TWI_CR_SVDIS | TWI_CR_MSDIS;

  PMC->PMC_PCER0 = PMC_PCER0_PID11;  // PIOA power ON
  PIOA->PIO_PER |= PIO_PER_P18;      // TWCK0 pin (SCL1) back to GPIO
  PIOA->PIO_OER |= PIO_OER_P18;
  PIOA->PIO_OWER |= PIO_OWER_P18;

  PIOA->PIO_PER |= PIO_PER_P17;      // TWD0 pin (SDA1)  back to GPIO
  PIOA->PIO_OER |= PIO_OER_P17;
  PIOA->PIO_OWER |= PIO_OWER_P17;

#define SCL1 (71)
#define SDA1 (70)
  // Generate 9 clock pulses
  for (uint8_t i = 0; i < 10; i++) {
    digitalWrite(SCL1, HIGH);
    delay(10);
    digitalWrite(SCL1, LOW);
    delay(10);
  }

  // Send a STOP
  digitalWrite(SCL1, LOW);
  digitalWrite(SDA1, LOW);
  delay(20);
  digitalWrite(SCL1, HIGH);
  digitalWrite(SDA1, HIGH);

  // Back to TWI0
  //PMC->PMC_PCDR0 = PMC_PCDR0_PID11;     // PIOA power OFF
  PIOA->PIO_PDR |= PIO_PDR_P17            // Enable peripheral control
                   | PIO_PDR_P18;
  PIOA->PIO_ABSR &= ~(PIO_PA17A_TWD0      // TWD0 & TWCK0 Peripherals type A
                      | PIO_PA18A_TWCK0);

  TWI0->TWI_CR = TWI_CR_MSEN;
  delay(30);
}

/****************************     Start    ****************************/

void I2c_Start(Twi* pTWI, uint8_t slave_address, uint8_t mread) { //read=1, write=0
  //set slave address
  pTWI->TWI_MMR = (pTWI->TWI_MMR & ~TWI_MMR_DADR_Msk)
                  | TWI_MMR_DADR(slave_address);
  //set read/write direction
  if (mread == write) { //write
    pTWI->TWI_MMR &= ~TWI_MMR_MREAD;
  }
  else if (mread == read) { //read
    pTWI->TWI_MMR |= TWI_MMR_MREAD;
  }
  //send start
  pTWI->TWI_CR |= TWI_CR_START;

  //wait for ack
  while (!(pTWI->TWI_SR & TWI_SR_TXRDY));
}

/***************************   Stop    ****************************/

void I2c_Stop(Twi* pTWI) {
  pTWI->TWI_CR |= TWI_CR_STOP;
}

/**********************     Read 1 byte   **************************/

uint8_t I2c_ReadByte(Twi* pTWI) {
  uint8_t receivedByte;
  //If the stop bit in the control register is not set,
  //Sam3x will automatically ACK after reading TWIn_RHR register
  //RXRDY will be set when data arrives in TWIn_RHR register

  while (!(pTWI->TWI_SR & TWI_SR_RXRDY));
  //reading data will clear RXRDY bit in the status register
  receivedByte = pTWI->TWI_RHR;
  return receivedByte;
}

/*****************     Read the last byte   ***********************/

uint8_t I2c_ReadLastByte(Twi* pTWI) {
  uint8_t receivedByte;
  //Sam3x requires stop bit to be set before data is set on the TWIn_RHR
  //when stop bit is set, Sam3x will send a NACK instead of an ACK automatically
  I2c_Stop(pTWI);
  //When data arrives in the TWIn_RHR register RXRDY is set in the Status Register
  while (!(pTWI->TWI_SR & TWI_SR_RXRDY));
  //reading data will clear RXRDY bit in the status register
  receivedByte = pTWI->TWI_RHR;
  while (!(pTWI->TWI_SR & TWI_SR_TXCOMP));
  return receivedByte;
}

/************************     Write 1 byte     *******************/

void I2c_WriteByte(Twi* pTWI, uint8_t data) {
  //write data or slave register to THR
  pTWI->TWI_THR |= data;
  //wait for ack
  while (!(pTWI->TWI_SR & TWI_SR_TXRDY));
}

/***********************    Write last byte    *******************/

void I2c_WriteLastByte(Twi* pTWI, uint8_t data) {
  //write data or slave register to THR
  pTWI->TWI_THR |= data;
  I2c_Stop(pTWI);
  //wait for ack
  while (!(pTWI->TWI_SR & TWI_SR_TXRDY));
  while (!(pTWI->TWI_SR & TWI_SR_TXCOMP));
}

/*****************   Set Interrupt Configuration   *********************/

void I2c_Interrupt(Twi* pTWI, uint32_t InterrConfig) {

  pTWI->TWI_IER = InterrConfig;
  if (pTWI == TWI0) {
    NVIC_EnableIRQ(TWI0_IRQn);
  }
  else {
    NVIC_EnableIRQ(TWI1_IRQn);
  }
}

/*******************        TWI1 Handler    ***********************/

void TWI1_Handler(void) {

  uint32_t status = TWI1->TWI_SR;

  if (status & TWI_SR_SVREAD) {
    TWI1->TWI_RHR;
    TWI1->TWI_THR = temperatur();
  }
}

/**********************    Configure clock    *********************/

void SetClock(Twi* pTWI , uint32_t frequency) {
  uint32_t CLDIV = 0;
  uint32_t CKDIV = 0;
  uint8_t readyByte = 0;

  while (!readyByte) {
    CLDIV = ((VARIANT_MCK / (2 * frequency)) - 4) / (1 << CKDIV) ;
    if ( CLDIV <= 255 ) {
      readyByte = 1 ;
    }
    else {
      CKDIV++ ;
    }
  }
  pTWI->TWI_CWGR = (CKDIV << 16) | (CLDIV << 8) | CLDIV;
}

void setup() {

  // Power OFF all peripherals
  PMC->PMC_PCDR0 = 0xFFFFFFFF;
  PMC->PMC_PCDR1 = 0xFFFFFFFF;
  Serial.begin(250000);
  PMC->PMC_PCER1 |= PMC_PCER1_PID37;
  ADC->ADC_ACR |= ADC_ACR_TSON;

  I2c_Init(TWI0, 1);  // TWI0 Master
  I2c_Init(TWI1, 0);  // TWI1 Slave
  uint32_t InterruptConfig = TWI_IER_SVACC; 
  I2c_Interrupt(TWI1, InterruptConfig);
}

void loop() {

  uint8_t ReceivedByte1, ReceivedByte2;
  static uint32_t Oldmillis;

  if ((millis() - Oldmillis) > 1000) {
    Oldmillis = millis();
    I2c_Start(TWI0, TemperatureAddress, 1);
   
    ReceivedByte2 = I2c_ReadLastByte(TWI0);
    printf("%d\xB0" "C\n", ReceivedByte2 );
    I2c_Start(TWI0, TemperatureAddress, 1);
    ReceivedByte2 = I2c_ReadLastByte(TWI0);
    printf("%d\xB0" "C\n", ReceivedByte2 );
  }
}

/*****************   Temperature function    *******************/

uint8_t temperatur() {
  float trans = 3.3 / 4096;
  float offset = 0.76;
  float factor = 0.00265;
  float fixtemp = 0;
  uint32_t ulValue;
  float treal;
  uint8_t __treal;

  ADC->ADC_CHER |= 1 << ADC_TEMPERATURE_SENSOR;

  ADC->ADC_CR = ADC_CR_START;

  while (!(ADC->ADC_ISR & ADC_ISR_DRDY));

  ulValue = ADC->ADC_LCDR;

  ADC->ADC_CHDR |= 1 << ADC_TEMPERATURE_SENSOR;

  treal = fixtemp + (( trans * ulValue ) - offset ) / factor;
  __treal = (uint8_t) treal;
  return __treal;
}


biccius

Following my answer (more than 9000 characters !!):
The previous ard_newbie example lends itself to be used to implement a TWI auto-test feature on the Due.

I attach a version that transfers one byte at a time between TWI0 and TWI1 and vice versa and increments the byte each time.

https://pastebin.com/gHwX4qpj


Go Up