Electronic – Large delay between I2C messages on STM32F303 – trying to read as fast as possible

communicationhal-libraryi2cstm32

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!

Best Answer

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.