5
\$\begingroup\$

I've got a sensor that I'm reading via I2C. My end goal is to be able to maximize the rate at which I can take these readings.

In order to read, I have to write an zero-byte I2C message to the slave to 'wake it', after which I can send a read and receive 2 bytes back with the measurement.

To test my maximum read rate, I set up a basic scheme in which the TX-complete interrupt for the wake message sends out the RX message to the slave, and the RX-complete interrupt sends out another wake-up.

I'm using the HAL libraries, and the relevant code is below:

void HAL_I2C_MasterRxCpltCallback(I2C_HandleTypeDef *hi2c)
{  
    //RX finished, wake the sensor back up for a new reading
    HAL_I2C_Master_Transmit_IT(&hi2c1,0x28<<1,0,0);         
}

void HAL_I2C_MasterTxCpltCallback(I2C_HandleTypeDef *hi2c)
{
    //Wakeup is finished, read measurement into buffer
    HAL_I2C_Master_Receive_IT(&hi2c1, 0x28<<1, buffer, 2);    
}

Right now I have no other code running, either in the main loop or in these callback functions (I will eventually implement data processing, but I want to benchmark the maximum speed first).

When I look at the I2C Bus with a Logic Analyzer, I see the following:

Logic Analyzer Results - Large Gap Between Messages

There's a fairly large 'gap' between all of my messages, and I don't know what's causing it. There's no computations or anything that should be delaying the chip.

The bus is configured to run at 400 KHz.

Any idea what's causing this delay, and if there's a way to eliminate it to increase my maximum polling speed? Is this just a byproduct of how the I2C protocol needs to work?

Thanks for your help!

\$\endgroup\$
  • 3
    \$\begingroup\$ "The bus is running at 400 KHz." No, it isn't. Your analysis shows a 3-byte (27-bit) transaction taking about 90 us, which means that you may have asked for 400 kHz, but you're only getting around 300 kHz. With regard to the gaps, no, that isn't part of the I2C protocol. Roughly 100 us of overhead doesn't actually sound like all that much, but you'll have to dig into the actual HAL code to figure out how the interrupts and callbacks work. It's possible that someone here has already done that and can provide an answer. \$\endgroup\$ – Dave Tweed Nov 12 '19 at 15:02
  • \$\begingroup\$ I should clarify - the bus is configured to be 400 kHz - I'll edit the original. \$\endgroup\$ – pileunderflow Nov 12 '19 at 15:05
  • \$\begingroup\$ A workaround can be (if possible) to send longer messages (minimizing the number of gaps), or trying to use DMA with I2C. \$\endgroup\$ – Michel Keijzers Nov 12 '19 at 15:05
  • 3
    \$\begingroup\$ What is your system clock? Are you using the PLL? Handcrafting I2C code and busy waiting on the appropriate hardware flag will probably be faster if there is nothing you need to do in the meantime. \$\endgroup\$ – Chris Stratton Nov 12 '19 at 15:13
  • 3
    \$\begingroup\$ The HAL libraries are very useful and handy, but they are not particularly efficient. You could instrument them (toggle an I/O bit for example at entry and exit) to see if that is the cause of the gaps between communication. \$\endgroup\$ – Peter Smith Nov 12 '19 at 15:18
5
\$\begingroup\$

These Callback functions are called from the I2C interrupt handler. You shouldn't make other API calls from them. HAL_I2C_Master_Transmit_IT() check timeouts etc which might not work from an interrupt handler. This might a reason for slow execution, but regardless it's bad practice. A safer route is to set a global (and volatile) variable, and check for it in the main loop:

volatile bool RxComplete = false;

void HAL_I2C_MasterRxCpltCallback(I2C_HandleTypeDef *hi2c) {
  RxComplete = true;
}

void main(void) {
  while (true) {
    if (RxComplete) {
      RxComplete = false;
      HAL_I2C_Master_Transmit_IT(&hi2c1,0x28<<1,0,0);
    }
  }
}

In fact, in my copy of FreeRTOS 9.0, there's a bug in HAL_I2C_Master_Transmit_IT(). It calls

I2C_WaitOnFlagUntilTimeout(hi2c, I2C_FLAG_BUSY, SET, I2C_TIMEOUT_BUSY_FLAG)

But the signature is

static HAL_StatusTypeDef I2C_WaitOnFlagUntilTimeout(I2C_HandleTypeDef *hi2c, uint32_t Flag, FlagStatus Status, uint32_t Timeout);

The argument Timeout gets the value I2C_TIMEOUT_BUSY_FLAG, which is not a timeout period. I haven't checked if newer versions fix this bug.

In general, I suggest to set/clear a GPIO pin at select points in your code. This will be very telling to track down where the delay is.

\$\endgroup\$
  • \$\begingroup\$ This might be the approach to take - I'll play around with using a volatile flag and report back the results! \$\endgroup\$ – pileunderflow Nov 12 '19 at 17:27
  • \$\begingroup\$ I tried what you recommended (and thanks for the advice on best practice!), but it didn't solve the problem. I ended up finding another fun little weirdness, but that's an issue for another post. Thanks! \$\endgroup\$ – pileunderflow Nov 12 '19 at 22:16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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