2023-09-13 12:27 PM
After inspecting blocking I2C functions from HAL libraries I found that apparently I2C blocking functions don't return "HAL_TIMEOUT" status if timeout is reached during I2C transfer. The following function does the work of verifying whether timeout was reached or not:
static HAL_StatusTypeDef I2C_WaitOnFlagUntilTimeout(I2C_HandleTypeDef *hi2c, uint32_t Flag, FlagStatus Status,
uint32_t Timeout, uint32_t Tickstart)
{
while (__HAL_I2C_GET_FLAG(hi2c, Flag) == Status)
{
/* Check for the Timeout */
if (Timeout != HAL_MAX_DELAY)
{
if (((HAL_GetTick() - Tickstart) > Timeout) || (Timeout == 0U))
{
hi2c->ErrorCode |= HAL_I2C_ERROR_TIMEOUT;
hi2c->State = HAL_I2C_STATE_READY;
hi2c->Mode = HAL_I2C_MODE_NONE;
/* Process Unlocked */
__HAL_UNLOCK(hi2c);
return HAL_ERROR;
}
}
}
return HAL_OK;
}
As you see, there is no "HAL_TIMEOUT" return status. Even where "I2C_WaitOnFlagUntilTimeout()" is called, e.g. inside of "HAL_I2C_Master_Transmit()" or "HAL_I2C_Master_Receive()", if error is returned from "I2C_WaitOnFlagUntilTimeout()", user-called function still returns "HAL_ERROR" and not "HAL_TIMEOUT". Use of this function inside of "HAL_I2C_Master_Transmit()" is shown below:
/* Wait until TCR flag is set */
if (I2C_WaitOnFlagUntilTimeout(hi2c, I2C_FLAG_TCR, RESET, Timeout, tickstart) != HAL_OK)
{
return HAL_ERROR;
}
But when I inspected UART library, upon calling this timeout checking function from "HAL_UART_Transmit()", the timeout checking function is implemented like shown below:
if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_TC, RESET, tickstart, Timeout) != HAL_OK)
{
return HAL_TIMEOUT;
}
Is this a bug or is I2C operation such that timeout from blocking operation is generally not possible and error status is returned instead?
P.S.: I posted this issue under "Other:software" but later saw HAL topics are posted under "STM32 MCUs" category.
2023-09-13 02:04 PM
Perhaps this can be qualified as documentation bug: the return values should be described better.
But nothing else in the documentation warrants that HAL I2C functions behave like UART.
Any such assumptions can and should be validated by unit testing in the target environment.
Tests for fail cases are important, they validate that when things fail this goes in expected way.
For example:
// Test that [my I2C device] is detected
void test_device_X_is_detected()
{
HAL_StatusTypeDef st = HAL_I2C_IsDeviceReady(hI2c, MY_DEV, 2, 10);
ASSERT_TRUE(st == HAL_OK);
}
// Test that detection of nonexisting I2C device fails
void test_bad_device_not_detected_buggy()
{
HAL_StatusTypeDef st = HAL_I2C_IsDeviceReady(hI2c, NONEXISTING_DEV, 3, 20);
ASSERT_TRUE(st == HAL_TIMEOUT); // >>> this will fail
}
// This should pass:
void test_bad_device_not_detected()
{
HAL_StatusTypeDef st = HAL_I2C_IsDeviceReady(hI2c, NONEXISTING_DEV, 3, 20);
ASSERT_TRUE(st == HAL_ERROR && (hi2c->ErrorCode & HAL_I2C_ERROR_TIMEOUT));
//or: ASSERT_TRUE(st == HAL_ERROR && hi2c->ErrorCode == HAL_I2C_ERROR_TIMEOUT);
}
2023-09-13 02:57 PM
I wouldn't call this documentation error since HAL docs always provide explanation over returned status as "HAL Status". Maybe inconsistency between different but somewhat similar HAL libraries like serial protocols.
Kudos for pointing out that internal "hi2c->ErrorCode" is externally accessible via I2C handle struct. It seems this member provides additional resolution in terms of provided error statuses.