We ran two STMicroelectronics MEMS sensors over I3C from a Binho Supernova and the Binho I3C Target Board: an LSM6DSV six-axis IMU on a STEVAL-MKI239AA, and an LPS22DF pressure sensor on a STEVAL-MKI224V1A. Both boards drop into the DIL24 socket. The only connections are clock, data, ground and a 3.3 V supply.
This post covers what it took to get each part enumerated, streaming data, and raising in-band interrupts, and the specific registers that decide whether an interrupt arrives. The full write-up is AN0005, linked at the end.
Enumeration
Bus initialization is two common command codes. RSTDAA clears any dynamic address already assigned. ENTDAA then runs dynamic address assignment, with each target arbitrating on its 48-bit provisional identifier and receiving an address.
$ python i3c_mems.py scan
1 target(s) on the bus at 3300 mV, PUSH_PULL_5_MHZ_50_DC, OPEN_DRAIN_1_MHZ
dynamic address 0x08
PID 02 08 00 70 12 0B device id 0x0070
BCR 0x07 DCR 0x44
HDR SDR only IBI capable
profile lsm6dsvNo address was configured on the host, and the part's static address was not used. The values below the address are read from the target itself.
Identity
Three registers, each read with one command.
The provisional identifier holds a manufacturer code and a part number: device id 0x0070 on the LSM6DSV and 0x00B4 on the LPS22DF. Each embeds the value the part also reports in WHO_AM_I.
The device characteristics register is a class code from the MIPI registry: 0x44 for a 6-axis IMU, 0x62 for a pressure sensor. It identifies the class, not the individual part.
The bus characteristics register reports capabilities. Both parts return 0x07:
BCR 0x07
device role I3C target
advanced capabilities (bit 5) no, so SDR only
IBI mandatory payload yes
IBI capable yes
max data speed limit yesBit 5 clear means single data rate only. Bits 1 and 2 set mean the part raises in-band interrupts and sends a payload with them.
Registers and data
Register access is a private transfer. Both parts use an 8-bit register address with 8-bit data and no dummy bytes before the read payload.
$ python i3c_mems.py stream --device lsm6dsv --seconds 5
x -0.029 g y 0.014 g z 1.011 g magnitude 1.012 g
x -0.029 g y 0.013 g z 1.010 g magnitude 1.010 g
x -0.028 g y 0.013 g z 1.011 g magnitude 1.011 gThe magnitude column is a useful check. At rest an accelerometer measures gravity, so the magnitude should read 1 g. That single number verifies the register framing, byte order, sign handling and scale factor together.
The LPS22DF reports pressure as a 24-bit value at 4096 counts per hPa and temperature as a 16-bit value at 100 counts per degree:
pressure 1006.297 hPa temperature 22.520 C
pressure 1006.295 hPa temperature 22.520 CIn-band interrupts
An in-band interrupt lets the sensor signal the host on the data line, so no interrupt pin is routed or connected. Setting one up is three steps, in order:
- Enable the source in the sensor
- Route that source to the in-band interrupt
- Send ENEC with its ENINT bit
The third step is the one most often missed. Neither the routing write nor the controller's own interrupt configuration causes anything to be delivered. Measured on the LSM6DSV one step at a time, each over a one-second window: nothing arrives at any stage, and the configured rate appears only once ENEC has been sent.
Step two is where the LSM6DSV differs from what a reader might assume. Accelerometer data-ready is routed by INT1_CTRL at 0x0D. Every other interrupt feature, including wake-up, tap, 6D orientation, free-fall and activity, is gated by FUNCTIONS_ENABLE bit 7 and routed through MD1_CFG at 0x5E, one bit per feature. Arming wake-up through INT1_CTRL produces no interrupts, and every register reads back the value that was written.
Separately, CTRL5 bit 0 is named INT_EN_I3C but does not enable the in-band interrupt. It activates the physical interrupt pins while the I3C interface is in use.
With data-ready routed, the interrupt rate follows the configured output data rate:
$ python i3c_mems.py ibi --device lsm6dsv --seconds 3
IBI from 0x08 MDB 02 data ready=True
183 interrupts in 3.00 s = 61.00/s
configured output data rate 60 Hz (+1.7%)Measured across four rates on the LSM6DSV: 15.67/s at 15 Hz, 32.00/s at 30 Hz, 61.00/s at 60 Hz and 121.67/s at 120 Hz. The LPS22DF returns 9.62/s against a configured 10 Hz. The rate tracks the configured rate; individual arrival times do not, because the host transport delivers interrupts in batches.
Motion-triggered interrupts
The LSM6DSV can detect motion itself and report it over the bus. Wake-up detection compares the slope filter output against a threshold in units of full scale over 64, so at ±2 g one count is about 31 mg. A threshold of 2 produced no interrupts over 35 seconds with the board stationary.
$ python i3c_mems.py wake --device lsm6dsv --seconds 90
lsm6dsv at 0x08: wake-up armed at threshold 2, routed to the I3C in-band interrupt
t= 9.67s MDB 04 payload says WU axes X
t= 9.91s MDB 04 payload says WU axes X,Y,Z
t= 10.36s MDB 04 payload says WU axes X,Y
t= 11.05s MDB 04 payload says WU axes Y
t= 13.17s MDB 04 payload says WU axes Z
80 wake-up interrupt(s) in 90.2 s
peak deviation from 1 g during the window: 0.428 gEighty interrupts arrived, all within the seven seconds the board was being tapped. Peak acceleration during the window was 0.428 g, which distinguishes the motion from noise.
The mandatory data byte reads 0x04 for wake-up and 0x02 for data-ready, so the host can identify the source before reading any register. The per-axis bits are in WAKE_UP_SRC, and a latched interrupt that is re-triggering can clear them before the host reads them, so a blank axis field does not mean the interrupt was spurious.
Two reasons interrupts do not arrive
The declared interrupt payload exceeds what the controller accepts. A target declares its maximum interrupt payload in the third byte of GETMRL. The LSM6DSV declares ten bytes at reset and pads its payload to the declared size; the Supernova delivers up to eight. With the declared size at ten, interrupts are raised and none are delivered. Measured against declared sizes of 1, 2, 4, 7 and 8, all deliver, and the payload arrives at exactly the declared length; 9 and 10 deliver nothing. Our utility checks and reports this:
IBI payload negotiation the target declared 10 byte(s), the adapter takes 8,
so it was asked for 8The source is a level rather than a pulse. Data-ready on the LPS22DF stays asserted until the sample is read, so enabling it delivers one interrupt and then nothing. Reading the sample after each interrupt, or setting DRDY_PLS so the part emits a pulse, both give 29 interrupts in three seconds against a configured 10 Hz.
What the parts implement
We established support from observable effects rather than result codes: a value set and read back, an address moved and the old one confirmed to stop answering, interrupts counted against a configured rate.
Both parts implement dynamic address assignment and reassignment, the identity commands, interrupt enable and disable, maximum read and write length, timing control, and group addressing. Neither offers high-data-rate modes, which BCR bit 5 reports directly, and neither raised a hot-join request.
Group addressing is worth a note because the obvious test gives the wrong answer. A group address is a write destination that several targets can share, so a read from it has no defined answer and a target may refuse a group-address read while implementing the feature correctly. The test used here assigns a group address, writes a register through it, and reads that register back at the target's own dynamic address. The write is refused before assignment, lands while the group address is assigned, and is refused again after RSTGRPA.
Bus rates
Both parts pass every rate the adapter offers: push-pull to 12.5 MHz at 50% duty cycle and open drain to 4.17 MHz. The method is 100 consecutive reads of the identity register with no errors, and a rate passes only if every iteration succeeds. This is a register read loop rather than a throughput measurement, and no conclusion about sustained data rate follows from it.
The application note
AN0005 covers all of the above in detail: identity fields decoded individually, register access per part, a figure mapping the three interrupt steps to the register each one uses, the measured capability matrix with the evidence behind each row, the bus rate results with the method, and a practical-notes section listing the per-part behaviour that costs bench time.
The archive includes the utility used throughout. A new part is described by one profile entry covering five things: how its registers are framed, what identity to expect, how to put it into a measuring state, how to route its interrupt onto the bus, and how to decode the payload that arrives.


