From 87886788d1b7022636b49101c7fd41ce6e0b1e43 Mon Sep 17 00:00:00 2001 From: wdfk-prog <1425075683@qq.com> Date: Thu, 7 May 2026 13:36:48 +0800 Subject: [PATCH] fix[STM32][I2C]: stabilize async completion and recovery flow for hard i2c enable I2C error irq when async transfer paths are used fall back to polling when scheduler or interrupt context cannot wait for completion complete the wait path on HAL error callback and abort the transfer on async timeout or error allow INT and DMA mode flags to coexist and keep recovery behavior aligned across STM32 I2C IPs --- .../HAL_Drivers/drivers/drv_hard_i2c.c | 155 +++++++++++++----- 1 file changed, 112 insertions(+), 43 deletions(-) diff --git a/bsp/stm32/libraries/HAL_Drivers/drivers/drv_hard_i2c.c b/bsp/stm32/libraries/HAL_Drivers/drivers/drv_hard_i2c.c index 48fb6a6981e..fc7634422db 100644 --- a/bsp/stm32/libraries/HAL_Drivers/drivers/drv_hard_i2c.c +++ b/bsp/stm32/libraries/HAL_Drivers/drivers/drv_hard_i2c.c @@ -10,6 +10,7 @@ * 2024-12-10 zzk597 add support for STM32F1 series * 2024-06-23 wdfk-prog Add blocking modes and distinguish POLL,INT,DMA modes * 2024-06-23 wdfk-prog Distinguish STM32 I2C timing semantics by IP generation + * 2026-04-20 wdfk-prog Stabilize async completion and recovery flow */ #include "drv_hard_i2c.h" @@ -176,8 +177,8 @@ static rt_err_t stm32_i2c_init(struct stm32_i2c *i2c_drv) } #endif /* defined(BSP_I2C_TX_USING_DMA) */ #if defined(BSP_I2C_USING_IRQ) - if ((i2c_drv->i2c_dma_flag & RT_DEVICE_FLAG_DMA_TX || i2c_drv->i2c_dma_flag & RT_DEVICE_FLAG_DMA_RX) - || (i2c_drv->i2c_dma_flag & RT_DEVICE_FLAG_INT_TX || i2c_drv->i2c_dma_flag & RT_DEVICE_FLAG_INT_RX)) + if (((i2c_drv->i2c_dma_flag & RT_DEVICE_FLAG_DMA_TX) || (i2c_drv->i2c_dma_flag & RT_DEVICE_FLAG_DMA_RX)) + || ((i2c_drv->i2c_dma_flag & RT_DEVICE_FLAG_INT_TX) || (i2c_drv->i2c_dma_flag & RT_DEVICE_FLAG_INT_RX))) { /* In the data transfer function stm32_i2c_master_xfer(), the IT transfer function HAL_I2C_Master_Seq_Transmit_IT() is used when DMA is not used, so the IT interrupt @@ -185,6 +186,8 @@ static rt_err_t stm32_i2c_init(struct stm32_i2c *i2c_drv) the rt_completion_wait() will always timeout. */ HAL_NVIC_SetPriority(i2c_drv->config->evirq_type, 2, 0); HAL_NVIC_EnableIRQ(i2c_drv->config->evirq_type); + HAL_NVIC_SetPriority(i2c_drv->config->erirq_type, 2, 0); + HAL_NVIC_EnableIRQ(i2c_drv->config->erirq_type); } #endif /* defined(BSP_I2C_USING_IRQ) */ @@ -205,7 +208,8 @@ static rt_err_t stm32_i2c_configure(struct rt_i2c_bus_device *bus) * @param handle Pointer to the HAL I2C handle. * @param msg Pointer to the RT-Thread I2C message descriptor. * @param mode HAL sequential transfer mode. - * @param timeout Timeout in RT-Thread ticks for polling transfer. + * @param timeout Timeout in milliseconds for polling transfer. + * @param async_allowed RT_TRUE when the current context allows IRQ/DMA wait. * @param need_wait Output flag set to RT_TRUE when IT/DMA path is used. * @return HAL status returned by the selected HAL receive API. * @retval HAL_OK Transfer start succeeded. @@ -216,6 +220,7 @@ static HAL_StatusTypeDef stm32_i2c_master_receive_start(struct stm32_i2c *i2c_ob struct rt_i2c_msg *msg, uint32_t mode, rt_uint32_t timeout, + rt_bool_t async_allowed, rt_bool_t *need_wait) { RT_UNUSED(i2c_obj); @@ -229,7 +234,7 @@ static HAL_StatusTypeDef stm32_i2c_master_receive_start(struct stm32_i2c *i2c_ob *need_wait = RT_FALSE; #if defined(BSP_I2C_RX_USING_DMA) - if ((i2c_obj->i2c_dma_flag & RT_DEVICE_FLAG_DMA_RX) && (msg->len >= DMA_TRANS_MIN_LEN)) + if (async_allowed && (i2c_obj->i2c_dma_flag & RT_DEVICE_FLAG_DMA_RX) && (msg->len >= DMA_TRANS_MIN_LEN)) { *need_wait = RT_TRUE; return HAL_I2C_Master_Seq_Receive_DMA(handle, (msg->addr << 1), msg->buf, msg->len, mode); @@ -237,7 +242,7 @@ static HAL_StatusTypeDef stm32_i2c_master_receive_start(struct stm32_i2c *i2c_ob #endif /* defined(BSP_I2C_RX_USING_DMA) */ #if defined(BSP_I2C_RX_USING_INT) - if (i2c_obj->i2c_dma_flag & RT_DEVICE_FLAG_INT_RX) + if (async_allowed && (i2c_obj->i2c_dma_flag & RT_DEVICE_FLAG_INT_RX)) { *need_wait = RT_TRUE; return HAL_I2C_Master_Seq_Receive_IT(handle, (msg->addr << 1), msg->buf, msg->len, mode); @@ -257,7 +262,8 @@ static HAL_StatusTypeDef stm32_i2c_master_receive_start(struct stm32_i2c *i2c_ob * @param handle Pointer to the HAL I2C handle. * @param msg Pointer to the RT-Thread I2C message descriptor. * @param mode HAL sequential transfer mode. - * @param timeout Timeout in RT-Thread ticks for polling transfer. + * @param timeout Timeout in milliseconds for polling transfer. + * @param async_allowed RT_TRUE when the current context allows IRQ/DMA wait. * @param need_wait Output flag set to RT_TRUE when IT/DMA path is used. * @return HAL status returned by the selected HAL transmit API. * @retval HAL_OK Transfer start succeeded. @@ -268,6 +274,7 @@ static HAL_StatusTypeDef stm32_i2c_master_transmit_start(struct stm32_i2c *i2c_o struct rt_i2c_msg *msg, uint32_t mode, rt_uint32_t timeout, + rt_bool_t async_allowed, rt_bool_t *need_wait) { RT_UNUSED(i2c_obj); @@ -281,7 +288,7 @@ static HAL_StatusTypeDef stm32_i2c_master_transmit_start(struct stm32_i2c *i2c_o *need_wait = RT_FALSE; #if defined(BSP_I2C_TX_USING_DMA) - if ((i2c_obj->i2c_dma_flag & RT_DEVICE_FLAG_DMA_TX) && (msg->len >= DMA_TRANS_MIN_LEN)) + if (async_allowed && (i2c_obj->i2c_dma_flag & RT_DEVICE_FLAG_DMA_TX) && (msg->len >= DMA_TRANS_MIN_LEN)) { *need_wait = RT_TRUE; return HAL_I2C_Master_Seq_Transmit_DMA(handle, (msg->addr << 1), msg->buf, msg->len, mode); @@ -289,7 +296,7 @@ static HAL_StatusTypeDef stm32_i2c_master_transmit_start(struct stm32_i2c *i2c_o #endif /* defined(BSP_I2C_TX_USING_DMA) */ #if defined(BSP_I2C_TX_USING_INT) - if (i2c_obj->i2c_dma_flag & RT_DEVICE_FLAG_INT_TX) + if (async_allowed && (i2c_obj->i2c_dma_flag & RT_DEVICE_FLAG_INT_TX)) { *need_wait = RT_TRUE; return HAL_I2C_Master_Seq_Transmit_IT(handle, (msg->addr << 1), msg->buf, msg->len, mode); @@ -386,7 +393,8 @@ static rt_ssize_t stm32_i2c_master_xfer(struct rt_i2c_bus_device *bus, struct stm32_i2c *i2c_obj; rt_bool_t is_last = RT_FALSE; uint32_t mode = 0; - rt_uint32_t timeout; + rt_uint32_t timeout_ms; + if (num == 0) { return 0; @@ -398,6 +406,7 @@ static rt_ssize_t stm32_i2c_master_xfer(struct rt_i2c_bus_device *bus, I2C_HandleTypeDef *handle = &i2c_obj->handle; RT_ASSERT(handle != RT_NULL); #if defined(BSP_I2C_USING_IRQ) + rt_bool_t need_abort = RT_FALSE; struct rt_completion *completion; completion = &i2c_obj->completion; #endif /* defined(BSP_I2C_USING_IRQ) */ @@ -405,32 +414,49 @@ static rt_ssize_t stm32_i2c_master_xfer(struct rt_i2c_bus_device *bus, for (i = 0; i < num; i++) { rt_bool_t need_wait = RT_FALSE; + const rt_bool_t scheduler_available = rt_scheduler_is_available(); + const rt_bool_t irq_disabled = rt_hw_interrupt_is_disabled(); + const rt_bool_t async_allowed = (scheduler_available && !irq_disabled); + msg = &msgs[i]; is_last = (i == (num - 1)); next_msg = is_last ? RT_NULL : &msgs[i + 1]; mode = stm32_i2c_get_xfer_mode(i, msg, next_msg, is_last); LOG_D("xfer msgs[%d] addr=0x%2x buf=0x%x len= 0x%x flags= 0x%x", i, msg->addr, msg->buf, msg->len, msg->flags); #if defined(STM32_I2C_TIMINGR_IP) - timeout = bus->timeout ? bus->timeout : 100U; + timeout_ms = bus->timeout ? (bus->timeout * 1000U + RT_TICK_PER_SECOND - 1U) / RT_TICK_PER_SECOND : 100U; #else - timeout = TIMEOUT_CALC(msg); -#endif + timeout_ms = TIMEOUT_CALC(msg); +#endif /* defined(STM32_I2C_TIMINGR_IP) */ + +#if defined(BSP_I2C_USING_IRQ) + rt_tick_t timeout_tick = rt_tick_from_millisecond(timeout_ms); + rt_completion_init(completion); +#endif /* defined(BSP_I2C_USING_IRQ) */ if (msg->flags & RT_I2C_RD) { LOG_D("xfer rec msgs[%d] hal mode = %s", i, stm32_i2c_mode_name(mode)); - ret = stm32_i2c_master_receive_start(i2c_obj, handle, msg, mode, timeout, &need_wait); + ret = stm32_i2c_master_receive_start(i2c_obj, handle, msg, mode, timeout_ms, async_allowed, &need_wait); if (ret != HAL_OK) { - LOG_E("I2C[%s] Read error(%d)!\n", bus->parent.parent.name, ret); + LOG_E("I2C[%s] Read error(%d)!", bus->parent.parent.name, ret); goto out; } #if defined(BSP_I2C_USING_IRQ) if (need_wait) { - ret = rt_completion_wait(completion, timeout); + ret = rt_completion_wait(completion, timeout_tick); if (ret != RT_EOK) { - LOG_W("I2C[%s] receive wait failed %d, timeout %d", bus->parent.parent.name, ret, timeout); + need_abort = RT_TRUE; + LOG_W("I2C[%s] receive wait failed %d, timeout %u ms", bus->parent.parent.name, ret, timeout_ms); + goto out; + } + + if (handle->ErrorCode != HAL_I2C_ERROR_NONE) + { + need_abort = RT_TRUE; + LOG_E("I2C[%s] receive failed, error code: 0x%08x", bus->parent.parent.name, handle->ErrorCode); goto out; } } @@ -439,19 +465,27 @@ static rt_ssize_t stm32_i2c_master_xfer(struct rt_i2c_bus_device *bus, else { LOG_D("xfer trans msgs[%d] hal mode = %s", i, stm32_i2c_mode_name(mode)); - ret = stm32_i2c_master_transmit_start(i2c_obj, handle, msg, mode, timeout, &need_wait); + ret = stm32_i2c_master_transmit_start(i2c_obj, handle, msg, mode, timeout_ms, async_allowed, &need_wait); if (ret != HAL_OK) { - LOG_E("I2C[%s] Write error(%d)!\n", bus->parent.parent.name, ret); + LOG_E("I2C[%s] Write error(%d)!", bus->parent.parent.name, ret); goto out; } #if defined(BSP_I2C_USING_IRQ) if (need_wait) { - ret = rt_completion_wait(completion, timeout); + ret = rt_completion_wait(completion, timeout_tick); if (ret != RT_EOK) { - LOG_W("I2C[%s] transmit wait failed %d, timeout %d", bus->parent.parent.name, ret, timeout); + need_abort = RT_TRUE; + LOG_W("I2C[%s] transmit wait failed %d, timeout %u ms", bus->parent.parent.name, ret, timeout_ms); + goto out; + } + + if (handle->ErrorCode != HAL_I2C_ERROR_NONE) + { + need_abort = RT_TRUE; + LOG_E("I2C[%s] transmit failed, error code: 0x%08x", bus->parent.parent.name, handle->ErrorCode); goto out; } } @@ -469,20 +503,20 @@ static rt_ssize_t stm32_i2c_master_xfer(struct rt_i2c_bus_device *bus, out: ret = i; /* - * On TIMINGR-based STM32 I2C IPs (currently F7/H7 in this driver), - * STOPI only enables STOP-event interrupt handling. - * It does not actively generate a STOP condition on the bus. - * - * For legacy STM32 I2C IPs, the HAL error handler already generates a - * STOP condition on AF in master/memory modes, so this driver does not - * manually issue another STOP in the AF path. - */ + * On TIMINGR-based STM32 I2C IPs (currently F7/H7 in this driver), + * STOPI only enables STOP-event interrupt handling. + * It does not actively generate a STOP condition on the bus. + * + * For legacy STM32 I2C IPs, the HAL error handler already generates a + * STOP condition on AF in master/memory modes, so this driver does not + * manually issue another STOP in the AF path. + */ if (handle->ErrorCode & HAL_I2C_ERROR_AF) { LOG_W("I2C[%s] NACK Error", bus->parent.parent.name); #if defined(STM32_I2C_TIMINGR_IP) handle->Instance->CR1 |= I2C_IT_STOPI; -#endif /* defined(STM32_I2C_TIMINGR_IP) */ +#endif /* defined(STM32_I2C_TIMINGR_IP) */ } if (handle->ErrorCode & HAL_I2C_ERROR_BERR) { @@ -491,8 +525,46 @@ static rt_ssize_t stm32_i2c_master_xfer(struct rt_i2c_bus_device *bus, handle->Instance->CR1 |= I2C_IT_STOPI; #else handle->Instance->CR1 |= I2C_CR1_STOP; -#endif /* defined(STM32_I2C_TIMINGR_IP) */ +#endif /* defined(STM32_I2C_TIMINGR_IP) */ } +#if defined(BSP_I2C_USING_IRQ) + if (need_abort && (msg != RT_NULL)) + { + if (HAL_I2C_Master_Abort_IT(handle, (msg->addr << 1)) != HAL_OK) + { + LOG_W("I2C[%s] abort start failed, state: %d, error: 0x%08x", + bus->parent.parent.name, + handle->State, + handle->ErrorCode); + } + else + { + rt_uint32_t timeout = timeout_ms; + const rt_bool_t scheduler_available = rt_scheduler_is_available(); + const rt_bool_t irq_disabled = rt_hw_interrupt_is_disabled(); + + while (HAL_I2C_GetState(handle) != HAL_I2C_STATE_READY) + { + if (timeout-- > 0) + { + if (scheduler_available && !irq_disabled) + { + rt_thread_mdelay(1); + } + else + { + rt_hw_us_delay(1000); + } + } + else + { + LOG_E("I2C[%s] timeout! state did not become READY after abort.", bus->parent.parent.name); + break; + } + } + } + } +#endif /* defined(BSP_I2C_USING_IRQ) */ return ret; #undef TIMEOUT_FREQ_KHZ @@ -690,27 +762,24 @@ void HAL_I2C_MasterRxCpltCallback(I2C_HandleTypeDef *hi2c) void HAL_I2C_ErrorCallback(I2C_HandleTypeDef *hi2c) { - LOG_W("%s error code %d", hi2c->Instance == I2C1 ? "I2C1" - : hi2c->Instance == I2C2 ? "I2C2" - : hi2c->Instance == I2C3 ? "I2C3" -#ifdef I2C4 - : hi2c->Instance == I2C4 ? "I2C4" -#endif /* I2C4 */ - : "unknown", - hi2c->ErrorCode); + struct stm32_i2c *i2c_drv = rt_container_of(hi2c, struct stm32_i2c, handle); + + LOG_W("%s error code 0x%08x", i2c_drv->config->name, hi2c->ErrorCode); #if defined(STM32_I2C_TIMINGR_IP) - /* Send stop signal to prevent bus lock-up */ - if (hi2c->ErrorCode == HAL_I2C_ERROR_AF) + /* + * Trigger STOP handling immediately in IRQ context so the peripheral can + * leave the error state before the waiting thread starts its recovery path. + */ + if (hi2c->ErrorCode & HAL_I2C_ERROR_AF) { - LOG_W("I2C NACK Error now stoped"); hi2c->Instance->CR1 |= I2C_IT_STOPI; } - if (hi2c->ErrorCode == HAL_I2C_ERROR_BERR) + if (hi2c->ErrorCode & HAL_I2C_ERROR_BERR) { - LOG_W("I2C BUS Error now stoped"); hi2c->Instance->CR1 |= I2C_IT_STOPI; } #endif /* defined(STM32_I2C_TIMINGR_IP) */ + rt_completion_done(&i2c_drv->completion); } #ifdef BSP_USING_HARD_I2C1