diff --git a/cores/arduino/stm32/stm32yyxx_hal_conf.h b/cores/arduino/stm32/stm32yyxx_hal_conf.h index 191414bac5..0a70baed9b 100644 --- a/cores/arduino/stm32/stm32yyxx_hal_conf.h +++ b/cores/arduino/stm32/stm32yyxx_hal_conf.h @@ -46,6 +46,12 @@ #undef HAL_I2S_MODULE_ENABLED #endif +#if !defined(HAL_I3C_MODULE_DISABLED) + #define HAL_I3C_MODULE_ENABLED +#else + #undef HAL_I3C_MODULE_ENABLED +#endif + #if !defined(HAL_RTC_MODULE_DISABLED) #define HAL_RTC_MODULE_ENABLED #else diff --git a/libraries/CMakeLists.txt b/libraries/CMakeLists.txt index ff0a3b0557..7f947e7235 100644 --- a/libraries/CMakeLists.txt +++ b/libraries/CMakeLists.txt @@ -13,3 +13,4 @@ add_subdirectory(SrcWrapper) add_subdirectory(USBDevice) add_subdirectory(VirtIO) add_subdirectory(Wire) +add_subdirectory(I3C_Controller) diff --git a/libraries/I3C_Controller/CMakeLists.txt b/libraries/I3C_Controller/CMakeLists.txt new file mode 100644 index 0000000000..793cba5a70 --- /dev/null +++ b/libraries/I3C_Controller/CMakeLists.txt @@ -0,0 +1,33 @@ +# v3.21 implemented semantic changes regarding $ +# See https://cmake.org/cmake/help/v3.21/command/target_link_libraries.html#linking-object-libraries-via-target-objects +cmake_minimum_required(VERSION 3.21) + +add_library(I3C_Controller INTERFACE) +add_library(I3C_usage INTERFACE) + +target_include_directories(I3C_usage INTERFACE + src +) + + +target_link_libraries(I3C_usage INTERFACE + base_config +) + +target_link_libraries(I3C_Controller INTERFACE I3C_usage) + + + +add_library(I3C_bin OBJECT EXCLUDE_FROM_ALL + src/I3C_Controller.cpp + ${BUILD_SYSTEM_PATH}/Drivers/STM32H5xx_HAL_Driver/Src/stm32h5xx_util_i3c.c + # ${BUILD_SYSTEM_PATH}/Drivers/STM32U3xx_HAL_Driver/Src/stm32u3xx_util_i3c.c + + +) +target_link_libraries(I3C_bin PUBLIC I3C_usage) + +target_link_libraries(I3C_Controller INTERFACE + I3C_bin + $ +) diff --git a/libraries/I3C_Controller/examples/Entdaa-LPS22DF/Entdaa-LPS22DF.ino b/libraries/I3C_Controller/examples/Entdaa-LPS22DF/Entdaa-LPS22DF.ino new file mode 100644 index 0000000000..7d40e2849e --- /dev/null +++ b/libraries/I3C_Controller/examples/Entdaa-LPS22DF/Entdaa-LPS22DF.ino @@ -0,0 +1,61 @@ +#include "I3C_Controller.h" +#include "lps22df.h" + +#if defined(STM32H5xx) + #define I3C_BUS I3C1Bus + #define I3C_SDA_PIN PB7 + #define I3C_SCL_PIN PB6 +#elif defined(STM32U3xx) + #define I3C_BUS I3C2Bus + #define I3C_SDA_PIN PB14 + #define I3C_SCL_PIN PB13_ALT1 +#else + #error "Unsupported STM32 family" +#endif + +Lps22dfI3cCtx g_lpsCtx; +lps22df_ctx_t g_lpsDrvCtx; +uint8_t g_dynAddr = 0; + +void setup() +{ + Serial.begin(115200); + while (!Serial) {} + Serial.println("\n=== Test ENTDAA begin ==="); + + if (!I3C_BUS.begin(I3C_SDA_PIN, I3C_SCL_PIN, 1000000)) { + Serial.println("I3CBus.begin FAILED"); + while (1) {} + } + Serial.println("I3CBus initialized"); + + // ENTDAA + init LPS22DF driver C + if (!LPS22DF_I3C_EntdaaInit(I3C_BUS, + 0x30, // first DA to use + g_dynAddr, + g_lpsDrvCtx, + g_lpsCtx)) { + Serial.println("LPS22DF ENTDAA init FAILED"); + while (1) {} + } + + Serial.print("LPS22DF ready at DA=0x"); + Serial.println(g_dynAddr, HEX); +} + +void loop() +{ + lps22df_data_t data; + + if (lps22df_data_get(&g_lpsDrvCtx, &data) == LPS22DF_OK) { + Serial.print("P = "); + Serial.print(data.pressure.hpa, 3); + Serial.print(" hPa | T = "); + Serial.print(data.heat.deg_c, 2); + Serial.println(" °C"); + } else { + Serial.println("lps22df_data_get FAILED"); + } + + delay(500); +} diff --git a/libraries/I3C_Controller/examples/Entdaa-LPS22DF/lps22df.cpp b/libraries/I3C_Controller/examples/Entdaa-LPS22DF/lps22df.cpp new file mode 100644 index 0000000000..57ebd6bb12 --- /dev/null +++ b/libraries/I3C_Controller/examples/Entdaa-LPS22DF/lps22df.cpp @@ -0,0 +1,98 @@ +#include "lps22df.h" + +// limit the discovery +static const size_t MAX_I3C_DEVICES = 4; + +// Low-level callbacks for C driver +static int32_t lps_i3c_write(void *handle, uint8_t reg, uint8_t *buf, uint16_t len) +{ + auto *ctx = static_cast(handle); + int rc = ctx->bus->writeRegisterBlock(ctx->dynAddr, reg, buf, len, 1000); + return (rc == 0) ? 0 : -1; +} + +static int32_t lps_i3c_read(void *handle, uint8_t reg, uint8_t *buf, uint16_t len) +{ + auto *ctx = static_cast(handle); + int rc = ctx->bus->readRegisterBlock(ctx->dynAddr, reg, buf, len, 1000); + return (rc == 0) ? 0 : -1; +} + +bool LPS22DF_I3C_EntdaaInit(I3CBus &bus, + uint8_t firstDynAddr, + uint8_t &outDynAddr, + lps22df_ctx_t &outDrvCtx, + Lps22dfI3cCtx &outCtx) +{ + outDynAddr = 0; + + // 1) Optional: global RSTDAA + bus.rstdaaOnce(); + + for (uint8_t addr = 1; addr < 0x7F; ++addr) { + bus.isI2CDeviceReady(addr, 2, 10); + } + + + // 2) ENTDAA: discovery of I3C devices on the bus + I3CDiscoveredDevice devices[MAX_I3C_DEVICES]; + int n = bus.entdaa(devices, MAX_I3C_DEVICES, firstDynAddr, 1000); + if (n < 0) { + Serial.print("ENTDAA FAILED, rc="); + Serial.println(n); + return false; + } + + Serial.print("ENTDAA found "); + Serial.print(n); + Serial.println(" I3C device(s)"); + + // 3) Identify the LPS22DF via WHO_AM_I + for (int i = 0; i < n; ++i) { + uint8_t id = 0; + int rc = bus.readRegister(devices[i].dynAddr, LPS22DF_WHO_AM_I, id, 1000); + Serial.print("WHO_AM_I @DA 0x"); Serial.print(devices[i].dynAddr, HEX); + Serial.print(" rc="); Serial.print(rc); + Serial.print(" id=0x"); Serial.println(id, HEX); + + if (rc == 0 && id == LPS22DF_ID) { + outDynAddr = devices[i].dynAddr; + Serial.print("LPS22DF detected at DA=0x"); + Serial.println(outDynAddr, HEX); + break; + } + } + + if (outDynAddr == 0) { + Serial.println("No LPS22DF detected via ENTDAA"); + return false; + } + + // 4) Prepare contexts for the C driver + outCtx.bus = &bus; + outCtx.dynAddr = outDynAddr; + + outDrvCtx.write_reg = lps_i3c_write; + outDrvCtx.read_reg = lps_i3c_read; + outDrvCtx.handle = &outCtx; + + // 5) Recommended init (BDU + IF_INC etc.) + if (lps22df_init_set(&outDrvCtx, LPS22DF_DRV_RDY) != LPS22DF_OK) { + Serial.println("lps22df_init_set FAILED"); + return false; + } + + // 6) Configure mode (ODR, AVG, LPF) + lps22df_md_t md; + md.odr = lps22df_md_t::LPS22DF_25Hz; + md.avg = lps22df_md_t::LPS22DF_4_AVG; + md.lpf = lps22df_md_t::LPS22DF_LPF_ODR_DIV_4; + + if (lps22df_mode_set(&outDrvCtx, &md) != LPS22DF_OK) { + Serial.println("lps22df_mode_set FAILED"); + return false; + } + + Serial.println("LPS22DF configured via ENTDAA path."); + return true; +} diff --git a/libraries/I3C_Controller/examples/Entdaa-LPS22DF/lps22df.h b/libraries/I3C_Controller/examples/Entdaa-LPS22DF/lps22df.h new file mode 100644 index 0000000000..473b465e30 --- /dev/null +++ b/libraries/I3C_Controller/examples/Entdaa-LPS22DF/lps22df.h @@ -0,0 +1,24 @@ +#pragma once + +#include "I3C_Controller.h" +#include "LPS22DFSensor.h" + + +// Context for LPS22DF in I3C mode (C driver) +struct Lps22dfI3cCtx { + I3CBus *bus; // pointer to the I3C bus used + uint8_t dynAddr; // dynamic address assigned via ENTDAA +}; + +// Initialize LPS22DF via ENTDAA + C driver on dynamic address. +// +// - bus : I3CBus instance (I3C1Bus or I3C2Bus) +// - firstDynAddr : first DA to try +// - outDynAddr : receives the DA that was ultimately assigned +// - outDrvCtx : C driver context lps22df_ctx_t +// - outCtx : backend context (bus + dynAddr) to keep globally +// +// Return: +// true : LPS22DF detected and initialized +// false : ENTDAA, WHO_AM_I or driver init failed +bool LPS22DF_I3C_EntdaaInit(I3CBus &bus, uint8_t firstDynAddr, uint8_t &outDynAddr, lps22df_ctx_t &outDrvCtx, Lps22dfI3cCtx &outCtx); diff --git a/libraries/I3C_Controller/examples/SetDASA-LPS22DF/SetDASA-LPS22DF.ino b/libraries/I3C_Controller/examples/SetDASA-LPS22DF/SetDASA-LPS22DF.ino new file mode 100644 index 0000000000..f2c9eee4d9 --- /dev/null +++ b/libraries/I3C_Controller/examples/SetDASA-LPS22DF/SetDASA-LPS22DF.ino @@ -0,0 +1,70 @@ +#include +#include "I3C_Controller.h" +#include "LPS22DFSensor.h" + +static const uint8_t LPS22DF_STATIC_ADDR_7BIT = 0x5D; +static const uint8_t LPS22DF_DYN_ADDR_7BIT = 0x30; + +#if defined(STM32H5xx) + LPS22DFSensor ps(&I3C1Bus, LPS22DF_STATIC_ADDR_7BIT, LPS22DF_DYN_ADDR_7BIT); + #define I3C_BUS I3C1Bus + #define I3C_SCL PB6 + #define I3C_SDA PB7 +#elif defined(STM32U3xx) + LPS22DFSensor ps(&I3C2Bus, LPS22DF_STATIC_ADDR_7BIT, LPS22DF_DYN_ADDR_7BIT); + #define I3C_BUS I3C2Bus + #define I3C_SCL PB13_ALT1 + #define I3C_SDA PB14 +#endif + +void setup() +{ + Serial.begin(115200); + while (!Serial) {} + Serial.println("\n=== Test SetDASA Begin ==="); + +#if defined(STM32H5xx) + if (!I3C_BUS.begin(I3C_SDA, I3C_SCL, 1000000)) { +#elif defined(STM32U3xx) + if (!I3C_BUS.begin(I3C_SDA, I3C_SCL, 1000000)) { +#endif + + Serial.println("I3C_BUS.begin FAILED"); + while (1) {} + } + + + Serial.println("I3CBus initialized"); + + if (ps.begin() != LPS22DF_OK) { + Serial.println("LPS22DF begin FAILED"); + while (1) {} + } + Serial.println("LPS22DF begin OK"); + + if (ps.Enable() != LPS22DF_OK) { + Serial.println("LPS22DF Enable FAILED"); + while (1) {} + } + Serial.println("LPS22DF Enable OK"); + +} + + +void loop() +{ + float p, t; + + if (ps.GetPressure(&p) == LPS22DF_OK && + ps.GetTemperature(&t) == LPS22DF_OK) { + Serial.print("P = "); + Serial.print(p, 3); + Serial.print(" hPa | T = "); + Serial.print(t, 2); + Serial.println(" °C"); + } else { + Serial.println("Read failed"); + } + + delay(500); +} diff --git a/libraries/I3C_Controller/keywords.txt b/libraries/I3C_Controller/keywords.txt new file mode 100644 index 0000000000..3b83cdf41d --- /dev/null +++ b/libraries/I3C_Controller/keywords.txt @@ -0,0 +1,23 @@ +I3CBus KEYWORD1 +I3CDiscoveredDevice KEYWORD1 +I3CAddressController KEYWORD1 +LPS22DFSensor KEYWORD1 + +begin KEYWORD2 +writeRegister KEYWORD2 +readRegister KEYWORD2 +writeRegisterBlock KEYWORD2 +readRegisterBlock KEYWORD2 +isI2CDeviceReady KEYWORD2 +isI3CDeviceReady KEYWORD2 +rstdaaOnce KEYWORD2 +disecOnce KEYWORD2 +setdasa KEYWORD2 +entdaa KEYWORD2 +setBusFrequency KEYWORD2 + +GetPressure KEYWORD2 +GetTemperature KEYWORD2 +Enable KEYWORD2 +Disable KEYWORD2 +ReadID KEYWORD2 diff --git a/libraries/I3C_Controller/library.properties b/libraries/I3C_Controller/library.properties new file mode 100644 index 0000000000..14c88f0d17 --- /dev/null +++ b/libraries/I3C_Controller/library.properties @@ -0,0 +1,10 @@ +name=STM32 I3C Controller +version=0.1.0 +author=Bahssain Aymane +maintainer=stm32duino +sentence=Arduino-friendly I3C controller for STM32 (H5/U3) with LPS22DF I3C support. +paragraph=Provides a lightweight I3C bus controller class (I3CBus). +category=Communication +url=https://github.com/STMicroelectronics/STM32I3C +architectures=stm32 +depends=STM32duino LPS22DF diff --git a/libraries/I3C_Controller/src/I3C_Controller.cpp b/libraries/I3C_Controller/src/I3C_Controller.cpp new file mode 100644 index 0000000000..ef8cc14397 --- /dev/null +++ b/libraries/I3C_Controller/src/I3C_Controller.cpp @@ -0,0 +1,658 @@ +#include "I3C_Controller.h" +#include + +#if defined(I3C1) + I3CBus I3C1Bus(I3C1); +#endif + +#if defined(I3C2) + I3CBus I3C2Bus(I3C2); +#endif + + +#define COUNTOF(__BUFFER__) (sizeof(__BUFFER__) / sizeof(*(__BUFFER__))) + +// ============================================================================ +// 1. GPIO & clock initialization (AN5879: I3C controller setup) +// ============================================================================ + +bool I3CBus::initGPIO() +{ + if (_sdaPin == NC || _sclPin == NC) { + return false; + } + + // Check that pins are mapped to an I3C instance via PinMap + void *periphSDA = pinmap_peripheral(_sdaPin, PinMap_I3C_SDA); + void *periphSCL = pinmap_peripheral(_sclPin, PinMap_I3C_SCL); + void *periph = pinmap_merge_peripheral(periphSDA, periphSCL); + + if (periph == (void *)NP) { + // Invalid SDA/SCL combination for I3C on this variant + return false; + } + + // Ensure these pins map to the selected instance + PinName sdaInst = pinmap_pin((void *) _instance, PinMap_I3C_SDA); + PinName sclInst = pinmap_pin((void *) _instance, PinMap_I3C_SCL); + (void)sdaInst; + (void)sclInst; + + uint32_t sdaPortIndex = STM_PORT(_sdaPin); + uint32_t sclPortIndex = STM_PORT(_sclPin); + uint32_t sdaPinIndex = STM_PIN(_sdaPin); + uint32_t sclPinIndex = STM_PIN(_sclPin); + + GPIO_TypeDef *sdaPort = get_GPIO_Port(sdaPortIndex); + GPIO_TypeDef *sclPort = get_GPIO_Port(sclPortIndex); + + if (!sdaPort || !sclPort || sdaPort != sclPort) { + // Only same-port SDA/SCL supported here + return false; + } + + uint32_t sdaMask = (1U << sdaPinIndex); + uint32_t sclMask = (1U << sclPinIndex); + uint32_t pinMask = sclMask | sdaMask; + + // Retrieve correct AF for each + uint32_t fnSDA = pinmap_function(_sdaPin, PinMap_I3C_SDA); + uint32_t fnSCL = pinmap_function(_sclPin, PinMap_I3C_SCL); + uint32_t afSDA = STM_PIN_AFNUM(fnSDA); + uint32_t afSCL = STM_PIN_AFNUM(fnSCL); + + if (afSDA != afSCL) { + return false; + } + + // Enable GPIO port clock + switch (sdaPortIndex) { + case 0: __HAL_RCC_GPIOA_CLK_ENABLE(); break; + case 1: __HAL_RCC_GPIOB_CLK_ENABLE(); break; + case 2: __HAL_RCC_GPIOC_CLK_ENABLE(); break; + case 3: __HAL_RCC_GPIOD_CLK_ENABLE(); break; +#if defined(GPIOE) + case 4: __HAL_RCC_GPIOE_CLK_ENABLE(); break; +#endif +#if defined(GPIOF) + case 5: __HAL_RCC_GPIOF_CLK_ENABLE(); break; +#endif +#if defined(GPIOG) + case 6: __HAL_RCC_GPIOG_CLK_ENABLE(); break; +#endif +#if defined(GPIOH) + case 7: __HAL_RCC_GPIOH_CLK_ENABLE(); break; +#endif + default: return false; + } + + GPIO_InitTypeDef GPIO_InitStruct{}; + + // Phase 1: AF with pull-up (to stabilize bus at power-up) + GPIO_InitStruct.Pin = pinMask; + GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; + GPIO_InitStruct.Pull = GPIO_PULLUP; + GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; + GPIO_InitStruct.Alternate = afSDA; + HAL_GPIO_Init(sdaPort, &GPIO_InitStruct); + + HAL_Delay(1); + + // Phase 2: AF no-pull (I3C normal operation) + GPIO_InitStruct.Pull = GPIO_NOPULL; + HAL_GPIO_Init(sdaPort, &GPIO_InitStruct); + + return true; +} + +bool I3CBus::initClocks() +{ + HAL_StatusTypeDef status; + + if (_instance == I3C1) { +#if defined(I3C1) + RCC_PeriphCLKInitTypeDef PeriphClkInit {}; + PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_I3C1; + PeriphClkInit.I3c1ClockSelection = RCC_I3C1CLKSOURCE_PCLK1; + status = HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit); + if (status != HAL_OK) { + return false; + } + + __HAL_RCC_I3C1_CLK_ENABLE(); + + HAL_NVIC_SetPriority(I3C1_EV_IRQn, 0, 0); + HAL_NVIC_EnableIRQ(I3C1_EV_IRQn); + HAL_NVIC_SetPriority(I3C1_ER_IRQn, 0, 0); + HAL_NVIC_EnableIRQ(I3C1_ER_IRQn); +#else + return false; +#endif + } +#if defined(I3C2) + else if (_instance == I3C2) { + RCC_PeriphCLKInitTypeDef PeriphClkInit{}; + PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_I3C2; +#if defined(STM32U3xx) + PeriphClkInit.I3c2ClockSelection = RCC_I3C2CLKSOURCE_PCLK2; +#elif defined(STM32H5xx) + PeriphClkInit.I3c2ClockSelection = RCC_I3C2CLKSOURCE_PCLK3; +#endif + status = HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit); + if (status != HAL_OK) { + return false; + } + + __HAL_RCC_I3C2_CLK_ENABLE(); + + HAL_NVIC_SetPriority(I3C2_EV_IRQn, 0, 0); + HAL_NVIC_EnableIRQ(I3C2_EV_IRQn); + HAL_NVIC_SetPriority(I3C2_ER_IRQn, 0, 0); + HAL_NVIC_EnableIRQ(I3C2_ER_IRQn); + } +#endif + else { + return false; + } + + return true; +} + +// ============================================================================ +// 2. Bus initialization (Arduino-like begin) +// ============================================================================ + +bool I3CBus::begin(uint32_t sda, uint32_t scl, uint32_t freq) +{ + _sdaPin = digitalPinToPinName(sda); + _sclPin = digitalPinToPinName(scl); + return begin(freq); +} + +bool I3CBus::begin(uint32_t freq) +{ + + if (_initialized) { + return true; + } + + if (!initClocks()) { + return false; + } + if (!initGPIO()) { + return false; + } + + std::memset(&_hi3c, 0, sizeof(_hi3c)); + _hi3c.Instance = _instance; + _hi3c.Mode = HAL_I3C_MODE_CONTROLLER; + + if (HAL_I3C_Init(&_hi3c) != HAL_OK) { + return false; + } + + // Configure FIFO thresholds and disable Control/Status FIFOs (private transfers) + I3C_FifoConfTypeDef fifoCfg{}; + fifoCfg.RxFifoThreshold = HAL_I3C_RXFIFO_THRESHOLD_4_4; + fifoCfg.TxFifoThreshold = HAL_I3C_TXFIFO_THRESHOLD_4_4; + fifoCfg.ControlFifo = HAL_I3C_CONTROLFIFO_DISABLE; + fifoCfg.StatusFifo = HAL_I3C_STATUSFIFO_DISABLE; + if (HAL_I3C_SetConfigFifo(&_hi3c, &fifoCfg) != HAL_OK) { + return false; + } + + // Generic controller configuration (no hot-join, no stall) + I3C_CtrlConfTypeDef ctrlCfg{}; + ctrlCfg.DynamicAddr = 0; + ctrlCfg.StallTime = 0x00; + ctrlCfg.HotJoinAllowed = DISABLE; + ctrlCfg.ACKStallState = DISABLE; + ctrlCfg.CCCStallState = DISABLE; + ctrlCfg.TxStallState = DISABLE; + ctrlCfg.RxStallState = DISABLE; + ctrlCfg.HighKeeperSDA = DISABLE; + + if (HAL_I3C_Ctrl_Config(&_hi3c, &ctrlCfg) != HAL_OK) { + return false; + } + + _initialized = true; + + // Apply bus frequency (timing computation) if requested + if (freq == 0) { + freq = 1000000; // default to 1 MHz mixed I3C+I2C + } + + if (setBusFrequency(freq) != 0) { + return false; + } + return true; +} + +// ============================================================================ +// 3. Private transfers +// ============================================================================ + +int I3CBus::writeRegisterBlock(uint8_t devAddr, uint8_t reg, const uint8_t *pData, size_t len, uint32_t timeout) +{ + if (!_initialized || pData == nullptr || len == 0 || len > 31) { + return -1; + } + + I3C_XferTypeDef context{}; + I3C_PrivateTypeDef priv{}; + uint32_t controlBuffer[0xF]; + uint8_t data[32] = {0}; + + // Prefix first byte with register address + data[0] = reg; + memcpy(&data[1], pData, len); + + priv.TargetAddr = devAddr; + priv.TxBuf.pBuffer = data; + priv.TxBuf.Size = static_cast(1 + len); + priv.RxBuf.pBuffer = nullptr; + priv.RxBuf.Size = 0; + priv.Direction = HAL_I3C_DIRECTION_WRITE; + + memset(&context, 0, sizeof(context)); + context.CtrlBuf.pBuffer = controlBuffer; + context.CtrlBuf.Size = 1; + context.TxBuf.pBuffer = data; + context.TxBuf.Size = static_cast(1 + len); + + HAL_StatusTypeDef st = HAL_I3C_AddDescToFrame( + &_hi3c, + nullptr, + &priv, + &context, + context.CtrlBuf.Size, + I3C_PRIVATE_WITH_ARB_RESTART + ); + if (st != HAL_OK) { + return -static_cast(HAL_I3C_GetError(&_hi3c)); + } + + st = HAL_I3C_Ctrl_Transmit(&_hi3c, &context, timeout); + if (st != HAL_OK) { + return -static_cast(HAL_I3C_GetError(&_hi3c)); + } + + return 0; +} + +int I3CBus::readRegisterBlock(uint8_t devAddr, uint8_t reg, uint8_t *pData, size_t len, uint32_t timeout) +{ + if (!_initialized || pData == nullptr || len == 0 || len > 32) { + return -1; + } + + I3C_XferTypeDef context{}; + I3C_PrivateTypeDef priv{}; + uint32_t controlBuffer[0xF]; + uint8_t regBuf[1] = { reg }; + uint8_t data[32] = {0}; + + // 1) Write register address + priv.TargetAddr = devAddr; + priv.TxBuf.pBuffer = regBuf; + priv.TxBuf.Size = 1; + priv.RxBuf.pBuffer = nullptr; + priv.RxBuf.Size = 0; + priv.Direction = HAL_I3C_DIRECTION_WRITE; + + memset(&context, 0, sizeof(context)); + context.CtrlBuf.pBuffer = controlBuffer; + context.CtrlBuf.Size = 1; + context.TxBuf.pBuffer = regBuf; + context.TxBuf.Size = 1; + + HAL_StatusTypeDef st = HAL_I3C_AddDescToFrame( + &_hi3c, + nullptr, + &priv, + &context, + context.CtrlBuf.Size, + I3C_PRIVATE_WITH_ARB_RESTART + ); + if (st != HAL_OK) { + return -static_cast(HAL_I3C_GetError(&_hi3c)); + } + + st = HAL_I3C_Ctrl_Transmit(&_hi3c, &context, timeout); + if (st != HAL_OK) { + return -static_cast(HAL_I3C_GetError(&_hi3c)); + } + + // 2) Read data + priv.TargetAddr = devAddr; + priv.TxBuf.pBuffer = nullptr; + priv.TxBuf.Size = 0; + priv.RxBuf.pBuffer = data; + priv.RxBuf.Size = static_cast(len); + priv.Direction = HAL_I3C_DIRECTION_READ; + + memset(&context, 0, sizeof(context)); + context.CtrlBuf.pBuffer = controlBuffer; + context.CtrlBuf.Size = 1; + context.RxBuf.pBuffer = data; + context.RxBuf.Size = static_cast(len); + + st = HAL_I3C_AddDescToFrame( + &_hi3c, + nullptr, + &priv, + &context, + context.CtrlBuf.Size, + I3C_PRIVATE_WITH_ARB_STOP + ); + if (st != HAL_OK) { + return -static_cast(HAL_I3C_GetError(&_hi3c)); + } + + st = HAL_I3C_Ctrl_Receive(&_hi3c, &context, timeout); + if (st != HAL_OK) { + return -static_cast(HAL_I3C_GetError(&_hi3c)); + } + + memcpy(pData, data, len); + return 0; +} + + +int I3CBus::writeRegister(uint8_t devAddr, uint8_t reg, + uint8_t value, uint32_t timeout) +{ + return writeRegisterBlock(devAddr, reg, &value, 1, timeout); +} + +int I3CBus::readRegister(uint8_t devAddr, uint8_t reg, + uint8_t &value, uint32_t timeout) +{ + return readRegisterBlock(devAddr, reg, &value, 1, timeout); +} + +// ============================================================================ +// 4. Device readiness helpers +// ============================================================================ + +bool I3CBus::isI2CDeviceReady(uint8_t addr, uint32_t trials, uint32_t timeout) +{ + if (!_initialized) { + return false; + } + HAL_StatusTypeDef st = HAL_I3C_Ctrl_IsDeviceI2C_Ready(&_hi3c, addr, trials, timeout); + return (st == HAL_OK); +} + +bool I3CBus::isI3CDeviceReady(uint8_t dynAddr, uint32_t trials, uint32_t timeout) +{ + if (!_initialized) { + return false; + } + HAL_StatusTypeDef st = HAL_I3C_Ctrl_IsDeviceI3C_Ready(&_hi3c, dynAddr, trials, timeout); + return (st == HAL_OK); +} + +// ============================================================================ +// 5. CCC broadcast / direct (RSTDAA, DISEC, SETDASA) +// ============================================================================ + +// RSTDAA (0x06) – broadcast CCC: reset dynamic addresses of all I3C targets. +// Ignores CE2 (broadcast 7'h7E NACK) because this is expected when no target +// has a dynamic address yet. +int I3CBus::rstdaaOnce() +{ + static uint8_t rstdaa_done = 0; + if (rstdaa_done) { + return 0; + } + + uint32_t aControlBuffer[0xFF]; + I3C_XferTypeDef aContextBuffers{}; + uint8_t aTxBuffer[0x0F]; + + I3C_CCCTypeDef aSET_CCC_RST[] = { + { 0x00, 0x06, { nullptr, 0 }, HAL_I3C_DIRECTION_WRITE }, + }; + + aContextBuffers.CtrlBuf.pBuffer = aControlBuffer; + aContextBuffers.CtrlBuf.Size = COUNTOF(aControlBuffer); + aContextBuffers.TxBuf.pBuffer = aTxBuffer; + aContextBuffers.TxBuf.Size = 4; + + HAL_StatusTypeDef st = HAL_I3C_AddDescToFrame( + &_hi3c, + aSET_CCC_RST, + nullptr, + &aContextBuffers, + COUNTOF(aSET_CCC_RST), + I3C_BROADCAST_WITHOUT_DEFBYTE_RESTART + ); + if (st != HAL_OK) { + return -1; + } + + st = HAL_I3C_Ctrl_TransmitCCC(&_hi3c, &aContextBuffers, 1000); + if (st != HAL_OK) { + uint32_t err = HAL_I3C_GetError(&_hi3c); + + // CE2: broadcast 7'h7E NACKed – can be ignored in most scenarios. + if (err & HAL_I3C_ERROR_CE2) { + } else { + return -1; + } + } + + rstdaa_done = 1; + return 0; +} + +// DISEC (0x01) – broadcast CCC: disable some target capabilities (e.g. HotJoin). +// pCCCData: optional data bytes for DISEC (e.g. 0x08 ). +int I3CBus::disecOnce(const uint8_t *pCCCData, uint16_t length) +{ + static uint8_t disec_done = 0; + if (disec_done) { + return 0; + } + + uint32_t aControlBuffer[0xFF]; + I3C_XferTypeDef aContextBuffers{}; + uint8_t aTxBuffer[0x0F]; + + I3C_CCCTypeDef aSET_CCC_DISEC[] = { + { 0x00, 0x01, { const_cast(pCCCData), length }, HAL_I3C_DIRECTION_WRITE }, + }; + + aContextBuffers.CtrlBuf.pBuffer = aControlBuffer; + aContextBuffers.CtrlBuf.Size = COUNTOF(aControlBuffer); + aContextBuffers.TxBuf.pBuffer = aTxBuffer; + aContextBuffers.TxBuf.Size = 4; + + uint32_t option = (length > 0) + ? I3C_BROADCAST_WITH_DEFBYTE_RESTART + : I3C_BROADCAST_WITHOUT_DEFBYTE_RESTART; + + HAL_StatusTypeDef st = HAL_I3C_AddDescToFrame( + &_hi3c, + aSET_CCC_DISEC, + nullptr, + &aContextBuffers, + COUNTOF(aSET_CCC_DISEC), + option + ); + if (st != HAL_OK) { + return -1; + } + + st = HAL_I3C_Ctrl_TransmitCCC(&_hi3c, &aContextBuffers, 1000); + if (st != HAL_OK) { + return -1; + } + + disec_done = 1; + return 0; +} + +// SETDASA (0x87) – direct CCC: assign dynAddr7 to target at staticAddr7. +// staticAddr7: 7-bit static address +// dynAddr7: 7-bit dynamic address; DA byte = (dynAddr7 << 1) on the wire. +int I3CBus::setdasa(uint8_t staticAddr7, uint8_t dynAddr7) +{ + if (!_initialized) { + return -1; + } + + uint32_t aControlBuffer[0xFF]; + I3C_XferTypeDef aContextBuffers{}; + uint8_t aTxBuffer[0x0F]; + + // DA byte: bits [7:1] = dynAddr, bit0 = 0 (as per I3C spec) + uint8_t daByte = static_cast((dynAddr7 & 0x7F) << 1); + + I3C_CCCTypeDef aSET_DASA_Desc[] = { + { static_cast(staticAddr7 & 0x7F), 0x87, { &daByte, 1 }, HAL_I3C_DIRECTION_WRITE }, + }; + + aContextBuffers.CtrlBuf.pBuffer = aControlBuffer; + aContextBuffers.CtrlBuf.Size = COUNTOF(aControlBuffer); + aContextBuffers.TxBuf.pBuffer = aTxBuffer; + aContextBuffers.TxBuf.Size = 4; + + HAL_StatusTypeDef st = HAL_I3C_AddDescToFrame( + &_hi3c, + aSET_DASA_Desc, + nullptr, + &aContextBuffers, + COUNTOF(aSET_DASA_Desc), + I3C_DIRECT_WITHOUT_DEFBYTE_RESTART + ); + if (st != HAL_OK) { + return -1; + } + + st = HAL_I3C_Ctrl_TransmitCCC(&_hi3c, &aContextBuffers, 1000); + if (st != HAL_OK) { + return -1; + } + + return 0; +} + +// ============================================================================ +// 6. Dynamic addressing (ENTDAA + SetDynAddr) +// ============================================================================ + +int I3CBus::entdaa(I3CDiscoveredDevice *devices, size_t maxDevices, uint8_t firstDynAddr, uint32_t timeout) +{ + if (!_initialized || devices == nullptr || maxDevices == 0) { + return -1; + } + + HAL_StatusTypeDef st; + uint64_t entdaaPayload = 0; + size_t count = 0; + + // First dynamic address to assign; default to 0x10 if 0 + uint8_t nextDynAddr = (firstDynAddr != 0) ? firstDynAddr : 0x10; + + do { + entdaaPayload = 0ULL; // HAL will OR into this field + + st = HAL_I3C_Ctrl_DynAddrAssign(&_hi3c, + &entdaaPayload, + I3C_RSTDAA_THEN_ENTDAA, // RSTDAA + ENTDAA + timeout); + if (st == HAL_BUSY) { + // A target responded: entdaaPayload contains BCR+DCR+PID packed + if (count >= maxDevices) { + return -2; // device array too small + } + + uint8_t da = nextDynAddr; + nextDynAddr += 2; // leave a gap of 2 for convenience + + if (HAL_I3C_Ctrl_SetDynAddr(&_hi3c, da) != HAL_OK) { + return -3; + } + + devices[count].dynAddr = da; + devices[count].dcrBcrPid = entdaaPayload; + count++;} + else if (st != HAL_OK) { + uint32_t err = HAL_I3C_GetError(&_hi3c); + Serial.print("ENTDAA: DynAddrAssign failed, st="); + Serial.print((int)st); + Serial.print(" err=hhh0x"); + Serial.println(err, HEX); + return -4; + } + + } while (st == HAL_BUSY); + + // st == HAL_OK: no more I3C targets to discover + return static_cast(count); +} + +// ============================================================================ +// 7. Bus timing / frequency configuration +// ============================================================================ + +int I3CBus::setBusFrequency(uint32_t i3cFreq) +{ +#if !defined(HAL_I3C_MODULE_ENABLED) + (void)i3cFreq; + return 0; +#else + if (!_initialized) { + // Option: could allow pre-init timing configuration, but not supported here + return -1; + } + + if (i3cFreq == 0) { + // 0 = keep current timings + return 0; + } + + I3C_CtrlTimingTypeDef inTiming{}; + LL_I3C_CtrlBusConfTypeDef outCtrl{}; + + // Peripheral clock frequency + uint32_t srcFreq = 0; + + if (_instance == I3C1) { + srcFreq = HAL_RCCEx_GetPeriphCLKFreq(RCC_PERIPHCLK_I3C1); + } +#if defined(I3C2) + else if (_instance == I3C2) { + srcFreq = HAL_RCCEx_GetPeriphCLKFreq(RCC_PERIPHCLK_I3C2); + } +#endif + + if (srcFreq == 0U) { + return -1; + } + + // Fill timing input structure + inTiming.clockSrcFreq = srcFreq; + inTiming.i3cPPFreq = i3cFreq; // I3C push-pull speed + inTiming.i2cODFreq = (i3cFreq >= 1000000U) // I2C open-drain speed + ? 1000000U + : 400000U; + inTiming.dutyCycle = 50; // ~50% duty cycle SDR + inTiming.busType = I3C_MIXED_BUS; // I3C + legacy I2C on same bus + + // Compute TIMINGR0/TIMINGR1 + if (I3C_CtrlTimingComputation(&inTiming, &outCtrl) != SUCCESS) { + return -1; + } + + // Apply these timings to the controller + if (HAL_I3C_Ctrl_BusCharacteristicConfig(&_hi3c, &outCtrl) != HAL_OK) { + return -1; + } + + return 0; +#endif +} diff --git a/libraries/I3C_Controller/src/I3C_Controller.h b/libraries/I3C_Controller/src/I3C_Controller.h new file mode 100644 index 0000000000..a8269adfef --- /dev/null +++ b/libraries/I3C_Controller/src/I3C_Controller.h @@ -0,0 +1,109 @@ +#pragma once + +#include +#if defined(STM32H5xx) + #include "stm32h5xx_util_i3c.h" +#elif defined(STM32U3xx) + #include "stm32u3xx_util_i3c.h" +#else + #error "I3C: unsupported STM32 family" +#endif +#include "stm32_def.h" + +// ----------------------------------------------------------------------------- +// ENTDAA discovered device descriptor (BCR+DCR+PID packed payload) +// ----------------------------------------------------------------------------- +struct I3CDiscoveredDevice { + uint8_t dynAddr; // Assigned dynamic address (7-bit) + uint64_t dcrBcrPid; // Raw ENTDAA payload (BCR + DCR + PID packed) +}; + +// ----------------------------------------------------------------------------- +// I3CBus: lightweight I3C controller wrapper for STM32 (H5/U3) +// +// Features (aligned with AN5879): +// - Bus / clock / GPIO initialization +// - Private transfers (I3C / legacy I2C-like) +// - Legacy I2C device readiness checks on an I3C bus +// - CCC broadcast / direct (RSTDAA, DISEC, SETDASA) +// - Dynamic addressing via ENTDAA + SetDynAddr (HAL_I3C_Ctrl_DynAddrAssign) +// - Bus timing configuration via I3C_CtrlTimingComputation +// ----------------------------------------------------------------------------- +class I3CBus { + public: + explicit I3CBus(I3C_TypeDef *inst = I3C1) : _instance(inst) {} + + bool begin(uint32_t sda, uint32_t scl, uint32_t freq = 1000000); + bool begin(uint32_t freq = 1000000); + + // --------------------------------------------------------------------------- + // Private transfers (I3C / legacy I2C-like) + // --------------------------------------------------------------------------- + // 8-bit register read/write, I2C-style transactions + int writeRegister(uint8_t devAddr, uint8_t reg, uint8_t value, uint32_t timeout = 1000); + int readRegister(uint8_t devAddr, uint8_t reg, uint8_t &value, uint32_t timeout = 1000); + + int writeRegisterBlock(uint8_t devAddr, uint8_t reg, const uint8_t *pData, size_t len, uint32_t timeout = 1000); + int readRegisterBlock(uint8_t devAddr, uint8_t reg, uint8_t *pData, size_t len, uint32_t timeout = 1000); + + // --------------------------------------------------------------------------- + // Device readiness checks + // --------------------------------------------------------------------------- + bool isI2CDeviceReady(uint8_t addr, uint32_t trials = 3, uint32_t timeout = 1000); + bool isI3CDeviceReady(uint8_t dynAddr, uint32_t trials = 3, uint32_t timeout = 1000); + + // --------------------------------------------------------------------------- + // CCC broadcast & direct commands // api naming to be discussed !! + // --------------------------------------------------------------------------- + // Broadcast CCC: + // - RSTDAA (0x06): reset dynamic addresses of all I3C devices + int rstdaaOnce(); + // - DISEC (0x01): disable some target capabilities (HotJoin, events, etc.) + int disecOnce(const uint8_t *pCCCData, uint16_t length); + + // Direct CCC: + // - SETDASA (0x87): assign a dynamic address to a target known by its static address + // staticAddr: 7-bit static address + // dynAddr: 7-bit dynamic address to assign + int setdasa(uint8_t staticAddr, uint8_t dynAddr); + + // --------------------------------------------------------------------------- + // Dynamic addressing (ENTDAA + SetDynAddr) + // --------------------------------------------------------------------------- + // ENTDAA: run dynamic address assignment sequence (0x7E) + // - devices: output array of discovered devices (dynAddr + ENTDAA payload) + // - maxDevices: size of devices[] array + // - firstDynAddr: first dynamic address to use (default 0x10) + // - timeout: HAL polling timeout in ms + int entdaa(I3CDiscoveredDevice *devices, size_t maxDevices, uint8_t firstDynAddr = 0x10, uint32_t timeout = 1000); + + // --------------------------------------------------------------------------- + // Bus timing / frequency (via stm32xx_util_i3c I3C_CtrlTimingComputation) + // --------------------------------------------------------------------------- + // hz: desired I3C push-pull bus frequency (Hz). 0 = keep current timings. + int setBusFrequency(uint32_t hz); + + I3C_HandleTypeDef *halHandle() + { + return &_hi3c; + } + + private: + I3C_HandleTypeDef _hi3c{}; + bool _initialized = false; + I3C_TypeDef *_instance; + + PinName _sdaPin = NC; + PinName _sclPin = NC; + + bool initClocks(); + bool initGPIO(); +}; + +// Global instances +#if defined(I3C1) + extern I3CBus I3C1Bus; +#endif +#if defined(I3C2) + extern I3CBus I3C2Bus; +#endif diff --git a/libraries/SrcWrapper/src/HAL/stm32yyxx_hal.c b/libraries/SrcWrapper/src/HAL/stm32yyxx_hal.c index 7fe8ee664d..0ba29edfe4 100644 --- a/libraries/SrcWrapper/src/HAL/stm32yyxx_hal.c +++ b/libraries/SrcWrapper/src/HAL/stm32yyxx_hal.c @@ -22,8 +22,6 @@ #include "stm32g4xx_hal.c" #elif STM32H5xx #include "stm32h5xx_hal.c" -#elif STM32H5xx - #include "stm32h5xx_hal.c" #elif STM32H7xx #include "stm32h7xx_hal.c" #elif STM32L0xx @@ -40,8 +38,6 @@ #include "stm32u0xx_hal.c" #elif STM32U3xx #include "stm32u3xx_hal.c" -#elif STM32U3xx - #include "stm32u3xx_hal.c" #elif STM32U5xx #include "stm32u5xx_hal.c" #elif STM32WBxx diff --git a/libraries/SrcWrapper/src/HAL/stm32yyxx_util_i3c.c b/libraries/SrcWrapper/src/HAL/stm32yyxx_util_i3c.c new file mode 100644 index 0000000000..f871886170 --- /dev/null +++ b/libraries/SrcWrapper/src/HAL/stm32yyxx_util_i3c.c @@ -0,0 +1,10 @@ +/* HAL raised several warnings, ignore them */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-parameter" + +#ifdef STM32H5xx + #include "stm32h5xx_util_i3c.c" +#elif STM32U3xx + #include "stm32u3xx_util_i3c.c" +#endif +#pragma GCC diagnostic pop