I2C ocupado marca extraño comportamiento

2

He estado usando STMCUBE combinado con Keil desde hace algún tiempo. En su mayor parte, me gusta la biblioteca HAL y la documentación para los controladores STM32f1xx es bastante buena.

Estoy haciendo un proyecto en el que estoy usando la tarjeta Nucleo STM32f103rb combinada con un gyro / acelerómetro MPU6050. Utilizo la herramienta de generación de código STM32CubeMX para generar la función de iniciación. Sin embargo, cuando quiero implementar I2C tengo un problema extraño. STM32Cube genera todos los pasos de inicio necesarios, el identificador se configura y luego los pines GPIO se configuran como OD; finalmente, el reloj se habilita utilizando la macro __HAL_RCC_I2C1_CLK_ENABLE() . Sin embargo, cuando esta macro se ejecuta dentro de HAL_I2C_MspInit , el indicador de ocupado I2C parece estar configurado, y no se borra, por lo tanto no puedo comunicarme con el dispositivo MPU6050.

Noté que si pongo algo (por ejemplo, una sonda de medición) en la línea SDA mientras se ejecuta la macro __HAL_RCC_I2C1_CLK_ENABLE() , el indicador de ocupado no se establece y mi comunicación I2C funciona hasta que reinicio el microcontrolador.

Otra forma (que es mejor que poner una sonda física?) que parece funcionar es que después de ejecutar la macro __HAL_RCC_I2C1_CLK_ENABLE() , uso macros __HAL_RCC_I2C1_FORCE_RESET() y __HAL_RCC_I2C1_RELEASE_RESET() . De esta manera mi comunicación I2C funciona bien.

Creo que es extraño y realmente no puedo explicar el comportamiento. Pero desde que agregué las macros de reinicio forzado y liberación reinicio, no he tenido ningún problema de I2C, funciona perfectamente.

Avísame si necesito compartir un código más.

    
pregunta Nelle

5 respuestas

4

ST ha publicado una hoja de erratas llamada:

STM32F100xC, STM32F10 0xD y STM32F100xE limitaciones de dispositivos de línea de valor de alta densidad.

El punto interesante aquí es:

  

2.9.7 El filtro analógico I2C puede proporcionar un valor incorrecto, bloquear el indicador BUSY y evitar la entrada en el modo maestro

Hay una solución detallada de 15 pasos que funcionó para mí, sorprendentemente para un STM32F446, por lo que los periféricos I2C de cada serie STM32 CORTEX-M podrían verse afectados.

Durante esta operación, las líneas no deben ser subidas o bajadas activamente por un miembro del bus. Por lo tanto, si conecta dos interfaces I2C de la misma MCU al bus, primero configure las patillas de ambas para Función alternativa / Desagüe abierto, luego llame a la rutina, ya que se requiere una transición de niveles lógicos.

Este es un ejemplo de las bibliotecas HAL que uso después de la primera inicialización y durante el tiempo de ejecución, si se produce un error. Como se dijo anteriormente, esto es para STM32F4, las bibliotecas para SMT32F1 pueden diferir un poco.

struct I2C_Module
{
  I2C_HandleTypeDef   instance;
  uint16_t            sdaPin;
  GPIO_TypeDef*       sdaPort;
  uint16_t            sclPin;
  GPIO_TypeDef*       sclPort;
};

void I2C_ClearBusyFlagErratum(struct I2C_Module* i2c)
{
  GPIO_InitTypeDef GPIO_InitStructure;

  // 1. Clear PE bit.
  i2c->instance.Instance->CR1 &= ~(0x0001);

  //  2. Configure the SCL and SDA I/Os as General Purpose Output Open-Drain, High level (Write 1 to GPIOx_ODR).
  GPIO_InitStructure.Mode         = GPIO_MODE_OUTPUT_OD;
  GPIO_InitStructure.Alternate    = I2C_PIN_MAP;
  GPIO_InitStructure.Pull         = GPIO_PULLUP;
  GPIO_InitStructure.Speed        = GPIO_SPEED_FREQ_HIGH;

  GPIO_InitStructure.Pin          = i2c->sclPin;
  HAL_GPIO_Init(i2c->sclPort, &GPIO_InitStructure);
  HAL_GPIO_WritePin(i2c->sclPort, i2c->sclPin, GPIO_PIN_SET);

  GPIO_InitStructure.Pin          = i2c->sdaPin;
  HAL_GPIO_Init(i2c->sdaPort, &GPIO_InitStructure);
  HAL_GPIO_WritePin(i2c->sdaPort, i2c->sdaPin, GPIO_PIN_SET);

  // 3. Check SCL and SDA High level in GPIOx_IDR.
  while (GPIO_PIN_SET != HAL_GPIO_ReadPin(i2c->sclPort, i2c->sclPin))
  {
    asm("nop");
  }

  while (GPIO_PIN_SET != HAL_GPIO_ReadPin(i2c->sdaPort, i2c->sdaPin))
  {
    asm("nop");
  }

  // 4. Configure the SDA I/O as General Purpose Output Open-Drain, Low level (Write 0 to GPIOx_ODR).
  HAL_GPIO_WritePin(i2c->sdaPort, i2c->sdaPin, GPIO_PIN_RESET);

  //  5. Check SDA Low level in GPIOx_IDR.
  while (GPIO_PIN_RESET != HAL_GPIO_ReadPin(i2c->sdaPort, i2c->sdaPin))
  {
    asm("nop");
  }

  // 6. Configure the SCL I/O as General Purpose Output Open-Drain, Low level (Write 0 to GPIOx_ODR).
  HAL_GPIO_WritePin(i2c->sclPort, i2c->sclPin, GPIO_PIN_RESET);

  //  7. Check SCL Low level in GPIOx_IDR.
  while (GPIO_PIN_RESET != HAL_GPIO_ReadPin(i2c->sclPort, i2c->sclPin))
  {
    asm("nop");
  }

  // 8. Configure the SCL I/O as General Purpose Output Open-Drain, High level (Write 1 to GPIOx_ODR).
  HAL_GPIO_WritePin(i2c->sclPort, i2c->sclPin, GPIO_PIN_SET);

  // 9. Check SCL High level in GPIOx_IDR.
  while (GPIO_PIN_SET != HAL_GPIO_ReadPin(i2c->sclPort, i2c->sclPin))
  {
    asm("nop");
  }

  // 10. Configure the SDA I/O as General Purpose Output Open-Drain , High level (Write 1 to GPIOx_ODR).
  HAL_GPIO_WritePin(i2c->sdaPort, i2c->sdaPin, GPIO_PIN_SET);

  // 11. Check SDA High level in GPIOx_IDR.
  while (GPIO_PIN_SET != HAL_GPIO_ReadPin(i2c->sdaPort, i2c->sdaPin))
  {
    asm("nop");
  }

  // 12. Configure the SCL and SDA I/Os as Alternate function Open-Drain.
  GPIO_InitStructure.Mode         = GPIO_MODE_AF_OD;
  GPIO_InitStructure.Alternate    = I2C_PIN_MAP;

  GPIO_InitStructure.Pin          = i2c->sclPin;
  HAL_GPIO_Init(i2c->sclPort, &GPIO_InitStructure);

  GPIO_InitStructure.Pin          = i2c->sdaPin;
  HAL_GPIO_Init(i2c->sdaPort, &GPIO_InitStructure);

  // 13. Set SWRST bit in I2Cx_CR1 register.
  i2c->instance.Instance->CR1 |= 0x8000;

  asm("nop");

  // 14. Clear SWRST bit in I2Cx_CR1 register.
  i2c->instance.Instance->CR1 &= ~0x8000;

  asm("nop");

  // 15. Enable the I2C peripheral by setting the PE bit in I2Cx_CR1 register
  i2c->instance.Instance->CR1 |= 0x0001;

  // Call initialization function.
  HAL_I2C_Init(&(i2c->instance));
}
    
respondido por el AxelBe
1

Basándome en la respuesta de AxelBe, creé esta versión de la solución, sin bloquear las operaciones, y agregando el DeInit de la instancia I2C. Espero que esto te resulte útil.

typedef struct
 {
    I2C_HandleTypeDef* instance;
    uint16_t sdaPin;
    GPIO_TypeDef* sdaPort;
    uint16_t sclPin;
    GPIO_TypeDef* sclPort;
} I2C_Module_t;

static uint8_t wait_for_gpio_state_timeout(GPIO_TypeDef *port, uint16_t pin, GPIO_PinState state, uint32_t timeout)
 {
    uint32_t Tickstart = HAL_GetTick();
    uint8_t ret = TRUE;
    /* Wait until flag is set */
    for(;(state != HAL_GPIO_ReadPin(port, pin)) && (TRUE == ret);)
    {
        /* Check for the timeout */
        if (timeout != HAL_MAX_DELAY)
        {
            if ((timeout == 0U) || ((HAL_GetTick() - Tickstart) > timeout))
            {
                ret = FALSE;
            }
            else
            {
            }
        }
        asm("nop");
    }
    return ret;
}

static void I2C_ClearBusyFlagErratum(I2C_Module_t* i2c, uint32_t timeout)
{
    GPIO_InitTypeDef GPIO_InitStructure;

    I2C_HandleTypeDef* handler = NULL;

    handler = i2c->instance;

    // 1. Clear PE bit.
    CLEAR_BIT(handler->Instance->CR1, I2C_CR1_PE);

    //  2. Configure the SCL and SDA I/Os as General Purpose Output Open-Drain, High level (Write 1 to GPIOx_ODR).
    HAL_I2C_DeInit(handler);

    GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_OD;
    GPIO_InitStructure.Pull = GPIO_NOPULL;

    GPIO_InitStructure.Pin = i2c->sclPin;
    HAL_GPIO_Init(i2c->sclPort, &GPIO_InitStructure);

    GPIO_InitStructure.Pin = i2c->sdaPin;
    HAL_GPIO_Init(i2c->sdaPort, &GPIO_InitStructure);

    // 3. Check SCL and SDA High level in GPIOx_IDR.
    HAL_GPIO_WritePin(i2c->sdaPort, i2c->sdaPin, GPIO_PIN_SET);
    HAL_GPIO_WritePin(i2c->sclPort, i2c->sclPin, GPIO_PIN_SET);

    wait_for_gpio_state_timeout(i2c->sclPort, i2c->sclPin, GPIO_PIN_SET, timeout);
    wait_for_gpio_state_timeout(i2c->sdaPort, i2c->sdaPin, GPIO_PIN_SET, timeout);

    // 4. Configure the SDA I/O as General Purpose Output Open-Drain, Low level (Write 0 to GPIOx_ODR).
    HAL_GPIO_WritePin(i2c->sdaPort, i2c->sdaPin, GPIO_PIN_RESET);

    // 5. Check SDA Low level in GPIOx_IDR.
    wait_for_gpio_state_timeout(i2c->sdaPort, i2c->sdaPin, GPIO_PIN_RESET, timeout);

    // 6. Configure the SCL I/O as General Purpose Output Open-Drain, Low level (Write 0 to GPIOx_ODR).
    HAL_GPIO_WritePin(i2c->sclPort, i2c->sclPin, GPIO_PIN_RESET);

    // 7. Check SCL Low level in GPIOx_IDR.
    wait_for_gpio_state_timeout(i2c->sclPort, i2c->sclPin, GPIO_PIN_RESET, timeout);

    // 8. Configure the SCL I/O as General Purpose Output Open-Drain, High level (Write 1 to GPIOx_ODR).
    HAL_GPIO_WritePin(i2c->sclPort, i2c->sclPin, GPIO_PIN_SET);

    // 9. Check SCL High level in GPIOx_IDR.
    wait_for_gpio_state_timeout(i2c->sclPort, i2c->sclPin, GPIO_PIN_SET, timeout);

    // 10. Configure the SDA I/O as General Purpose Output Open-Drain , High level (Write 1 to GPIOx_ODR).
    HAL_GPIO_WritePin(i2c->sdaPort, i2c->sdaPin, GPIO_PIN_SET);

    // 11. Check SDA High level in GPIOx_IDR.
    wait_for_gpio_state_timeout(i2c->sdaPort, i2c->sdaPin, GPIO_PIN_SET, timeout);

    // 12. Configure the SCL and SDA I/Os as Alternate function Open-Drain.
    GPIO_InitStructure.Mode = GPIO_MODE_AF_OD;
    GPIO_InitStructure.Alternate = GPIO_AF4_I2C2;

    GPIO_InitStructure.Pin = i2c->sclPin;
    HAL_GPIO_Init(i2c->sclPort, &GPIO_InitStructure);

    GPIO_InitStructure.Pin = i2c->sdaPin;
    HAL_GPIO_Init(i2c->sdaPort, &GPIO_InitStructure);

    // 13. Set SWRST bit in I2Cx_CR1 register.
    SET_BIT(handler->Instance->CR1, I2C_CR1_SWRST);
    asm("nop");

    /* 14. Clear SWRST bit in I2Cx_CR1 register. */
    CLEAR_BIT(handler->Instance->CR1, I2C_CR1_SWRST);
    asm("nop");

    /* 15. Enable the I2C peripheral by setting the PE bit in I2Cx_CR1 register */
    SET_BIT(handler->Instance->CR1, I2C_CR1_PE);
    asm("nop");

    // Call initialization function.
    HAL_I2C_Init(handler);
}

Ejecuto esto tan pronto como recibo la respuesta HAL_BUSY en las funciones HAL de I2C, aquí hay un ejemplo:

    /* HAL Write */
    status = HAL_I2C_Mem_Write(eeprom_handler.instance, (uint16_t)device_address, mem_addr_masked, I2C_MEMADD_SIZE_8BIT, src, bytes_to_write, 1000);
    if (HAL_OK == status)
    {
        bytes_written = bytes_to_write;
    }
    else if (HAL_BUSY == status)
    {
        I2C_ClearBusyFlagErratum(&eeprom_handler, 1000);
    }
    
respondido por el Hugo Arganda
1

Esto es menos una respuesta y más una advertencia ... Me quedé atascado con el mismo problema y el código anterior ayudó (junto con extender el reinicio de I2C e inicializar el reloj antes de GPIO, ver las respuestas relacionadas).

Sin embargo pasé medio día más en esto de lo que necesitaba debido a un error de principiante: cuando restableciste el STM32, en mi caso con un programador externo conectado a través de SwD, por lo general, no está restableciendo su periférico I2C.

Si agrega el código anterior, o alguna otra solución alternativa, recuerde intentar ocasionalmente un ciclo de energía o un reinicio completo de su placa para que los periféricos I2C también se reinicien.

    
respondido por el Happy Heyoka
1

Basado en el código publicado por @ Hugo-Arganda y @AxelBe. Tengo un STM32F051 y parece que tengo el mismo problema. ¡La solución parece resolver esto también!

A tu código: No veo de dónde sacas la definición de pin / puerto. OMI, el uso de esta estructura no debería funcionar en absoluto, ya que la HAL no le proporciona esa información.

Ver mi código revisado a continuación. Me deshice de su estructura y simplemente uso las definiciones de pin / puerto codificadas.

Sugerencia: si usa CubeMX y etiqueta las líneas SDA / SCL manualmente, obtendrá esas definiciones automáticamente:

#define I2C1_SCL_Pin GPIO_PIN_6
#define I2C1_SCL_GPIO_Port GPIOB
#define I2C1_SDA_Pin GPIO_PIN_7
#define I2C1_SDA_GPIO_Port GPIOB

Código revisado:

static bool wait_for_gpio_state_timeout(GPIO_TypeDef *port, uint16_t pin, GPIO_PinState state, uint32_t timeout)
 {
    uint32_t Tickstart = HAL_GetTick();
    bool ret = true;
    /* Wait until flag is set */
    for(;(state != HAL_GPIO_ReadPin(port, pin)) && (true == ret);)
    {
        /* Check for the timeout */
        if (timeout != HAL_MAX_DELAY)
        {
            if ((timeout == 0U) || ((HAL_GetTick() - Tickstart) > timeout))
            {
                ret = false;
            }
            else
            {
            }
        }
        asm("nop");
    }
    return ret;
}


static void I2C_ClearBusyFlagErratum(I2C_HandleTypeDef* handle, uint32_t timeout)
{
    GPIO_InitTypeDef GPIO_InitStructure;

    // 1. Clear PE bit.
    CLEAR_BIT(handle->Instance->CR1, I2C_CR1_PE);

    //  2. Configure the SCL and SDA I/Os as General Purpose Output Open-Drain, High level (Write 1 to GPIOx_ODR).
    HAL_I2C_DeInit(handle);

    GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_OD;
    GPIO_InitStructure.Pull = GPIO_NOPULL;

    GPIO_InitStructure.Pin = I2C1_SCL_Pin;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStructure);

    GPIO_InitStructure.Pin = I2C1_SDA_Pin;
    HAL_GPIO_Init(I2C1_SDA_GPIO_Port, &GPIO_InitStructure);

    // 3. Check SCL and SDA High level in GPIOx_IDR.
    HAL_GPIO_WritePin(I2C1_SDA_GPIO_Port, I2C1_SDA_Pin, GPIO_PIN_SET);
    HAL_GPIO_WritePin(I2C1_SCL_GPIO_Port, I2C1_SCL_Pin, GPIO_PIN_SET);

    wait_for_gpio_state_timeout(I2C1_SCL_GPIO_Port, I2C1_SCL_Pin, GPIO_PIN_SET, timeout);
    wait_for_gpio_state_timeout(I2C1_SDA_GPIO_Port, I2C1_SDA_Pin, GPIO_PIN_SET, timeout);

    // 4. Configure the SDA I/O as General Purpose Output Open-Drain, Low level (Write 0 to GPIOx_ODR).
    HAL_GPIO_WritePin(I2C1_SDA_GPIO_Port, I2C1_SDA_Pin, GPIO_PIN_RESET);

    // 5. Check SDA Low level in GPIOx_IDR.
    wait_for_gpio_state_timeout(I2C1_SDA_GPIO_Port, I2C1_SDA_Pin, GPIO_PIN_RESET, timeout);

    // 6. Configure the SCL I/O as General Purpose Output Open-Drain, Low level (Write 0 to GPIOx_ODR).
    HAL_GPIO_WritePin(I2C1_SCL_GPIO_Port, I2C1_SCL_Pin, GPIO_PIN_RESET);

    // 7. Check SCL Low level in GPIOx_IDR.
    wait_for_gpio_state_timeout(I2C1_SCL_GPIO_Port, I2C1_SCL_Pin, GPIO_PIN_RESET, timeout);

    // 8. Configure the SCL I/O as General Purpose Output Open-Drain, High level (Write 1 to GPIOx_ODR).
    HAL_GPIO_WritePin(I2C1_SCL_GPIO_Port, I2C1_SCL_Pin, GPIO_PIN_SET);

    // 9. Check SCL High level in GPIOx_IDR.
    wait_for_gpio_state_timeout(I2C1_SCL_GPIO_Port, I2C1_SCL_Pin, GPIO_PIN_SET, timeout);

    // 10. Configure the SDA I/O as General Purpose Output Open-Drain , High level (Write 1 to GPIOx_ODR).
    HAL_GPIO_WritePin(I2C1_SDA_GPIO_Port, I2C1_SDA_Pin, GPIO_PIN_SET);

    // 11. Check SDA High level in GPIOx_IDR.
    wait_for_gpio_state_timeout(I2C1_SDA_GPIO_Port, I2C1_SDA_Pin, GPIO_PIN_SET, timeout);

    // 12. Configure the SCL and SDA I/Os as Alternate function Open-Drain.
    GPIO_InitStructure.Mode = GPIO_MODE_AF_OD;
    GPIO_InitStructure.Alternate = GPIO_AF1_I2C1;

    GPIO_InitStructure.Pin = I2C1_SCL_Pin;
    HAL_GPIO_Init(I2C1_SCL_GPIO_Port, &GPIO_InitStructure);

    GPIO_InitStructure.Pin = I2C1_SDA_Pin;
    HAL_GPIO_Init(I2C1_SDA_GPIO_Port, &GPIO_InitStructure);

    // 13. Set SWRST bit in I2Cx_CR1 register.
    SET_BIT(handle->Instance->CR1, I2C_CR1_SWRST);
    asm("nop");

    /* 14. Clear SWRST bit in I2Cx_CR1 register. */
    CLEAR_BIT(handle->Instance->CR1, I2C_CR1_SWRST);
    asm("nop");

    /* 15. Enable the I2C peripheral by setting the PE bit in I2Cx_CR1 register */
    SET_BIT(handle->Instance->CR1, I2C_CR1_PE);
    asm("nop");

    // Call initialization function.
    HAL_I2C_Init(handle);
}
    
respondido por el CacO3
0

El filtro analógico I2C puede proporcionar un valor incorrecto, bloqueando el indicador BUSY y previniendo la entrada en modo maestro

Los filtros analógicos I2C incrustados en las E / S I2C pueden estar vinculados a un nivel bajo, mientras que SCL y Las líneas de SDA se mantienen a alto nivel. Esto puede ocurrir después de un reinicio de encendido de la MCU, o durante Estrés ESD. En consecuencia, se establece el indicador I2C BUSY y el I2C no puede entrar en modo maestro (No se puede enviar la condición START). La bandera I2C BUSY no puede ser borrada por SWRST bit de control, ni por un periférico o un reinicio del sistema. El bit OCUPADO se borra bajo reinicio, pero es establezca el valor alto nuevamente tan pronto como se libere el reinicio, ya que la salida del filtro analógico todavía está en el nivel bajo nivel. Este problema ocurre aleatoriamente.

Nota : En las mismas condiciones, los filtros analógicos I2C también pueden proporcionar un alto nivel, mientras que Las líneas SCL y SDA se mantienen a un nivel bajo. Esto no debería crear problemas como los filtros de salida. será correcto después de la próxima transición de SCL y SDA.

Solución La salida del filtro analógico SCL y SDA se actualiza después de que se produce una transición en el SCL y Línea SDA respectivamente. La transición de SCL y SDA se puede forzar mediante la configuración del software El I2C I / Os en modo de salida. Luego, una vez que los filtros analógicos están desbloqueados y salen el SCL y el nivel de líneas SDA, el indicador BUSY se puede restablecer con un restablecimiento de software, y el I2C puede ingresar 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 uso general de drenaje abierto, nivel alto (Escribe 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 bajo, 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 bajo, 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 (escriba 1 en GPIOx_ODR).

9. Comprueba el nivel alto de SCL en GPIOx_IDR.

10. Configure la E / S SDA como salida de propósito general de drenaje abierto, nivel alto (escriba 1 en GPIOx_ODR).

11. Compruebe 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. Habilite el periférico I2C configurando el bit PE en el registro I2Cx_CR1.

more

    
respondido por el Hamed

Lea otras preguntas en las etiquetas

Comentarios Recientes

con algunos dispositivos USB de hasta 9 Ghz gracias a PCA y> información de errores de fallas aún mayor en el seguimiento de errores de GPF> documentación. >> Tenía una razón para investigar el error en mi emulador, un dispositivo arbitrario> que produce un tiempo de ciclo rápido si se llama desde gpioinfo ya que supongo que la pepita anterior es la causa del error. Por defecto, al presionar el botón Atrás de gpio, la fuente "privada" para gpio 1 (junto con cualquier otra fuente en el host, dependiendo de la... Lees verder