I2C stall during transmit.

Sorry for cross-posting, but no response on the networking section, and it’s really a programming question, and probably should have been in here in the first place …

I’ve been trying to understand how the AVR I2C and Wire library work. It appears to me that, if you send 100 bytes at 100 KHz, the processor will be sitting in the while() loop below doing not much of anything for 10-msec, while the ISR is repeatedly entered and exited automatically, etc. Kind of stalls the foreground tasks, just like using a delay(10). Yes, no, maybe?

For AVR in twi.c:
-----------------

uint8_t twi_writeTo(uint8_t address, uint8_t* data, uint8_t length, uint8_t wait, uint8_t sendStop)
{
........
  twi_state = TWI_MTX;
........

  if (true == twi_inRepStart) {
    twi_inRepStart = false;
........
  }
  else
    // send start condition
    TWCR = _BV(TWINT) | _BV(TWEA) | _BV(TWEN) | _BV(TWIE) | _BV(TWSTA);    // enable INTs

  // wait for write operation to complete
  while(wait && (TWI_MTX == twi_state)){    <---[twi_state not cleared in ISR till all data sent]
    continue;
  }
..............
}


void twi_reply(uint8_t ack)
{
  // transmit master read ready signal, with or without ack
  if(ack){
    TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWINT) | _BV(TWEA);
  }else{
	  TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWINT);
  }
}


ISR(TWI_vect)
{
....
    // 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);                           <---[setting TWINT here initiates data xfer]
      }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;                  <---- [getting to here ends the data xfer]
    }
      }
      break;
....
}

I've not look at this version of the I2C "lib" but yes I expect it blocks in this way. You can expect the same with SPI "lib"s such as lcd, as well.

3.

  1. Answers Rewrite the lib so that it does not block.

  2. Find a non blocking version (I never seen one)

  3. Don't write big chunks of data!

Mark

Yeah, so you agree with me about the blocking. Good.

Your #3 might be the best solution.

However, I was just looking at it again. Since you have dedicated buffers for out-going data, and the "entire" transfers are taken care of via [automatic] sequential re-entries into the ISR, heck, why not just remove the while() wait loop from twi_writeTo(), and just go on?

Huh, I bet that would work. You would just need to avoid recalling the high-level Wire routines [begin(), write(), and endTransmission()] until the buffer has been sent. So, just add a busy flag for that. Or else massage the code to use a circular data buffer. Hmmm, #1 solution, good thinking.

See hardwareSerial.

Mark

oric_dan: I've been trying to understand how the AVR I2C and Wire library work. It appears to me that, if you send 100 bytes at 100 KHz, the processor will be sitting in the while() loop below doing not much of anything for 10-msec, while the ISR is repeatedly entered and exited automatically, etc. Kind of stalls the foreground tasks, just like using a delay(10). Yes, no, maybe?

The send buffer of the Arduino Wire library is 32 bytes only, so you can only send max. 32 bytes at once when using the default configuration.

But yes: The function returns from sending to caller after all bytes have been sent.

Same as with reading from or writing to EEPROM. Same as with Liquidcrystal library and printing a string to the LCD display: It takes some time until finished.

Wire.endTransmission lets you know if the transmission went OK or not so yes, it blocks.

/* 
 * Function twi_writeTo
 * Desc     attempts to become twi bus master and write a
 *          series of bytes to a device on the bus
 * Input    address: 7bit i2c device address
 *          data: pointer to byte array
 *          length: number of bytes in array
 *          wait: boolean indicating to wait for write or not
 *          sendStop: boolean indicating whether or not to send a stop at the end
 * Output   0 .. success
 *          1 .. length to long for buffer
 *          2 .. address send, NACK received
 *          3 .. data send, NACK received
 *          4 .. other twi error (lost bus arbitration, bus error, ..)
 */

Notice the "wait" argument?

Darn, exactly right, I blew right by that. Just re-write endTransmission with 0 instead of the 1, and it doesn't block.

uint8_t TwoWire::endTransmission(uint8_t sendStop)
{
  // transmit buffer (blocking)
  int8_t ret = twi_writeTo(txAddress, txBuffer, txBufferLength, 1, sendStop);
  // reset tx buffer iterator vars
  txBufferIndex = 0;
  txBufferLength = 0;
  // indicate that we are done transmitting
  transmitting = 0;
  return ret;
}

And twi_state is static inside twi.c (static volatile uint8_t twi_state;), but a fcn could be written something like boolean spi_busy() to provide a busy check.

[and thanks for redirecting the other thread, I was gonna add a link myself to preclude double-postings].

The send buffer of the Arduino Wire library is 32 bytes only, so you can only send max. 32 bytes at once when using the default configuration.

Yeah, I noticed that, which is why I'd patch it too with my little editor, while I'm "fixing" things.

I was looking at this recently as well and noticed that, unlike hardware serial, if you send more bytes than the size of the buffer it fails. The hardware serial write will wait until there is room in the ring buffer to drop the bytes but the wire library returns an error.

I decided to rid my project of I2C entirely. If I had to use it I’d look seriously at writing my own wire library. But before that I’d check to see if someone else already did so. Have they?

http://dsscircuits.com/articles/arduino-i2c-master-library

I was looking at this recently as well and noticed that, unlike hardware serial, if you send more bytes than the size of the buffer it fails.

Yes but I2C is more timing-dependent than async serial. Serial has no particular requirement for the time between bytes, I2C does.

That's why beginTransmission, and Wire.write don't actually write anything. It's all done in endTransmission. For a good reason.

[quote author=Nick Gammon link=msg=2102080 date=1424399093] http://dsscircuits.com/articles/arduino-i2c-master-library [/quote] Thanks for the link.

I wonder how that library compares to the Wire library in terms of RAM usage? One of the reasons I replaced the lone I2C device I was using with an SPI version was that the Wire library consumed over 200 bytes of precious RAM. It ate up a substantial amount of flash as well.

The biggest problem I found with Wire, and which drove me nuts trying to understand it, was the many layers of overhead involved in the "Wire" library functions overlaid on top of the low-level "twi" routines. Roughly speaking, there are something like 100-200 C-language statements involved before you get down to finally executing 2 or 3 register operations in twi. Those 2-3 ops are the real meat, the rest is overhead.

I suppose it's nice to have all that bounds-checking and whatnot for general use, but one could make the entire process much more compact for specific-use.

I was completely lost initially because I was approaching it from a top-down direction, meaning wading through levels and levels of Wire overhead. Finally, I looked at it from a bottom-up perspective, to see what the actual meat was [ie, the register ops], and finally it all made some sense.

I felt the same way reading through it. I think it's trying to solve a more general problem which made it overkill for my needs at the time.

There are five 32 byte buffers: an RX and TX buffer in Wire, an RX and TX buffer in twi and a master buffer in twi. That's 160 bytes and yet you can only send 32 bytes at a time.