cancel
Showing results for 
Search instead for 
Did you mean: 

STM32 I2C Slave mode HAL bug

TDJ
Senior III

Recently I have tried to implement a solution employing I2C interface in slave mode.

The solution follows the pattern presented in the official I2C_TwoBoards_RestartAdvComIT example available in CubeMX packages and another solution presented here: STM32F072 I2C slave Receive callback.

After fighting with incorrect timings I2C generated by CubeMX (see Possible CubeMX I2C timing config bug (STM32L0x and other)) I stumbled upon another problem: the last received frame is written twice at two consecutive buffer offsets which may result in buffer overrun and unpredictable results.

I found that the problem is caused by imperfect HAL driver code. The solution: before calling HAL_I2C_Slave_Seq_Receive_IT make sure there is more data available:  

if (I2C_CHECK_FLAG(hi2c->Instance->ISR, I2C_FLAG_RXNE) == SET) ...

The problem occurs with STM32L041 and probably all recent MCUs using new I2C architecture/control registry layout. Please find details and sequence of calls in response to this post. (for any reason I am unable to add code snippets to this initial post)

Package: STM32Cube_FW_L0_V1.12.1

#I2C​ 

2 REPLIES 2
TDJ
Senior III

Detailed call sequence (pseudocode)

-> HAL_I2C_EV_IRQHandler()
   -> I2C_Slave_ISR_IT()
      -> I2C_ITSlaveCplt()
         // problem 1: I2C_FLAG_RXNE flag is cleared but on local/temp flags copy only
         tmpITFlags &= ~I2C_FLAG_RXNE;
         *hi2c->pBuffPtr = (uint8_t)hi2c->Instance->RXDR;
         pBuffPtr++;
         -> I2C_ITSlaveSeqCplt()
            -> HAL_I2C_SlaveRxCpltCallback()
               // problem 2: HAL_I2C_Slave_Seq_Receive_IT is called when it is already known that there is no more data 
               -> HAL_I2C_Slave_Seq_Receive_IT(Size=1)
                  hi2c->XferCount = Size; // here XferCount is set to 1 again while receiving of the current frame is not finished
	    <- HAL_I2C_SlaveRxCpltCallback()
         <- I2C_ITSlaveSeqCplt()
      <- I2C_ITSlaveCplt()
   <- I2C_Slave_ISR_IT()
      // problem 3: data from RXDR register is read again without checking that I2C_FLAG_RXNE flag is still set
      if (hi2c->XferCount > 0U) *hi2c->pBuffPtr = (uint8_t)hi2c->Instance->RXDR;
<- HAL_I2C_EV_IRQHandler()

Possible solution:

void HAL_I2C_SlaveRxCpltCallback(I2C_HandleTypeDef *hi2c) {
    if (_isFirstFrame) {
        // start address/offset received
        _isFirstFrame = false;
    } else {
        // process _rxBuffer[_rxBufferOffset] value here
        _rxBufferOffset++;
    }
 
    if (I2C_CHECK_FLAG(hi2c->Instance->ISR, I2C_FLAG_RXNE) == SET) {  // check more data is available
        HAL_StatusTypeDef ret = HAL_I2C_Slave_Seq_Receive_IT(hi2c, &_rxBuffer[_rxBufferOffset], 1, I2C_NEXT_FRAME);
    }
}

Note: I2C_NEXT_FRAME and I2C_FIRST_FRAME flags appear to be equivalent - I found no difference in HAL code, both values are checked everywhere.

TDJ
Senior III