# How to properly implement I2C communication between two or more STM32s?

I have a master board with STM32F030R8 and two daughter boards with STM32F030C6. The objective is to communicate parameters between the boards periodically and also on-demand. With some effort I configured I2C in normal mode and both boards and got the communication to work. But then I merged the I2C part with my main code and things started misbehaving. It seemed as if I needed to synchronize the HAL_I2C_MasterTransmit and HAL_I2C_SlaveRecieve. The communication works once when I reset the boards together, otherwise it fails. Then I implemented a timer which resets the I2C peripheral after a few seconds if communication has failed. Which worked somewhat and I was receiving data from slave almost constantly without fail. But then I had to connect the second daughter board and now it takes about 15 seconds to properly transmit data once.

I am really dispirited by the amount of work that had to be done and even then with no stable results. I thought the peripheral would manage the synchronization and bus allocation and so on, but now I feel like have to do it all manually in the code. So, the question is, what is a robust way to implement this system so that I can get updates on the master instantaneously?

Notes:

• I'm using the TXCplt and RXCplt callbacks to see if communication was successful or not before attempting the next board.

• I do it piece by piece. First I send a transfer request to slave, then the slave has to acknowledge the transfer before it sends the data.

Also: How to properly restart I2C communication? Currently I'm using __HAL_I2C_Disable and then calling MX_I2C_Init to reinit communications.

What could be an optimum time delay for restart? Code for reference:

void I2C_Timer() {
if(i2cFlag) i2cTimer++;
else i2cTimer = 0;
if(i2cTimer > 3) {
// Reset I2C
__HAL_I2C_DISABLE(&hi2c1);
MX_I2C1_Init();
bTransferRequest = prevTransferRequest;
i2cTimer = 0;
i2cFlag = 0;
comComplete = 0;

switch(bTransferRequest) {
if(comCount < 2) {
/* If comm fails to complete, restart com to the same board */
switch(comIndex) {
case 0: case 1: comIndex = 0; break; // Chiller
case 2: case 3: comIndex = 2; break; // Discharge
}
}
else {
/* Switching to the next board if prev board comm fails for more than 2 tries */
switch(comIndex) {
case 0: case 1: comIndex = 2; break; // Chiller
case 2: case 3: comIndex = 0; break; // Discharge
}
comComplete = 1;
comCount = 0;
}
break;
case MASTER_REQ_WRITE:

// case MASTER_REQ_MASSWRITE:
}
if(comCount == 4) {

}
comCount++;
}


}

void I2C_Routine(void) {
if(i2cFlag == 0) {
switch(bTransferRequest) {
switch(comIndex) {
case 0:
i2cTimerFlag = 1;
if(HAL_I2C_Master_Transmit_DMA(&hi2c1, (uint16_t)I2C_ADD_CHILLER, &bTransferRequest, 1)!= HAL_OK) {
/* Error_Handler() function is called when error occurs. */
Error_Handler();
}
i2cFlag = 1;
comComplete=0;
break;
case 1:
/* Error_Handler() function is called when error occurs. */
Error_Handler();
}
i2cFlag = 1;
comComplete = 1;
break;
case 2:
if(HAL_I2C_Master_Transmit_DMA(&hi2c1, (uint16_t)I2C_ADD_DISCHARGE, &bTransferRequest, 1)!= HAL_OK) {
/* Error_Handler() function is called when error occurs. */
Error_Handler();
}
i2cFlag = 1;
comComplete = 0;
break;
case 3:
/* Error_Handler() function is called when error occurs. */
Error_Handler();
}
i2cFlag = 1;
comComplete = 1;
break;
}
break;
case MASTER_REQ_WRITE:
if(i2cWriteParamFlag == SET) {
switch(comIndex) {
case 0:
comComplete=0;
if(HAL_I2C_Master_Transmit_DMA(&hi2c1, (uint16_t)I2C_Address, &bTransferRequest, 1)!= HAL_OK) {
/* Error_Handler() function is called when error occurs. */
Error_Handler();
}
i2cFlag = 1;
break;
case 1:
/* Error_Handler() function is called when error occurs. */
Error_Handler();
}
i2cFlag = 1;
break;
case 2:
if(bTransferAck == MASTER_REQ_WRITE) {
while(HAL_I2C_GetState(&hi2c1) == HAL_I2C_STATE_BUSY);
HAL_Delay(20);
if(HAL_I2C_Master_Transmit_DMA(&hi2c1, (uint16_t)I2C_Address, (uint8_t *)aTxBuffer, 3)!= HAL_OK) {
/* Error_Handler() function is called when error occurs. */
Error_Handler();
}
i2cFlag = 1;
i2cWriteParamFlag = RESET;
comComplete=1;
}
}
}
prevTransferRequest = MASTER_REQ_WRITE;
break;
case MASTER_REQ_MASSWRITE:
switch(comIndex) {
case 0:
if(HAL_I2C_Master_Transmit_DMA(&hi2c1, (uint16_t)I2C_ADD_CHILLER, &bTransferRequest, 1)!= HAL_OK) {
/* Error_Handler() function is called when error occurs. */
Error_Handler();
}
i2cFlag = 1;
break;
case 1:
if(HAL_I2C_Master_Transmit_DMA(&hi2c1, (uint16_t)I2C_ADD_CHILLER, (uint8_t *)aMassTxBuffer, CHILLER_TRANSMIT_BYTES)!= HAL_OK) {
/* Error_Handler() function is called when error occurs. */
Error_Handler();
}
i2cFlag = 1;
break;
case 2:
if(HAL_I2C_Master_Transmit_DMA(&hi2c1, (uint16_t)I2C_ADD_DISCHARGE, &bTransferRequest, 1)!= HAL_OK) {
/* Error_Handler() function is called when error occurs. */
Error_Handler();
}
i2cFlag = 1;
break;
case 3:
if(HAL_I2C_Master_Transmit_DMA(&hi2c1, (uint16_t)I2C_ADD_DISCHARGE, (uint8_t *)aMassTxBuffer, DISCHARGE_TRANSMIT_BYTES)!= HAL_OK) {
/* Error_Handler() function is called when error occurs. */
Error_Handler();
}
i2cFlag = 1;
break;
}
prevTransferRequest = MASTER_REQ_MASSWRITE;
break;
}
}
}
}

comIndex = 0;
}

void I2C_Write(uint8_t Id) {
bTransferRequest = MASTER_REQ_WRITE;
// Id is chiller parameter
aTxBuffer[0] = Id - ChillerStartAddr - 0x98;
// Id is discharge parameter
//  aTxBuffer[0] = Id - DichargeStartAddr;

//  Discharge and chiller parameter ranges
EEPROM_Unlock();
EEPROM_Lock();
aTxBuffer[1] = (Val & 0xFF00) >> 8;
aTxBuffer[2] = (Val & 0x00FF);
i2cWriteParamFlag = SET;
comIndex = 0;
i2cFlag = 0;
}

void I2C_MassWrite() {
bTransferRequest = MASTER_REQ_MASSWRITE;
comIndex = 0;
// Fill aMassRxBuffer_Chiller
// Fill aMassRxBuffer_Discharge
}

void HAL_I2C_MasterTxCpltCallback(I2C_HandleTypeDef *I2cHandle) {
switch(bTransferRequest) {
switch(comIndex) {
case 0: comIndex = 1; break;
case 2: comIndex = 3; comComplete = 1; break;
}
break;
case MASTER_REQ_WRITE:
switch(comIndex) {
case 0: comIndex = 1; comComplete = 1; break;
case 2: comIndex = 0; comComplete = 1; break;
}
break;
}
// i2cTimer = 0;
i2cFlag=0;
}

void HAL_I2C_MasterRxCpltCallback(I2C_HandleTypeDef *I2cHandle) {
switch(bTransferRequest) {
switch(comIndex) {
case 1:
for(uint8_t i = 0, j = 0; i < (i2cChillerParamCount*2) ; i+=2, j++) {
uint8_t ValHigh = aChillrRxBuffer[i];
uint8_t ValLow = aChillrRxBuffer[i + 1];
uint16_t Val = (ValHigh << 8) | ValLow;
PARAM_SetVal(i2CChillerArray[j], Val*(0.1));
}
comIndex = 2; break; // To read Discharge board
case 3:
for(uint8_t i = 0, j = 0; i < (i2cDichargeParamCount*2) ; i+=2, j++) {
uint8_t ValLow = aDischRxBuffer[i + 1];
uint16_t Val = (ValHigh << 8) | ValLow;
PARAM_SetVal(i2CDischrArray[j], Val*(0.1));
}
comIndex = 0; break; // To read Chiller board
}
break;
case MASTER_REQ_WRITE:
switch(comIndex) {
case 1: comIndex = 2; break;
}
break;
}
// i2cTimer = 0;
i2cFlag = 0;
}

• This is not directly an answer to your question, but I would strongly recommend using an oscilloscope and understanding how the I2C protocol works on the wire. Otherwise it can be very hard to get a good grasp of how the code you're writing relates to what the devices are actually sending and receiving, especially since different I2C libraries use inconsistent terminology. – Glenn Willen May 25 at 4:50
• The posted code does not compile! When asking a question about a run time problem, as this question is doing, please post a [mcve] so we can reproduce the problem and help you debug it. – user3629249 May 26 at 16:59
• for a thorough understanding of the I2C protocol especially the section: 2.1GeneralI2C Operation – user3629249 May 26 at 17:21