|
| 1 | +# I3C |
| 2 | + |
| 3 | +## Introduction |
| 4 | + |
| 5 | +The I3C library gives access to the I3C peripheral (MIPI I3C) of the STM32 |
| 6 | +series that provide it (the core enables it for STM32C5, STM32H5 and STM32U3). The |
| 7 | +peripheral can work as a **controller** or as a **target**, on a pure I3C bus or |
| 8 | +on a mixed bus with legacy I2C devices. |
| 9 | + |
| 10 | +The library defines the `I3CBus` class and a predefined instance named `I3C`. |
| 11 | +Everything is only available when the device has an I3C peripheral |
| 12 | +(`I3C1_BASE` or `I3C2_BASE`). |
| 13 | + |
| 14 | +```Arduino |
| 15 | +#include <I3C.h> |
| 16 | +``` |
| 17 | + |
| 18 | +Conventions: |
| 19 | + |
| 20 | +* Addresses are 7-bit addresses. |
| 21 | +* Timeouts are in milliseconds (default: 1000). |
| 22 | +* Transfer functions return `0` on success and a negative value on error: `-1` |
| 23 | + if the arguments are invalid or the bus is not initialized in the expected |
| 24 | + role, otherwise the opposite of the HAL error code. |
| 25 | + |
| 26 | +The default pins are `I3C_SDA` and `I3C_SCL`. SDA and SCL must belong to the |
| 27 | +same I3C peripheral. |
| 28 | + |
| 29 | +## Controller |
| 30 | + |
| 31 | +### Initialization |
| 32 | + |
| 33 | +```Arduino |
| 34 | +#include <I3C.h> |
| 35 | +
|
| 36 | +I3CDiscoveredDevice devices[8]; |
| 37 | +
|
| 38 | +void setup() { |
| 39 | + if (!I3C.begin(I3C_SDA, I3C_SCL, 1000000U)) { |
| 40 | + while (1) {} |
| 41 | + } |
| 42 | +
|
| 43 | + size_t found = 0; |
| 44 | + I3C.discover(devices, 8, &found); // assign dynamic addresses (DAA) |
| 45 | +
|
| 46 | + uint8_t value; |
| 47 | + for (size_t i = 0; i < found; i++) { |
| 48 | + I3C.readReg(devices[i].dynAddr, 0x0F, value); |
| 49 | + } |
| 50 | +} |
| 51 | +``` |
| 52 | + |
| 53 | +| Method | Description | |
| 54 | +| ------ | ----------- | |
| 55 | +| `bool begin(uint32_t freq = 1000000, I3CBusType type = I3CBusType::Pure, uint32_t mixedOdHz = 1000000)` | Starts the controller on the default pins. `freq` is the push-pull frequency in Hz. | |
| 56 | +| `bool begin(pin_size_t sda, pin_size_t scl, uint32_t freq = 1000000, I3CBusType type = I3CBusType::Pure, uint32_t mixedOdHz = 1000000)` | Starts the controller on the given pins. | |
| 57 | +| `bool begin(..., const I3CControllerConfig &ctrlCfg)` | Same as above with a controller configuration (FIFO thresholds, stall options, ...). Both pin variants exist. | |
| 58 | +| `bool setClock(uint32_t hz)` | Changes the push-pull frequency. | |
| 59 | +| `void setBusType(I3CBusType type)` / `I3CBusType getBusType()` | Sets or gets the bus type for the next timing computation: `I3CBusType::Pure` or `I3CBusType::Mixed`. | |
| 60 | +| `void setMixedBusOpenDrainFrequency(uint32_t hz)` / `uint32_t getMixedBusOpenDrainFrequency()` | Sets or gets the open-drain frequency of a mixed bus. | |
| 61 | +| `I3CRole getRole()` | `I3CRole::None`, `I3CRole::Controller` or `I3CRole::Target`. | |
| 62 | +| `bool isController()` / `bool isTarget()` | Role of the instance. | |
| 63 | +| `bool flushAllFifos()` | Flushes all the FIFOs of the peripheral. | |
| 64 | +| `void end()` | Disables the peripheral, its pins and its clock. | |
| 65 | + |
| 66 | +### Transfers to I3C targets |
| 67 | + |
| 68 | +Private SDR transfers using the dynamic address of the target. |
| 69 | + |
| 70 | +| Method | Description | |
| 71 | +| ------ | ----------- | |
| 72 | +| `int write(uint8_t dynAddr, const uint8_t *buf, size_t len, uint32_t timeout = 1000)` | Writes `len` bytes (`len` must not be 0). | |
| 73 | +| `int read(uint8_t dynAddr, uint8_t *buf, size_t len, uint32_t timeout = 1000)` | Reads `len` bytes. | |
| 74 | +| `int writeReg(uint8_t dynAddr, uint8_t reg, uint8_t value, uint32_t timeout = 1000)` | Writes a register. | |
| 75 | +| `int readReg(uint8_t dynAddr, uint8_t reg, uint8_t &value, uint32_t timeout = 1000)` | Reads a register. | |
| 76 | +| `int writeRegBuffer(uint8_t dynAddr, uint8_t reg, const uint8_t *pData, size_t len, uint32_t timeout = 1000)` | Writes consecutive registers. | |
| 77 | +| `int readRegBuffer(uint8_t dynAddr, uint8_t reg, uint8_t *pData, size_t len, uint32_t timeout = 1000)` | Reads consecutive registers. | |
| 78 | + |
| 79 | +### Unified transfers |
| 80 | + |
| 81 | +The same functions exist with an extra `I3CTransferType type` parameter before |
| 82 | +the timeout, to select the transport at run time: `I3CTransferType::I3C` |
| 83 | +(`addr` is the dynamic address) or `I3CTransferType::I2C` (`addr` is the static |
| 84 | +address). This allows a driver to talk to a device whichever the bus it is on. |
| 85 | + |
| 86 | +### Legacy I2C devices (mixed bus) |
| 87 | + |
| 88 | +Available only in controller mode, when the bus type is `I3CBusType::Mixed`. |
| 89 | +Static addresses range from 0x08 to 0x77. |
| 90 | + |
| 91 | +| Method | Description | |
| 92 | +| ------ | ----------- | |
| 93 | +| `int i2cWrite(uint8_t staticAddr, const uint8_t *buf, size_t len, uint32_t timeout = 1000)` | Writes data. | |
| 94 | +| `int i2cRead(uint8_t staticAddr, uint8_t *buf, size_t len, uint32_t timeout = 1000)` | Reads data. | |
| 95 | +| `int i2cWriteRead(uint8_t staticAddr, const uint8_t *txBuf, size_t txLen, uint8_t *rxBuf, size_t rxLen, uint32_t timeout = 1000)` | Writes then reads with a repeated start. | |
| 96 | +| `int i2cWriteReg(...)` / `int i2cReadReg(...)` | Writes or reads one register. | |
| 97 | +| `int i2cWriteRegBuffer(...)` / `int i2cReadRegBuffer(...)` | Writes or reads consecutive registers. | |
| 98 | +| `int scanI2CDevices(uint8_t *addrs, size_t maxAddrs, size_t *foundCount, uint32_t trials = 2, uint32_t timeout = 10)` | Scans the legacy I2C devices and marks their addresses as used. | |
| 99 | + |
| 100 | +### Device readiness |
| 101 | + |
| 102 | +| Method | Description | |
| 103 | +| ------ | ----------- | |
| 104 | +| `bool isI3CDeviceReady(uint8_t dynAddr, uint32_t trials = 3, uint32_t timeout = 10)` | Checks that an I3C device acknowledges its dynamic address. | |
| 105 | +| `bool isI2CDeviceReady(uint8_t staticAddr, uint32_t trials = 3, uint32_t timeout = 10)` | Checks that an I2C device acknowledges its address. | |
| 106 | + |
| 107 | +### Discovery and dynamic addresses |
| 108 | + |
| 109 | +| Method | Description | |
| 110 | +| ------ | ----------- | |
| 111 | +| `int discover(I3CDiscoveredDevice *discovered, size_t maxDiscovered, size_t *outCount, uint8_t firstDynAddr = 0x10)` | Resets the dynamic addresses, then assigns them to all the devices with the DAA (ENTDAA). | |
| 112 | +| `int discover(const I3CKnownDevice *knownDevices, size_t numKnownDevices, I3CDiscoveredDevice *discovered, size_t maxDiscovered, size_t *outCount, bool discoverUnknown = true, uint8_t firstDynAddr = 0x10)` | Assigns the known devices (SETDASA), then optionally discovers the unknown ones. | |
| 113 | +| `int discover(const I3CBusBeginConfig &cfg, const I3CKnownDevice *knownDevices, size_t numKnownDevices, I3CDiscoveredDevice *discovered, size_t maxDiscovered, size_t *outCount)` | Runs the full initialization sequence selected by `cfg`. | |
| 114 | +| `int assignDynamicAddresses(I3CDiscoveredDevice *devices, size_t maxDevices, uint8_t firstDynAddr = 0x10, uint32_t timeout = 1000)` | Runs the DAA only. Returns the number of devices found, or a negative value on error. | |
| 115 | + |
| 116 | +`discover()` returns `0` on success and stores the number of devices in `outCount`. |
| 117 | + |
| 118 | +Descriptions used by these functions: |
| 119 | + |
| 120 | +* `I3CDiscoveredDevice`: `dynAddr`, `staticAddr`, `bcr`, `dcr`, `pid` (48 bits), |
| 121 | + and the flags `fromDaa`, `assignedBySetdasa` and `matchedKnown`. |
| 122 | +* `I3CKnownDevice`: `pid`, `staticAddr`, `preferredDynAddr`, `supportsSetaasa` |
| 123 | + and `present`. |
| 124 | +* `I3CBusBeginConfig`: selects the steps of the sequence (`doRstact`, |
| 125 | + `doRstdaa`, `disableEvents`, `doSetdasa`, `doDaa`, `retryDaaOnce`, |
| 126 | + `allowHotJoinAfterInit`, `firstAutoDynAddr`, `discoverUnknownAfterKnown`). |
| 127 | + |
| 128 | +### Common command codes (CCC) |
| 129 | + |
| 130 | +| Method | Description | |
| 131 | +| ------ | ----------- | |
| 132 | +| `bool resetDynamicAddresses()` | RSTDAA broadcast. | |
| 133 | +| `bool disableEvents(const uint8_t *pCCCData, uint16_t length)` | DISEC broadcast. | |
| 134 | +| `bool assignAllStaticAsDynamic()` | SETAASA broadcast. | |
| 135 | +| `bool assignDynamicAddress(uint8_t staticAddr, uint8_t dynAddr)` | SETDASA direct command. | |
| 136 | +| `bool getbcr(uint8_t dynAddr, uint8_t &bcr, uint32_t timeout = 1000)` | GETBCR. | |
| 137 | +| `bool getdcr(uint8_t dynAddr, uint8_t &dcr, uint32_t timeout = 1000)` | GETDCR. | |
| 138 | +| `bool getpid(uint8_t dynAddr, uint64_t &pid, uint32_t timeout = 1000)` | GETPID. | |
| 139 | +| `bool setEvents(uint8_t dynAddr, bool enable, uint8_t events, uint32_t timeout = 1000)` | ENEC/DISEC direct command. `events`: `0x01` In-Band Interrupt, `0x02` controller role request, `0x08` Hot-Join. | |
| 140 | + |
| 141 | +### In-Band Interrupts (IBI) |
| 142 | + |
| 143 | +| Method | Description | |
| 144 | +| ------ | ----------- | |
| 145 | +| `int configureControllerDevice(uint8_t deviceIndex, uint8_t dynAddr, bool ibiAck, bool ibiPayload, bool ctrlRoleReqAck = false, bool stopTransfer = false)` | Configures how the controller handles the requests of a target. `deviceIndex` is from 1 to 4. | |
| 146 | +| `int enableIbi(uint8_t deviceIndex, uint8_t dynAddr, bool withPayload, bool stopTransfer = false, uint32_t timeout = 1000)` | Configures the controller and enables the IBI event on the target. | |
| 147 | +| `int enableControllerEvents(uint32_t interruptMask = LL_I3C_IER_IBIIE)` / `int disableControllerEvents(...)` | Enables or disables the controller event notifications. | |
| 148 | +| `bool hasIbi()` | `true` if an IBI was received and not read yet. | |
| 149 | +| `bool readIbi(I3CControllerIbiInfo &out)` | Reads and clears the last IBI. | |
| 150 | +| `bool peekIbi(I3CControllerIbiInfo &out)` | Reads the last IBI without clearing it. | |
| 151 | +| `void onIbi(I3CIbiCallback cb, void *user = nullptr)` | Registers a callback, `NULL` to unregister. | |
| 152 | + |
| 153 | +`I3CControllerIbiInfo` contains `pending`, `eventId`, `sourceDa`, `payloadSize`, |
| 154 | +`payloadRaw` and `timestampMs`. |
| 155 | + |
| 156 | +> [!NOTE] |
| 157 | +> The callback registered with `onIbi()` is called from the interrupt context. |
| 158 | +
|
| 159 | +## Target |
| 160 | + |
| 161 | +```Arduino |
| 162 | +#include <I3C.h> |
| 163 | +
|
| 164 | +uint8_t rxBuf[16]; |
| 165 | +
|
| 166 | +void setup() { |
| 167 | + I3CTargetConfig cfg{}; |
| 168 | + cfg.identifier = 0x62; // DCR |
| 169 | + cfg.mipiIdentifier = 0x1; |
| 170 | +
|
| 171 | + if (!I3C.beginTarget(I3C_SDA, I3C_SCL, cfg)) { |
| 172 | + while (1) {} |
| 173 | + } |
| 174 | +
|
| 175 | + while (!I3C.hasAddress()) { // wait for the controller to assign an address |
| 176 | + delay(10); |
| 177 | + } |
| 178 | +
|
| 179 | + I3C.receive(rxBuf, sizeof(rxBuf), 10000); |
| 180 | +} |
| 181 | +``` |
| 182 | + |
| 183 | +| Method | Description | |
| 184 | +| ------ | ----------- | |
| 185 | +| `bool beginTarget(const I3CTargetConfig &cfg)` | Starts the target on the default pins. | |
| 186 | +| `bool beginTarget(pin_size_t sda, pin_size_t scl, const I3CTargetConfig &cfg)` | Starts the target on the given pins. | |
| 187 | +| `int configureTarget(const I3CTargetConfig &cfg)` | Applies a target configuration. | |
| 188 | +| `bool hasAddress()` / `uint8_t address()` | Tells whether the controller assigned a dynamic address, and returns it (`0` if none). | |
| 189 | +| `int send(uint8_t *buf, uint16_t len, uint32_t timeout = 1000)` | Sends data to the controller. | |
| 190 | +| `int receive(uint8_t *buf, uint16_t len, uint32_t timeout = 1000)` | Receives data from the controller. | |
| 191 | +| `int sendIbi(const uint8_t *payload, uint8_t payloadSize, uint32_t timeout = 1000)` | Requests an IBI. | |
| 192 | +| `int enableTargetEvents(const I3CTargetEventConfig &cfg, uint32_t interruptMask)` / `int disableTargetEvents(uint32_t interruptMask)` | Enables or disables the notification of the target events. | |
| 193 | +| `bool hasTargetEvent()` | `true` if an event was notified and not read yet. | |
| 194 | +| `bool readTargetEvent(uint32_t &eventId)` | Reads and clears the last event. | |
| 195 | +| `bool getTargetCccInfo(uint32_t eventId, I3CTargetCccInfo &out)` | Gets the content of an ENEC/DISEC CCC received by the target. | |
| 196 | + |
| 197 | +`I3CTargetConfig` sets, among others, the DCR (`identifier`), the MIPI instance |
| 198 | +ID (`mipiIdentifier`), the requests the target can raise (`hotJoinRequest`, |
| 199 | +`ibiRequest`, `ibiPayload`, `ctrlRoleRequest`) and the maximum read and write |
| 200 | +sizes. |
| 201 | + |
| 202 | +## Examples |
| 203 | + |
| 204 | +The library provides these examples: `BasicBegin`, `ScanBus`, `DeviceReady`, |
| 205 | +`Controller_Target` (`ControllerWrite` and `TargetReceive`), `TargetIBI` and |
| 206 | +`TargetNotifications` (each with a `Controller` and a `Target` sketch). |
0 commit comments