I2C Slave mode

Preface

I2C has two different modes of operation: master and slave mode. In most cases, when controlling sensor MCUs, the master mode is used for operation. Some EEPROMs may require the MCU to operate as a slave. However, when using the slave HAL, a known length is required for normal operation. Here, we will introduce the LL mode to address the issue of undefined length.

STM32CubeMX Setting

The settings for Master and Slave on Stm32cubeMX are basically the same, except that the slave address cannot be 0x00

As for why the Slave address cannot be 0, it is currently speculated that it may be due to the following reason: ST I2C slave addr uses an AND GATE to compare with OAR1. If it is the same, an interrupt is triggered. Setting it to 0X00 will cause the interrupt to never trigger. The HAL functions used for Slave include

STM32CubeIDE(HAL)

The HAL functions used for Slave include

HAL_I2C_Slave_Transmit()
HAL_I2C_Slave_Receive()
HAL_I2C_Slave_Transmit_IT()
HAL_I2C_Slave_Receive_IT()
HAL_I2C_Slave_Seq_Transmit_IT()
HAL_I2C_Slave_Seq_Receive_IT()
HAL_I2C_Slave_Transmit_DMA()
HAL_I2C_Slave_Receive_DMA()
HAL_I2C_Slave_Seq_Transmit_DMA()
HAL_I2C_Slave_Seq_Receive_DMA()
HAL_I2C_SlaveTxCpltCallback()
HAL_I2C_SlaveRxCpltCallback()

HAL_I2C_Slave_Receive, which is used here for a simple demonstration and is usually combined with interrupts

while(1)
{
HAL_I2C_Slave_Receive(&hi2c1, testArr, 4, 50);
decode();
if(old_vel != vel){
	__HAL_TIM_SET_COMPARE(&htim2,TIM_CHANNEL_1,vel);//ccw
        __HAL_TIM_SET_COMPARE(&htim2,TIM_CHANNEL_2,0);//cw
	old_vel = vel;
}
}

In addition, the callback will be used for the interrupt.(HAL_I2C_SlaveTxCpltCallback()/HAL_I2C_SlaveRxCpltCallback())

void HAL_I2C_AddrCallback(I2C_HandleTypeDef *hi2c, uint8_t TransferDirection, uint16_t AddrMatchCode)
{
  Transfer_Direction = TransferDirection;
  if (Transfer_Direction != 0)
  {
     /*##- Start the transmission process #####################################*/
  /* While the I2C in reception process, user can transmit data through
     "aTxBuffer" buffer */
  if (HAL_I2C_Slave_Seq_Transmit_IT(&hi2c1, (uint8_t *)aTxBuffer, TXBUFFERSIZE, I2C_FIRST_AND_LAST_FRAME) != HAL_OK)

    {
    /* Transfer error in transmission process */
    Error_Handler();
  }

  }
  else
  {

      /*##- Put I2C peripheral in reception process ###########################*/
  if (HAL_I2C_Slave_Seq_Receive_IT(&hi2c1, (uint8_t *)aRxBuffer, RXBUFFERSIZE, I2C_FIRST_AND_LAST_FRAME) != HAL_OK)
    {
    /* Transfer error in reception process */
    Error_Handler();
  }

  }

}

STM32CubeMX(LL) Setting

Here, we do not use HAL because we need to edit the underlying code, and there are two differences in the settings:

  • changing I2C to LL for editing
  • separating the .c/.h files for easier editing.

STM32CubeIDE(LL)

First, enable the interrupt and edit what needs to be executed in the I2C1_IRQHandler(void) function in stm32g0xx_it.c. Basically, interrupt flag judgment will be used, and an example is shown below.

if(LL_I2C_IsActiveFlag_ADDR(I2C1))
	{
		I2C_Start_Flag = 1;
		//printf("S");
		/* Verify the Address Match with the OWN Slave address */
		if(LL_I2C_GetAddressMatchCode(I2C1) == 0xA0)
		{
			//printf("R");
			/* Verify the transfer direction, a read direction, Slave enters transmitter mode */
			if(LL_I2C_GetTransferDirection(I2C1) == LL_I2C_DIRECTION_READ)
			{
				//printf("R");
				/* Clear ADDR flag value in ISR register */
				LL_I2C_ClearFlag_ADDR(I2C1);
				if(I2C_R_W_Flag == I2C_W_Mode)
					I2C_R_W_Flag = I2C_WR_Mode;
				else
					I2C_R_W_Flag = I2C_R_Mode;
			}
			else
			{
				//printf("W");
				/* Clear ADDR flag value in ISR register */
				LL_I2C_ClearFlag_ADDR(I2C1);
				I2C_R_W_Flag = I2C_W_Mode;
				I2C_WriteAddrByte_Counter = 1;
			}
		}
		else
		{
			/* Clear ADDR flag value in ISR register */
			LL_I2C_ClearFlag_ADDR(I2C1);

		}
	}
	/* Check NACK flag value in ISR register */
	else if(LL_I2C_IsActiveFlag_NACK(I2C1))
	{
		//printf("N");
		/* End of Transfer */
		LL_I2C_ClearFlag_NACK(I2C1);
	}
	/* Check TXIS flag value in ISR register */
	else if(LL_I2C_IsActiveFlag_TXIS(I2C1))
	{
		/* Call function Slave Ready to Transmit Callback */
		//printf("d");
		LL_I2C_TransmitData8(I2C1,A0_Ram_Table[A0_Ram_Pointer++]);
	}
	/* Check STOP flag value in ISR register */
	else if(LL_I2C_IsActiveFlag_STOP(I2C1))
	{
		/* Clear STOP flag value in ISR register */
		LL_I2C_ClearFlag_STOP(I2C1);

		/* Check TXE flag value in ISR register */
		if(!LL_I2C_IsActiveFlag_TXE(I2C1))
		{
			/* Flush the TXDR register */
			LL_I2C_ClearFlag_TXE(I2C1);
		}
		I2C_Start_Flag = 0;
		I2C_R_W_Flag = I2C_NONE_Mode;
		//printf("P\n\r");
	}
	else if(LL_I2C_IsActiveFlag_RXNE(I2C1))
	{
		//printf("D");
		if(I2C_WriteAddrByte_Counter == 1)
		{
			A0_Ram_Pointer = LL_I2C_ReceiveData8(I2C1);
			I2C_WriteAddrByte_Counter--;
		}
		else
		{
			A0_Ram_Table[A0_Ram_Pointer++] = LL_I2C_ReceiveData8(I2C1);
		}
	}  
	/* Check TXE flag value in ISR register */
	else if(!LL_I2C_IsActiveFlag_TXE(I2C1))
	{
		//printf("e");	
		/* Do nothing */
		/* This Flag will be set by hardware when the TXDR register is empty */
		/* If needed, use LL_I2C_ClearFlag_TXE() interface to flush the TXDR register  */
	}
	else if(!LL_I2C_IsActiveFlag_BERR(I2C1))
	{
		//printf("e");
		LL_I2C_ClearFlag_BERR(I2C1);
		I2C1_SoftwareReset();
		I2C_Start_Flag = 0;
		I2C_R_W_Flag = I2C_NONE_Mode;
	}
	else if(!LL_I2C_IsActiveFlag_ARLO(I2C1))
	{
		//printf("e");
		LL_I2C_ClearFlag_ARLO(I2C1);
		I2C1_SoftwareReset();
		I2C_Start_Flag = 0;
		I2C_R_W_Flag = I2C_NONE_Mode;
	}
	else if(!LL_I2C_IsActiveFlag_OVR(I2C1))
	{
		//printf("e");
		LL_I2C_ClearFlag_OVR(I2C1);
		I2C1_SoftwareReset();
		I2C_Start_Flag = 0;
		I2C_R_W_Flag = I2C_NONE_Mode;
	}   
	else
	{
		//printf("e");
	}

  /* USER CODE END I2C1_IRQn 0 */
  
  /* USER CODE BEGIN I2C1_IRQn 1 */

  /* USER CODE END I2C1_IRQn 1 */
}

Note

When executing the interrupt program, it is necessary to ensure that the I2C interrupt is turned on, and it is recommended to reset it before use to ensure normal signal transmission, as shown in the following example.”

void Activate_I2C1_IT(void)
{
	LL_I2C_EnableIT_TX(I2C1);
	LL_I2C_EnableIT_RX(I2C1);
	LL_I2C_EnableIT_ADDR(I2C1);
	LL_I2C_EnableIT_NACK(I2C1);
	LL_I2C_EnableIT_ERR(I2C1);
	LL_I2C_EnableIT_STOP(I2C1);
	LL_I2C_Enable(I2C1);
}
void I2C1_SoftwareReset(void)
{
	/* Disable peripheral */
	LL_I2C_Disable(I2C1);

	/* Perform a dummy read to delay the disable of peripheral for minimum
	3 APB clock cycles to perform the software reset functionality */
	*(__IO uint32_t *)(uint32_t)I2C1; 

	/* Enable peripheral */
	LL_I2C_Enable(I2C1);
}

1 thought on “I2C Slave mode on STM32 Introduction”

  1. Pingback: Inter-Integrated Circuit(I2C) - AMS and STM32

Leave a Comment

Your email address will not be published. Required fields are marked *

Shopping Cart