2026-03-09 7:12 AM - last edited on 2026-03-10 6:13 AM by Andrew Neil
I've developed a board using an STM32F767ZI to manage a set of power supplies via PMBUS. I've connected 4 I2C buses, each with 5 slave devices (power supplies), managing a total of 20 slave devices.
My application continuously polls the slave devices to get their status. When I use a single I2C peripheral, I never get any read errors. However, when multiple I2C peripherals are running simultaneously, I get bursts of read errors on all 4 buses (though not all at the same time) approximately every 30 seconds to 1 minute.
Each I2C polling routine runs in a separate FreeRTOS task.
I've tried using the HAL drivers I2C in Polling, IRQ, and DMA modes. I've also tried using the I2C peripherals in PMBus mode (CubeMX Setup). The bus speed is 100KHz. I tested enabling one peripheral at a time and noticed that the frequency of errors increases as the number of enabled peripherals increases (no errors with only one I2C enabled, errors every 30s/1m with 4 I2C enabled).
Can anyone suggest what I should investigate?
Thanks
2026-03-09 7:45 AM
> noticed that the frequency of errors increases as the number of enabled peripherals increases
If the HAL_I2C functions return HAL_OK, the problem is likely elsewhere in your code. Look at the data flow between this function and where you observe it. Use blocking functions while debugging.
2026-03-10 12:03 AM - edited 2026-03-10 12:07 AM
/**
* @brief PMBus Command Read
* Addr: PMBus target address
* ComIdx: PMBus command index
* ReadData: Buffer to store read data
* @return 0: OK, >1: Error
*/
uint8_t PMBusCommRead(uint8_t BusIdx, uint8_t Addr, uint8_t ComIdx, uint8_t *ReadData)
{
HAL_StatusTypeDef HAL_Res;
BaseType_t SemaphoreFlag;
uint8_t DataI2C[PMBUS_MAX_BYTE];
uint8_t Comm;
uint8_t NumBytes;
uint8_t i;
I2C_HandleTypeDef * I2C_Bus;
switch(BusIdx)
{
case PMBUS1:
I2C_Bus = &hi2c4;
break;
case PMBUS2:
I2C_Bus = &hi2c3;
break;
case PMBUS3:
I2C_Bus = &hi2c2;
break;
case PMBUS4:
I2C_Bus = &hi2c1;
break;
}
// Set I2C Semaphore
if (I2C_Semaphore[BusIdx] != NULL)
{
SemaphoreFlag = xSemaphoreTake(I2C_Semaphore[BusIdx], 0);
while (SemaphoreFlag != pdTRUE)
{
SemaphoreFlag = xSemaphoreTake(I2C_Semaphore[BusIdx], 0);
}
}
Comm = RCP1600Commands[ComIdx].Cmd;
NumBytes = RCP1600Commands[ComIdx].Len;
// Send SMBUS message
HAL_Res=HAL_I2C_Master_Transmit(I2C_Bus, (uint16_t)(Addr), &Comm, 1,I2C_TO);
if (HAL_Res != HAL_OK)
{
//Release I2C Semaphore
if (I2C_Semaphore[BusIdx] != NULL)
{
xSemaphoreGive(I2C_Semaphore[BusIdx]);
}
return 1;
}
// Wait for SMBUS transfer end
while (HAL_I2C_GetState(I2C_Bus) != HAL_I2C_STATE_READY)
{
}
HAL_Res=HAL_I2C_Master_Receive(I2C_Bus, (uint16_t) Addr, DataI2C, (uint16_t) NumBytes,I2C_TO);
if (HAL_Res != HAL_OK)
{
//Release I2C Semaphore
if (I2C_Semaphore[BusIdx] != NULL)
{
xSemaphoreGive(I2C_Semaphore[BusIdx]);
}
return 1;
}
while (HAL_I2C_GetState(I2C_Bus) != HAL_I2C_STATE_READY)
{
}
//Release I2C Semaphore
if (I2C_Semaphore[BusIdx] != NULL)
{
xSemaphoreGive(I2C_Semaphore[BusIdx]);
}
for (i = 0; i < NumBytes; i++)
{
ReadData[i] = DataI2C[i];
}
return 0;
}this is my code. I never get return different from HAL_OK. But sometimes the ReadDat a contains 0xFF or wrong value. Observing the I2C bus on oscilloscope sometimes the transmitted message is not correct but sometimes is right. I have no idea about the errors happen only when multiple tasks run the "PMBusCommRead" function.
2026-03-10 5:01 AM
> Observing the I2C bus on oscilloscope sometimes the transmitted message is not correct but sometimes is right.
In what way is it not correct? The chip can only read the data on the bus. Only one thread can use the I2C bus at a time. If multiple call it at the same, time, it won't work.
To me, it looks like your semaphore check is not sufficient. If the semaphore is already taken, it just ignores it.
2026-03-10 5:46 AM
uint8_t PMBusCommRead(uint8_t BusIdx, uint8_t Addr, uint8_t ComIdx, uint8_t *ReadData)
{
HAL_StatusTypeDef HAL_Res;
uint8_t DataI2C[PMBUS_MAX_BYTE];
uint8_t Comm;
uint8_t NumBytes;
uint8_t i;
I2C_HandleTypeDef * I2C_Bus;
switch(BusIdx)
{
case PMBUS1:
I2C_Bus = &hi2c4;
break;
case PMBUS2:
I2C_Bus = &hi2c3;
break;
case PMBUS3:
I2C_Bus = &hi2c2;
break;
case PMBUS4:
I2C_Bus = &hi2c1;
break;
default:
return 1;
break;
}
// Set I2C Semaphore
if (I2C_Semaphore != NULL)
{
if (xSemaphoreTake(I2C_Semaphore, pdMS_TO_TICKS(100)) != pdTRUE) {
return 1;
}
}
Comm = RCP1600Commands[ComIdx].Cmd;
NumBytes = RCP1600Commands[ComIdx].Len;
// Send SMBUS message
HAL_Res=HAL_I2C_Master_Transmit(I2C_Bus, (uint16_t)(Addr), &Comm, 1,I2C_TO);
if (HAL_Res != HAL_OK)
{
PMBus_ReleaseBus(BusIdx); // Use cleanup function
return 1;
}
// Wait for ready
if (WaitForI2CReady(I2C_Bus, BusIdx) != 0) {
PMBus_ReleaseBus(BusIdx); // Use cleanup function
return 1;
}
HAL_Res=HAL_I2C_Master_Receive(I2C_Bus, (uint16_t) Addr, DataI2C, (uint16_t) NumBytes,I2C_TO);
if (HAL_Res != HAL_OK)
{
PMBus_ReleaseBus(BusIdx); // Use cleanup function
return 1;
}
// Wait for receive complete
if (WaitForI2CReady(I2C_Bus, BusIdx) != 0) {
PMBus_ReleaseBus(BusIdx); // Use cleanup function
return 1;
}
// Copy data
for (i = 0; i < NumBytes; i++)
{
ReadData[i] = DataI2C[i];
}
PMBus_ReleaseBus(BusIdx); // Success path
return 0;
} I've changed to code to manage correctly the semaphore but, if different semaphores (one for every i2c peripherals) the code still doesn't work. I had wrongly supposed that calling the HAL_I2C with different peripheral doesn't need a "single" "global" I2C semaphore for all the I2C busses but for only the single bus.
Changing the code using single semaphore for all I2C peripherals (as attached) solves the issue.
2026-03-10 6:00 AM
Your code still has race conditions, and you've removed xSemaphoreGive entirely for some reason?
This isn't a HAL issue, it's a bug in your code.
2026-03-10 6:12 AM
Sorry, I've moved the xSemaphoreGive in the PMBus_ReleaseBus function. Please could you help me, are race conditions in my code? Where? Thanks in advance for your help.
void PMBus_ReleaseBus(uint8_t BusIdx) {
if (BusIdx < PMBUS_NUM && I2C_Semaphore != NULL) {
xSemaphoreGive(I2C_Semaphore);
}
}
// Helper function to wait for I2C ready
uint8_t WaitForI2CReady(I2C_HandleTypeDef *I2C_Bus, uint8_t BusIdx) {
TickType_t start_tick = xTaskGetTickCount();
const TickType_t state_timeout = pdMS_TO_TICKS(50);
while (HAL_I2C_GetState(I2C_Bus) != HAL_I2C_STATE_READY) {
if ((xTaskGetTickCount() - start_tick) > state_timeout) {
return 1; // Timeout
}
taskYIELD();
}
return 0;
}