STM32: Busy-Flag wird nach I2C-Initialisierung gesetzt

Als Referenz: Das gleiche Problem wird dort beschrieben, aber die Lösung des Autors funktioniert bei mir nicht - I2C-Busy-Flag seltsames Verhalten

Ich habe STM32CubeMX verwendet, um eine Projektvorlage mit I2C-Peripherie-Initialisierung zu generieren. Leider funktioniert es irgendwie merkwürdig: nach HAL_I2C_MspInit(I2C1)dem Aufruf gilt der Bus als permanent besetzt.

Wenn ich versuche mich zu bewerben

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

Das löst das Problem mit dem BUSYFlag, verursacht aber ein Problem - das SBBit wird nicht gesetzt, nachdem STARTes generiert wurde. Laut Debugger werden die I2C-Register nach dem Zurücksetzen vollständig gelöscht - ich vermute, dass dies das Problem mit dieser Methode ist.

Ich habe auch einen kurzen Spannungsabfall an der SDA-Leitung während des Starts bestätigt, das ist wahrscheinlich die Ursache des Problems. Ich habe mir den von CubeMX generierten Initialisierungscode für SDA/SCL-Pins genauer angesehen:

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 */
  }

}

Ich habe es geändert, um die Uhr vor dem HAL_GPIO_Init()Aufruf zu aktivieren, und jetzt funktioniert meine I2C-Kommunikation (zumindest habe ich noch nichts Seltsames bemerkt).

Abschließend meine Frage: Gibt es dafür eine bessere Lösung? CubeMX platziert den Uhraktivierungscode nach dem GPIO-Init-Methodenaufruf. Ich kann bei zwei Aufrufen von bleiben __HAL_RCC_I2C1_CLK_ENABLE(), aber das ist meiner Meinung nach ziemlich hässlich, also suche ich nach einer besseren Lösung, entweder Software oder Hardware.

Das Gerät ist STM32F100RB auf der STM32VLDiscovery-Karte (mit STLink v1), falls dies wichtig ist.

Beachten Sie, dass sich das richtige Errata-Blatt unter st.com/content/ccc/resource/technical/document/errata_sheet/a9/… befindet, das für den STM32F100x4/6/8/B gilt, den Sie haben. Abschnitt 2.10.7 befasst sich mit der BUSY-Sperre. Aus irgendeinem Grund posten Leute Errata von den 10xxC/D/E- und den 10(1-3)8/B-Chips. Ja, manchmal haben alle Chips innerhalb einer Serie die gleichen Errata, aber darauf können Sie sich nicht verlassen.
@Alexey Malev Ich hatte die gleichen Probleme mit einem STM32F103C8 und einem M24C02-WMN6TP EEPROM (von STM). Es war nicht in der Lage, den I2C aus diesem Zustand wiederherzustellen, selbst mit I2C D-Init - warten - I2C-Init oder Herumspielen mit Clock Disable - Wait - Clock enable und anderen "schmutzigen Tricks" ... Meine Vermutung / das Problem war : Beim Starten einer Debug-Sitzung beginnt das übertragene Programm für einige Millisekunden mit der Ausführung, bevor der Debugger einsetzt und die Ausführung zum Debuggen stoppt. Da ich alle EEPROM-Lese-/Schreiboperationen gleich zu Beginn meines Programms durchführe, unterbrach der Debugger gelegentlich die Ausführung

Antworten (6)

Meiner Meinung nach sollte STM32CubeMX-Code nicht als fertiger Code betrachtet werden, sondern als Beispiel, mit dem Sie beginnen können. Bei den meisten Mikrocontrollern funktioniert es, aber es gibt einige seltene Fälle, in denen es nicht so ist.

Wenn Sie wissen, dass es nicht funktioniert und Sie die Lösung auch gefunden haben, müssen Sie sich nicht an den ursprünglichen Code halten. In Ihrem Fall können Sie den __HAL_RCC_I2C1_CLK_ENABLE()Aufruf nach der GPIO-Initialisierung weglassen und den davor belassen. Wenn es funktioniert und Sie gesagt haben, dass es funktioniert, dann verwenden Sie den funktionierenden Weg. Auch die Software von ST kann Fehler enthalten.

Sie verwenden ein offizielles Board, daher sollte die Hardware in Ordnung sein, aber Sie können überprüfen, ob die Pull-up-Widerstandswerte korrekt sind. Oder wenn ein Slave-Gerät während der Initialisierung etwas tut.

Am besten führen Sie Ihren Code so aus, dass alles von der Discovery getrennt ist (abgesehen von den Pullups), und überprüfen Sie, ob er immer noch beschäftigt ist. Wenn ja, ist es in Ordnung, wenn Sie diese Zeile im generierten Code ersetzen. Es ist keine so große Modifikation.

Leider gibt es kein I2C-Beispiel im STM32CubeF1- Beispielpaket (dies ist nicht der Codegenerator) unter STM32Cube_FW_F1_V1.4.0\Projects\STM32VL-Discovery\Examples. Aber wenn Sie die MspInitFunktionen des UART oder SPI überprüfen. Die Uhren werden in beiden vor dem GPIO init aktiviert .

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);

Also ich finde deine Lösung völlig in Ordnung.

Ich habe dieses Verhalten mit keinem anderen Gerät als Discovery Board auf SDA- und SCL-Leitungen überprüft, immer noch BUSY:( Ihr letztes Argument ist ziemlich solide, habe wirklich nichts einzuwenden :) Danke.
Ich verwende die HAL-Bibliothek von ST nicht, aber ich stoße auf die gleichen Probleme - das Belegt-Flag ist immer eingeschaltet. Und ich kann bestätigen, dass die Initialisierung von GPIO nach der I2C-Uhr wirklich geholfen hat.
Auch hier bleibt die Leitung besetzt, auf einer stm32 f103 "blauen Pille". Behoben, indem zuerst die Uhr initialisiert wurde.

Hier ist ein Code, der Ihnen helfen könnte. Im Grunde ist es eine Realisierung des Errata-Blatts (Abschnitt 2.14.7), das in einer früheren Antwort erwähnt wurde. Ich verwende die HAL-Bibliothek, und es gibt einige Verweise auf die Header-Definitionen des IKS01A1-Treibers (mein Peripheriegerät mit dem Problem war der Kreisel auf dieser Platine).

/* 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;
}

Nur noch etwas zu beachten: In diesem ERRATA- Dokument (Seite 24) finden Sie einen Fehler im I2C-Analogfilter, der zum BUSYHängen des Flags führen kann. Es gibt auch eine Problemumgehung, die Sie ausprobieren können - bei mir funktioniert es.

Siehe Eratta-Blatt: Eratta-Blatt

Problemumgehung: Die SCL- und SDA-Analogfilterausgabe wird aktualisiert, nachdem ein Übergang auf der SCL- bzw. SDA-Leitung auftritt. Der SCL- und SDA-Übergang kann durch Software erzwungen werden, indem die I2C-I/Os im Ausgangsmodus konfiguriert werden. Sobald die analogen Filter entsperrt sind und die SCL- und SDA-Leitungspegel ausgeben, kann das BUSY-Flag mit einem Software-Reset zurückgesetzt werden, und der I2C kann in den Master-Modus wechseln. Daher ist folgende Reihenfolge einzuhalten:

  1. Deaktivieren Sie das I2C-Peripheriegerät, indem Sie das PE-Bit im I2Cx_CR1-Register löschen.
  2. Konfigurieren Sie die SCL- und SDA-I/Os als Mehrzweck-Ausgang Open-Drain, High-Pegel (Schreiben Sie 1 in GPIOx_ODR).

  3. Überprüfen Sie den SCL- und SDA-High-Pegel in GPIOx_IDR.

  4. Konfigurieren Sie den SDA I/O als Allzweckausgang Open-Drain, Low-Pegel (schreiben Sie 0 in GPIOx_ODR).
  5. Überprüfen Sie den niedrigen SDA-Pegel in GPIOx_IDR.
  6. Konfigurieren Sie die SCL-E/A als Allzweckausgang Open-Drain, Low-Pegel (0 in GPIOx_ODR schreiben).
  7. Überprüfen Sie den SCL-Low-Pegel in GPIOx_IDR.
  8. Konfigurieren Sie die SCL-E/A als Allzweckausgang Open-Drain, High-Pegel (Schreiben Sie 1 in GPIOx_ODR).
  9. Überprüfen Sie den SCL-High-Pegel in GPIOx_IDR.
  10. Konfigurieren Sie den SDA I/O als Allzweckausgang Open-Drain, High-Pegel (Schreiben Sie 1 in GPIOx_ODR).
  11. Überprüfen Sie den hohen SDA-Pegel in GPIOx_IDR.
  12. Konfigurieren Sie die SCL- und SDA-I/Os als Alternativfunktion Open-Drain.
  13. Setzen Sie das SWRST-Bit im I2Cx_CR1-Register.
  14. SWRST-Bit im I2Cx_CR1-Register löschen.
  15. Aktivieren Sie die I2C-Peripherie durch Setzen des PE-Bits im I2Cx_CR1-Register.

Ich habe das gleiche Problem bei STM32F429 mit Cube V1.15.0.

Trotzdem ist mir aufgefallen, dass SCL beim Soft-Reset (z. B. beim Debuggen) unmittelbar nach der Initialisierung des HAL_GPIO_Init()Anrufs auf LOW geht.

Ich habe einen Bus-Reset versucht, indem ich gemäß der Empfehlung von i2c-bus.org 16 Uhr an init gesendet habe .

Aber es half nichts. Das Hinzufügen des "Reset" -Codes löste den Trick:

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

Nach einigen Tests fand ich heraus, dass eine Verzögerung von 2 ms ausreicht. Ich habe die manuelle Neuinitialisierung der Uhr beibehalten, da eine Übertragung beim Zurücksetzen der CPU hängen bleiben kann.

Ich hatte das gleiche Problem mit dem STM32F411 Discovery Board. Als ich die Schaltpläne der Platine überprüfte (vergessen Sie nicht, die Version der Platine anzupassen), stellte ich fest, dass es keinen Pull-up-Widerstand auf den SDA- und SCL-Leitungen gibt. Also habe ich einen internen Pull-Widerstand an SCL- und SDA-Pins verwendet (CubeMX aktiviert den Pull-up-Widerstand bei der automatischen Codegenerierung nicht) und das Problem ist für mich gelöst.