2

How long does it take to read I2C?

I'm reading data from I2C sensor through a 20Hz timer interrupt. The interrupt handler only set the readSensorFlag, and I double checked that it happens at 20Hz through putting a Serial print there.

void TIMER2_Interrupt(void) {
  if (NRF_TIMER2->EVENTS_COMPARE[0] != 0) {
        readSensorFlag = true;
        NRF_TIMER2->EVENTS_COMPARE[0] = 0;
      }
    }

Now in the main loop I perform the reading of sensors.

void loop() {
  // Reading data from sensors to a buffer
  if (readSensorFlag) {
    readSensorFlag = false;

    if (bufferCounter < BUFLEN - 2*SAMPLESIZEINCHAR - 1) {
      epoch = millis();
      proximity = vcnl.readProximity();
      ambientLight = vcnl.readAmbient();

The strange this is that: if I set proximity and ambient light to be some constant and not reading from I2C, then the recorded sampling frequency is 20Hz:

  1988,    7,   10
  2038,    8,   10
  2088,    9,   10
  2188,    1,   10
  2238,    2,   10
  2288,    3,   10

If I revert back reading I2C code (second column is data from sensor), then frequency is only 8Hz!!!

 17637,    8,   33
 17763,    9,   33
 17937,    1,   34
 18067,    2,   35
 18194,    3,   34
 18320,    4,   34

How could reading I2C block the timer interrupt? And how could it be so slow? (I've tried a similar sensor before, and could reach 100Hz with that).

1
  • 2
    That sensor needs time for a measurement. Are you using the Adafruit library ? github.com/adafruit/Adafruit_VCNL4010 That library starts a measurement and waits for it to finish. The code has delays, polling, waiting loops and even wrong use of the Wire library. It was not written to be fast.
    – Jot
    Jun 5, 2017 at 8:58

1 Answer 1

2

Let's take a look at a typical function for reading this sensor. This is from the Adafruit_VCNL4010 library and is for reading the proximity:

uint16_t  Adafruit_VCNL4010::readProximity(void) {
  uint8_t i = read8(VCNL4010_INTSTAT);
  i &= ~0x80;
  write8(VCNL4010_INTSTAT, i);

  write8(VCNL4010_COMMAND, VCNL4010_MEASUREPROXIMITY);
  while (1) {
    //Serial.println(read8(VCNL4010_INTSTAT), HEX);
    uint8_t result = read8(VCNL4010_COMMAND);
    //Serial.print("Ready = 0x"); Serial.println(result, HEX);
    if (result & VCNL4010_PROXIMITYREADY) {
      return read16(VCNL4010_PROXIMITYDATA);
    }
    delay(1);
  }
}

So let's tot up all the I2C transactions in that one function:

read8
write8
write8
{repeated
    read8
    [read16]
    delay(1)
}

So in a best case scenario, assuming it's in a "ready" state, that's the minimum number of transactions, and the delay doesn't occur. So what is in each of those transactions?

  • read8:

Begin the transaction, write the address, end transaction, delay 170µs, begin request, read 1 byte, end transaction. Total 4 bytes plus 170µs

  • write8:

Begin transaction, write address, write data. Total 3 bytes.

  • read16:

Begin the transaction, write the address, end transaction, begin request, read 2 bytes, end transaction. Total 5 bytes (I notice this one has no delay...?)

So now we can see a best case scenario total of bytes transferred:

4 read8 *
3 write8
3 write8
{repeated
    4 read8 *
    5 [read16]
    delay(1)
}

4+3+3+4+5 = 19 bytes, plus 2x170µs delay.

The default clock speed for I2C on the Arduino is 100kHz. We have 19 bytes, plus start/stop/ack bits. So 19 * 8 for the data bytes (152 bits) plus 19 ACK bits (171 bits) and 5 * start + 5 * stop. That's 181 bits in total.

181 bits, at 100kHz, takes 1.81ms to transfer. Add to that the 2*170µs delay, and we are up to a best case scenario of 2.15ms transfer.

Double that up for reading the ambient light as well, and we're up to 4.3ms transfer. That's a theoretical absolute maximum of about 230Hz readings.

Remember: that's a best case scenario. If the sensor isn't ready to return a reading at the time that there will immediately be a 1ms delay imposed and the number of transactions will increase.

So why would it not be ready to send a reading? The simplest explanation is that it's not set to a suitable sampling rate.

The library doesn't appear to change the sampling rate for the proximity from the default 1.95 samples per second, nor the ambient light sampling rate from the default of 2 samples per second - so unless you are using a different library, or manually changing these settings through your own Wire calls, even at a reduced triggering speed you'll not be getting anywhere near the kind of information you are after.

The fastest you can get for ambient light sampling is 10 samples per second. Proximity caps out at a more respectable 250 samples per second. But only if you tell it to...

1
  • Thank you so much, you've always been able to find the root of the problem! I set the sampling frequency to 20Hz (in a register of VCNL4010), and voila, it works! Jun 5, 2017 at 19:11

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.