I2C Long Distance

Hello All, I am trying to initiate a communication link between 2 Arduinos “Mega and Uno”, over about 100 meters of Cat6 cable.

After reading various topics on long I2C bus, I used 2 x P82B96 powered by 12V and I’m using this configuration : SCL,GND (Orange, Orange-White) - SDA,GND (Green, Green-White).

I’m using Wire library, The bus is initiated at 50 KHz using Wire.setClock(), and I measured SCL, SDA using Oscilloscope at the master “Mega” before connecting the slave on the other end of the bus everything is okay. also did the same at the slave terminal. After I have connected the slave the bus has stopped.

Then I tried to modify Wire, TWI libraries to get to 500 Hz and disable the internal pull-ups :

void twi_init(void)
{
  // initialize state
  twi_state = TWI_READY;
  twi_sendStop = true; // default value
  twi_inRepStart = false;
  /*Edit - Remotify
  
  // activate internal pullups for twi.
  digitalWrite(SDA, 1);
  digitalWrite(SCL, 1);

  // initialize twi prescaler and bit rate
  cbi(TWSR, TWPS0);
  cbi(TWSR, TWPS1);
  TWBR = ((F_CPU / TWI_FREQ) - 16) / 2;

  / twi bit rate formula from atmega128 manual pg 204
  SCL Frequency = CPU Clock Frequency / (16 + (2 * TWBR))
  note: TWBR should be 10 or higher for master mode
  It is 72 for a 16mhz Wiring board with 100kHz TWI /
  
  Edit - Remotify*/

  digitalWrite(SDA, 0);
  digitalWrite(SCL, 0);

  cbi(TWSR, TWPS0);
  cbi(TWSR, TWPS1);

  sbi(TWSR, TWPS0);
  TWBR = 63;
  
  // enable twi module, acks, and twi interrupt
  TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWEA);
}

And I noticed that the bus would stop if an illegal error occurred so I modified it too :

ISR(TWI_vect)
{
  switch(TW_STATUS){
    // All Master
    case TW_START:     // sent start condition
    case TW_REP_START: // sent repeated start condition
      // copy device address and r/w bit to output register and ack
      TWDR = twi_slarw;
      twi_reply(1);
      break;

    // Master Transmitter
    case TW_MT_SLA_ACK:  // slave receiver acked address
    case TW_MT_DATA_ACK: // slave receiver acked data
      // if there is data to send, send it, otherwise stop 
      if(twi_masterBufferIndex < twi_masterBufferLength){
        // copy data to output register and ack
        TWDR = twi_masterBuffer[twi_masterBufferIndex++];
        twi_reply(1);
      }else{
 if (twi_sendStop)
          twi_stop();
 else {
  twi_inRepStart = true; // we're gonna send the START
  // don't enable the interrupt. We'll generate the start, but we 
  // avoid handling the interrupt until we're in the next transaction,
  // at the point where we would normally issue the start.
  TWCR = _BV(TWINT) | _BV(TWSTA)| _BV(TWEN) ;
  twi_state = TWI_READY;
 }
      }
      break;
    case TW_MT_SLA_NACK:  // address sent, nack received
      twi_error = TW_MT_SLA_NACK;
      twi_stop();
      break;
    case TW_MT_DATA_NACK: // data sent, nack received
      twi_error = TW_MT_DATA_NACK;
      twi_stop();
      break;
    case TW_MT_ARB_LOST: // lost bus arbitration
      twi_error = TW_MT_ARB_LOST;
      twi_releaseBus();
      break;

    // Master Receiver
    case TW_MR_DATA_ACK: // data received, ack sent
      // put byte into buffer
      twi_masterBuffer[twi_masterBufferIndex++] = TWDR;
    case TW_MR_SLA_ACK:  // address sent, ack received
      // ack if more bytes are expected, otherwise nack
      if(twi_masterBufferIndex < twi_masterBufferLength){
        twi_reply(1);
      }else{
        twi_reply(0);
      }
      break;
    case TW_MR_DATA_NACK: // data received, nack sent
      // put final byte into buffer
      twi_masterBuffer[twi_masterBufferIndex++] = TWDR;
 if (twi_sendStop)
          twi_stop();
 else {
  twi_inRepStart = true; // we're gonna send the START
  // don't enable the interrupt. We'll generate the start, but we 
  // avoid handling the interrupt until we're in the next transaction,
  // at the point where we would normally issue the start.
  TWCR = _BV(TWINT) | _BV(TWSTA)| _BV(TWEN) ;
  twi_state = TWI_READY;
 }    
 break;
    case TW_MR_SLA_NACK: // address sent, nack received
      twi_stop();
      break;
    // TW_MR_ARB_LOST handled by TW_MT_ARB_LOST case

    // Slave Receiver
    case TW_SR_SLA_ACK:   // addressed, returned ack
    case TW_SR_GCALL_ACK: // addressed generally, returned ack
    case TW_SR_ARB_LOST_SLA_ACK:   // lost arbitration, returned ack
    case TW_SR_ARB_LOST_GCALL_ACK: // lost arbitration, returned ack
      // enter slave receiver mode
      twi_state = TWI_SRX;
      // indicate that rx buffer can be overwritten and ack
      twi_rxBufferIndex = 0;
      twi_reply(1);
      break;
    case TW_SR_DATA_ACK:       // data received, returned ack
    case TW_SR_GCALL_DATA_ACK: // data received generally, returned ack
      // if there is still room in the rx buffer
      if(twi_rxBufferIndex < TWI_BUFFER_LENGTH){
        // put byte in buffer and ack
        twi_rxBuffer[twi_rxBufferIndex++] = TWDR;
        twi_reply(1);
      }else{
        // otherwise nack
        twi_reply(0);
      }
      break;
    case TW_SR_STOP: // stop or repeated start condition received
      // ack future responses and leave slave receiver state
      twi_releaseBus();
      // put a null char after data if there's room
      if(twi_rxBufferIndex < TWI_BUFFER_LENGTH){
        twi_rxBuffer[twi_rxBufferIndex] = '\0';
      }
      // callback to user defined callback
      twi_onSlaveReceive(twi_rxBuffer, twi_rxBufferIndex);
      // since we submit rx buffer to "wire" library, we can reset it
      twi_rxBufferIndex = 0;
      break;
    case TW_SR_DATA_NACK:       // data received, returned nack
    case TW_SR_GCALL_DATA_NACK: // data received generally, returned nack
      // nack back at master
      twi_reply(0);
      break;
    
    // Slave Transmitter
    case TW_ST_SLA_ACK:          // addressed, returned ack
    case TW_ST_ARB_LOST_SLA_ACK: // arbitration lost, returned ack
      // enter slave transmitter mode
      twi_state = TWI_STX;
      // ready the tx buffer index for iteration
      twi_txBufferIndex = 0;
      // set tx buffer length to be zero, to verify if user changes it
      twi_txBufferLength = 0;
      // request for txBuffer to be filled and length to be set
      // note: user must call twi_transmit(bytes, length) to do this
      twi_onSlaveTransmit();
      // if they didn't change buffer & length, initialize it
      if(0 == twi_txBufferLength){
        twi_txBufferLength = 1;
        twi_txBuffer[0] = 0x00;
      }
      // transmit first byte from buffer, fall
    case TW_ST_DATA_ACK: // byte sent, ack returned
      // copy data to output register
      TWDR = twi_txBuffer[twi_txBufferIndex++];
      // if there is more to send, ack, otherwise nack
      if(twi_txBufferIndex < twi_txBufferLength){
        twi_reply(1);
      }else{
        twi_reply(0);
      }
      break;
    case TW_ST_DATA_NACK: // received nack, we are done 
    case TW_ST_LAST_DATA: // received ack, but we are done already!
      // ack future responses
      twi_reply(1);
      // leave slave receiver state
      twi_state = TWI_READY;
      break;

    // All
    case TW_NO_INFO:   // no state information
      break;
    case TW_BUS_ERROR: // bus error, illegal stop/start
      twi_error = TW_BUS_ERROR;
      twi_stop();
      //Edit - Remotify
      twi_init();
      //Edit - Remotify
      break;
  }
}

All of these didn’t work too. I will appreciate if anyone can help me with this.

I2C is designed for chip to chip communication ON THE SAME BOARD. I don't believe I've ever seen a circuit board that was 100 meters in any dimension.

There are other means of communicating when I2C is not appropriate, as it is not in your case.

Why are you welded to the I2C idea?

You could try an I2C accelerator chip, and reduce the bit rate down to very slow

PaulS:
I2C is designed for chip to chip communication ON THE SAME BOARD. I don't believe I've ever seen a circuit board that was 100 meters in any dimension.

There are other means of communicating when I2C is not appropriate, as it is not in your case.

Why are you welded to the I2C idea?

Yes, I2C is a short distance communication link but with already available buffers it supports up to 200 meters according to their datasheets.

I'm working on a circuit that implements I2C and their is no way to modify the circuit ATM !

mcnobby:
You could try an I2C accelerator chip, and reduce the bit rate down to very slow

As I mentioned in my post, the bus already have no problem ! Which is totally weird !

The oscilloscope shows accurate waveforms ! But when I wire the slave, I2C bus shuts down !

mostafagamal:
The oscilloscope shows accurate waveforms ! But when I wire the slave, I2C bus shuts down !

What do you mean by "shut down"?

lg, couka

couka:
What do you mean by “shut down”?

lg, couka

The master disables TWI, and everything else works fine.

mostafagamal:
The master disables TWI, and everything else works fine.

What does the oscilloscope show, when that happens?

lg, couka

Why not using a serial communication? Apply some line-drivers on both sides; that might be easier.

HansFu:
Why not using a serial communication? Apply some line-drivers on both sides; that might be easier.

I cannot modify the circuits right now, I have to find a way to get I2C working using the current buffer "P82B96"

Hi, did you think about having one of your Arduinos to be a slave? You can't have two masters in one I2C bus to communicate with each other. Did you try to link both MCUs without the I2C drivers on a low distance?

Martymart:
You can't have two masters in one I2C-bus.

That's not true, but a single-master bus is easier to do, yes.

lg, couka

Martymart:
Hi, did you think about having one of your Arduinos to be a slave? You can't have two masters in one I2C bus to communicate with each other. Did you try to link both MCUs without the I2C drivers on a low distance?

I'm already using a master/slave configuration.

Both controllers are working perfectly without the buffer, also a short distance cable works.

The problem appears only on long distance cables.

mostafagamal:
Hello All, I am trying to initiate a communication link between 2 Arduinos "Mega and Uno", over about 100 meters of Cat6 cable.

After reading various topics on long I2C bus, I used 2 x P82B96 powered by 12V and I'm using this configuration : SCL,GND (Orange, Orange-White) - SDA,GND (Green, Green-White).

I'm using Wire library, The bus is initiated at 50 KHz using Wire.setClock(), and I measured SCL, SDA using Oscilloscope at the master "Mega" before connecting the slave on the other end of the bus everything is okay. also did the same at the slave terminal. After I have connected the slave the bus has stopped.

Then I tried to modify Wire, TWI libraries to get to 500 Hz and disable the internal pull-ups :

void twi_init(void)

{
 // initialize state
 twi_state = TWI_READY;
 twi_sendStop = true; // default value
 twi_inRepStart = false;
 /*Edit - Remotify
 
 // activate internal pullups for twi.
 digitalWrite(SDA, 1);
 digitalWrite(SCL, 1);

// initialize twi prescaler and bit rate
 cbi(TWSR, TWPS0);
 cbi(TWSR, TWPS1);
 TWBR = ((F_CPU / TWI_FREQ) - 16) / 2;

/ twi bit rate formula from atmega128 manual pg 204
 SCL Frequency = CPU Clock Frequency / (16 + (2 * TWBR))
 note: TWBR should be 10 or higher for master mode
 It is 72 for a 16mhz Wiring board with 100kHz TWI /
 
 Edit - Remotify*/

digitalWrite(SDA, 0);
 digitalWrite(SCL, 0);

cbi(TWSR, TWPS0);
 cbi(TWSR, TWPS1);

sbi(TWSR, TWPS0);
 TWBR = 63;
 
 // enable twi module, acks, and twi interrupt
 TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWEA);
}





All of these didn't work too. I will appreciate if anyone can help me with this.

you hardcoded the TWBR to 63? 112.676kHz? I thought you wanted 500hz?

The TI spec Sheet calculates the max speed for 100m of cable at 185khz, but, this is based on propagation delay. The capacitance of the cable is assumed to not be a problem. I would slow it down to the mimimum (31khz) and try it.

The slowest the I2C hardware can be programmed to is 30.418khz, if TWBR is 255.

Remove your TWBR=63, just use the Wire.setClock(), it's valid range is from 444,444 to 30,418.

The only way to go slower with the hardware is to change the crystal from 16mhz to something slower.

Chuck.

chucktodd:
you hardcoded the TWBR to 63? 112.676kHz? I thought you wanted 500hz?

The TI spec Sheet calculates the max speed for 100m of cable at 185khz, but, this is based on propagation delay. The capacitance of the cable is assumed to not be a problem. I would slow it down to the mimimum (31khz) and try it.

The slowest the I2C hardware can be programmed to is 30.418khz, if TWBR is 255.

Remove your TWBR=63, just use the Wire.setClock(), it's valid range is from 444,444 to 30,418.

The only way to go slower with the hardware is to change the crystal from 16mhz to something slower.

Chuck.

You can go slower, without setClock() as it doesn't use pre-scaler.

Sorry for replying too late, having exams ATM :frowning: !

Anyway, as I already mentioned I can go down to 500 Hz easily by setting TWPS bits as indicated in attached photo.

Also I really don’t have a problem with the bus speed ! the output measured on my buffer is okay, just I don’t know what is the problem with my MCUs ! they just disable the TWI.

prescaler.jpg