STM32 - I2C - HAL_BUSY
Про проблему с i2c у микроконтроллеров stm32, в частности F103 (а может и ещё в каких-то).
Суть проблемы: при старте МК, аналоговый фильтр
Это касается функций без прерывания.
Если же использовать функции с прерыванием — _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, хотя в других функциях, типа
Например 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, но это уже излишество, и ретурны можно выпилить.
Тут можно взять это, оформленное в виде хедера. Добавить файл
#include "i2c_er.h"
Всё, теперь можно проверять что возвращает любая HALовская функция I2C, и в случае неудачи I2C будет переинициализироваться.
Все эти методы можно использовать вместе.
Это всё, всем спасибо
Примеры взяты из сети и немного изменены.
Телеграм-чат istarik
Телеграм-чат STM32
- 0
- stD
29819
Поддержать автора
Комментарии (0)