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 BUSY
bandera, pero causa un problema: SB
el bit no se establece después de START
que 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.
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 MspInit
funciones 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.
BUSY
:( Su último argumento es bastante sólido, realmente no tengo nada que objetar :) Gracias.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 BUSY
cuelgue 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:
Configure las E/S SCL y SDA como salida de uso general, drenaje abierto, nivel alto (escritura 1 en GPIOx_ODR).
Compruebe el nivel alto de SCL y SDA en GPIOx_IDR.
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í.
GB-AE7OO
pm4812