STM32 - I2C - HAL_BUSY





Про проблему с i2c у микроконтроллеров stm32, в частности F103 (а может и ещё в каких-то).

Суть проблемы: при старте МК, аналоговый фильтр (есть такая штуковина не имеющая в данном случае отношения к аналоговому входу) блокирует флаг I2C_FLAG_BUSY, и тот не может сброситься. В итоге I2C всегда занят. То есть, любая HALовская функция, работающая с i2c, возвращает HAL_BUSY. Эта проблема описана в Errata (п 2.13.7).

Это касается функций без прерывания.


Если же использовать функции с прерыванием — _IT, то ситуация совсем скверная. Допустим вы отправили данные с помощью HAL_I2C_Master_Transmit_IT() и пошли дальше заниматься своими делами думая что всё хорошо. Однако на самом деле функция вместо того чтоб отправить данные, ждёт пока сброситься флаг I2C_FLAG_BUSY, а он и не думает сбрасываться. Таймаут у функции 10 сек, в течении которых она ждёт когда сброситься флаг, естественно не дожидается этого, и в итоге вместо отправки данных возвращает HAL_TIMEOUT.



Решения этой проблемы для функций без прерывания


Первая рекомендация:


В файле stm32f1xx_hal_msp.c, в функции…

void HAL_I2C_MspInit(I2C_HandleTypeDef* hi2c)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(hi2c->Instance==I2C1)
  {
  /* USER CODE BEGIN I2C1_MspInit 0 */
	
  /* USER CODE END I2C1_MspInit 0 */
  
    __HAL_RCC_GPIOB_CLK_ENABLE();
    /**I2C1 GPIO Configuration    
    PB8     ------> I2C1_SCL
    PB9     ------> I2C1_SDA 
    */
    GPIO_InitStruct.Pin = GPIO_PIN_8|GPIO_PIN_9;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

    __HAL_AFIO_REMAP_I2C1_ENABLE();

    /* Peripheral clock enable */
    __HAL_RCC_I2C1_CLK_ENABLE();
  /* USER CODE BEGIN I2C1_MspInit 1 */

  /* USER CODE END I2C1_MspInit 1 */
  }
}


Включение клоков интерфейса (__HAL_RCC_I2C1_CLK_ENABLE()) сделано после включения клоков GPIO, хотя в других функциях, типа void HAL_SPI_MspInit(SPI_HandleTypeDef* hspi) или void HAL_UART_MspInit(UART_HandleTypeDef* huart) сделано наоборот, сначала клоки интерфейса, а потом GPIO.

Например SPI…

void HAL_SPI_MspInit(SPI_HandleTypeDef* hspi)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(hspi->Instance==SPI1)
  {
  /* USER CODE BEGIN SPI1_MspInit 0 */

  /* USER CODE END SPI1_MspInit 0 */
    /* Peripheral clock enable */
    __HAL_RCC_SPI1_CLK_ENABLE();  //////// сначала интерфейс
  
    __HAL_RCC_GPIOA_CLK_ENABLE(); //////// затем GPIO
    /**SPI1 GPIO Configuration    
    PA5     ------> SPI1_SCK
    PA6     ------> SPI1_MISO
    PA7     ------> SPI1_MOSI 
    */
    GPIO_InitStruct.Pin = GPIO_PIN_5|GPIO_PIN_7;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = GPIO_PIN_6;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

  /* USER CODE BEGIN SPI1_MspInit 1 */

  /* USER CODE END SPI1_MspInit 1 */
  }
}



Поэтому первой попыткой исправить проблему, будет сделать…

void HAL_I2C_MspInit(I2C_HandleTypeDef* hi2c)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(hi2c->Instance==I2C1)
  {
  /* USER CODE BEGIN I2C1_MspInit 0 */
      __HAL_RCC_I2C1_CLK_ENABLE(); ///////////// ЭТО !!! ////////////////
  /* USER CODE END I2C1_MspInit 0 */
  
    __HAL_RCC_GPIOB_CLK_ENABLE();
    /**I2C1 GPIO Configuration    
    PB8     ------> I2C1_SCL
    PB9     ------> I2C1_SDA 
    */
    GPIO_InitStruct.Pin = GPIO_PIN_8|GPIO_PIN_9;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

    __HAL_AFIO_REMAP_I2C1_ENABLE();

    /* Peripheral clock enable */
    __HAL_RCC_I2C1_CLK_ENABLE(); //////// это можно закоментировать
  /* USER CODE BEGIN I2C1_MspInit 1 */

  /* USER CODE END I2C1_MspInit 1 */
  }
}


Однако как показывает практика, это не очень то помогает. Всё равно шина виснет и даже не помогает ресет платы.



Поэтому применим второй метод:


Перед инициализацией всего и вся, нужно добавить ресет всего чего только можно у I2C…

__HAL_RCC_I2C1_CLK_ENABLE();
  HAL_Delay(100);
  __HAL_RCC_I2C1_FORCE_RESET();
  HAL_Delay(100);
  __HAL_RCC_I2C1_RELEASE_RESET();
  HAL_Delay(100);


То есть выглядеть код будет так…

/* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */ // сюда добавили
  __HAL_RCC_I2C1_CLK_ENABLE();
  HAL_Delay(100);
  __HAL_RCC_I2C1_FORCE_RESET();
  HAL_Delay(100);
  __HAL_RCC_I2C1_RELEASE_RESET();
  HAL_Delay(100);
  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART1_UART_Init();
  MX_ADC1_Init();
  MX_SPI1_Init();
  MX_I2C1_Init();
  MX_TIM4_Init();
  /* USER CODE BEGIN 2 */


После этого всё становиться почти хорошо, зависания шины если и происходят, то редко, и при нажатии ресета шина тут же приходит в себя. Тем не менее в процессе работы случаются зависоны.



Поэтому третий метод:


Запуская функцию, не важно, отправка, приём, или ещё что-то, надо проверять что она возвращает…

uint32_t status = HAL_I2C_Master_Transmit(&hi2c1, (uint16_t)LCD_ADDR, &bt, 1, 1000);


Если статус не HAL_OK, тогда используем метод описанный в STM32F10xx8 STM32F10xxB Errata sheet, глава 2.13.7 — I2C analog filter may provide wrong value, locking BUSY flag and preventing master mode entry


При вызове функции проверяем статус…

uint32_t status = HAL_I2C_Master_Transmit(&hi2c1, (uint16_t)LCD_ADDR, &bt, 1, 1000);

if(status != HAL_OK)
{
	I2C_ClearBusyFlagErratum(&hi2c1, 1000);
}


Если он плохой, то вызываем самописную функцию I2C_ClearBusyFlagErratum(&hi2c1, 1000), представляющую из себя 15 пунктов описанных в еррата.

На HAL она выглядит так…

static void I2C_ClearBusyFlagErratum(I2C_HandleTypeDef *hi2c, uint32_t timeout)
{
	// 2.13.7 I2C analog filter may provide wrong value, locking BUSY. STM32F10xx8 STM32F10xxB Errata sheet

    GPIO_InitTypeDef GPIO_InitStructure = {0};

    // 1. Clear PE bit.
    CLEAR_BIT(hi2c->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(hi2c);

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

    GPIO_InitStructure.Pin = GPIO_PIN_8; // SCL // если пин другой, то укажите нужный
    HAL_GPIO_Init(GPIOB, &GPIO_InitStructure); // если порт другой, то укажите нужную букву GPIOх, и ниже там все порты и пины поменяйте на своё

    GPIO_InitStructure.Pin = GPIO_PIN_9; // SDA
    HAL_GPIO_Init(GPIOB, &GPIO_InitStructure);

    // 3. Check SCL and SDA High level in GPIOx_IDR.
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_SET);
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_SET);

    wait_for_gpio_state_timeout(GPIOB, GPIO_PIN_8, GPIO_PIN_SET, timeout);
    wait_for_gpio_state_timeout(GPIOB, GPIO_PIN_9, 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(GPIOB, GPIO_PIN_9, GPIO_PIN_RESET);

    // 5. Check SDA Low level in GPIOx_IDR.
    wait_for_gpio_state_timeout(GPIOB, GPIO_PIN_9, 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(GPIOB, GPIO_PIN_8, GPIO_PIN_RESET);

    // 7. Check SCL Low level in GPIOx_IDR.
    wait_for_gpio_state_timeout(GPIOB, GPIO_PIN_8, 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(GPIOB, GPIO_PIN_8, GPIO_PIN_SET);

    // 9. Check SCL High level in GPIOx_IDR.
    wait_for_gpio_state_timeout(GPIOB, GPIO_PIN_8, 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(GPIOB, GPIO_PIN_9, GPIO_PIN_SET);

    // 11. Check SDA High level in GPIOx_IDR.
    wait_for_gpio_state_timeout(GPIOB, GPIO_PIN_9, 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; // F4

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

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

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

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

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

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


Эта функция вызывает ещё одну функцию проверки состояния ножек после их включения/отключения…

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 = 1;

    for(;(state != HAL_GPIO_ReadPin(port, pin)) && (1 == ret);) // Wait until flag is set
    {
        if(timeout != HAL_MAX_DELAY) // Check for the timeout
        {
            if((timeout == 0U) || ((HAL_GetTick() - Tickstart) > timeout)) ret = 0;
        }

        asm("nop");
    }
    return ret;
}

При желании можно проверять что она возвращает, 0 или 1, но это уже излишество, и ретурны можно выпилить.


Тут можно взять это, оформленное в виде хедера. Добавить файл i2c_er.h в проект, и там где вызываются функции с проверкой статуса добавить инклюд…

#include "i2c_er.h"


Всё, теперь можно проверять что возвращает любая HALовская функция I2C, и в случае неудачи I2C будет переинициализироваться.

Все эти методы можно использовать вместе.



Что же касается функций с прерываниями, то первые два метода могут решить проблему во время старта, но не во время работы. Как тут поступить я не знаю. Придётся корректировать HALовский код, и как-то приделывать туда описанную в последнем методе проверку статуса.


Это всё, всем спасибо


Примеры взяты из сети и немного изменены.


Телеграм-чат istarik

Телеграм-чат STM32


  • 0
  • 3066
Поддержать автора




Telegram-чат istarik

Задать вопрос по статье
Telegram-канал istarik

Известит Вас о новых публикациях






Комментарии (0)

Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.