Skip to main content
Kyle Klumper
Associate
September 26, 2017
Solved

[Solved] STM32F4 I2C Master Clock Stretching Fail

  • September 26, 2017
  • 10 replies
  • 5878 views
Posted on September 26, 2017 at 02:56

Hi there,

I am attempting to get an STM32F411CE microcontroller to interface with a BNO055 IMU using the I2C peripheral. This particular IMU uses clock stretching which doesn't seem to work with the HAL_I2C library that was auto-generated from STM32CubeMX. See image below:

0690X00000608QMQAY.png

I attached a second I2C device to the bus and was able to communicate with the library so I'm pretty sure it is only the clock stretching that is causing an issue.

As a reference, here is a waveform of an Atmel microcontroller interfacing with the same BNO055 device and it appears to be working as intended:

0690X00000608N3QAI.png

Any ideas on what could be causing this issue?

Thanks,

Kyle

    This topic has been closed for replies.
    Best answer by Kyle Klumper
    Posted on October 01, 2017 at 19:26

    It seems that the STM32CubeMX created a HAL_I2C_MspInit function with the wrong pins. Modifying it to PB7 and PB8 seems to make everything work as intended:

    void HAL_I2C_MspInit(I2C_HandleTypeDef* hi2c)

    {

    GPIO_InitTypeDef GPIO_InitStruct;

    if(hi2c->Instance==I2C1)

    {

    /* USER CODE BEGIN I2C1_MspInit 0 */

    /* USER CODE END I2C1_MspInit 0 */

    /**I2C1 GPIO Configuration

    PB7 ------> I2C1_SDA

    PB8 ------> I2C1_SCL

    */

    GPIO_InitStruct.Pin = GPIO_PIN_7|GPIO_PIN_8;

    GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;

    GPIO_InitStruct.Pull = GPIO_PULLUP;

    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;

    GPIO_InitStruct.Alternate = GPIO_AF4_I2C1;

    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

    /* Peripheral clock enable */

    __HAL_RCC_I2C1_CLK_ENABLE();

    /* USER CODE BEGIN I2C1_MspInit 1 */

    /* USER CODE END I2C1_MspInit 1 */

    }

    }

    10 replies

    waclawek.jan
    Super User
    September 26, 2017
    Posted on September 26, 2017 at 09:59

    How did you post those attachments? I am unable to access them.

    Nevertheless, in 'F4 I2C the master clock stretching sense is governed by a parameter in the I2C_TRISE register, see 'SCL master clock generation' subchapter in I2C chapter.

    JW

    Kyle Klumper
    Associate
    September 26, 2017
    Posted on September 26, 2017 at 17:45

    Hi JW,

    Thanks for the quick response. I've re-uploaded the images so you should be able to see them now.

    I tried modifying TRISE but it had no effect on the waveform. I made sure to modify with I2C disabled as per the reference manual. It looks like the peripheral is waiting until the I2C clock is released by the slave before continuing to cycle SCL, however it appears to ignore releasing the SDA line. Here is the code snippet of the I2C transmit function.

    HAL_StatusTypeDef HAL_I2C_Master_Transmit(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout)

    {

    uint32_t tickstart = 0x00U;

    /* Init tickstart for timeout management*/

    tickstart = HAL_GetTick();

    //

    __HAL_I2C_DISABLE(hi2c);

    hi2c->Instance->TRISE = 0x3F;

    //

    if(hi2c->State == HAL_I2C_STATE_READY)

    {

    /* Wait until BUSY flag is reset */

    if(I2C_WaitOnFlagUntilTimeout(hi2c, I2C_FLAG_BUSY, SET, I2C_TIMEOUT_BUSY_FLAG, tickstart) != HAL_OK)

    {

    return HAL_BUSY;

    }

    /* Process Locked */

    __HAL_LOCK(hi2c);

    /* Check if the I2C is already enabled */

    if((hi2c->Instance->CR1 & I2C_CR1_PE) != I2C_CR1_PE)

    {

    /* Enable I2C peripheral */

    __HAL_I2C_ENABLE(hi2c);

    }

    /* Disable Pos */

    hi2c->Instance->CR1 &= ~I2C_CR1_POS;

    hi2c->State = HAL_I2C_STATE_BUSY_TX;

    hi2c->Mode = HAL_I2C_MODE_MASTER;

    hi2c->ErrorCode = HAL_I2C_ERROR_NONE;

    /* Prepare transfer parameters */

    hi2c->pBuffPtr = pData;

    hi2c->XferCount = Size;

    hi2c->XferOptions = I2C_NO_OPTION_FRAME;

    hi2c->XferSize = hi2c->XferCount;

    /* Send Slave Address */

    if(I2C_MasterRequestWrite(hi2c, DevAddress, Timeout, tickstart) != HAL_OK)

    {

    if(hi2c->ErrorCode == HAL_I2C_ERROR_AF)

    {

    /* Process Unlocked */

    __HAL_UNLOCK(hi2c);

    return HAL_ERROR;

    }

    else

    {

    /* Process Unlocked */

    __HAL_UNLOCK(hi2c);

    return HAL_TIMEOUT;

    }

    }

    /* Clear ADDR flag */

    __HAL_I2C_CLEAR_ADDRFLAG(hi2c);

    while(hi2c->XferSize > 0U)

    {

    /* Wait until TXE flag is set */

    if(I2C_WaitOnTXEFlagUntilTimeout(hi2c, Timeout, tickstart) != HAL_OK)

    {

    if(hi2c->ErrorCode == HAL_I2C_ERROR_AF)

    {

    /* Generate Stop */

    hi2c->Instance->CR1 |= I2C_CR1_STOP;

    return HAL_ERROR;

    }

    else

    {

    return HAL_TIMEOUT;

    }

    }

    /* Write data to DR */

    hi2c->Instance->DR = (*hi2c->pBuffPtr++);

    hi2c->XferCount--;

    hi2c->XferSize--;

    if((__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_BTF) == SET) && (hi2c->XferSize != 0U))

    {

    /* Write data to DR */

    hi2c->Instance->DR = (*hi2c->pBuffPtr++);

    hi2c->XferCount--;

    hi2c->XferSize--;

    }

    /* Wait until BTF flag is set */

    if(I2C_WaitOnBTFFlagUntilTimeout(hi2c, Timeout, tickstart) != HAL_OK)

    {

    if(hi2c->ErrorCode == HAL_I2C_ERROR_AF)

    {

    /* Generate Stop */

    hi2c->Instance->CR1 |= I2C_CR1_STOP;

    return HAL_ERROR;

    }

    else

    {

    return HAL_TIMEOUT;

    }

    }

    }

    /* Generate Stop */

    hi2c->Instance->CR1 |= I2C_CR1_STOP;

    hi2c->State = HAL_I2C_STATE_READY;

    hi2c->Mode = HAL_I2C_MODE_NONE;

    /* Process Unlocked */

    __HAL_UNLOCK(hi2c);

    return HAL_OK;

    }

    else

    {

    return HAL_BUSY;

    }

    }
    Kyle Klumper
    Associate
    September 28, 2017
    Posted on September 28, 2017 at 17:18

    Just an update. It appears the SCL line is not dropping low to finalize the transfer. As a fix, I added a function at the end of each data write to manually pull the SCL line low which seems to get the peripheral working again. Here is the fix:

    static void i2c_tx_scl_fix(void)

    {

    uint16_t SCL_PIN = GPIO_PIN_8;

    // Re-configure SCL as input

    GPIO_InitTypeDef gpio;

    gpio.Pin = SCL_PIN;

    gpio.Mode = GPIO_MODE_INPUT;

    gpio.Pull = GPIO_PULLUP;

    gpio.Speed = GPIO_SPEED_FREQ_LOW;

    HAL_GPIO_Init(GPIOB, &gpio);

    // Wait until SCL line goes high

    while(HAL_GPIO_ReadPin(GPIOB, SCL_PIN)==GPIO_PIN_RESET){}

    // Now configure SCL to output and force low to

    // regain control of SDA

    gpio.Pin = SCL_PIN;

    gpio.Mode = GPIO_MODE_OUTPUT_OD;

    gpio.Pull = GPIO_NOPULL;

    gpio.Speed = GPIO_SPEED_FREQ_LOW;

    HAL_GPIO_WritePin(GPIOB, SCL_PIN, GPIO_PIN_RESET);

    HAL_GPIO_Init(GPIOB, &gpio);

    // Reset back to I2C configuration

    gpio.Pin = SCL_PIN;

    gpio.Mode = GPIO_MODE_AF_OD;

    gpio.Alternate = GPIO_AF4_I2C1;

    gpio.Pull = GPIO_PULLUP;

    HAL_GPIO_WritePin(GPIOB, SCL_PIN, GPIO_PIN_RESET);

    HAL_GPIO_Init(GPIOB, &gpio);

    }

    and here is the updated HAL_I2C_Master_Transmit function:

    HAL_StatusTypeDef HAL_I2C_Master_Transmit(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout)

    {

    uint32_t tickstart = 0x00U;

    /* Init tickstart for timeout management*/

    tickstart = HAL_GetTick();

    if(hi2c->State == HAL_I2C_STATE_READY)

    {

    /* Wait until BUSY flag is reset */

    if(I2C_WaitOnFlagUntilTimeout(hi2c, I2C_FLAG_BUSY, SET, I2C_TIMEOUT_BUSY_FLAG, tickstart) != HAL_OK)

    {

    return HAL_BUSY;

    }

    /* Process Locked */

    __HAL_LOCK(hi2c);

    /* Check if the I2C is already enabled */

    if((hi2c->Instance->CR1 & I2C_CR1_PE) != I2C_CR1_PE)

    {

    /* Enable I2C peripheral */

    __HAL_I2C_ENABLE(hi2c);

    }

    /* Disable Pos */

    hi2c->Instance->CR1 &= ~I2C_CR1_POS;

    hi2c->State = HAL_I2C_STATE_BUSY_TX;

    hi2c->Mode = HAL_I2C_MODE_MASTER;

    hi2c->ErrorCode = HAL_I2C_ERROR_NONE;

    /* Prepare transfer parameters */

    hi2c->pBuffPtr = pData;

    hi2c->XferCount = Size;

    hi2c->XferOptions = I2C_NO_OPTION_FRAME;

    hi2c->XferSize = hi2c->XferCount;

    /* Send Slave Address */

    if(I2C_MasterRequestWrite(hi2c, DevAddress, Timeout, tickstart) != HAL_OK)

    {

    if(hi2c->ErrorCode == HAL_I2C_ERROR_AF)

    {

    /* Process Unlocked */

    __HAL_UNLOCK(hi2c);

    return HAL_ERROR;

    }

    else

    {

    /* Process Unlocked */

    __HAL_UNLOCK(hi2c);

    return HAL_TIMEOUT;

    }

    }

    /* Clear ADDR flag */

    __HAL_I2C_CLEAR_ADDRFLAG(hi2c);

    while(hi2c->XferSize > 0U)

    {

    i2c_tx_scl_fix();

    /* Wait until TXE flag is set */

    if(I2C_WaitOnTXEFlagUntilTimeout(hi2c, Timeout, tickstart) != HAL_OK)

    {

    if(hi2c->ErrorCode == HAL_I2C_ERROR_AF)

    {

    /* Generate Stop */

    hi2c->Instance->CR1 |= I2C_CR1_STOP;

    return HAL_ERROR;

    }

    else

    {

    return HAL_TIMEOUT;

    }

    }

    /* Write data to DR */

    hi2c->Instance->DR = (*hi2c->pBuffPtr++);

    hi2c->XferCount--;

    hi2c->XferSize--;

    if((__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_BTF) == SET) && (hi2c->XferSize != 0U))

    {

    /* Write data to DR */

    hi2c->Instance->DR = (*hi2c->pBuffPtr++);

    hi2c->XferCount--;

    hi2c->XferSize--;

    }

    /* Wait until BTF flag is set */

    if(I2C_WaitOnBTFFlagUntilTimeout(hi2c, Timeout, tickstart) != HAL_OK)

    {

    if(hi2c->ErrorCode == HAL_I2C_ERROR_AF)

    {

    /* Generate Stop */

    hi2c->Instance->CR1 |= I2C_CR1_STOP;

    return HAL_ERROR;

    }

    else

    {

    return HAL_TIMEOUT;

    }

    }

    }

    /* Generate Stop */

    hi2c->Instance->CR1 |= I2C_CR1_STOP;

    hi2c->State = HAL_I2C_STATE_READY;

    hi2c->Mode = HAL_I2C_MODE_NONE;

    /* Process Unlocked */

    __HAL_UNLOCK(hi2c);

    i2c_tx_scl_fix();

    return HAL_OK;

    }

    else

    {

    return HAL_BUSY;

    }

    }

    I am still having trouble with the HAL_I2C_Master_Receive function. See waveform below:

    0690X00000608QaQAI.png

    Again, the peripheral seems to get stuck in a state that is preventing it from sending the next byte.

    Kyle Klumper
    Kyle KlumperAuthorBest answer
    Associate
    October 1, 2017
    Posted on October 01, 2017 at 19:26

    It seems that the STM32CubeMX created a HAL_I2C_MspInit function with the wrong pins. Modifying it to PB7 and PB8 seems to make everything work as intended:

    void HAL_I2C_MspInit(I2C_HandleTypeDef* hi2c)

    {

    GPIO_InitTypeDef GPIO_InitStruct;

    if(hi2c->Instance==I2C1)

    {

    /* USER CODE BEGIN I2C1_MspInit 0 */

    /* USER CODE END I2C1_MspInit 0 */

    /**I2C1 GPIO Configuration

    PB7 ------> I2C1_SDA

    PB8 ------> I2C1_SCL

    */

    GPIO_InitStruct.Pin = GPIO_PIN_7|GPIO_PIN_8;

    GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;

    GPIO_InitStruct.Pull = GPIO_PULLUP;

    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;

    GPIO_InitStruct.Alternate = GPIO_AF4_I2C1;

    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

    /* Peripheral clock enable */

    __HAL_RCC_I2C1_CLK_ENABLE();

    /* USER CODE BEGIN I2C1_MspInit 1 */

    /* USER CODE END I2C1_MspInit 1 */

    }

    }

    ARama.1
    Associate II
    September 14, 2020

    Hi Kyle,

    Thanks for your update. did I2C clock strectching got solved just with pin changes in HAL_I2C_MspInit()? or with the previous changes you did? can you pls clarify?

    I just want to know whether I2C clock stretching is straight away idea with cube mx? or needs some manual modification. thanks.

    BR,

    Anantha R

    Amel NASRI
    ST Technical Moderator
    October 2, 2017
    Posted on October 02, 2017 at 10:28

    Hi

    kyle.klumper

    ‌,

    Even if you consider that the problem is already fixed, it is interesting to understand its root cause, and fix it if possible.

    So, could you please share the .ioc file leading to generate a wrong configuration for the I2C pins?

    -Amel

    BTW, instead of updating the title and write 'Solved', please use the feature 'mark as answered' or select one post as the correct answer. Refer to

    https://community.st.com/0D50X00009bMM5DSAW

    To give better visibility on the answered topics, please click on "Best Answer" on the reply which solved your issue or answered your question.
    smhn24
    Associate
    April 11, 2023

    Hi everyone, I have exactly the same problem with the stm32f405 microcontroller and reading BNO055 module with i2c.

    How should to resolve this problem?

    Bob S
    Super User
    April 11, 2023

    Have you tried the suggestions in the old posts above?

    Post your configuration and initialization functions. And any scope or logic analyzer screen shots that show the issue.

    smhn24
    Associate
    April 12, 2023

    Yes I tried.

    My i2c configuration is:

    0693W00000bhwXqQAI.png 

    And this is my read function:

    int16_tBNO055_read(void)
    {
     uint8_t angleData[2]; 
     int16_t angle; 
     uint8_t regAddr = 0x1A; 
     HAL_I2C_Master_Transmit(&hI2C, BNO055_ADDRESS, &regAddr, 1, 100);
     HAL_I2C_Master_Receive(&hI2C, BNO055_ADDRESS, angleData, 2, 100); 
     angle = ((int16_t)(angleData[0] | angleData[1] << 8) / 16);
     return angle;
    }

    I saw the line with a logic analyzer and then I thought may BNO055 keeps the lines low.

    Bob S
    Super User
    April 12, 2023

    What makes you think your code isn't working?

    You thought maybe it keeps the line low? Show us the logic analyzer traces along with the data that your code receives.

    Does the device really expect two separate I2C transactions to read data? Or is it expecting one transaction with a "repeat start" in the middle? Check the return values from the HAL calls and make sure they return HAL_OK before you try to use the angleData.