STM32: el indicador de ocupado se establece después de la inicialización de I2C

6

Para la referencia: el mismo problema se describe allí, pero la solución del autor no me funciona - I2C bandera ocupada extraño comportamiento

Utilicé STM32CubeMX para generar una plantilla de proyecto con la inicialización de periféricos I2C. Desafortunadamente, funciona de alguna manera extraña: después de invocar HAL_I2C_MspInit(I2C1) , el bus se considera permanentemente ocupado.

Si trato de aplicar

__HAL_RCC_I2C1_FORCE_RESET();
HAL_Delay(1000);
__HAL_RCC_I2C1_RELEASE_RESET();

Eso resuelve el problema con el indicador BUSY , pero causa el problema - el bit SB no se configura después de que se genera el START . De acuerdo con el depurador, los registros I2C se borran completamente después del reinicio; sospecho que este es el problema con ese método.

También confimé una caída de voltaje corta en la línea SDA durante el inicio, esa es probablemente la causa del problema. Eché un vistazo más de cerca al código de inicialización de pines SDA / SCL generado por CubeMX:

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    
    PB6     ------> I2C1_SCL
    PB7     ------> I2C1_SDA 
    */
    GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_7;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    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 */
  }

}

Lo cambié para poner el reloj habilitado antes de invocar HAL_GPIO_Init() y ahora funcionan mis comunicaciones I2C (al menos no noté nada extraño todavía).

Finalmente, mi pregunta es: ¿hay alguna solución mejor para esto? CubeMX coloca el código de habilitación del reloj después de la invocación del método de inicio GPIO. Puedo quedarme con dos invocaciones de __HAL_RCC_I2C1_CLK_ENABLE() , pero en mi opinión eso es bastante feo, así que estoy buscando una solución mejor, ya sea software o hardware.

El dispositivo es STM32F100RB en la placa de descubrimiento STM32VLD (con STLink v1), en caso de que sea importante.

    
pregunta Alexey Malev

5 respuestas

4

En mi opinión, el código STM32CubeMX no debe considerarse como un código listo para usar, pero algunos como un ejemplo con el que puede comenzar. Con la mayoría de los microcontroladores funciona, pero hay algunos casos raros en los que no.

Si sabe que no está funcionando y también ha encontrado la solución, no tiene que atenerse al código original. En su caso, puede omitir la llamada __HAL_RCC_I2C1_CLK_ENABLE() después de la inicialización de GPIO, y dejar la anterior. Si funciona, y usted ha dicho que funciona, entonces use la forma de trabajar. Incluso el software de ST puede tener errores.

Está utilizando una placa oficial, por lo que el hardware debería estar bien, pero puede verificar si los valores de la resistencia de pull-up son correctos. O si un dispositivo esclavo hace algo durante la inicialización.

Lo mejor sería ejecutar su código con todo lo que esté desconectado del Discovery (aparte de los pull-ups), y verificar si todavía está atascado. Si es así, está bien si reemplaza esa línea en el código generado. No es esa gran modificación.

Lamentablemente, no hay ningún exapítulo I2C en STM32CubeF1 (no es el generador de código), bajo el STM32Cube_FW_F1_V1.4.0 \ Projects \ STM32VL-Discovery \ Example. Pero si verifica las funciones MspInit de la UART o SPI. Los relojes están habilitados en ambos antes del inicio de GPIO .

void HAL_SPI_MspInit(SPI_HandleTypeDef *hspi)
{
  GPIO_InitTypeDef  GPIO_InitStruct;

  if (hspi->Instance == SPIx)
  {
    /*##-1- Enable peripherals and GPIO Clocks #################################*/
    /* Enable GPIO TX/RX clock */
    SPIx_SCK_GPIO_CLK_ENABLE();
    SPIx_MISO_GPIO_CLK_ENABLE();
    SPIx_MOSI_GPIO_CLK_ENABLE();
    /* Enable SPI clock */
    SPIx_CLK_ENABLE();

    /*##-2- Configure peripheral GPIO ##########################################*/
    /* SPI SCK GPIO pin configuration  */
    GPIO_InitStruct.Pin       = SPIx_SCK_PIN;
    GPIO_InitStruct.Mode      = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull      = GPIO_PULLDOWN;
    GPIO_InitStruct.Speed     = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(SPIx_SCK_GPIO_PORT, &GPIO_InitStruct);
void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{  
  GPIO_InitTypeDef  GPIO_InitStruct;

  /*##-1- Enable peripherals and GPIO Clocks #################################*/
  /* Enable GPIO TX/RX clock */
  USARTx_TX_GPIO_CLK_ENABLE();
  USARTx_RX_GPIO_CLK_ENABLE();


  /* Enable USARTx clock */
  USARTx_CLK_ENABLE(); 

  /*##-2- Configure peripheral GPIO ##########################################*/  
  /* UART TX GPIO pin configuration  */
  GPIO_InitStruct.Pin       = USARTx_TX_PIN;
  GPIO_InitStruct.Mode      = GPIO_MODE_AF_PP;
  GPIO_InitStruct.Pull      = GPIO_PULLUP;
  GPIO_InitStruct.Speed     = GPIO_SPEED_FREQ_HIGH;

  HAL_GPIO_Init(USARTx_TX_GPIO_PORT, &GPIO_InitStruct);

Así que creo que tu solución está perfectamente bien.

    
respondido por el Bence Kaulics
3

Aquí hay un código que podría ayudarte. Básicamente, es una realización de la hoja de errata (sección 2.14.7) mencionada en una respuesta anterior. Estoy usando la biblioteca HAL, y hay algunas referencias a las definiciones de encabezado del controlador IKS01A1 (mi periferal con el problema fue el giro en esa placa).

/* USER CODE BEGIN 1 */
/**
1. Disable the I2C peripheral by clearing the PE bit in I2Cx_CR1 register.
2. Configure the SCL and SDA I/Os as General Purpose Output Open-Drain, High level
(Write 1 to GPIOx_ODR).
3. Check SCL and SDA High level in GPIOx_IDR.
4. Configure the SDA I/O as General Purpose Output Open-Drain, Low level (Write 0 to
GPIOx_ODR).
5. Check SDA Low level in GPIOx_IDR.
6. Configure the SCL I/O as General Purpose Output Open-Drain, Low level (Write 0 to
GPIOx_ODR).
7. Check SCL Low level in GPIOx_IDR.
8. Configure the SCL I/O as General Purpose Output Open-Drain, High level (Write 1 to
GPIOx_ODR).
9. Check SCL High level in GPIOx_IDR.
10. Configure the SDA I/O as General Purpose Output Open-Drain , High level (Write 1 to
GPIOx_ODR).
11. Check SDA High level in GPIOx_IDR.
12. Configure the SCL and SDA I/Os as Alternate function Open-Drain.
13. Set SWRST bit in I2Cx_CR1 register.
14. Clear SWRST bit in I2Cx_CR1 register.
15. Enable the I2C peripheral by setting the PE bit in I2Cx_CR1 register.
**/
void HAL_I2C_ClearBusyFlagErrata_2_14_7(I2C_HandleTypeDef *hi2c) {

    static uint8_t resetTried = 0;
    if (resetTried == 1) {
        return ;
    }
    uint32_t SDA_PIN = NUCLEO_I2C_EXPBD_SDA_PIN;
    uint32_t SCL_PIN = NUCLEO_I2C_EXPBD_SCL_PIN;
    GPIO_InitTypeDef GPIO_InitStruct;

    // 1
    __HAL_I2C_DISABLE(hi2c);

    // 2
    GPIO_InitStruct.Pin = SDA_PIN|SCL_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FAST;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

    HAL_GPIO_WRITE_ODR(GPIOB, SDA_PIN);
    HAL_GPIO_WRITE_ODR(GPIOB, SCL_PIN);

    // 3
    GPIO_PinState pinState;
    if (HAL_GPIO_ReadPin(GPIOB, SDA_PIN) == GPIO_PIN_RESET) {
        for(;;){}
    }
    if (HAL_GPIO_ReadPin(GPIOB, SCL_PIN) == GPIO_PIN_RESET) {
        for(;;){}
    }

    // 4
    GPIO_InitStruct.Pin = SDA_PIN;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

    HAL_GPIO_TogglePin(GPIOB, SDA_PIN);

    // 5
    if (HAL_GPIO_ReadPin(GPIOB, SDA_PIN) == GPIO_PIN_SET) {
        for(;;){}
    }

    // 6
    GPIO_InitStruct.Pin = SCL_PIN;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

    HAL_GPIO_TogglePin(GPIOB, SCL_PIN);

    // 7
    if (HAL_GPIO_ReadPin(GPIOB, SCL_PIN) == GPIO_PIN_SET) {
        for(;;){}
    }

    // 8
    GPIO_InitStruct.Pin = SDA_PIN;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

    HAL_GPIO_WRITE_ODR(GPIOB, SDA_PIN);

    // 9
    if (HAL_GPIO_ReadPin(GPIOB, SDA_PIN) == GPIO_PIN_RESET) {
        for(;;){}
    }

    // 10
    GPIO_InitStruct.Pin = SCL_PIN;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

    HAL_GPIO_WRITE_ODR(GPIOB, SCL_PIN);

    // 11
    if (HAL_GPIO_ReadPin(GPIOB, SCL_PIN) == GPIO_PIN_RESET) {
        for(;;){}
    }

    // 12
    GPIO_InitStruct.Pin = SDA_PIN|SCL_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;
    GPIO_InitStruct.Alternate = NUCLEO_I2C_EXPBD_SCL_SDA_AF;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

   // 13
   hi2c->Instance->CR1 |= I2C_CR1_SWRST;

   // 14
   hi2c->Instance->CR1 ^= I2C_CR1_SWRST;

   // 15
   __HAL_I2C_ENABLE(hi2c);

   resetTried = 1;
}

void HAL_GPIO_WRITE_ODR(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
  /* Check the parameters */
  assert_param(IS_GPIO_PIN(GPIO_Pin));

  GPIOx->ODR |= GPIO_Pin;
}
    
respondido por el Chinmay Nagarkar
2

Otra cosa a considerar: en este ERRATA (página 24) puede encontrar un fallo en el filtro analógico I2C que puede causa BUSY bandera colgando. También hay una solución que puedes probar: funciona para mí.

    
respondido por el klasyc
1

Consulte la hoja eratta: eratta sheet

Solución alternativa: La salida del filtro analógico SCL y SDA se actualiza después de que se produce una transición en la línea SCL y SDA respectivamente. La transición de SCL y SDA se puede forzar mediante el software que configura las E / S de I2C en el modo de salida. Luego, una vez que se desbloquean los filtros analógicos y salen las líneas SCL y SDA, se puede restablecer el indicador BUSY con un reinicio del software, y el I2C puede entrar en el modo maestro. Por lo tanto, se debe aplicar la siguiente secuencia:

  1. Deshabilite el periférico I2C borrando el bit PE en el registro I2Cx_CR1.
  2. Configure las E / S de SCL y SDA como salida de propósito general de drenaje abierto, nivel alto (Escriba 1 en GPIOx_ODR).

  3. Verifique el nivel alto de SCL y SDA en GPIOx_IDR.

  4. Configure la E / S SDA como salida de uso general de drenaje abierto, nivel bajo (escritura 0 en GPIOx_ODR).
  5. Verifique el nivel bajo de SDA en GPIOx_IDR.
  6. Configure la E / S de SCL como salida de uso general de drenaje abierto, nivel bajo (escriba 0 en GPIOx_ODR).
  7. Compruebe el nivel bajo de SCL en GPIOx_IDR.
  8. Configure la E / S de SCL como salida de uso general de drenaje abierto, nivel alto (escritura 1 en GPIOx_ODR).
  9. Compruebe el nivel alto de SCL en GPIOx_IDR.
  10. Configure la E / S de SDA como salida de uso general de drenaje abierto, nivel alto (escritura 1 en GPIOx_ODR).
  11. Verifique el nivel alto de SDA en GPIOx_IDR.
  12. Configure las E / S de SCL y SDA como función alternativa de drenaje abierto.
  13. Establezca el bit SWRST en el registro I2Cx_CR1.
  14. Borre el bit SWRST en el registro I2Cx_CR1.
  15. Habilitar el periphe I2C Para ello, configure el bit PE en el registro I2Cx_CR1.
respondido por el elect-gombe
0

Tengo el mismo problema en STM32F429, usando el cubo V1.15.0.

Aún noté que en el reinicio por software (cuando se depura por ejemplo), SCL se pone BAJO justo después de inicializar la llamada HAL_GPIO_Init() .

Intenté reiniciar el bus enviando 16 horas al inicio, de acuerdo con i2c-bus.org recomendación.

Pero no ayudó. Al agregar el código de "reinicio" se resolvió el truco:

__HAL_RCC_I2C1_FORCE_RESET();
HAL_Delay(2);
__HAL_RCC_I2C1_RELEASE_RESET();

Después de algunas pruebas descubrí que un retraso de 2 ms es suficiente. Mantuve la reinicialización manual del reloj porque una transmisión puede bloquearse al reiniciar la CPU.

    
respondido por el Selso Liberado

Lea otras preguntas en las etiquetas