I2C
Two wires, many devices. core_i2c is a blocking I2C master — probe for a device, then read and write its registers by 7-bit address. The hal_i2c and ll_i2c layers sit underneath. Use the Core / HAL / LL toggle at the top of the sidebar to switch.
Overview
You pick an I2C instance (I2C1, I2C3, …) and a bus speed — I2C_100K (standard), I2C_400K (fast), or I2C_1M (fast-mode plus). The bus timing for your kernel clock is computed for you. Everything is blocking and 7-bit addressed.
core_i2c is handle-based (Tier 1) with a full register API. A small Tier 2 *_bus surface (byte read/write/probe by config-declared bus id) is what Studio emits.
Probe & scan
probe sends an address and checks for an ACK — the quickest way to confirm a device is alive. scan walks the whole bus:
#include "core.h"
core_i2c_t i2c;
core_i2c_init(&i2c, I2C1, I2C_400K);
if (core_i2c_probe(&i2c, 0x68) == I2C_OK) {
// device acked at 0x68
}
uint8_t found[16];
int n = core_i2c_scan(&i2c, found, /*count*/ 0, sizeof(found));Reading & writing registers
Most I2C devices are register files. The *_reg / *_byte helpers do the write-address-then-read dance (repeated START) for you. Register addresses can be 8- or 16-bit:
uint8_t who;
core_i2c_read_byte(&i2c, 0x68, 0x75, &who); // read WHO_AM_I
core_i2c_write_byte(&i2c, 0x68, 0x6B, 0x00); // wake the device
uint8_t accel[6];
core_i2c_read_reg(&i2c, 0x68, 0x3B, accel, sizeof(accel)); // burst read*_bus surface does single-byte register read/write/probe only — bulk transfers and scan are Tier 1.Cross-architecture support
Standard and fast mode are the verified baseline everywhere they apply; fast-mode plus (1 MHz) needs stronger drivers and isn’t on every Core. Interrupt/DMA and slave mode are still ahead:
See the implementation status for the full matrix.
Known gaps
What the SDK itself lists as missing here, straight from the header:
The current Tier 2 surface is write_byte_bus / read_byte_bus / probe_bus. DSL programs that need to push a multi-byte payload (display init sequences, IMU FIFO drains) or discover what's on a bus (`scan -> int[]`) drop back to Tier 1 with a hal_i2c_t handle. Adds need the array-IN / array-OUT host-call ABI prototyped on the tile-driver side — track with the DSL Capability Coverage close.
All current operations are polled. A non-blocking variant (with completion callback or status poll) would let DSL programs interleave bus traffic without stalling the main loop. Tracked on the SDK roadmap as 'I2C interrupt-driven'.
No DMA path for bulk reads/writes. Long FIFO drains (e.g., IMU water-level batch reads) currently block on polled byte loops.
Target (device) mode landed at Tier 1: core_i2c_target_init() lets a Core answer as an I2C device on a host bus, using a register-file model with an optional ISR callback. There is no Tier 2 / DSL surface, because a register buffer and a callback have no representation in the DSL host-call ABI. A Studio-level version would need a declarative "expose these variables as registers" binding, close to what the BLE characteristic binding does.
startup_stm32h523xx.s carries vectors for I2C1_EV, I2C1_ER and I2C2_EV only. There is no I2C2_ER or I2C3 entry, and slot 58 is labelled as I2C2 Error shared with USART1, so target mode is restricted to I2C1 on Core.ST.H5. I2C1 + I2C3 both work on Core.ST.L4 and Core.ST.W5; Core.ST.L0 has only I2C1.
API takes uint8_t addr — 7-bit only. No path for 10-bit-addressed peripherals (rare in practice but in spec).
No PEC, no Alert Response, no block read/write protocol framing. Not requested by any current tile.
From the @studio unsupported notes in core_i2c.h — tiles@6af026f.
API reference
hal_status_t core_i2c_write_byte_bus(uint8_t bus, uint8_t addr, uint16_t reg, uint8_t value);int core_i2c_read_byte_bus(uint8_t bus, uint8_t addr, uint16_t reg);int core_i2c_probe_bus(uint8_t bus, uint8_t addr);uint32_t _core_i2c_timing(uint32_t speed_hz);hal_status_t core_i2c_init(core_i2c_t * h, I2C_TypeDef * instance, uint32_t speed_hz);hal_status_t core_i2c_init_cfg(core_i2c_t * h, I2C_TypeDef * instance, const hal_i2c_config_t * cfg);hal_status_t core_i2c_write(core_i2c_t * h, uint8_t addr, const uint8_t * data, uint32_t len);hal_status_t core_i2c_read(core_i2c_t * h, uint8_t addr, uint8_t * buf, uint32_t len);hal_status_t core_i2c_write_reg(core_i2c_t * h, uint8_t addr, uint16_t reg, const uint8_t * data, uint32_t len);hal_status_t core_i2c_read_reg(core_i2c_t * h, uint8_t addr, uint16_t reg, uint8_t * buf, uint32_t len);hal_status_t core_i2c_write_byte(core_i2c_t * h, uint8_t addr, uint16_t reg, uint8_t value);hal_status_t core_i2c_read_byte(core_i2c_t * h, uint8_t addr, uint16_t reg, uint8_t * value);hal_status_t core_i2c_probe(core_i2c_t * h, uint8_t addr);void core_i2c_scan(core_i2c_t * h, uint8_t * found, uint8_t * count, uint8_t max_count);hal_i2c_t * core_i2c_handle_for_bus(uint8_t bus);hal_status_t core_i2c_target_init(core_i2c_target_t * t, I2C_TypeDef * instance, uint8_t addr, uint32_t speed, uint8_t * regs, uint16_t n_regs, hal_i2c_target_cb_t cb, void * ctx);hal_status_t core_i2c_target_init_rw(core_i2c_target_t * t, I2C_TypeDef * instance, uint8_t addr, uint32_t speed, uint8_t * regs, uint16_t n_regs, uint16_t first, uint16_t count, hal_i2c_target_cb_t cb, void * ctx);void core_i2c_target_deinit(core_i2c_target_t * t);Generated from core_i2c.h — tiles@f70bca2.

