I2C Slave mode

前言

I2C有2種不同模式可以操作使用master 與 slave模式,大部分在操控sensor MCU都是以master模式去做操控,部分EEPROM會需要搭配MCU作為slave操作,但當slave HAL會需要已知長度才能正常work,這邊會特別介紹LL模式修正不特定長度

STM32CubeMX Setting

Stm32cubeMX上的設定Master與Slave基本上沒有太大差異,唯一要注意的是slave address不能為0x00

至於為甚麼Slave address為什麼不能為0目前猜測可能為下
ST I2C slave addr 是用AND GATE,來跟OAR1作比對相同就成立中斷,而設定成0X00會造成永遠不中斷

STM32CubeIDE(HAL)

這邊(HAL)slave會用到函數如下

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做簡單示範一般會搭配中斷做撰寫

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;
}
}

另外針對中段部分會使用到callback(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

這邊因為要編輯底層所以不選用HAL在設定上有以下2個地方的不同

  • 把I2C修改成LL編輯
  • 把.c/.h檔案分離出來方便單獨編輯

STM32CubeIDE(LL)

首先要開啟中斷並在中斷stm32g0xx_it.c後面_it.c內找到void I2C1_IRQHandler(void)編輯中斷要執行的事情,基本上會用到的是中斷flag判讀等等範例如下

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 */
}

注意事項

在執行中斷程式時要注意必須把I2C中斷打開與使用前建議會讓他重新開起reset確保訊號正常,其範例如下

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