Getting started with I3C

Revision as of 17:22, 28 August 2023 by Registered User (→‎Dynamic addressing using ENTDAA CCC)
Under construction.png Coming soon

This article explains what the I3C is and It describes how to set up a controller I3C: STM32H5 microcontroller and I3C Target: shield board IKS01A3 (LSM6DSO accelerometer/gyroscope and LPS22HH barometer)..

1. What is inter-integrated circuit (I3C)?

MIPI I3C is a serial communication interface specification that improves upon the features, performance, and power use of I²C, while maintaining backward compatibility for most devices. An I3C bus Controller device drives the push-pull SCL line at the communication bus speed, up to 12.5 MHz.

  • SDA (Serial Data) is the line on which master and slave send or receive the information (sequence of bits).
  • SCL (Serial Clock) is the clock-dedicated line for data flow synchronization.

File:busconfig.png

The main purpose of MIPI I3C is threefold: 1. To standardize communication inside embedded systems with a reconfigurable bus, 2. To reduce the number of physical pins used, 3. To support low-power, high-speed data transfer, up to 33 Mbps.

1.1. I3C Bus Configuration

I3C bus can be configured with multiple devices. There are four main types of devices: • I3C main controller
• I3C secondary controller
• I3C target
• Legacy I2C target

1.2. Features

The I3C SDR-only peripheral supports different modes and operations of various message types: • Broadcast and direct common command code CCC messages to communicate with multiple devices or a specific one. • Dynamic addressing: assigns a dynamic address unlike I2C, which has a static address. • Private read/write transfers. • Legacy I2C messages: the I3C controller can communicate with I2C devices on the I3C bus. • In Band interrupt IBI: the target device, which is connected on the bus can send an interrupt to the controller over the two-wire (SCL / SDA) • Hot-Join request: the target can join the I3C bus after the initialization. • Controller role request

1.3. I2C vs. I3C: What Are the Differences?

The I3C SDR mode allows only for legacy I2C target devices to coexist with I3C devices on the same I3C bus. Note: The i3c bus does not support I2C controller devices. The following table compares I2C versus I3C interface types and mentions difference between I2C and I3C:

create I2CvsI3C.PNG

2. Setup & demo examples

2.1. Hardware prerequisites

• 1x STM32 Nucleo development board (NUCLEO-H503RB)

• 1x Motion MEMS and environmental sensor expansion board (X-NUCLEO-IKS01A3):

The X-NUCLEO-IKS01A3 is compatible with the Arduino UNO R3 connector layout and features the LSM6DSO 3-axis accelerometer + 3-axis gyroscope, the LIS2MDL 3-axis magnetometer, the LIS2DW12 3-axis accelerometer, the HTS221 humidity and temperature sensor, the LPS22HH pressure sensor, and the STTS751 temperature sensor. The X-NUCLEO-IKS01A3 interfaces with the STM32 microcontroller via the I2C/I3C pin, and it is possible to change the default I2C/I3C port.

X-NUCLEO-IKS01A3 plugged on an STM32 Nucleo board

create plugged.PNG

Info white.png Information
The X-NUCLEO-IKS01A3 must be connected on the matching pins of any STM32 Nucleo board with the Arduino UNO R3 connector.

2.2. Examples

Example 1: Assign a dynamic address to the LSM6DSO using ENTDAA CCC. (Keep only JP2).

Example 2: Assign a dynamic address to the LPS22HH using SETDASA CCC. (Keep only JP4).

3. Configure I3C Controller to communicate with I3C Targets (LSM6DSO and LPS22HH):

3.1. Objective

Assign a dynamic address using NUCLEO-STM32H503 as I3C controller and LSM6DSO & LPS22HH as Targets with two different modes.

3.2. Create a project in STM32CubeIDE

  • File > New > STM32 Project in main panel.

create STM32CubeIDE project.png

This example uses the NUCLEO-H503RB board.

  • Select NUCLEO-h503rb board using Board Selector as shown in the figure below.

board.png

In case you haven't downloaded the STM32H5 Cube library, it will be downloaded automatically. This however may take some time.

  • Save the project.

3.3. Configure I3C

Open the STM32CubeMX project from the workspace

  • Under Connectivity, select I3C1
  • Select PB6: SCL and PB7: SDA for I3C1
  • Select Controller mode.

cntrl.PNG

  • Set the I3C frequency at 3000 KHz
  • Select I3C pure bus (Only I3C devices on the bus)

i3c1.png

NVIC settings

In the NVIC settings tab :

  • Enable event interrupt and error interrupt in the NVIC tab

nvic1.png

Clock configuration

Select the system Clock at 250MHz :

  • Select HSI from PLL1 Source Mux
  • Select PLLCLK from System Clock Mux
  • Set HCLK at 250MHz.
  • Select PCLK from the I3C1 Clock Mux
  • PLLM = 4, PLLN = 31, PLLP = 2, PLLQ = 2, PLLR = 2, APB1 Prescaler =1.

clock.png

3.4. Generate source code and edit main.c

  • Click "Ctrl+S" to generate the project.

Generate project.png

4. Software settings

4.1. Dynamic addressing using ENTDAA CCC

  • Open main.h in Project Explorer / project_name / Inc / main.h.
Info white.png Information
Insert your code between /* USER CODE BEGIN ET */ and /* USER CODE END ET */ tags
/* USER CODE BEGIN ET */
typedef struct {
  char *        TARGET_NAME;          /*!< Marketing Target reference */
  uint32_t      TARGET_ID;            /*!< Target Identifier on the Bus */
  uint64_t      TARGET_BCR_DCR_PID;   /*!< Concatenation value of PID, BCR and DCR of the target */
  uint8_t       STATIC_ADDR;          /*!< Static Address of the target, value found in the datasheet of the device */
  uint8_t       DYNAMIC_ADDR;         /*!< Dynamic Address of the target preset by software/application */
} TargetDesc_TypeDef;

/* USER CODE END ET */

Insert the following line also :

/* USER CODE BEGIN Private defines */
/* Define Target Identifier */
#define DEVICE_ID1      0U
  • Create target.h in Project Explorer / project_name / Inc / target.h  :
/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __STM32_I3C_DESC_TARGET1_H
#define __STM32_I3C_DESC_TARGET1_H

/* Includes ------------------------------------------------------------------*/
/* Exported types ------------------------------------------------------------*/
/* Exported constants --------------------------------------------------------*/
#define TARGET1_DYN_ADDR        0x32

/********************/
/* Target Descriptor */
/********************/
TargetDesc_TypeDef TargetDesc1 =
{
  "TARGET_ID1",
  DEVICE_ID1,
  0x0000000000000000,
  0x00,
  TARGET1_DYN_ADDR,
};

#endif /* __STM32_I3C_DESC_TARGET1_H */
  • Open main.c in Project Explorer / project_name / Src / main.c  :
Info white.png Information
Insert your code between /* USER CODE BEGIN 2 */ and /* USER CODE END 2 */ tags
  • Blocking Mode
/* USER CODE BEGIN 2 */
/* Buffer that contain payload data, mean PID, BCR, DCR */
uint64_t payload [64];
uint8_t dynamic_adr = 0x32;  
 HAL_StatusTypeDef status = HAL_OK;
     
/* Assign dynamic address processus */
      do
      {
         status = HAL_I3C_Ctrl_DynAddrAssign(&hi3c1, &payload[1], I3C_ONLY_ENTDAA, 5000);
        if (status == HAL_BUSY)
        {
          HAL_I3C_Ctrl_SetDynAddr(&hi3c1, dynamic_adr);
        }
          TargetDesc1.TARGET_BCR_DCR_PID = (uint64_t) payload;  
      } while (status == HAL_BUSY);

/* USER CODE END 2 */


  • Interrupt Mode
/* USER CODE BEGIN 2 */
  /* Assign dynamic address processus ## Initiate a RSTDAA before a ENTDAA procedure ##*/
  if (HAL_I3C_Ctrl_DynAddrAssign_IT(&hi3c1, I3C_ONLY_ENTDAA  ) != HAL_OK)
  {
    Error_Handler();
  }
/* USER CODE END 2 */

Callback

/* USER CODE BEGIN 4 */
void HAL_I3C_TgtReqDynamicAddrCallback(I3C_HandleTypeDef *hi3c, uint64_t targetPayload)
{
  TargetDesc1.TARGET_BCR_DCR_PID = targetPayload;
  HAL_I3C_Ctrl_SetDynAddr(hi3c, TargetDesc1.DYNAMIC_ADDR);
}
void HAL_I3C_CtrlDAACpltCallback(I3C_HandleTypeDef *hi3c)
{ 
  HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET);
}
/* USER CODE END 4 */

4.1.1. On SLAVE Board

  • Open main.c in Project Explorer / myproject / Src / main.c.
Info white.png Information
Insert your code between /* USER CODE BEGIN PV */ and /* USER CODE END PV */ tags
/* USER CODE BEGIN PV */
/* Private variables */
uint8_t RX_Buffer [1] ; // DATA to receive
/* USER CODE END PV */
Info white.png Information
Insert your code between /* USER CODE BEGIN 2 */ and /* USER CODE END 2 */ tags
  • Blocking Mode
/* USER CODE BEGIN 2 */
  HAL_I2C_Slave_Receive(&hi2c1 ,(uint8_t *)RX_Buffer, 1,1000); //Receiving in Blocking mode
  HAL_Delay(100);
/* USER CODE END 2 */
  • Interrupt Mode
/* USER CODE BEGIN 2 */
  HAL_I2C_Slave_Receive_IT(&hi2c1 ,(uint8_t *)RX_Buffer, 1); //Receiving in Interrupt mode
  HAL_Delay(100);
/* USER CODE END 2 */
  • DMA Mode
/* USER CODE BEGIN 2 */
  HAL_I2C_Slave_Receive_DMA(&hi2c1 ,(uint8_t *)RX_Buffer, 1); //Receiving in DMA mode
  HAL_Delay(100);
/* USER CODE END 2 */

4.2. Compile and flash

  • Click on Build button Built.png
  • Click on Debug button (to run step by step) Debug.png
  • Or on Run button (to execute) Run.png

4.3. Project workspace

The following table shows the connection between 2 STM32 boards for using I2C bus. It requires only three wires.

NUCLEO-L476RG MASTER NUCLEO-L476RG SLAVE Pin description
PB8 PB8 SCL
PB9 PB9 SDA
GND GND Ground

Master-Slave Nucleo.png