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.
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:
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
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 the I3C Controller to communicate with the I3C Targets (LSM6DSO and LPS22HH):
3.1. Objective
Assign a dynamic address using NUCLEO-H503RB 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.
- Select NUCLEO-H503RB board using Board Selector as shown in the figure below.
In case you haven't downloaded the STM32H5Cube 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.
- Set the I3C frequency at 3000 KHz
- Select I3C pure bus (Only I3C devices on the bus)
NVIC settings
In the NVIC settings tab :
- Enable event interrupt and error interrupt in the NVIC tab
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.
3.4. Generate source code and edit main.c
- Click "Ctrl+S" to generate the project.
4. Software settings
4.1. Dynamic addressing using ENTDAA CCC
- Open main.h in Project Explorer / project_name / Inc / main.h.
/* 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 */
Blocking Mode
- Open main.c in Project Explorer / project_name / Src / main.c :
/* 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
Include the Target lib into the project :
/* USER CODE BEGIN Includes */
#include "target.h"
/* USER CODE END Includes */
Add the target descriptor :
/* USER CODE BEGIN PV */
/* Array contain targets descriptor */
TargetDesc_TypeDef *aTargetDesc[1] = \
{
&TargetDesc1, /* DEVICE_ID1 */
};
/* Buffer that contain payload data, mean PID, BCR, DCR */
uint8_t aPayloadBuffer[64*COUNTOF(aTargetDesc)];
/* USER CODE END PV */
/* 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.2. Dynamic addressing using SETDASA CCC (LPS22HH)
- Open main.c in Project Explorer / project_name / Src / main.c :
/* USER CODE BEGIN PTD */
#define Unicast_SETDASA 0x87
#define Brodacast_DISEC 0x01
#define Brodacast_RST 0x06
#define SETNEWDA 0x88
#define LPS22HH_DYNAMIC_ADDR 0x32
/* USER CODE END PTD */
/* USER CODE BEGIN PD */
#define I3C_IDX_FRAME_1 0U /* Index of Frame 1 */
#define I3C_IDX_FRAME_2 1U /* Index of Frame 2 */
/* USER CODE END PD */
/* USER CODE BEGIN PV */
uint8_t aSETDASA_LPS22HH_data[1] = {(LPS22HH_DYNAMIC_ADDR << 1)};
uint8_t aDISEC_data[1] = {0x01};
/* Context buffer related to Frame context, contain different buffer value for a communication */
I3C_XferTypeDef aContextBuffers[2];
I3C_XferTypeDef ContextBuffers[2];
/* Descriptor for direct write SETDASA CCC */
I3C_CCCTypeDef aSET_DASA_LPS22HH[] =
{
/*Target Addr CCC Value CCC data + defbyte pointer CCC size + defbyte Direction */
{0x5D,Unicast_SETDASA,{aSETDASA_LPS22HH_data,1},HAL_I3C_DIRECTION_WRITE},
};
/* Descriptor for direct write DISEC CCC */
I3C_CCCTypeDef aSET_CCC_DISEC[] =
{
{0x5D,Brodacast_DISEC, {aDISEC_data,1},LL_I3C_DIRECTION_WRITE},
};
/* Descriptor for direct write RST CCC */
I3C_CCCTypeDef aSET_CCC_RST[] =
{
{0x5D,Brodacast_RST, {NULL,0},LL_I3C_DIRECTION_WRITE},
};
/* USER CODE END PV */
/* USER CODE BEGIN 2 */
/* Send a DISEC to disable target HotJoin */
aContextBuffers[I3C_IDX_FRAME_1].CtrlBuf.pBuffer = aControlBuffer;
aContextBuffers[I3C_IDX_FRAME_1].CtrlBuf.Size = COUNTOF(aControlBuffer);
aContextBuffers[I3C_IDX_FRAME_1].TxBuf.pBuffer = aTxBuffer;
aContextBuffers[I3C_IDX_FRAME_1].TxBuf.Size = 4;
/* Add context buffer Set CCC frame in Frame context */
if (HAL_I3C_AddDescToFrame(&hi3c1,
aSET_CCC_DISEC,
NULL,
aContextBuffers,
COUNTOF(aSET_CCC_DISEC),
I3C_BROADCAST_WITHOUT_DEFBYTE_RESTART) != HAL_OK)
{
Error_Handler();
}
if (HAL_I3C_Ctrl_TransmitCCC(&hi3c1, aContextBuffers, 1000) != HAL_OK)
{
Error_Handler();
}
while (HAL_I3C_GetState(&hi3c1) != HAL_I3C_STATE_READY)
{
}
/* Send a RSTDAA to reset all the dynamic addresses of the targets */
aContextBuffers[I3C_IDX_FRAME_1].CtrlBuf.pBuffer = aControlBuffer;
aContextBuffers[I3C_IDX_FRAME_1].CtrlBuf.Size = COUNTOF(aControlBuffer);
aContextBuffers[I3C_IDX_FRAME_1].TxBuf.pBuffer = aTxBuffer;
aContextBuffers[I3C_IDX_FRAME_1].TxBuf.Size = 4;
/*Add context buffer Set CCC frame in Frame context */
if (HAL_I3C_AddDescToFrame(&hi3c1,
aSET_CCC_RST,
NULL,
aContextBuffers,
COUNTOF(aSET_CCC_RST),
I3C_BROADCAST_WITHOUT_DEFBYTE_RESTART) != HAL_OK)
{
Error_Handler();
}
if (HAL_I3C_Ctrl_TransmitCCC(&hi3c1, aContextBuffers, 1000) != HAL_OK)
{
Error_Handler();
}
while (HAL_I3C_GetState(&hi3c1) != HAL_I3C_STATE_READY)
{
}
/* Send a SETDASA to set the dynamic on LPS22HH using his static address */
aContextBuffers[I3C_IDX_FRAME_1].CtrlBuf.pBuffer = aControlBuffer;
aContextBuffers[I3C_IDX_FRAME_1].CtrlBuf.Size = COUNTOF(aControlBuffer);
aContextBuffers[I3C_IDX_FRAME_1].TxBuf.pBuffer = aTxBuffer;
aContextBuffers[I3C_IDX_FRAME_1].TxBuf.Size = 4;
/* Add context buffer Set CCC frame in Frame context */
if (HAL_I3C_AddDescToFrame(&hi3c1,
aSET_DASA_LPS22HH,
NULL,
&aContextBuffers[I3C_IDX_FRAME_1],
1,
I3C_DIRECT_WITHOUT_DEFBYTE_RESTART) != HAL_OK)
{
Error_Handler();
}
if (HAL_I3C_Ctrl_TransmitCCC_IT(&hi3c1, &aContextBuffers[I3C_IDX_FRAME_1]) != HAL_OK)
{
Error_Handler();
}
while (HAL_I3C_GetState(&hi3c1) != HAL_I3C_STATE_READY)
{
}
/* After a dynamic address has been assigned, the sensor is recognized as an I3C device */
/* Check if the LPS22HH sensor is ready to communicate in I3C */
if (HAL_I3C_Ctrl_IsDeviceI3C_Ready(&hi3c1, LPS22HH_DYNAMIC_ADDR, 300, 1000) != HAL_OK)
{
Error_Handler();
}
/* USER CODE END 2 */
4.3. Compile and flash
5. References