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

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

Usé STM32CubeMX para generar una plantilla de proyecto con inicialización de periféricos I2C. Desafortunadamente, funciona de alguna manera extraña: después HAL_I2C_MspInit(I2C1)de que se invoca, el autobús 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 la BUSYbandera, pero causa un problema: SBel bit no se establece después de STARTque se genera. Según el depurador, los registros I2C se borran por completo después del reinicio; sospecho que este es el problema con ese método.

También confirmé una caída de voltaje corta en la línea SDA durante el arranque, 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 habilitar el reloj antes de la HAL_GPIO_Init()invocación y ahora mis comunicaciones I2C funcionan (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 de GPIO. Puedo quedarme con dos invocaciones de __HAL_RCC_I2C1_CLK_ENABLE(), pero eso es bastante feo en mi opinión, por lo que estoy buscando una solución mejor, ya sea de software o de hardware.

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

Tenga en cuenta que la hoja de errata correcta se encuentra en st.com/content/ccc/resource/technical/document/errata_sheet/a9/… Que es para el STM32F100x4/6/8/B que es lo que tiene. La Sección 2.10.7 trata sobre el bloqueo OCUPADO. Por alguna razón, la gente está publicando erratas de los chips 10xxC/D/E y 10(1-3)8/B. Sí, a veces todas las fichas dentro de una serie tendrán la misma errata, pero no puedes contar con eso.
@Alexey Malev Experimenté los mismos problemas con un STM32F103C8 y un EEPROM M24C02-WMN6TP (de STM). No fue capaz de recuperar el I2C de este estado, incluso con I2C D-Init - espera - I2C-Init o jugando con Clock Disable - espera - Clock enable y otros "trucos sucios"... Mi suposición/el problema era : Al iniciar una sesión de depuración, el programa transferido comienza a ejecutarse durante unos milisegundos antes de que el depurador se active y detenga la ejecución para la depuración. Como realizo todas las operaciones de lectura/escritura de EEPROM justo al comienzo de mi programa, el depurador interrumpió la ejecución en ocasiones.

Respuestas (6)

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

Si sabe que no funciona y también ha encontrado la solución, no tiene que ceñirse al código original. En su caso, puede omitir la __HAL_RCC_I2C1_CLK_ENABLE()llamada 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 trabajo. 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 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 desconectado del Discovery (aparte de los pull-ups) y verificar si todavía está ocupado. En caso afirmativo, está bien si reemplaza esa línea en el código generado. No es esa gran modificación.

Lamentablemente, no hay ningún ejemplo I2C en el paquete de ejemplo STM32CubeF1 (este no es el generador de código), en STM32Cube_FW_F1_V1.4.0\Projects\STM32VL-Discovery\Examples. Pero si revisa las MspInitfunciones del UART o SPI. Los relojes se habilitan 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.

He verificado ese comportamiento sin otros dispositivos que la placa Discovery en las líneas SDA y SCL, aún así BUSY:( Su último argumento es bastante sólido, realmente no tengo nada que objetar :) Gracias.
No estoy usando la biblioteca HAL de ST, pero me encuentro con el mismo problema: el indicador de ocupado siempre está ENCENDIDO. Y puedo confirmar que inicializar GPIO después del reloj I2C realmente ayudó.
Lo mismo aquí, la línea permanece ocupada, en una "píldora azul" stm32 f103. Solucionado inicializando el reloj primero.

Aquí hay un código que podría ayudarte. Básicamente, es una realización de la hoja de erratas (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 periférico con el problema era el giroscopio 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;
}

Solo otra cosa a considerar: en este documento de ERRATA (página 24) puede encontrar que hay una falla en el filtro analógico I2C que puede causar que se BUSYcuelgue la bandera. También hay una solución alternativa que puede probar: funciona para mí.

Ver hoja de eratta: hoja de eratta

Solución alternativa: la salida del filtro analógico SCL y SDA se actualiza después de que ocurre una transición en la línea SCL y SDA respectivamente. La transición SCL y SDA se puede forzar mediante software que configura las E/S I2C en modo de salida. Luego, una vez que los filtros analógicos se desbloquean y emiten el nivel de las líneas SCL y SDA, el indicador BUSY se puede restablecer con un reinicio de software y el I2C puede ingresar al 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 SCL y SDA como salida de uso general, drenaje abierto, nivel alto (escritura 1 en GPIOx_ODR).

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

  4. Configure la E/S de SDA como salida de uso general, drenaje abierto, nivel bajo (escriba 0 en GPIOx_ODR).
  5. Compruebe el nivel bajo de SDA en GPIOx_IDR.
  6. Configure la E/S de SCL como salida de uso general, 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 propósito general, drenaje abierto, nivel alto (Escriba 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, drenaje abierto, nivel alto (escritura 1 en GPIOx_ODR).
  11. Compruebe el nivel alto de SDA en GPIOx_IDR.
  12. Configure las E/S SCL y SDA como función alternativa Open-Drain.
  13. Establezca el bit SWRST en el registro I2Cx_CR1.
  14. Borre el bit SWRST en el registro I2Cx_CR1.
  15. Habilite el periférico I2C configurando el bit PE en el registro I2Cx_CR1.

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

Aún así, noté que en el reinicio por software (al depurar, por ejemplo), SCL pasa a BAJO justo después de inicializar la HAL_GPIO_Init()llamada.

Intenté restablecer el bus enviando 16 clock al inicio, de acuerdo con la recomendación de i2c-bus.org .

Pero no ayudó. Agregar su código de "restablecimiento" 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.

Tuve el mismo problema con la placa Discovery STM32F411. Al revisar los esquemas de la placa (no olvide hacer coincidir la versión de la placa), me di cuenta de que no hay una resistencia de extracción en las líneas SDA y SCL. Así que usé una resistencia de extracción interna en los pines SCL y SDA (CubeMX no activa la resistencia de extracción en la generación automática de código) y el problema se resolvió para mí.