# Bergsonne Labs — full documentation corpus > Bergsonne Tiles are millimeter-scale, fully-encapsulated microelectronic modules — each a complete subsystem behind a standard pad interface. Composable across processing, sensing, actuation, power, memory, and communication, Tiles bring embedded intelligence to physical AI, from ideation to benchtop prototype to mass production. Bergsonne Studio is the browser IDE that composes and programs them. This file inlines every page that is generated from our manifests: tile driver references, the SDK reference, and the pad / I²C lookup tables. Hand-authored guides are not included — see /llms.txt for those. Tiles are identified as `Family.Name` (`Sense.TOF`, `Drive.H`). Each driver section carries a **Driver gaps** list: chip capabilities the driver does NOT expose, tagged hardware-gated or driver-deferred. Trust it — do not infer an API from the chip datasheet alone. Generated 2026-10-07 from tiles@afe442f. ## Contents - Core.ST.H5 datasheet — https://bergsonne.io/catalog/core/sth5 - Core.ST.L0 datasheet — https://bergsonne.io/catalog/core/stl0 - Core.ST.L4 datasheet — https://bergsonne.io/catalog/core/stl4 - Core.ST.L4.2 datasheet — https://bergsonne.io/catalog/core/stl42 - Core.ST.W5 datasheet — https://bergsonne.io/catalog/core/stw5 - Display.RGBW datasheet — https://bergsonne.io/catalog/display/rgbw - Drive.A.2 datasheet — https://bergsonne.io/catalog/drive/a2 - Drive.DC.H datasheet — https://bergsonne.io/catalog/drive/dch - Drive.H datasheet — https://bergsonne.io/catalog/drive/h - Drive.P datasheet — https://bergsonne.io/catalog/drive/p - Power.BB datasheet — https://bergsonne.io/catalog/power/bb - Power.L.1N datasheet — https://bergsonne.io/catalog/power/l1n - Power.L.1T datasheet — https://bergsonne.io/catalog/power/l1t - Sense.CAM.P datasheet — https://bergsonne.io/catalog/sense/camp - Sense.CAP datasheet — https://bergsonne.io/catalog/sense/cap - Sense.HR datasheet — https://bergsonne.io/catalog/sense/hr - Sense.I.6P6 datasheet — https://bergsonne.io/catalog/sense/i6p6 - Sense.M.3G datasheet — https://bergsonne.io/catalog/sense/m3g - Sense.MIC datasheet — https://bergsonne.io/catalog/sense/mic - Sense.T.C datasheet — https://bergsonne.io/catalog/sense/tc - Sense.TOF datasheet — https://bergsonne.io/catalog/sense/tof - Store.O.128 datasheet — https://bergsonne.io/catalog/store/o128 - Display.RGBW driver — https://bergsonne.io/docs/tiles/display/rgbw - Drive.A.2 driver — https://bergsonne.io/docs/tiles/drive/a2 - Drive.DC.H driver — https://bergsonne.io/docs/tiles/drive/dch - Drive.H driver — https://bergsonne.io/docs/tiles/drive/h - Drive.P driver — https://bergsonne.io/docs/tiles/drive/p - Power.L.1N driver — https://bergsonne.io/docs/tiles/power/l1n - Power.L.1T driver — https://bergsonne.io/docs/tiles/power/l1t - Sense.CAM.P driver — https://bergsonne.io/docs/tiles/sense/camp - Sense.CAP driver — https://bergsonne.io/docs/tiles/sense/cap - Sense.HR driver — https://bergsonne.io/docs/tiles/sense/hr - Sense.I.6P6 driver — https://bergsonne.io/docs/tiles/sense/i6p6 - Sense.M.3G driver — https://bergsonne.io/docs/tiles/sense/m3g - Sense.MIC driver — https://bergsonne.io/docs/tiles/sense/mic - Sense.T.C driver — https://bergsonne.io/docs/tiles/sense/tc - Sense.TOF driver — https://bergsonne.io/docs/tiles/sense/tof - Store.O.128 driver — https://bergsonne.io/docs/tiles/store/o128 - Analog (ADC / DAC) — SDK reference — https://bergsonne.io/docs/sdk/adc - Audio (PDM mic) — SDK reference — https://bergsonne.io/docs/sdk/audio - BLE — SDK reference — https://bergsonne.io/docs/sdk/ble - Clocks — SDK reference — https://bergsonne.io/docs/sdk/clocks - GPIO — SDK reference — https://bergsonne.io/docs/sdk/gpio - I2C — SDK reference — https://bergsonne.io/docs/sdk/i2c - Onboard LED — SDK reference — https://bergsonne.io/docs/sdk/led - Power — SDK reference — https://bergsonne.io/docs/sdk/power - Scope (live streaming) — SDK reference — https://bergsonne.io/docs/sdk/scope - SPI — SDK reference — https://bergsonne.io/docs/sdk/spi - Stepper (STEP/DIR) — SDK reference — https://bergsonne.io/docs/sdk/stepper - Storage & ID — SDK reference — https://bergsonne.io/docs/sdk/storage - Timers & PWM — SDK reference — https://bergsonne.io/docs/sdk/timers - UART — SDK reference — https://bergsonne.io/docs/sdk/uart - USB — SDK reference — https://bergsonne.io/docs/sdk/usb - Tile I²C addresses — https://bergsonne.io/resources/i2c-addresses - Tile pad assignments — https://bergsonne.io/resources/pad-assignments ============================================================================== ## Core.ST.H5 > 250MHz Cortex-M33 | | | | --- | --- | | Tile | `Core.ST.H5` | | Revision | `a` | | Family | Core | | Package | T44 · 14 pads | | Dimensions | 4 mm × 4 mm | | Packaging | 12 mm embossed carrier tape (EIA-481), 4.3 × 4.3 × 1.8 mm pockets at 8 mm pitch, 500 per 7 in (178 mm) reel, pin 1 in quadrant 1, cold seal cover tape. Drawing: https://bergsonne.io/packages/T44-tape-reel.pdf | | Definition version | `0.41` | | Datasheet (PDF) | https://mosaic-datasheets.s3.eu-north-1.amazonaws.com/Core-ST-H5-a-0-41.pdf | | Canonical page | https://bergsonne.io/catalog/core/sth5 | ### Overview The Core.ST.H5 is built around the high-performance STM32H523 250-MHz 32-bit Cortex-M33 processor into a single T44 tile, providing a user-configurable combination of multiple communication interfaces (USB 2.0 full-speed, I2C/I3C, SPI, FDCAN, and UART), along with two 12-bit 5-Msps ADC inputs, one DAC output, and multiple timers. ### Onboard components | Manufacturer | Part | Datasheet | | --- | --- | --- | | ST Microelectronics | `STM32H523HE` | https://mosaic-component-datasheets.s3.eu-north-1.amazonaws.com/11/ST_Microelectronics-STM32H523HE.pdf | ### Power | Rail | Function | Direction | Min | Max | Max current | Pads | Required | | --- | --- | --- | --- | --- | --- | --- | --- | | system | | input | 1.71 | 3.6 | | +10, GND 1 | yes | ### Interfaces #### I2C1 (I2C) - Modes: master, slave - Addresses: `programmable` | Pad | Function | Role | Required | | --- | --- | --- | --- | | 4 | `I2C1.CLK` | bus | yes | | 5 | `I2C1.DAT` | bus | yes | #### I2C3 (I2C) - Modes: master, slave - Addresses: `programmable` | Pad | Function | Role | Required | | --- | --- | --- | --- | | 2 | `I2C3.CLK` | bus | yes | | 3 | `I2C3.DAT` | bus | yes | #### SPI1 (SPI) - Modes: master - Max clock: 45MHz | Pad | Function | Role | Required | | --- | --- | --- | --- | | 2 | `SPI1.RDY` | bus | no | | 3 | `SPI1.MISO` | bus | yes | | 8 | `SPI1.MOSI` | bus | yes | | 9 | `SPI1.CLK` | bus | yes | #### UART4 (UART) | Pad | Function | Role | Required | | --- | --- | --- | --- | | 4 | `UART4.RX` | bus | no | | 6 | `UART4.TX` | bus | no | | 7 | `UART4.RX` | bus | no | #### FDCAN1 (FD CAN) | Pad | Function | Role | Required | | --- | --- | --- | --- | | 4 | `FDCAN1.RX` | bus | no | | 5 | `FDCAN1.TX` | bus | no | | 6 | `FDCAN1.TX` | bus | no | | 7 | `FDCAN1.RX` | bus | no | #### USB (USB) | Pad | Function | Role | Required | | --- | --- | --- | --- | | 6 | `USB.DP` | bus | yes | | 7 | `USB.DM` | bus | yes | | 2 | `USB.SOF` | bus | no | #### I3C2 (I3C) - Modes: master, slave | Pad | Function | Role | Required | | --- | --- | --- | --- | | 2 | `I3C2.CLK` | bus | yes | | 3 | `I3C2.DAT` | bus | yes | #### I3C1 (I3C) - Modes: master, slave | Pad | Function | Role | Required | | --- | --- | --- | --- | | 4 | `I3C1.CLK` | bus | yes | | 5 | `I3C1.DAT` | bus | yes | ### Pad map | Pad | Default function | Type | Alternates | | --- | --- | --- | --- | | 1 | `GND` | power | — | | 2 | `A8` | digital | `USB.SOF`, `I2C3.CLK`, `I3C2.CLK`, `SPI1.RDY`, `TIM1.1`, `TIM8.BKIN2` | | 3 | `B4` | digital | `I2C3.DAT`, `I3C2.DAT`, `SPI1.MISO`, `TIM3.1`, `LPTIM1.2` | | 4 | `B8` | digital | `I2C1.CLK`, `I3C1.CLK`, `TIM4.3`, `UART4.RX`, `FDCAN1.RX` | | 5 | `B7` | digital | `I2C1.DAT`, `I3C1.DAT`, `TIM4.2`, `FDCAN1.TX` | | 6 | `A12` | digital | `USB.DP`, `TIM1.ETR`, `UART4.TX`, `FDCAN1.TX` | | 7 | `A11` | digital | `USB.DM`, `TIM1.4`, `UART4.RX`, `FDCAN1.RX` | | 8 | `A7` | digital | `SPI1.MOSI`, `ADC7+`, `ADC3-`, `TIM1.1N`, `TIM3.2`, `TIM8.1N` | | 9 | `A5` | digital | `SPI1.CLK`, `ADC19+`, `ADC18-`, `TIM2.1`, `TIM8.1N`, `TIM2.ETR`, `DAC1.OUT` | | 10 | `V+` | power | — | | 11 | `BOOT0` | system | — | | 12 | `NRST` | system | — | | 13 | `A14` | digital | `SWCLK` | | 14 | `A13` | digital | `SWDIO` | ### Application notes #### USB 2.0 Full-Speed Port To utilize the USB 2.0 Full-Speed (12Mbit/s) port, the supply voltage needs to be at least 3.0V. The Core.ST.H5 can serve as either a peripheral or a host. #### USB Bootloading Similar to the Core.ST.L4 tiles, when the chip is blank, it will default into the bootloader when connected over USB. Once there is code in the program space, you need to hold the BOOT0 pin HIGH during reset (either power-on or via the NRST pin) to enter the bootloader. #### Single-Wire Debug & Bootloading The single-wire debug port is available on pads 13 (SWCLK) and 14 (SWDIO). While not absolutely required, it is often helpful to have the ability to hold pad 12 (NRST) low when connecting to the debugger. You can likely also use BOOT0 to help the debugger connect. #### LED The onboard LED is connected to PA15 in an active-high configuration. --- Generated 2026-10-07 from the canonical tile definition (version 0.41). Canonical HTML: https://bergsonne.io/catalog/core/sth5 · PDF: https://mosaic-datasheets.s3.eu-north-1.amazonaws.com/Core-ST-H5-a-0-41.pdf ============================================================================== ## Core.ST.L0 > ultra-low-power Cortex-M0+ | | | | --- | --- | | Tile | `Core.ST.L0` | | Revision | `a` | | Family | Core | | Package | T44 · 14 pads | | Dimensions | 4 mm × 4 mm | | Packaging | 12 mm embossed carrier tape (EIA-481), 4.3 × 4.3 × 1.8 mm pockets at 8 mm pitch, 500 per 7 in (178 mm) reel, pin 1 in quadrant 1, cold seal cover tape. Drawing: https://bergsonne.io/packages/T44-tape-reel.pdf | | Definition version | `0.33` | | Datasheet (PDF) | https://mosaic-datasheets.s3.eu-north-1.amazonaws.com/Core-ST-L0-a-0-33.pdf | | Canonical page | https://bergsonne.io/catalog/core/stl0 | ### Overview Ultra-low-power ARM Cortex-M0+ core tile built around the STM32L011E4 with 16KB flash, 2KB RAM, and 512 bytes of data EEPROM. Optimized for battery-powered and energy-harvesting applications, with flexible I/O across 14 pads including I2C, USART, and LPUART interfaces, 6 ADC inputs, multiple timer channels, and an analog comparator. Runs from 1.8–3.6V with bootloading and hardware debug via SWD. ### Onboard components | Manufacturer | Part | Datasheet | | --- | --- | --- | | ST Microelectronics | `STM32L011E4` | https://mosaic-component-datasheets.s3.eu-north-1.amazonaws.com/12/ST_Microelectronics-STM32L011.pdf | ### Power | Rail | Function | Direction | Min | Max | Max current | Pads | Required | | --- | --- | --- | --- | --- | --- | --- | --- | | system | | input | 1.8 | 3.6 | | +10, GND 1 | yes | ### Interfaces #### I2C1 (I2C) - Modes: master, slave - Addresses: `programmable` | Pad | Function | Role | Required | | --- | --- | --- | --- | | 4 | `I2C1.CLK` | bus | no | | 5 | `I2C1.DAT` | bus | no | | 6 | `I2C1.SMBA` | bus | no | | 13 | `I2C1.SMBA` | bus | no | | 14 | `I2C1.DAT` | bus | no | #### USART2 (USART) | Pad | Function | Role | Required | | --- | --- | --- | --- | | 2 | `USART2.RX` | bus | no | | 3 | `USART2.RX` | bus | no | | 3 | `USART2.CTS` | bus | no | | 4 | `USART2.TX` | bus | no | | 5 | `USART2.RX` | bus | no | | 6 | `USART2.RTS_DE` | bus | no | | 7 | `USART2.TX` | bus | no | | 9 | `USART2.RTS_DE` | bus | no | | 13 | `USART2.TX` | bus | no | #### LPUART1 (LPUART) | Pad | Function | Role | Required | | --- | --- | --- | --- | | 2 | `LPUART1.RX` | bus | no | | 3 | `LPUART1.RX` | bus | no | | 4 | `LPUART1.TX` | bus | no | | 5 | `LPUART1.RX` | bus | no | | 6 | `LPUART1.TX` | bus | no | | 7 | `LPUART1.TX` | bus | no | | 13 | `LPUART1.TX` | bus | no | | 14 | `LPUART1.RX` | bus | no | ### Pad map | Pad | Default function | Type | Alternates | | --- | --- | --- | --- | | 1 | `GND` | power | — | | 2 | `A3` | digital | `ADC3`, `TIM21.2`, `TIM2.4`, `USART2.RX`, `LPUART1.RX`, `COMP2.IN+` | | 3 | `A0` | digital | `ADC0`, `TIM2.1`, `TIM2.ETR`, `LPTIM1.1`, `USART2.RX`, `USART2.CTS`, `LPUART1.RX`, `COMP1.OUT`, `COMP1.IN-` | | 4 | `B6` | digital | `I2C1.CLK`, `TIM2.3`, `LPTIM1.ETR`, `USART2.TX`, `LPUART1.TX`, `COMP2.IN+` | | 5 | `B7` | digital | `I2C1.DAT`, `TIM2.4`, `LPTIM1.2`, `USART2.RX`, `LPUART1.RX`, `COMP2.IN+` | | 6 | `A1` | digital | `I2C1.SMBA`, `ADC1`, `TIM2.2`, `TIM21.ETR`, `LPTIM1.2`, `USART2.RTS_DE`, `LPUART1.TX`, `COMP1.IN+` | | 7 | `A2` | digital | `ADC2`, `TIM21.1`, `TIM2.3`, `USART2.TX`, `LPUART1.TX`, `COMP2.IN-`, `COMP2.OUT` | | 8 | `A5` | digital | `ADC5`, `TIM2.1`, `TIM2.ETR`, `LPTIM1.2`, `COMP1.IN-`, `COMP2.IN-` | | 9 | `B0` | digital | `ADC8`, `TIM2.2`, `TIM2.3`, `USART2.RTS_DE` | | 10 | `V+` | power | — | | 11 | `B9` | digital | `BOOT0` | | 12 | `NRST` | system | — | | 13 | `A14` | digital | `I2C1.SMBA`, `LPTIM1.OUT`, `USART2.TX`, `LPUART1.TX`, `SWCLK`, `COMP2.OUT` | | 14 | `A13` | digital | `I2C1.DAT`, `LPTIM1.ETR`, `LPUART1.RX`, `COMP1.OUT`, `SWDIO` | ### Application notes #### Single-Wire Debug & Bootloading The single-wire debug port is available on pads 13 (SWCLK) and 14 (SWDIO). While not absolutely required, it is often helpful to have the ability to hold pad 12 (NRST) low when connecting to the debugger. You can likely also use BOOT0 to help the debugger connect. #### LED The onboard LED is connected to PA8 in an active-high configuration. --- Generated 2026-10-07 from the canonical tile definition (version 0.33). Canonical HTML: https://bergsonne.io/catalog/core/stl0 · PDF: https://mosaic-datasheets.s3.eu-north-1.amazonaws.com/Core-ST-L0-a-0-33.pdf ============================================================================== ## Core.ST.L4 > USB-programmable Cortex-M4 | | | | --- | --- | | Tile | `Core.ST.L4` | | Revision | `b` | | Family | Core | | Package | T44 · 14 pads | | Dimensions | 4 mm × 4 mm | | Packaging | 12 mm embossed carrier tape (EIA-481), 4.3 × 4.3 × 1.8 mm pockets at 8 mm pitch, 500 per 7 in (178 mm) reel, pin 1 in quadrant 1, cold seal cover tape. Drawing: https://bergsonne.io/packages/T44-tape-reel.pdf | | Definition version | `0.42` | | Datasheet (PDF) | https://mosaic-datasheets.s3.eu-north-1.amazonaws.com/Core-ST-L4-b-0-42.pdf | | Canonical page | https://bergsonne.io/catalog/core/stl4 | ### Overview The USB-programmable Core.ST.L4 is based on the 80-MHz 32-bit Cortex-M4 STM32L422 with 128KB flash, 40KB SRAM, and a CoreMark score of 273.55. The exposed pads provide a user-configurable combination of two fast-mode-plus I2C ports, one SPI port, three 12-bit ADCs, multiple timers, USART, an onboard comparator, and capacitive-touch input. A programmable onboard LED provides a convenient status indicator. If more I/O is needed, consider the larger Core.ST.L4.2 in a T48-22 package. ### Onboard components | Manufacturer | Part | Datasheet | | --- | --- | --- | | ST Microelectronics | `STM32L422TB` | https://mosaic-component-datasheets.s3.eu-north-1.amazonaws.com/1/ST_Microelectronics-STM32L422TB.pdf | ### Power | Rail | Function | Direction | Min | Max | Max current | Pads | Required | | --- | --- | --- | --- | --- | --- | --- | --- | | system | | input | 1.71 | 3.6 | | +10, GND 1 | yes | ### Interfaces #### I2C1 (I2C) - Modes: master, slave - Max clock: 1MHz - Addresses: `programmable` | Pad | Function | Role | Required | | --- | --- | --- | --- | | 3 | `I2C1.SMBA` | bus | no | | 4 | `I2C1.CLK` | bus | yes | | 5 | `I2C1.DAT` | bus | yes | #### I2C3 (I2C) - Modes: master, slave - Max clock: 1MHz - Addresses: `programmable` | Pad | Function | Role | Required | | --- | --- | --- | --- | | 2 | `I2C3.CLK` | bus | yes | | 8 | `I2C3.DAT` | bus | yes | #### SPI1 (SPI) - Modes: master, slave - Max clock: 40Mhz (master), 20MHz (slave) | Pad | Function | Role | Required | | --- | --- | --- | --- | | 3 | `SPI1.CLK` | bus | yes | | 7 | `SPI1.MISO` | bus | no | | 8 | `SPI1.MISO` | bus | no | | 9 | `SPI1.CS` | other | no | | 2 | `SPI1.MOSI` | bus | no | | 6 | `SPI1.MOSI` | bus | no | #### USB (USB) | Pad | Function | Role | Required | | --- | --- | --- | --- | | 6 | `USB.DP` | bus | yes | | 7 | `USB.DM` | bus | yes | #### USART1 (USART) | Pad | Function | Role | Required | | --- | --- | --- | --- | | 4 | `USART1.TX` | bus | yes | | 5 | `USART1.RX` | bus | yes | | 6 | `USART1.RTS_DE` | bus | no | | 7 | `USART1.CTS` | bus | no | | 8 | `USART1.CTS` | bus | no | ### Pad map | Pad | Default function | Type | Alternates | | --- | --- | --- | --- | | 1 | `GND` | power | — | | 2 | `A7` | digital | `I2C3.CLK`, `SPI1.MOSI`, `ADC12`, `TIM1.1N` | | 3 | `A1` | digital | `I2C1.SMBA`, `SPI1.CLK`, `ADC6`, `TIM2.2`, `TIM15.1N`, `COMP1.+` | | 4 | `B6` | digital | `I2C1.CLK`, `TIM16.1N`, `LPTIM1.ETR`, `USART1.TX`, `G2.IO3` | | 5 | `B7` | digital | `I2C1.DAT`, `LPTIM1.2`, `USART1.RX`, `G2.IO4` | | 6 | `A12` | digital | `SPI1.MOSI`, `TIM1.ETR`, `USART1.RTS_DE`, `USB.DP` | | 7 | `A11` | digital | `SPI1.MISO`, `TIM1.4`, `TIM1.BKIN2`, `TIM1.BKIN2_COMP1`, `USART1.CTS`, `COMP1.OUT`, `USB.DM` | | 8 | `B4` | digital | `I2C3.DAT`, `SPI1.MISO`, `USART1.CTS`, `G2.IO1` | | 9 | `A4` | digital | `SPI1.CS`, `ADC9`, `LPTIM2.OUT`, `COMP1.-` | | 10 | `V+` | power | — | | 11 | `BOOT0` | system | `H3` | | 12 | `NRST` | system | — | | 13 | `A14` | digital | `SWCLK`, `I2C1.SMBA`, `LPTIM1.OUT` | | 14 | `A13` | digital | `SWDIO` | ### Application notes #### Bootloading When the chip is blank, it will default into the bootloader when connected over USB. Once there is code in the program space, you need to hold the BOOT0 pin HIGH during reset (either power-on or via the NRST pin) to enter the bootloader. #### LED The onboard LED is connected to PA8 in an active-high configuration. #### Single-Wire Debug & Bootloading The single-wire debug port is available on pads 13 (SWCLK) and 14 (SWDIO). While not absolutely required, it is often helpful to have the ability to hold pad 12 (NRST) low when connecting to the debugger. You can likely also use BOOT0 to help the debugger connect. --- Generated 2026-10-07 from the canonical tile definition (version 0.42). Canonical HTML: https://bergsonne.io/catalog/core/stl4 · PDF: https://mosaic-datasheets.s3.eu-north-1.amazonaws.com/Core-ST-L4-b-0-42.pdf ============================================================================== ## Core.ST.L4.2 > expanded-I/0 USB-prog. Cortex-M4 | | | | --- | --- | | Tile | `Core.ST.L4.2` | | Revision | `a` | | Family | Core | | Package | T48 · 22 pads | | Dimensions | 4 mm × 8 mm | | Packaging | To be announced. Carrier tape not yet selected for T48.. | | Definition version | `0.61` | | Datasheet (PDF) | https://mosaic-datasheets.s3.eu-north-1.amazonaws.com/Core-ST-L4-2-a-0-61.pdf | | Canonical page | https://bergsonne.io/catalog/core/stl42 | ### Overview The expanded-I/O USB-programmable Core.ST.L4.2 is based on the same 80-MHz Cortex-M4 STM32L422 as the smaller Core.ST.L4, with the top 12 pads (1-5 and 16-22) matching the pad layout of the Core.ST.L4. The additional I/O pads provide a user-configurable combination of single-wire and trace debugging, two I2C ports, one SPI port, one quad SPI port, ten 12-bit ADC inputs, multiple timers, capacitive-touch inputs, and an on-board comparator. The board also includes an onboard LED and a physical button for reset and bootloading (see Application Notes). ### Onboard components | Manufacturer | Part | Datasheet | | --- | --- | --- | | ST Microelectronics | `STM32L422TB` | https://mosaic-component-datasheets.s3.eu-north-1.amazonaws.com/7/ST_Microelectronics-STM32L422TB.pdf | ### Power | Rail | Function | Direction | Min | Max | Max current | Pads | Required | | --- | --- | --- | --- | --- | --- | --- | --- | | system | | input | 1.8 | 3.6 | | +20, GND 1 | yes | ### Interfaces #### I2C1 (I2C) - Modes: master, slave - Max clock: 1MHz - Addresses: `programmable` | Pad | Function | Role | Required | | --- | --- | --- | --- | | 3 | `I2C1.SMBA` | bus | no | | 4 | `I2C1.CLK` | bus | yes | | 5 | `I2C1.DAT` | bus | yes | | 13 | `I2C1.SMBA` | bus | no | #### I2C3 (I2C) - Modes: master, slave - Max clock: 1MHz - Addresses: `programmable` | Pad | Function | Role | Required | | --- | --- | --- | --- | | 2 | `I2C3.CLK` | bus | yes | | 18 | `I2C3.DAT` | bus | yes | #### SPI1 (SPI) - Modes: master, slave - Max clock: 40Mhz (master), 20MHz (slave) | Pad | Function | Role | Required | | --- | --- | --- | --- | | 3 | `SPI1.CLK` | bus | no | | 2 | `SPI1.MOSI` | bus | no | | 9 | `SPI1.MISO` | bus | no | | 10 | `SPI1.CLK` | bus | no | | 11 | `SPI1.CS` | other | no | | 12 | `SPI1.CLK` | bus | no | | 16 | `SPI1.MOSI` | bus | no | | 17 | `SPI1.MISO` | bus | no | | 18 | `SPI1.MISO` | bus | no | | 19 | `SPI1.CS` | other | no | #### USB (USB) | Pad | Function | Role | Required | | --- | --- | --- | --- | | 16 | `USB.DP` | bus | yes | | 17 | `USB.DM` | bus | yes | #### USART1 (USART) | Pad | Function | Role | Required | | --- | --- | --- | --- | | 4 | `USART1.TX` | bus | yes | | 5 | `USART1.RX` | bus | yes | | 16 | `USART1.RTS_DE` | bus | no | | 17 | `USART1.CTS` | bus | no | | 18 | `USART1.CTS` | bus | no | | 12 | `USART1.RTS_DE` | bus | no | #### USART2 (USART) | Pad | Function | Role | Required | | --- | --- | --- | --- | | 3 | `USART2.RTS_DE` | bus | no | | 6 | `USART2.RX` | bus | no | | 7 | `USART2.TX` | bus | no | | 8 | `USART2.CTS` | bus | no | #### LPUART1 (LPUART) | Pad | Function | Role | Required | | --- | --- | --- | --- | | 6 | `LPUART1.RX` | bus | no | | 7 | `LPUART1.TX` | bus | no | | 9 | `LPUART1.CTS` | bus | no | | 15 | `LPUART1.RTS_DE` | bus | no | #### QSPI (QSPI) | Pad | Function | Role | Required | | --- | --- | --- | --- | | 2 | `QSPI.IO2` | bus | yes | | 6 | `QSPI.CLK` | bus | yes | | 7 | `QSPI.CS` | other | yes | | 9 | `QSPI.IO3` | bus | yes | | 11 | `QSPI.IO1` | bus | yes | | 15 | `QSPI.IO0` | bus | yes | ### Pad map | Pad | Default function | Type | Alternates | | --- | --- | --- | --- | | 1 | `GND` | power | — | | 2 | `A7` | digital | `I2C3.CLK`, `SPI1.MOSI`, `ADC12`, `TIM1.1N`, `QSPI.IO2` | | 3 | `A1` | digital | `I2C1.SMBA`, `SPI1.CLK`, `ADC6`, `TIM2.2`, `TIM15.1N`, `COMP1.+`, `USART2.RTS_DE` | | 4 | `B6` | digital | `I2C1.CLK`, `TIM16.1N`, `LPTIM1.ETR`, `USART1.TX`, `G2.IO3`, `TRACE.D3` | | 5 | `B7` | digital | `I2C1.DAT`, `LPTIM1.2`, `USART1.RX`, `TRACE.CK`, `G2.IO4` | | 6 | `A3` | digital | `QSPI.CLK`, `ADC8`, `TIM2.4`, `TIM15.2`, `USART2.RX`, `LPUART1.RX` | | 7 | `A2` | digital | `QSPI.CS`, `ADC7`, `TIM2.3`, `TIM15.1`, `USART2.TX`, `LPUART1.TX` | | 8 | `A0` | digital | `ADC5`, `TIM2.1`, `TIM2.ETR`, `USART2.CTS`, `COMP1.IN-`, `COMP1.OUT` | | 9 | `A6` | digital | `SPI1.MISO`, `QSPI.IO3`, `ADC11`, `TIM1.BKIN`, `TIM16.1`, `LPUART1.CTS`, `COMP1.OUT` | | 10 | `A5` | digital | `SPI1.CLK`, `ADC10`, `TIM2.1`, `TIM2.ETR`, `LPTIM2.ETR`, `COMP1.IN-` | | 11 | `B0` | digital | `TRACE.D0`, `SPI1.CS`, `QSPI.IO1`, `ADC15`, `TIM1.2N`, `COMP1.OUT` | | 12 | `B3` | digital | `TRACE.SWO`, `SPI1.CLK`, `TIM2.2`, `USART1.RTS_DE` | | 13 | `A14` | digital | `SWCLK`, `I2C1.SMBA`, `LPTIM1.OUT` | | 14 | `A13` | digital | `SWDIO` | | 15 | `B1` | digital | `TRACE.D1`, `QSPI.IO0`, `ADC16`, `TIM1.3N`, `LPTIM2.1`, `LPUART1.RTS_DE` | | 16 | `A12` | digital | `SPI1.MOSI`, `TIM1.ETR`, `USART1.RTS_DE`, `USB.DP` | | 17 | `A11` | digital | `SPI1.MISO`, `TIM1.4`, `TIM1.BKIN2`, `TIM1.BKIN2_COMP1`, `USART1.CTS`, `COMP1.OUT`, `USB.DM` | | 18 | `B4` | digital | `I2C3.DAT`, `SPI1.MISO`, `USART1.CTS`, `G2.IO1` | | 19 | `A4` | digital | `SPI1.CS`, `ADC9`, `LPTIM2.OUT`, `COMP1.-` | | 20 | `V+` | power | — | | 21 | `BOOT0` | system | `PH3` | | 22 | `NRST` | system | — | ### Application notes #### USB Bootloading Similar to the Core.ST.L4, when the chip is blank, it will default into the bootloader when connected over USB. Once there is code in the program space, you need to hold the BOOT0 pin HIGH during reset (either power-on or via the NRST pin) to enter the bootloader. The physical button will also execute this behavior. #### Single-Wire Debug & Bootloading The single-wire debug port is available on pads 13 (SWCLK) and 14 (SWDIO). While not absolutely required, it is often helpful to have the ability to hold pads 22 (NRST) low when connecting to the debugger. You can likely also use BOOT0 (via either the pad or the button) to help the debugger connect. #### Button The board button will issue a software reset via the NRST pin when pressed for a short (less than ~1sec) time. When held for more than ~2 seconds and released, the system will enter the bootloader. #### LED The onboard LED is connected to PA8 in an active-high configuration. --- Generated 2026-10-07 from the canonical tile definition (version 0.61). Canonical HTML: https://bergsonne.io/catalog/core/stl42 · PDF: https://mosaic-datasheets.s3.eu-north-1.amazonaws.com/Core-ST-L4-2-a-0-61.pdf ============================================================================== ## Core.ST.W5 > BLE-enabled 100MHz Cortex M33 | | | | --- | --- | | Tile | `Core.ST.W5` | | Revision | `b` | | Family | Core | | Package | T48 · 16 pads | | Dimensions | 4 mm × 8 mm | | Packaging | To be announced. Carrier tape not yet selected for T48.. | | Definition version | `0.40` | | Datasheet (PDF) | https://mosaic-datasheets.s3.eu-north-1.amazonaws.com/Core-ST-W5-b-0-40.pdf | | Canonical page | https://bergsonne.io/catalog/core/stw5 | ### Overview The Core.W tile combines the powerful STM32WBA55 100-MHz 32-bit Cortex-M33 processor together with an integrated 2.4GHz antenna on double-size T48 (4.0 x 8.0 mm) SMD package. The tile provides a user-configurable combination of: SW debug & bootloading; 1MB flash & 128KB SRAM; wireless communications (BLE 5.4, Thread, Matter, Zigbee, proprietary); two FM+ (1MHz) I2C ports; two SPI ports; one USART; five 12-bit 2.5Msps ADC inputs (16-bit with oversampling); many timers; ten capacitive touch inputs; one serial-audio interface; IR output; and an on-board LED ### Onboard components | Manufacturer | Part | Datasheet | | --- | --- | --- | | ST Microelectronics | `STM32WBA55HGF6` | https://mosaic-component-datasheets.s3.eu-north-1.amazonaws.com/8/ST_Microelectronics-STM32WBA55.pdf | ### Power | Rail | Function | Direction | Min | Max | Max current | Pads | Required | | --- | --- | --- | --- | --- | --- | --- | --- | | system | | input | 1.71 | 3.6 | | +14, GND 1 | yes | ### Interfaces #### I2C1 (I2C) - Modes: master, slave - Max clock: 1MHz - Addresses: `programmable` | Pad | Function | Role | Required | | --- | --- | --- | --- | | 10 | `I2C1.CLK` | bus | yes | | 11 | `I2C1.DAT` | bus | yes | #### I2C3 (I2C) - Modes: master, slave - Max clock: 1MHz - Addresses: `programmable` | Pad | Function | Role | Required | | --- | --- | --- | --- | | 4 | `I2C3.CLK` | bus | yes | | 5 | `I2C3.DAT` | bus | yes | #### SPI1 (SPI) - Modes: master, slave | Pad | Function | Role | Required | | --- | --- | --- | --- | | 8 | `SPI1.CS` | other | no | | 9 | `SPI1.CLK` | bus | no | | 10 | `SPI1.MOSI` | bus | no | | 11 | `SPI1.MISO` | bus | no | #### SPI3 (SPI) - Modes: master, slave | Pad | Function | Role | Required | | --- | --- | --- | --- | | 2 | `SPI3.CLK` | bus | no | | 3 | `SPI3.CS` | other | no | | 6 | `SPI3.MOSI` | bus | no | | 7 | `SPI3.MISO` | bus | no | #### USART2 (USART) | Pad | Function | Role | Required | | --- | --- | --- | --- | | 6 | `USART2.RX` | bus | no | | 8 | `USART2.TX` | bus | no | | 9 | `USART2.RX` | bus | no | | 10 | `USART2.RTS_DE` | bus | no | | 11 | `USART2.CK` | bus | no | | 13 | `USART2.TX` | bus | no | #### SAI1 (SAI) | Pad | Function | Role | Required | | --- | --- | --- | --- | | 3 | `SAI1.D2` | bus | no | | 4 | `SAI1.CK2` | bus | no | | 4 | `SAI1.MCLK_A` | bus | no | | 5 | `SAI1.SCK_A` | bus | no | | 9 | `SAI1.MCLK_B` | bus | no | ### Pad map | Pad | Default function | Type | Alternates | | --- | --- | --- | --- | | 1 | `GND` | power | — | | 2 | `A0` | digital | `SPI3.CLK`, `ADC9`, `TIM1.2N`, `TIM3.3`, `TIM3.ETR`, `LPTIM1.IN1`, `G2.IO2` | | 3 | `A5` | digital | `SPI3.CS`, `ADC4`, `TIM2.1`, `LPTIM2.ETR`, `SAI1.D2`, `G1.IO4` | | 4 | `A6` | digital | `I2C3.CLK`, `ADC3`, `TIM2.4`, `SAI1.CK2`, `SAI1.MCLK_A`, `G1.IO3` | | 5 | `A7` | digital | `I2C3.DAT`, `ADC2`, `TIM2.3`, `SAI1.SCK_A`, `G1.IO2` | | 6 | `B8` | digital | `SPI3.MOSI`, `TIM1.1`, `TIM3.ETR`, `TIM16.1N`, `LPTIM1.ETR`, `USART2.RX`, `G2.IO4` | | 7 | `B9` | digital | `SPI3.MISO`, `ADC10`, `TIM1.3N`, `TIM3.4`, `TIM16.1`, `LPTIM2.IN1`, `G2.IO3`, `IR_OUT` | | 8 | `A12` | digital | `SPI1.CS`, `TIM1.2`, `USART2.TX`, `G3.IO4` | | 9 | `B4` | digital | `SPI1.CLK`, `TIM1.3`, `TIM17.1`, `LPTIM2.IN2`, `USART2.RX`, `SAI1.MCLK_B`, `G3.IO1` | | 10 | `A15` | digital | `I2C1.CLK`, `SPI1.MOSI`, `TIM1.ETR`, `LPTIM1.2`, `USART2.RTS_DE`, `TIM17.BKIN`, `G3.IO3` | | 11 | `B3` | digital | `I2C1.DAT`, `SPI1.MISO`, `TIM1.4`, `TIM17.1N`, `LPTIM1.IN2`, `USART2.CK`, `G3.IO2` | | 12 | `A13` | digital | `SWDIO`, `IR_OUT` | | 13 | `A14` | digital | `SWCLK`, `USART2.TX` | | 14 | `V+` | power | — | | 15 | `BOOT0` | system | `H3` | | 16 | `NRST` | system | — | ### Application notes #### Single-Wire Debug & Bootloading The single-wire debug port is available on pads 13 (SWCLK) and 12 (SWDIO). While not absolutely required, it is often helpful to have the ability to hold pad 16 (NRST) low when connecting to the debugger. You can likely also use BOOT0 to help the debugger connect. #### Antenna Clearance For the best wireless performance, the antenna-end of the Tile (the end without any pads) should not have any copper traces or planes above or below it. #### LED The onboard LED is connected to PB12 in an active-high configuration. --- Generated 2026-10-07 from the canonical tile definition (version 0.40). Canonical HTML: https://bergsonne.io/catalog/core/stw5 · PDF: https://mosaic-datasheets.s3.eu-north-1.amazonaws.com/Core-ST-W5-b-0-40.pdf ============================================================================== ## Display.RGBW > Smart RGBW LED | | | | --- | --- | | Tile | `Display.RGBW` | | Revision | `a` | | Family | Display | | Package | T44 · 10 pads | | Dimensions | 4 mm × 4 mm | | Packaging | 12 mm embossed carrier tape (EIA-481), 4.3 × 4.3 × 1.8 mm pockets at 8 mm pitch, 500 per 7 in (178 mm) reel, pin 1 in quadrant 1, cold seal cover tape. Drawing: https://bergsonne.io/packages/T44-tape-reel.pdf | | Definition version | `0.3` | | Driver reference | https://bergsonne.io/docs/tiles/display/rgbw | | Datasheet (PDF) | https://mosaic-datasheets.s3.eu-north-1.amazonaws.com/Display-RGBW-a-0-3.pdf | | Canonical page | https://bergsonne.io/catalog/display/rgbw | ### Onboard components | Manufacturer | Part | Datasheet | | --- | --- | --- | | Texas Instruments | `LP5811` | https://mosaic-component-datasheets.s3.eu-north-1.amazonaws.com/58/Texas_Instruments-LP5811.pdf | ### Power | Rail | Function | Direction | Min | Max | Max current | Pads | Required | | --- | --- | --- | --- | --- | --- | --- | --- | | system | | input | 1.8 | 5.5 | | +10, GND 1 | yes | ### Interfaces #### I2C (I2C) - Modes: slave - Addresses: `0x50` | Pad | Function | Role | Required | | --- | --- | --- | --- | | 4 | `I2C.CLK` | bus | yes | | 5 | `I2C.DAT` | bus | yes | ### Pad map | Pad | Default function | Type | Alternates | | --- | --- | --- | --- | | 1 | `GND` | power | — | | 2 | `` | other | — | | 3 | `` | other | — | | 4 | `I2C.CLK` | digital | — | | 5 | `I2C.DAT` | digital | — | | 6 | `` | other | — | | 7 | `` | other | — | | 8 | `EN` | digital | — | | 9 | `` | other | — | | 10 | `V+` | power | — | --- Generated 2026-10-07 from the canonical tile definition (version 0.3). Canonical HTML: https://bergsonne.io/catalog/display/rgbw · PDF: https://mosaic-datasheets.s3.eu-north-1.amazonaws.com/Display-RGBW-a-0-3.pdf ============================================================================== ## Drive.A.2 > I2C/SPI-input dual 3W Class-D amplifier | | | | --- | --- | | Tile | `Drive.A.2` | | Revision | `a` | | Family | Drive | | Package | T44 · 10 pads | | Dimensions | 4 mm × 4 mm | | Packaging | 12 mm embossed carrier tape (EIA-481), 4.3 × 4.3 × 1.8 mm pockets at 8 mm pitch, 500 per 7 in (178 mm) reel, pin 1 in quadrant 1, cold seal cover tape. Drawing: https://bergsonne.io/packages/T44-tape-reel.pdf | | Definition version | `0.11` | | Driver reference | https://bergsonne.io/docs/tiles/drive/a2 | | Datasheet (PDF) | https://mosaic-datasheets.s3.eu-north-1.amazonaws.com/Drive-A-2-a-0-11.pdf | | Canonical page | https://bergsonne.io/catalog/drive/a2 | ### Overview The Drive.A.2 is a dual-channel audio output tile combining a DAC63202W 12-bit smart DAC with two TPA2028D1 Class-D audio power amplifiers. It provides two independent audio channels, each capable of delivering up to 3 W into 4 Ω or 880 mW into 8 Ω speakers. The DAC63202W generates analog waveforms from digital data with 12-bit resolution and supports built-in waveform generation (sine, cosine, triangular, sawtooth) for processor-less tone generation. The DAC connects to a host processor via either I2C (up to 1 Mbps) or SPI (up to 50 MHz), auto-detected at power-on. Each TPA2028D1 amplifier features I2C-programmable gain control (−28 dB to +30 dB in 1 dB steps), automatic gain control (AGC), and dynamic range compression (DRC) to prevent speaker overdrive and enhance perceived loudness. The amplifiers operate filter-free in Class-D mode for high efficiency and include thermal and short-circuit protection. The tile operates from a 2.5 V to 5.5 V supply. All three ICs are controlled via a shared I2C bus, while the DAC can alternatively be driven over SPI for higher-bandwidth waveform updates. ### Onboard components | Manufacturer | Part | Datasheet | | --- | --- | --- | | Texas Instruments | `DAC63202W` | https://mosaic-component-datasheets.s3.eu-north-1.amazonaws.com/13/Texas_Instruments-DAC63202W.pdf | | Texas Instruments | `TPA2028D1` | https://mosaic-component-datasheets.s3.eu-north-1.amazonaws.com/13/Texas_Instruments-TPA2028D1.pdf | ### Power | Rail | Function | Direction | Min | Max | Max current | Pads | Required | | --- | --- | --- | --- | --- | --- | --- | --- | | system | | input | 2.5 | 5.5 | | +10, GND 1 | yes | ### Interfaces #### I2C (I2C) - Modes: slave - Max clock: 1MHz - Addresses: `0x49` (default), `0x48`, `0x4A`, `0x4B` | Pad | Function | Role | Required | | --- | --- | --- | --- | | 4 | `I2C.CLK` | bus | yes | | 5 | `I2C.DAT` | bus | yes | #### SPI (SPI) - Modes: slave - Max clock: 50MHz | Pad | Function | Role | Required | | --- | --- | --- | --- | | 2 | `SPI.MISO` | bus | yes | | 3 | `SPI.MOSI` | bus | yes | | 5 | `SPI.CLK` | bus | yes | | 4 | `SPI.CS` | other | yes | ### Pad map | Pad | Default function | Type | Alternates | | --- | --- | --- | --- | | 1 | `GND` | power | — | | 2 | `GPIO` | digital | `SPI.MISO` | | 3 | `A0` | digital | `SPI.MOSI` | | 4 | `I2C.CLK` | digital | `SPI.CS` | | 5 | `I2C.DAT` | digital | `SPI.CLK` | | 6 | `OUT.0-` | drive | — | | 7 | `OUT.0+` | drive | — | | 8 | `OUT.1-` | drive | — | | 9 | `OUT.1+` | drive | — | | 10 | `V+` | power | — | ### Application notes #### Automatic Bus Detection The DAC in the Drive.A.2 tile automatically detects whether it is connected via I2C of SPI. #### I2C Addresses The Drive.A.2 can be configured for one of four I2C addresses by manipulating the connection of pad 3 (A0). Leaving the pad floating (or connecting to V+) uses the default address of 0x49. Connecting pad 3 to GND will change to address 0x48, while connecting to pad 3 to pad 4 (I2C.CLK) or pad 5 (I2C.DAT) will set the address to 0x4B or 0x4A, respectively. --- Generated 2026-10-07 from the canonical tile definition (version 0.11). Canonical HTML: https://bergsonne.io/catalog/drive/a2 · PDF: https://mosaic-datasheets.s3.eu-north-1.amazonaws.com/Drive-A-2-a-0-11.pdf ============================================================================== ## Drive.DC.H > 11V/2A H-bridge DC-motor driver | | | | --- | --- | | Tile | `Drive.DC.H` | | Revision | `a` | | Family | Drive | | Package | T44 · 10 pads | | Dimensions | 4 mm × 4 mm | | Packaging | 12 mm embossed carrier tape (EIA-481), 4.3 × 4.3 × 1.8 mm pockets at 8 mm pitch, 500 per 7 in (178 mm) reel, pin 1 in quadrant 1, cold seal cover tape. Drawing: https://bergsonne.io/packages/T44-tape-reel.pdf | | Definition version | `0.16` | | Driver reference | https://bergsonne.io/docs/tiles/drive/dch | | Datasheet (PDF) | https://mosaic-datasheets.s3.eu-north-1.amazonaws.com/Drive-DC-H-a-0-16.pdf | | Canonical page | https://bergsonne.io/catalog/drive/dch | ### Overview The Drive.DC.I is a single-channel H-bridge motor driver built around the DRV8214. It can deliver up to 4 A peak (2 A RMS continuous) and operates from a 1.65 V to 11 V supply, making it suitable for a wide range of small brushed DC motors in robotics, haptics, and actuation applications. The DRV8214 features sensorless ripple counting, which tracks motor commutation ripples in the current waveform to determine relative position and speed — eliminating the need for external encoders. Integrated speed regulation maintains constant motor speed across varying supply voltages, while voltage regulation allows driving the motor at a programmable lower terminal voltage to save energy. Motor speed can be controlled via either PWM at frequencies up to 200 kHz or via the I2C interface, which is also used for register-based configuration and diagnostics, including soft-start/stop, stall detection, and various limits. ### Onboard components | Manufacturer | Part | Datasheet | | --- | --- | --- | | Texas Instruments | `DRV8214` | https://mosaic-component-datasheets.s3.eu-north-1.amazonaws.com/14/Texas_Instruments-DRV8214.pdf | ### Power | Rail | Function | Direction | Min | Max | Max current | Pads | Required | | --- | --- | --- | --- | --- | --- | --- | --- | | system | | input | 1.8 | 5 | | +10, GND 1 | yes | | drive | | input | 1.8 | 11 | | +9, GND 1 | yes | ### Interfaces #### I2C (I2C) - Modes: slave - Addresses: `0x34` (default), `0x30`, `0x31`, `0x33` | Pad | Function | Role | Required | | --- | --- | --- | --- | | 4 | `I2C.CLK` | bus | yes | | 5 | `I2C.DAT` | bus | yes | ### Pad map | Pad | Default function | Type | Alternates | | --- | --- | --- | --- | | 1 | `GND` | power | — | | 2 | `EN` | digital | `IN1` | | 3 | `PH` | digital | `IN2` | | 4 | `I2C.CLK` | digital | — | | 5 | `I2C.DAT` | digital | — | | 6 | `NPROP` | analog | — | | 7 | `OUT2` | drive | — | | 8 | `OUT1` | drive | — | | 9 | `VM` | power | — | | 10 | `V+` | power | — | ### Application notes #### Integrated Current Sensing Integrated current sensing via an internal current mirror provides an analog output (NPROP) proportional to load current, with selectable gain for accurate sensing down to 10 mA. Current regulation limits motor current autonomously during stall or high-load events. #### I2C address variants Four different order-time variants exist for this tile, representing the four possible I2C addresses (0x30, 0x31, 0x33, 0x34), allowing up to four motors to be independently controlled on the same bus. --- Generated 2026-10-07 from the canonical tile definition (version 0.16). Canonical HTML: https://bergsonne.io/catalog/drive/dch · PDF: https://mosaic-datasheets.s3.eu-north-1.amazonaws.com/Drive-DC-H-a-0-16.pdf ============================================================================== ## Drive.H > haptic ERM/LRA driver | | | | --- | --- | | Tile | `Drive.H` | | Revision | `a` | | Family | Drive | | Package | T44 · 10 pads | | Dimensions | 4 mm × 4 mm | | Packaging | 12 mm embossed carrier tape (EIA-481), 4.3 × 4.3 × 1.8 mm pockets at 8 mm pitch, 500 per 7 in (178 mm) reel, pin 1 in quadrant 1, cold seal cover tape. Drawing: https://bergsonne.io/packages/T44-tape-reel.pdf | | Definition version | `0.5` | | Driver reference | https://bergsonne.io/docs/tiles/drive/h | | Datasheet (PDF) | https://mosaic-datasheets.s3.eu-north-1.amazonaws.com/Drive-H-a-0-5.pdf | | Canonical page | https://bergsonne.io/catalog/drive/h | ### Overview The Drive.H tile is designed for driving linear-resonant actuators (LRAs) or eccentric rotating-mass motors (ERMs) using the Texas Instruments DRV2605 IC.  Haptic signals can be generated either from an internal effects library, streamed in real time over I2C, or input as a high-speed pulse-width modulation (PWM) signal.  Additionally, an optional hardware trigger can be used to play back pre-configured effects.  Separate logic and motor supply connections allows for digital interfacing with logic down to 1.8V. ### Onboard components | Manufacturer | Part | Datasheet | | --- | --- | --- | | Texas Instruments | `DRV2605` | https://mosaic-component-datasheets.s3.eu-north-1.amazonaws.com/4/Texas_Instruments-DRV2605.pdf | ### Power | Rail | Function | Direction | Min | Max | Max current | Pads | Required | | --- | --- | --- | --- | --- | --- | --- | --- | | system | | input | 1.8 | 5 | | +10, GND 1 | yes | | drive | | input | 2.5 | 5.5 | | +9, GND 1 | yes | ### Interfaces #### I2C (I2C) - Modes: slave - Max clock: 400kHz - Addresses: `0x5A` (default) - Data bits: 8 | Pad | Function | Role | Required | | --- | --- | --- | --- | | 4 | `I2C.CLK` | bus | yes | | 5 | `I2C.DAT` | bus | yes | ### Pad map | Pad | Default function | Type | Alternates | | --- | --- | --- | --- | | 1 | `GND` | power | — | | 2 | `TRIG` | digital | — | | 3 | `EN` | digital | — | | 4 | `I2C.CLK` | digital | — | | 5 | `I2C.DAT` | digital | — | | 6 | `—` | — | — | | 7 | `OUT+` | drive | — | | 8 | `OUT-` | drive | — | | 9 | `V_MOTOR` | power | — | | 10 | `V+` | power | — | --- Generated 2026-10-07 from the canonical tile definition (version 0.5). Canonical HTML: https://bergsonne.io/catalog/drive/h · PDF: https://mosaic-datasheets.s3.eu-north-1.amazonaws.com/Drive-H-a-0-5.pdf ============================================================================== ## Drive.P > piezoelectric sensor/actuator driver | | | | --- | --- | | Tile | `Drive.P` | | Revision | `a` | | Family | Drive | | Package | T44 · 10 pads | | Dimensions | 4 mm × 4 mm × 1.8 mm | | Packaging | 12 mm embossed carrier tape (EIA-481), 4.3 × 4.3 × 1.8 mm pockets at 8 mm pitch, 500 per 7 in (178 mm) reel, pin 1 in quadrant 1, cold seal cover tape. Drawing: https://bergsonne.io/packages/T44-tape-reel.pdf | | Definition version | `0.8` | | Driver reference | https://bergsonne.io/docs/tiles/drive/p | | Datasheet (PDF) | https://mosaic-datasheets.s3.eu-north-1.amazonaws.com/Drive-P-a-0-8.pdf | | Canonical page | https://bergsonne.io/catalog/drive/p | ### Overview Built around the Boréas Technologies BOS1921 driver, the advanced DRIVE.P tile provides an ultra-compact, fully-integrated solution for interfacing with piezoelectric sensors and actuators. With a maximum peak-to-peak differential output amplitude of 190V, the ability to drive one or two capacitive loads up to a total of 820nF, a built-in waveform synthesizer and high-speed 1024-sample continuous-playback FIFO, as well as a sensing resolution of just 7.6mV, this tile provides an unparalleled level of integration for next-generation haptics and other piezoactuator solutions. ### Onboard components | Manufacturer | Part | Datasheet | | --- | --- | --- | | Boréas Technologies | `BOS1921` | https://mosaic-component-datasheets.s3.eu-north-1.amazonaws.com/5/Bor_as_Technologies-BOS1921.pdf | ### Power | Rail | Function | Direction | Min | Max | Max current | Pads | Required | | --- | --- | --- | --- | --- | --- | --- | --- | | system | | input | 1.8 | 5.5 | | +10, GND 1 | yes | | drive | | input | 3 | 5.5 | | +9, GND 1 | yes | ### Interfaces #### I2C (I2C) - Modes: slave - Max clock: 1MHz - Addresses: `0x44` (default) - Data bits: 8 | Pad | Function | Role | Required | | --- | --- | --- | --- | | 4 | `I2C.CLK` | bus | yes | | 5 | `I2C.DAT` | bus | yes | #### I3C (I3C) - Modes: slave | Pad | Function | Role | Required | | --- | --- | --- | --- | | 4 | `I3C.CLK` | bus | yes | | 5 | `I3C.DAT` | bus | yes | ### Pad map | Pad | Default function | Type | Alternates | | --- | --- | --- | --- | | 1 | `GND` | power | — | | 2 | `—` | — | — | | 3 | `GPIO` | digital | — | | 4 | `I2C.CLK` | digital | `I3C.CLK` | | 5 | `I2C.DAT` | digital | `I3C.DAT` | | 6 | `—` | — | — | | 7 | `OUT+` | drive | — | | 8 | `OUT-` | drive | — | | 9 | `V_DRIVE` | power | — | | 10 | `V+` | power | — | ### Application notes #### Single Piezo A single piezo connected across the OUT terminals can be used for both sensing and output, including bipolar output #### Dual Piezo Two piezos can be driven by in a unipolar configuration by connecting the positive lead of each piezo to each of the OUT pads, and the negative terminals to GND. Sensing is not possible in this configuration. --- Generated 2026-10-07 from the canonical tile definition (version 0.8). Canonical HTML: https://bergsonne.io/catalog/drive/p · PDF: https://mosaic-datasheets.s3.eu-north-1.amazonaws.com/Drive-P-a-0-8.pdf ============================================================================== ## Power.BB > multi-output buck-boost regulator | | | | --- | --- | | Tile | `Power.BB` | | Revision | `a` | | Family | Power | | Package | T44 · 10 pads | | Dimensions | 4 mm × 4 mm | | Packaging | 12 mm embossed carrier tape (EIA-481), 4.3 × 4.3 × 1.8 mm pockets at 8 mm pitch, 500 per 7 in (178 mm) reel, pin 1 in quadrant 1, cold seal cover tape. Drawing: https://bergsonne.io/packages/T44-tape-reel.pdf | | Definition version | `0.4` | | Datasheet (PDF) | https://mosaic-datasheets.s3.eu-north-1.amazonaws.com/Power-BB-a-0-4.pdf | | Canonical page | https://bergsonne.io/catalog/power/bb | ### Onboard components | Manufacturer | Part | Datasheet | | --- | --- | --- | | Texas Instruments | `TPS63901` | — | | Texas Instruments | `TPS7A20` | — | ### Power | Rail | Function | Direction | Min | Max | Max current | Pads | Required | | --- | --- | --- | --- | --- | --- | --- | --- | | system | | input | 1.8 | 5.5 | | +3, GND 1 | yes | | system | | output | 1.8 | 1.8 | | +10, GND 1 | no | | system | | output | 3.3 | 3.3 | | +9, GND 1 | no | | system | | output | 5 | 5 | | +8, GND 1 | no | ### Pad map | Pad | Default function | Type | Alternates | | --- | --- | --- | --- | | 1 | `GND` | power | — | | 2 | `` | | — | | 3 | `VIN` | power | — | | 4 | `—` | — | — | | 5 | `—` | — | — | | 6 | `` | | — | | 7 | `` | | — | | 8 | `5V0` | power | — | | 9 | `3V3` | power | — | | 10 | `1V8` | power | — | --- Generated 2026-10-07 from the canonical tile definition (version 0.4). Canonical HTML: https://bergsonne.io/catalog/power/bb · PDF: https://mosaic-datasheets.s3.eu-north-1.amazonaws.com/Power-BB-a-0-4.pdf ============================================================================== ## Power.L.1N > 1C Li-Ion charge manager w/ 1.8 & 3.3V out | | | | --- | --- | | Tile | `Power.L.1N` | | Revision | `a` | | Family | Power | | Package | T44 · 10 pads | | Dimensions | 4 mm × 4 mm | | Packaging | 12 mm embossed carrier tape (EIA-481), 4.3 × 4.3 × 1.8 mm pockets at 8 mm pitch, 500 per 7 in (178 mm) reel, pin 1 in quadrant 1, cold seal cover tape. Drawing: https://bergsonne.io/packages/T44-tape-reel.pdf | | Definition version | `0.12` | | Driver reference | https://bergsonne.io/docs/tiles/power/l1n | | Datasheet (PDF) | https://mosaic-datasheets.s3.eu-north-1.amazonaws.com/Power-L-1N-a-0-12.pdf | | Canonical page | https://bergsonne.io/catalog/power/l1n | ### Overview Single-cell Li-ion/Li-poly/LiFePO₄ battery charging and power management tile built around the Nordic nPM1300 PMIC. Provides both 1.8V and 3.3V regulated outputs via high-efficiency buck converters rated to 200mA each. An unregulated VSYS rail is also available for loads that require direct battery voltage or higher current. USB-C power input with CC pins exposed for connection detection. Supports charging currents from 32 to 800 mA for batteries up to 1000 mAh, and has three on-board LEDs for state observation. ### Onboard components | Manufacturer | Part | Datasheet | | --- | --- | --- | | Nordic Semiconductor | `NPM1300` | https://mosaic-component-datasheets.s3.eu-north-1.amazonaws.com/10/Nordic_Semiconductor-NPM1300.pdf | ### Power | Rail | Function | Direction | Min | Max | Max current | Pads | Required | | --- | --- | --- | --- | --- | --- | --- | --- | | system | LDO | output | 1.8 | 1.8 | 200mA | +10, GND 1 | no | | system | LDO | output | 3.3 | 3.3 | 200mA | +9, GND 1 | no | | rechargeable battery | BATT | input | 2.3 | 4.45 | 800mA (charge) | +7, GND 1 | yes | | charge | CHG | input | 3.4 | 5.5 | 1500mA | +6, GND 1 | no | | system | VSYS | output | 2.3 | 5.5 | 1340mA | +8, GND 1 | no | ### Interfaces #### I2C (I2C) - Modes: slave - Addresses: `0x6B` | Pad | Function | Role | Required | | --- | --- | --- | --- | | 4 | `I2C.CLK` | bus | no | | 5 | `I2C.DAT` | bus | no | ### Pad map | Pad | Default function | Type | Alternates | | --- | --- | --- | --- | | 1 | `GND` | power | — | | 2 | `CC1` | other | — | | 3 | `CC2` | other | — | | 4 | `I2C.CLK` | digital | — | | 5 | `I2C.DAT` | digital | — | | 6 | `CHG` | power | — | | 7 | `BATT` | power | — | | 8 | `VSYS` | power | — | | 9 | `3V3` | power | — | | 10 | `1V8` | power | — | ### Application notes #### Battery charging Battery charging, if desired, must be enabled via firmware. #### LEDs Three onboard LEDs (red, yellow, green) driven by the nPM1300's LED outputs provide visual indication of charging and system status. Red is connected to LED0 (default = error reporting), yellow to LED1 (default = charge indication), and green to LED2 (default = host control). The LED outputs must be enabled via firmware. #### CC pads for USB C The two CC pads can be connected to the CC lines of a standard USB C input to detect the source power capability. They have internal 5.1k pull-down resistors to conform to the standard for a sink device. Upon connection and detection, the source capability is reported in the USBCDETECTSTATUS register. #### Charging Current The default charging current limit is set to 100mA, and can be adjusted via firmware. --- Generated 2026-10-07 from the canonical tile definition (version 0.12). Canonical HTML: https://bergsonne.io/catalog/power/l1n · PDF: https://mosaic-datasheets.s3.eu-north-1.amazonaws.com/Power-L-1N-a-0-12.pdf ============================================================================== ## Power.L.1T > single-cell Li-Ion charge controller with adjustable LDO output | | | | --- | --- | | Tile | `Power.L.1T` | | Revision | `b` | | Family | Power | | Package | T44 · 10 pads | | Dimensions | 4 mm × 4 mm | | Packaging | 12 mm embossed carrier tape (EIA-481), 4.3 × 4.3 × 1.8 mm pockets at 8 mm pitch, 500 per 7 in (178 mm) reel, pin 1 in quadrant 1, cold seal cover tape. Drawing: https://bergsonne.io/packages/T44-tape-reel.pdf | | Definition version | `0.12` | | Driver reference | https://bergsonne.io/docs/tiles/power/l1t | | Datasheet (PDF) | https://mosaic-datasheets.s3.eu-north-1.amazonaws.com/Power-L-1T-b-0-12.pdf | | Canonical page | https://bergsonne.io/catalog/power/l1t | ### Overview Perfect for wearables and compact battery-powered devices, the Power.L1.T tile integrates a single-cell Lithium-Ion battery charger with a max charge current of 500mA with a programmable LDO output and a 12-bit ADC for battery and system monitoring. ### Onboard components | Manufacturer | Part | Datasheet | | --- | --- | --- | | Texas Instruments | `BQ25150` | https://mosaic-component-datasheets.s3.eu-north-1.amazonaws.com/6/Texas_Instruments-BQ25150.pdf | ### Power | Rail | Function | Direction | Min | Max | Max current | Pads | Required | | --- | --- | --- | --- | --- | --- | --- | --- | | system | | output | 0.6 | 3.7 | | +10, GND 1 | yes | | charge | | input | 3.4 | 5.5 | 500mA | +8, GND 9 | no | | rechargeable battery | single-cell Li-Ion battery | input | 3.6 | 4.6 | 500mA (charge) | +7, GND 6 | no | ### Interfaces #### I2C (I2C) - Modes: slave - Addresses: `0x6B` (default) - Data bits: 8 | Pad | Function | Role | Required | | --- | --- | --- | --- | | 4 | `I2C.CLK` | bus | yes | | 5 | `I2C.DAT` | bus | yes | ### Pad map | Pad | Default function | Type | Alternates | | --- | --- | --- | --- | | 1 | `GND` | power | — | | 2 | `LP` | digital | — | | 3 | `SW` | digital | — | | 4 | `I2C.CLK` | digital | — | | 5 | `I2C.DAT` | digital | — | | 6 | `BATT-` | power | — | | 7 | `BATT+` | power | — | | 8 | `SUPPLY+` | power | — | | 9 | `SUPPLY-` | power | — | | 10 | `V+` | power | — | ### Application notes #### Existing Ship Mode When a battery is first connected, the IC will enter the ultra-low-current ship mode, which it will exit when voltage is first applied to the SUPPLY pads. Ship mode can be re-entered either programmatically or by disconnecting/reconnecting the battery. #### Normally-On Operation When the SW pad is floating, the LDO output will be connected. Connect SW to GND/SUPPLY- to turn off the output. #### Low-Power Mode In the default low-power mode, the I2C interface is disabled. To exit low-power mode, drive the LP to greater than 1.35V. #### adjustable LDO output The LDO output can supply up to 150mA in a range from 0.6-3.7V in 0.1V steps. The power-on default output is set to 1.8V, and can be adjusted by writing to the LDOCTRL register. --- Generated 2026-10-07 from the canonical tile definition (version 0.12). Canonical HTML: https://bergsonne.io/catalog/power/l1t · PDF: https://mosaic-datasheets.s3.eu-north-1.amazonaws.com/Power-L-1T-b-0-12.pdf ============================================================================== ## Sense.CAM.P > monochrome QVGA camera | | | | --- | --- | | Tile | `Sense.CAM.P` | | Revision | `a` | | Family | Sense | | Package | T44 · 10 pads | | Dimensions | 4 mm × 4 mm | | Packaging | 12 mm embossed carrier tape (EIA-481), 4.3 × 4.3 × 1.8 mm pockets at 8 mm pitch, 500 per 7 in (178 mm) reel, pin 1 in quadrant 1, cold seal cover tape. Drawing: https://bergsonne.io/packages/T44-tape-reel.pdf | | Definition version | `0.8` | | Driver reference | https://bergsonne.io/docs/tiles/sense/camp | | Datasheet (PDF) | https://mosaic-datasheets.s3.eu-north-1.amazonaws.com/Sense-CAM-P-a-0-8.pdf | | Canonical page | https://bergsonne.io/catalog/sense/camp | ### Overview An ultra-low-power machine-vision tile built around the PixArt PAG7920J3, a 320 × 240 monochrome global-shutter camera module with an integrated lens. It captures 8-bit RAW frames over SPI (30 fps as configured; the chip's 180 fps is a parallel-bus figure), with 3.0 µm pixels, a 71.3° × 53.7° field of view, and 64 dB of dynamic range. Configuration is over I2C. A proprietary on-chip motion-detection mode draws under 100 µW (compared to 760 µW for QVGA output at 15 fps) making the tile practical to leave always-on in a battery-powered system, with auto exposure and gain control, auto black cancellation, mirror/flip, windowing, LED strobe, and multi-sensor frame sync built in. ### Onboard components | Manufacturer | Part | Datasheet | | --- | --- | --- | | PixArt | `PAG7920J3` | https://mosaic-component-datasheets.s3.eu-north-1.amazonaws.com/78/PixArt-PAG7920J3.pdf | | Texas Instruments | `TPS610995YFFT` | https://www.ti.com/lit/ds/symlink/tps61099.pdf | | Texas Instruments | `TPS7A2033PYCJR` | https://www.ti.com/lit/ds/symlink/tps7a20.pdf | ### Power | Rail | Function | Direction | Min | Max | Max current | Pads | Required | | --- | --- | --- | --- | --- | --- | --- | --- | | system | | input | 1.8 | 3.3 | 1.5mA | +10, GND 1 | yes | ### Interfaces #### I2C (I2C) - Modes: slave - Max clock: 1MHz - Addresses: `0x35` (default), `0x25`, `0x40` | Pad | Function | Role | Required | | --- | --- | --- | --- | | 4 | `I2C.CLK` | bus | yes | | 5 | `I2C.DAT` | bus | yes | | 3 | `GPIO2` | strap | no | #### SPI (SPI) - Modes: slave - Max clock: 25MHz | Pad | Function | Role | Required | | --- | --- | --- | --- | | 6 | `SPI.MISO` | bus | yes | | 7 | `SPI.CLK` | bus | yes | | 8 | `SPI.CS` | bus | yes | | 9 | `SPI.MOSI` | bus | yes | ### Pad map | Pad | Default function | Type | Alternates | | --- | --- | --- | --- | | 1 | `GND` | power | — | | 2 | `GPIO1` | digital | — | | 3 | `GPIO2` | digital | — | | 4 | `I2C.CLK` | digital | — | | 5 | `I2C.DAT` | digital | — | | 6 | `SPI.MISO` | digital | — | | 7 | `SPI.CLK` | digital | — | | 8 | `SPI.CS` | digital | — | | 9 | `SPI.MOSI` | digital | — | | 10 | `V+` | power | — | ### Application notes #### I2C address when GPIO2 (pad 3) is left floating, the I2C address will default to 0x35. Pulling GPIO2 (pad 3) high will change the address to 0x25, while grounding pad 3 will set it to 0x40. The camera will also respond to general broadcasts on 0x60. --- Generated 2026-10-07 from the canonical tile definition (version 0.8). Canonical HTML: https://bergsonne.io/catalog/sense/camp · PDF: https://mosaic-datasheets.s3.eu-north-1.amazonaws.com/Sense-CAM-P-a-0-8.pdf ============================================================================== ## Sense.CAP > capacitive touch controller | | | | --- | --- | | Tile | `Sense.CAP` | | Revision | `a` | | Family | Sense | | Package | T44 · 10 pads | | Dimensions | 4 mm × 4 mm | | Packaging | 12 mm embossed carrier tape (EIA-481), 4.3 × 4.3 × 1.8 mm pockets at 8 mm pitch, 500 per 7 in (178 mm) reel, pin 1 in quadrant 1, cold seal cover tape. Drawing: https://bergsonne.io/packages/T44-tape-reel.pdf | | Definition version | `0.1` | | Driver reference | https://bergsonne.io/docs/tiles/sense/cap | | Datasheet (PDF) | https://mosaic-datasheets.s3.eu-north-1.amazonaws.com/Sense-CAP-a-0-1.pdf | | Canonical page | https://bergsonne.io/catalog/sense/cap | ### Onboard components | Manufacturer | Part | Datasheet | | --- | --- | --- | | Azoteq | `IQS7211A` | — | ### Power | Rail | Function | Direction | Min | Max | Max current | Pads | Required | | --- | --- | --- | --- | --- | --- | --- | --- | | system | | input | 1.8 | 3.3 | | +10, GND 1 | yes | ### Interfaces #### I2C (I2C) - Modes: slave - Max clock: 1MHz - Addresses: `0x56` (default) | Pad | Function | Role | Required | | --- | --- | --- | --- | | 4 | `I2C.CLK` | bus | yes | | 5 | `I2C.DAT` | bus | yes | ### Pad map | Pad | Default function | Type | Alternates | | --- | --- | --- | --- | | 1 | `GND` | power | — | | 2 | `RX3` | analog | — | | 3 | `/MCLR` | digital | — | | 4 | `I2C.CLK` | digital | — | | 5 | `I2C.DAT` | digital | — | | 6 | `TX11` | analog | — | | 7 | `TX9/TX10` | analog | — | | 8 | `TX8` | analog | — | | 9 | `RX6` | analog | — | | 10 | `V+` | power | — | --- Generated 2026-10-07 from the canonical tile definition (version 0.1). Canonical HTML: https://bergsonne.io/catalog/sense/cap · PDF: https://mosaic-datasheets.s3.eu-north-1.amazonaws.com/Sense-CAP-a-0-1.pdf ============================================================================== ## Sense.HR > heart rate, pulse oximetry, and hydration | | | | --- | --- | | Tile | `Sense.HR` | | Revision | `a` | | Family | Sense | | Package | T44 · 10 pads | | Dimensions | 4 mm × 4 mm | | Packaging | 12 mm embossed carrier tape (EIA-481), 4.3 × 4.3 × 1.8 mm pockets at 8 mm pitch, 500 per 7 in (178 mm) reel, pin 1 in quadrant 1, cold seal cover tape. Drawing: https://bergsonne.io/packages/T44-tape-reel.pdf | | Definition version | `0.11` | | Driver reference | https://bergsonne.io/docs/tiles/sense/hr | | Datasheet (PDF) | https://mosaic-datasheets.s3.eu-north-1.amazonaws.com/Sense-HR-a-0-11.pdf | | Canonical page | https://bergsonne.io/catalog/sense/hr | ### Overview The fully-integrated Sense.HR optical heart-rate and SpO₂ sensor combines the advanced Analog Devices MAX86174A photoplethysmography (PPG) front end with dual Vishay VEMD1060X01 silicon PIN photodiodes and four LEDs (two green, one red, one IR) for multi-wavelength reflective optical sensing. The 20-bit ADC with a 256-word FIFO provides data at configurable rates from 1 to 2048 frames per second for external processing. ### Onboard components | Manufacturer | Part | Datasheet | | --- | --- | --- | | Analog Device | `MAX86174A` | https://mosaic-component-datasheets.s3.eu-north-1.amazonaws.com/21/Analog_Device-MAX86174A.pdf | | Vishay Semiconductors | `VEMD1060X01` | https://mosaic-component-datasheets.s3.eu-north-1.amazonaws.com/21/unknown-VEMD1060X01.pdf | | Inolux | `IN-S42CTQHIR` | — | | Inolux | `IN-S42ATR` | — | | Inolux | `IN-S42ATG` | — | ### Power | Rail | Function | Direction | Min | Max | Max current | Pads | Required | | --- | --- | --- | --- | --- | --- | --- | --- | | system | | input | 1.7 | 2 | 0.65mA | +10, GND 1 | yes | | system | | input | 2.7 | 5.5 | 100mA (pulsed) | +9, GND 1 | yes | ### Interfaces #### I2C (I2C) - Modes: slave - Max clock: 400kHz - Addresses: `0x6B` (default), `0x6A` | Pad | Function | Role | Required | | --- | --- | --- | --- | | 4 | `I2C.CLK` | bus | yes | | 5 | `I2C.DAT` | bus | yes | #### SPI (SPI) - Modes: slave - Max clock: 20MHz | Pad | Function | Role | Required | | --- | --- | --- | --- | | 2 | `SPI.MISO` | bus | no | | 3 | `SPI.CS` | other | no | | 4 | `SPI.CLK` | bus | no | | 5 | `SPI.MOSI` | bus | no | ### Pad map | Pad | Default function | Type | Alternates | | --- | --- | --- | --- | | 1 | `GND` | power | — | | 2 | `I2C.AD0` | digital | `SPI.MISO` | | 3 | `I2C.EN` | digital | `SPI.CS` | | 4 | `I2C.CLK` | digital | `SPI.CLK` | | 5 | `I2C.DAT` | digital | `SPI.MOSI` | | 6 | `—` | — | — | | 7 | `—` | — | — | | 8 | `INTB` | digital | — | | 9 | `VLED` | power | — | | 10 | `V+` | power | — | --- Generated 2026-10-07 from the canonical tile definition (version 0.11). Canonical HTML: https://bergsonne.io/catalog/sense/hr · PDF: https://mosaic-datasheets.s3.eu-north-1.amazonaws.com/Sense-HR-a-0-11.pdf ============================================================================== ## Sense.I.6P6 > extended-range 6-DOF IMU | | | | --- | --- | | Tile | `Sense.I.6P6` | | Revision | `a` | | Family | Sense | | Package | T44 · 10 pads | | Dimensions | 4 mm × 4 mm | | Packaging | 12 mm embossed carrier tape (EIA-481), 4.3 × 4.3 × 1.8 mm pockets at 8 mm pitch, 500 per 7 in (178 mm) reel, pin 1 in quadrant 1, cold seal cover tape. Drawing: https://bergsonne.io/packages/T44-tape-reel.pdf | | Definition version | `0.15` | | Driver reference | https://bergsonne.io/docs/tiles/sense/i6p6 | | Datasheet (PDF) | https://mosaic-datasheets.s3.eu-north-1.amazonaws.com/Sense-I-6P6-a-0-15.pdf | | Canonical page | https://bergsonne.io/catalog/sense/i6p6 | ### Overview The Sense.I.6P6 is a precision 6-DOF inertial measurement unit based on the extended-range TDK Invensense ICM-42686P. The 16-bit accelerometer full-scale range can be set to +/- 2g, 4g, 8g, 16g, or 32g with an output data rate of up between 1.5Hz and 32kHz, while the 16-bit gyro range can be set to +/- 15.6, 31.5, 62.5, 125, 250, 500, 1000, 2000, or 4000 degrees per second with an output data rate of 12.5Hz and 32kHz. The sensor includes a user-programmable filters, on-board motion functions (pedometer, tilt, tap, raise, etc.), and a 2kB FIFO. ### Onboard components | Manufacturer | Part | Datasheet | | --- | --- | --- | | TDK Invensense | `ICM-42686P` | https://mosaic-component-datasheets.s3.eu-north-1.amazonaws.com/22/TDK_Invensense-ICM-42686P.pdf | ### Power | Rail | Function | Direction | Min | Max | Max current | Pads | Required | | --- | --- | --- | --- | --- | --- | --- | --- | | system | | input | 1.8 | 3.6 | | +10, GND 1 | yes | ### Interfaces #### I2C (I2C) - Modes: slave - Addresses: `0x69` (default), `0x68` | Pad | Function | Role | Required | | --- | --- | --- | --- | | 4 | `I2C.CLK` | bus | yes | | 5 | `I2C.DAT` | bus | yes | | 2 | `I2C.AD0` | strap | no | | 3 | `I2C.EN` | strap | no | #### I3C (I3C) - Modes: slave - Max clock: 12.5MHz (SDR) | Pad | Function | Role | Required | | --- | --- | --- | --- | | 4 | `I3C.CLK` | bus | yes | | 5 | `I3C.DAT` | bus | yes | | 3 | `I3C.EN` | strap | no | #### SPI (SPI) - Modes: slave - Max clock: 24MHz | Pad | Function | Role | Required | | --- | --- | --- | --- | | 2 | `SPI.MISO` | bus | yes | | 3 | `SPI.CS` | other | yes | | 4 | `SPI.CLK` | bus | yes | | 5 | `SPI.MOSI` | bus | yes | ### Pad map | Pad | Default function | Type | Alternates | | --- | --- | --- | --- | | 1 | `GND` | power | — | | 2 | `I2C.AD0` | digital | `SPI.MISO` | | 3 | `I2C.EN` | digital | `SPI.CS` | | 4 | `I2C.CLK` | digital | `SPI.CLK`, `I3C.CLK` | | 5 | `I2C.DAT` | digital | `SPI.MOSI`, `I3C.DAT` | | 6 | `—` | — | — | | 7 | `—` | — | — | | 8 | `INT2` | digital | — | | 9 | `INT1` | digital | — | | 10 | `V+` | power | — | ### Application notes #### Sensor Axes The Z axis of the IMU protrudes through the center of the tile, while the XY plane is approximately 1.0mm from the bottom surface of the tile, with the X axis pointing toward the left side (pads 1-5), as shown. --- Generated 2026-10-07 from the canonical tile definition (version 0.15). Canonical HTML: https://bergsonne.io/catalog/sense/i6p6 · PDF: https://mosaic-datasheets.s3.eu-north-1.amazonaws.com/Sense-I-6P6-a-0-15.pdf ============================================================================== ## Sense.M.3G > triaxial geomagnetic magnetometer | | | | --- | --- | | Tile | `Sense.M.3G` | | Revision | `a` | | Family | Sense | | Package | T44 · 10 pads | | Dimensions | 4 mm × 4 mm | | Packaging | 12 mm embossed carrier tape (EIA-481), 4.3 × 4.3 × 1.8 mm pockets at 8 mm pitch, 500 per 7 in (178 mm) reel, pin 1 in quadrant 1, cold seal cover tape. Drawing: https://bergsonne.io/packages/T44-tape-reel.pdf | | Definition version | `0.4` | | Driver reference | https://bergsonne.io/docs/tiles/sense/m3g | | Datasheet (PDF) | https://mosaic-datasheets.s3.eu-north-1.amazonaws.com/Sense-M-3G-a-0-4.pdf | | Canonical page | https://bergsonne.io/catalog/sense/m3g | ### Overview The Sense.M.3G is a three-axis geomagnetic sensor built on the Bosch Sensortec BMM350. It measures fields up to ±2000 µT per axis with about 190 nT rms noise, which resolves the Earth's 25-65 µT field for compass headings, and reads nearby magnets well beyond it. Output rates run from 1.56 Hz to 400 Hz, with averaging to trade current for noise, and the part keeps a factory trim for offset, sensitivity, temperature drift and cross-axis coupling that the driver applies. A single measurement on demand, a data-ready interrupt and a 1.8 µA suspend mode suit battery-powered projects. ### Onboard components | Manufacturer | Part | Datasheet | | --- | --- | --- | | Bosch Sensortec | `BMM350` | — | | Texas Instruments | `TPS7A2018PYCKR` | https://www.ti.com/lit/ds/symlink/tps7a20.pdf | ### Power | Rail | Function | Direction | Min | Max | Max current | Pads | Required | | --- | --- | --- | --- | --- | --- | --- | --- | | system | | input | 1.72 | 3.6 | 0.5mA | +10, GND 1 | yes | ### Interfaces #### I2C (I2C) - Modes: slave - Max clock: 1MHz - Addresses: `0x14` (default) | Pad | Function | Role | Required | | --- | --- | --- | --- | | 4 | `I2C.CLK` | bus | yes | | 5 | `I2C.DAT` | bus | yes | ### Pad map | Pad | Default function | Type | Alternates | | --- | --- | --- | --- | | 1 | `GND` | power | — | | 2 | `—` | — | — | | 3 | `—` | — | — | | 4 | `I2C.CLK` | digital | — | | 5 | `I2C.DAT` | digital | — | | 6 | `—` | — | — | | 7 | `—` | — | — | | 8 | `—` | — | — | | 9 | `INT` | digital | — | | 10 | `V+` | power | — | --- Generated 2026-10-07 from the canonical tile definition (version 0.4). Canonical HTML: https://bergsonne.io/catalog/sense/m3g · PDF: https://mosaic-datasheets.s3.eu-north-1.amazonaws.com/Sense-M-3G-a-0-4.pdf ============================================================================== ## Sense.MIC > I2C-output MEMS microphone | | | | --- | --- | | Tile | `Sense.MIC` | | Revision | `a` | | Family | Sense | | Package | T44 · 10 pads | | Dimensions | 4 mm × 4 mm | | Packaging | 12 mm embossed carrier tape (EIA-481), 4.3 × 4.3 × 1.8 mm pockets at 8 mm pitch, 500 per 7 in (178 mm) reel, pin 1 in quadrant 1, cold seal cover tape. Drawing: https://bergsonne.io/packages/T44-tape-reel.pdf | | Definition version | `0.7` | | Driver reference | https://bergsonne.io/docs/tiles/sense/mic | | Datasheet (PDF) | https://mosaic-datasheets.s3.eu-north-1.amazonaws.com/Sense-MIC-a-0-7.pdf | | Canonical page | https://bergsonne.io/catalog/sense/mic | ### Overview The Sense.MIC pairs a Same Sky CMM-2718AT-38164W-TR analog MEMS microphone (omnidirectional, 20 Hz to 20 kHz, −38 dBV/Pa, 64 dBA SNR, 128 dB SPL overload) with an Analog Devices AD8605 amplifier and a Maxim MAX11645 12-bit ADC read over I2C (up to 1.7 MHz). The amplified microphone signal is also brought out to pad 8. Suited to sound-level monitoring and event detection: claps, knocks, voice presence, quiet versus busy. ### Onboard components | Manufacturer | Part | Datasheet | | --- | --- | --- | | Maxim | `MAX11645` | https://mosaic-component-datasheets.s3.eu-north-1.amazonaws.com/19/unknown-MAX11645.pdf | | Same Sky | `CMM-2718AT-38164W-TR` | https://www.sameskydevices.com/product/resource/cmm-2718at-38164w-tr.pdf | | Analog Devices | `AD8605` | https://www.analog.com/media/en/technical-documentation/data-sheets/AD8605_8606_8608.pdf | ### Power | Rail | Function | Direction | Min | Max | Max current | Pads | Required | | --- | --- | --- | --- | --- | --- | --- | --- | | system | | input | 2.7 | 3.6 | 2mA | +10, GND 1 | yes | ### Interfaces #### I2C (I2C) - Modes: slave - Max clock: 1.7MHz - Addresses: `0x36` (default) | Pad | Function | Role | Required | | --- | --- | --- | --- | | 4 | `I2C.CLK` | bus | yes | | 5 | `I2C.DAT` | bus | yes | ### Pad map | Pad | Default function | Type | Alternates | | --- | --- | --- | --- | | 1 | `GND` | power | — | | 2 | `—` | — | — | | 3 | `—` | — | — | | 4 | `I2C.CLK` | digital | — | | 5 | `I2C.DAT` | digital | — | | 6 | `—` | — | — | | 7 | `—` | — | — | | 8 | `AOUT` | analog | — | | 9 | `—` | — | — | | 10 | `V+` | power | — | --- Generated 2026-10-07 from the canonical tile definition (version 0.7). Canonical HTML: https://bergsonne.io/catalog/sense/mic · PDF: https://mosaic-datasheets.s3.eu-north-1.amazonaws.com/Sense-MIC-a-0-7.pdf ============================================================================== ## Sense.T.C > capacitive touch | | | | --- | --- | | Tile | `Sense.T.C` | | Revision | `a` | | Family | Sense | | Package | T44 · 10 pads | | Dimensions | 4 mm × 4 mm | | Packaging | 12 mm embossed carrier tape (EIA-481), 4.3 × 4.3 × 1.8 mm pockets at 8 mm pitch, 500 per 7 in (178 mm) reel, pin 1 in quadrant 1, cold seal cover tape. Drawing: https://bergsonne.io/packages/T44-tape-reel.pdf | | Definition version | `0.8` | | Driver reference | https://bergsonne.io/docs/tiles/sense/tc | | Datasheet (PDF) | https://mosaic-datasheets.s3.eu-north-1.amazonaws.com/Sense-T-C-a-0-8.pdf | | Canonical page | https://bergsonne.io/catalog/sense/tc | ### Overview Built around the Azoteq IQS323 ProxFusion controller, the Sense.T.C tile's entire top surface functions as a capacitive touch sensor, while a second input channel is available via one of the pads. The IQS323 provides both proximity and touch detection with all signal processing on-chip, exposing processed results over I2C. Each channel reports 16-bit filtered counts, a 16-bit long-term average baseline, and binary proximity/touch states with configurable thresholds, debounce, and hysteresis. When the surface and the pad 8 electrode are configured together as a slider, the controller provides a 16-bit position output with on-chip gesture recognition including tap, swipe, flick, and hold. Report rates are configurable per power mode, from every 16ms in normal self-capacitive mode (125µA average current consumption) up to every 160 ms in the 4 µA ultra-low-power mode. In addition, event-driven reporting can leave the host free between changes in state. ### Onboard components | Manufacturer | Part | Datasheet | | --- | --- | --- | | Azoteq | `IQS323` | https://mosaic-component-datasheets.s3.eu-north-1.amazonaws.com/18/Azoteq-IQ323.pdf | ### Power | Rail | Function | Direction | Min | Max | Max current | Pads | Required | | --- | --- | --- | --- | --- | --- | --- | --- | | system | | input | 1.71 | 3.5 | 0.13mA | +10, GND 1 | yes | ### Interfaces #### I2C (I2C) - Modes: slave - Max clock: 1MHz - Addresses: `0x44` (default) | Pad | Function | Role | Required | | --- | --- | --- | --- | | 4 | `I2C.CLK` | bus | no | | 5 | `I2C.DAT` | bus | no | ### Pad map | Pad | Default function | Type | Alternates | | --- | --- | --- | --- | | 1 | `GND` | power | — | | 2 | `—` | — | — | | 3 | `RDY` | digital | — | | 4 | `I2C.CLK` | digital | — | | 5 | `I2C.DAT` | digital | — | | 6 | `—` | — | — | | 7 | `—` | — | — | | 8 | `C0` | analog | — | | 9 | `—` | — | — | | 10 | `V+` | power | — | ### Application notes #### Sensing Surface The entire top surface of the tile connected to the C1 sensor input with a 470-ohm series resistor. #### Secondary Input The C0 input on the IQS323 is directly connected to pad 8 of the tile, allowing for user configuration of a secondary capacitive touch input. --- Generated 2026-10-07 from the canonical tile definition (version 0.8). Canonical HTML: https://bergsonne.io/catalog/sense/tc · PDF: https://mosaic-datasheets.s3.eu-north-1.amazonaws.com/Sense-T-C-a-0-8.pdf ============================================================================== ## Sense.TOF > time-of-flight distance | | | | --- | --- | | Tile | `Sense.TOF` | | Revision | `a` | | Family | Sense | | Package | T44 · 10 pads | | Dimensions | 4 mm × 4 mm | | Packaging | 12 mm embossed carrier tape (EIA-481), 4.3 × 4.3 × 1.8 mm pockets at 8 mm pitch, 500 per 7 in (178 mm) reel, pin 1 in quadrant 1, cold seal cover tape. Drawing: https://bergsonne.io/packages/T44-tape-reel.pdf | | Definition version | `0.12` | | Driver reference | https://bergsonne.io/docs/tiles/sense/tof | | Datasheet (PDF) | https://mosaic-datasheets.s3.eu-north-1.amazonaws.com/Sense-TOF-a-0-12.pdf | | Canonical page | https://bergsonne.io/catalog/sense/tof | ### Overview The Sense.TOF is a single-zone time-of-flight distance sensor based on the ams-Osram TMF8806, which uses a Class-1 eye-safe 940 nm VCSEL laser emitter with SPAD detection. It provides 1-mm sensing resolution over a range from 1 centimeter to 5 meters (extendable to 10 meters) with a max output data rate of 30Hz. The sensor features on-chip histogram processing, a 30-degree field of view (52 degrees below 20 cm), and pre-installed firmware suitable for obstacle detection, proximity sensing, collision avoidance, and autofocus applications. ### Onboard components | Manufacturer | Part | Datasheet | | --- | --- | --- | | ams-Osram | `TMF8806` | https://mosaic-component-datasheets.s3.eu-north-1.amazonaws.com/17/ams-Osram-TMF8806.pdf | ### Power | Rail | Function | Direction | Min | Max | Max current | Pads | Required | | --- | --- | --- | --- | --- | --- | --- | --- | | system | | input | 2.7 | 3.5 | 33mA | +10, GND 1 | yes | ### Interfaces #### I2C (I2C) - Modes: slave - Max clock: 1MHz - Addresses: `0x41` (default) | Pad | Function | Role | Required | | --- | --- | --- | --- | | 4 | `I2C.CLK` | bus | no | | 5 | `I2C.DAT` | bus | no | ### Pad map | Pad | Default function | Type | Alternates | | --- | --- | --- | --- | | 1 | `GND` | power | — | | 2 | `—` | — | — | | 3 | `GPIO` | digital | — | | 4 | `I2C.CLK` | digital | — | | 5 | `I2C.DAT` | digital | — | | 6 | `—` | — | — | | 7 | `—` | — | — | | 8 | `—` | — | — | | 9 | `INT` | digital | — | | 10 | `V+` | power | — | --- Generated 2026-10-07 from the canonical tile definition (version 0.12). Canonical HTML: https://bergsonne.io/catalog/sense/tof · PDF: https://mosaic-datasheets.s3.eu-north-1.amazonaws.com/Sense-TOF-a-0-12.pdf ============================================================================== ## Store.O.128 > 128Mbit NOR flash | | | | --- | --- | | Tile | `Store.O.128` | | Revision | `a` | | Family | Store | | Package | T44 · 10 pads | | Dimensions | 4 mm × 4 mm | | Packaging | 12 mm embossed carrier tape (EIA-481), 4.3 × 4.3 × 1.8 mm pockets at 8 mm pitch, 500 per 7 in (178 mm) reel, pin 1 in quadrant 1, cold seal cover tape. Drawing: https://bergsonne.io/packages/T44-tape-reel.pdf | | Definition version | `0.10` | | Driver reference | https://bergsonne.io/docs/tiles/store/o128 | | Datasheet (PDF) | https://mosaic-datasheets.s3.eu-north-1.amazonaws.com/Store-O-128-a-0-10.pdf | | Canonical page | https://bergsonne.io/catalog/store/o128 | ### Overview The Store.M contains 128Mbits of NOR flash memory for non-volatile code and data storage with support for single, dual, or quad SPI communications at bus-clock rates of up to 104MHz. ### Onboard components | Manufacturer | Part | Datasheet | | --- | --- | --- | | Renesas | `AT25QL128A` | https://mosaic-component-datasheets.s3.eu-north-1.amazonaws.com/15/Renesas-AT25QL128A.pdf | ### Power | Rail | Function | Direction | Min | Max | Max current | Pads | Required | | --- | --- | --- | --- | --- | --- | --- | --- | | system | | input | 1.71 | 2 | | +10, GND 1 | yes | ### Interfaces #### SPI (SPI) - Modes: slave - Max clock: 104MHz | Pad | Function | Role | Required | | --- | --- | --- | --- | | 2 | `SPI.MOSI` | bus | yes | | 3 | `SPI.CS` | other | yes | | 4 | `SPI.CLK` | bus | yes | | 5 | `SPI.MISO` | bus | yes | #### QSPI (QSPI) - Modes: slave - Max clock: 104MHz | Pad | Function | Role | Required | | --- | --- | --- | --- | | 2 | `QSPI.IO0` | bus | no | | 3 | `QSPI.CS` | other | no | | 4 | `QSPI.CLK` | bus | no | | 5 | `QSPI.IO1` | bus | no | | 6 | `QSPI.IO2` | bus | no | | 9 | `QSPI.IO3` | bus | no | ### Pad map | Pad | Default function | Type | Alternates | | --- | --- | --- | --- | | 1 | `GND` | power | — | | 2 | `SPI.MOSI` | digital | `QSPI.IO0` | | 3 | `SPI.CS` | digital | `QSPI.CS` | | 4 | `SPI.CLK` | digital | `QSPI.CLK` | | 5 | `SPI.MISO` | digital | `QSPI.IO1` | | 6 | `WP` | digital | `QSPI.IO2` | | 7 | `—` | — | — | | 8 | `—` | — | — | | 9 | `HOLD` | digital | `QSPI.IO3` | | 10 | `V+` | power | — | ### Application notes #### --- Generated 2026-10-07 from the canonical tile definition (version 0.10). Canonical HTML: https://bergsonne.io/catalog/store/o128 · PDF: https://mosaic-datasheets.s3.eu-north-1.amazonaws.com/Store-O-128-a-0-10.pdf ============================================================================== ## Display.RGBW driver > RGBW LED driver for the Display.RGBW tile (LP5811). | | | | --- | --- | | Tile | `Display.RGBW` | | Driver version | `2.5.0` | | Header | `tile_display_rgbw.h` | | Generated from | `tiles@afe442f` | | Canonical page | https://bergsonne.io/docs/tiles/display/rgbw | ### Overview @version 2.5.0 4-channel LED driver with independent PWM + current control. Channels: R (LED0), W (LED1), G (LED2), B (LED3): OUT1 (ball C1) drives white and OUT3 (ball D2) blue (tile schematic; confirmed on the board 2026-09-24). Before 2.5.0 the driver had blue and white swapped. Version history: v2.4.1 — Documentation only, no behavior change. init()'s own doc block still claimed "current limits to 50%" after v2.4.0 changed it to ~1 mA; @version had been stale at 2.1.0 since v2.2.0, and the generator emits it as the docs page's first line; and the eight-address note added in v2.4.0 sat in the file blurb, which the docs site does not render, so it reached no reader. Moved to find(). v2.4.0 — Safe-by-default drive. init() now leaves the per-channel current limit at LP5811_DC_DEFAULT (~1 mA at full PWM) instead of 0x80 (~25.6 mA). The old default was both painful to look at on a bare, undiffused LED and roughly 2x above the level at which an abrupt switch-on browns out a bench fixture. Raise it with set_current() when you actually want the light. Also documents the PWM > 25 precondition on LOD/LSD, and corrects the multi-address note below (page bits vs variant bits). v2.3.0 — init() ramps the boost 3.0 -> 4.5 V in 0.1 V committed steps instead of one slam. The single-step commit's inrush can brown-out a marginal supply (long leads, loaded USB rail) and reset the host MCU. v2.1.0 — Tier-2 idiomatic helpers (set_color, pulse, breathe, flash, is_faulted) + section= tagging for Coverage Table. v2.0.0 — Initial tier-1 surface (set, off, current, faults). ### Examples #### Quick start ```c tile_t led; tile_display_rgbw_init(&hal, 0, &led, NULL); tile_display_rgbw_set_color(&led, 255, 0, 0); // red tile_display_rgbw_pulse(&led, 0, 255, 0, 200); // 200ms green flash tile_display_rgbw_off(&led); // all off ``` ### API 31 public functions. Functions marked **Studio** are callable from the Studio behaviour DSL. #### Lifecycle ##### `tile_display_rgbw_sleep` — Studio ```c void tile_display_rgbw_sleep(tile_t *tile) ``` Enter sleep (disable chip). ##### `tile_display_rgbw_wake` — Studio ```c void tile_display_rgbw_wake(tile_t *tile) ``` Wake (re-enable chip, LEDs retain previous state). ##### `tile_display_rgbw_reset` — Studio ```c void tile_display_rgbw_reset(tile_t *tile) ``` Software reset. Must call init() again after. #### Runtime ##### `tile_display_rgbw_set` — Studio ```c void tile_display_rgbw_set(tile_t *tile, uint8_t r, uint8_t g, uint8_t b, uint8_t w) ``` Set RGBW output levels. - `r` — [0..255] Red PWM. - `g` — [0..255] Green PWM. - `b` — [0..255] Blue PWM. - `w` — [0..255] White PWM. ##### `tile_display_rgbw_off` — Studio ```c void tile_display_rgbw_off(tile_t *tile) ``` Turn all LEDs off (PWM = 0). ##### `tile_display_rgbw_set_current` — Studio ```c void tile_display_rgbw_set_current(tile_t *tile, uint8_t r, uint8_t g, uint8_t b, uint8_t w) ``` Set per-channel current limit. Peak sink current = full_scale * code / 255 (datasheet Eq. 4), where full_scale is 51 mA after init (see set_max_current()). The same code is written to Manual_DC (manual mode) and Auto_DC (autonomous animation mode, registers 0x50-0x53, reset 0), so animations run at the same current as set() / set_color(). continuous per die, 20 mA pulsed at 1/10 duty (LED datasheet, Absolute Maximum Ratings). At the 51 mA full scale that is code 25; at 25.5 mA it is code 50. Codes above that, with PWM near 255, exceed the LED's rating. - `r` — [0..255] Red current (fraction of full-scale max). - `g` — [0..255] Green current. - `b` — [0..255] Blue current. - `w` — [0..255] White current. ##### `tile_display_rgbw_read_faults` — Studio ```c void tile_display_rgbw_read_faults(tile_t *tile, disp_rgbw_faults_t *out) ``` Read the per-channel open / short / thermal fault state. Pulls TSD_Config_Status, LOD_Status_0, and LSD_Status_0 from the chip's page-3 register space (the LP5811 multiplexes register pages onto two extra address bits — the driver handles this transparently). Faults latch in the chip until cleared via `clear_faults()`. Open-circuit threshold (VLOD_TH) is fixed by the chip at ~70 mV (25.5 mA mode) or ~180 mV (51 mA mode). Short-circuit threshold (VLSD_TH) is configurable via `set_short_threshold()`. LP5811_PWM_FAULT_DETECT_MIN (25) — datasheet §7.3.5.3 and §7.3.5.4. A channel that is dark, or driven at a very low duty, reports NO fault whether or not one exists. Light the channel you care about, give it a moment to settle, and read faults while it is still lit. Reading them on an idle part returns a clean bill of health that means nothing. - `out` — Caller-allocated fault snapshot (zeroed on entry) ##### `tile_display_rgbw_clear_faults` — Studio ```c void tile_display_rgbw_clear_faults(tile_t *tile) ``` Clear all latched LED open / short / TSD fault flags. Writes 0x07 to Fault_Clear (W1C — write 1 to clear). After this call, `read_faults()` reflects only currently-active faults. ##### `tile_display_rgbw_set_color` — Studio ```c void tile_display_rgbw_set_color(tile_t *tile, uint8_t r, uint8_t g, uint8_t b) ``` Set a solid RGB colour (W channel forced off). Convenience wrapper around @ref tile_display_rgbw_set for the common case where only the RGB channels matter. White is held at zero — drop to `set()` directly if you want to mix white in. - `r` — Red PWM [0..255] - `g` — Green PWM [0..255] - `b` — Blue PWM [0..255] ##### `tile_display_rgbw_pulse` — Studio ```c void tile_display_rgbw_pulse(tile_t *tile, uint8_t r, uint8_t g, uint8_t b, uint16_t ms) ``` Show a colour for `ms` milliseconds, then turn off. Simple alert / acknowledgement idiom. Blocking — the call returns after `ms` has elapsed and the LEDs have been turned back off. White is forced off (RGB-only); use @ref tile_display_rgbw_set + manual delay if you need full RGBW pulse control. - `r` — Red PWM [0..255] - `g` — Green PWM [0..255] - `b` — Blue PWM [0..255] - `ms` — Duration in milliseconds ##### `tile_display_rgbw_breathe` — Studio ```c void tile_display_rgbw_breathe(tile_t *tile, uint8_t r, uint8_t g, uint8_t b, uint16_t period_ms) ``` Continuous fade in/out (one breath cycle). The "thinking" indicator. Plays a single up-and-down PWM ramp over `period_ms` (i.e., dark → peak → dark = one full breath). Loop in the caller for sustained breathing. Implementation is a software loop; for a breathe that runs without the MCU, use tile_display_rgbw_breathe_auto(), which programs the on-chip animation engine (AEU). - `r` — Peak red PWM [0..255] - `g` — Peak green PWM [0..255] - `b` — Peak blue PWM [0..255] - `period_ms` — One full breath duration in milliseconds (>= 64 recommended) **Note:** Blocking. Spends `period_ms` in `delay_ms()`. Call from a dedicated task or accept the stall — the function does not yield to other peripherals. **Note:** White is forced off (RGB-only). Step granularity is fixed at 32 PWM levels per half-cycle; with `period_ms < 64` the delay-per-step rounds to 1 ms and the breath becomes choppy. ##### `tile_display_rgbw_flash` — Studio ```c void tile_display_rgbw_flash(tile_t *tile, uint8_t r, uint8_t g, uint8_t b, uint8_t count) ``` N quick on/off blinks at the given colour. Notification idiom — alarm, error indication, "got it" ack. Each blink is 100 ms on, 100 ms off (200 ms per cycle). White is forced off (RGB-only). Leaves the LEDs off after the final blink. - `r` — Red PWM [0..255] - `g` — Green PWM [0..255] - `b` — Blue PWM [0..255] - `count` — Number of on/off cycles (1..255) **Note:** Blocking. Total runtime is approximately `count * 200` ms. Call from a dedicated task or accept the stall. ##### `tile_display_rgbw_is_faulted` — Studio ```c uint8_t tile_display_rgbw_is_faulted(tile_t *tile) ``` Quick yes/no on whether any LP5811 fault is currently latched. Wraps @ref tile_display_rgbw_read_faults and returns 1 if any fault bit is set — open-circuit (LOD), short-circuit (LSD), thermal shutdown (TSD), or configuration error. For per-channel detail (which LED is open / shorted), use `read_faults()` directly. Faults stay latched until cleared via @ref tile_display_rgbw_clear_faults. **Returns:** 1 if any fault bit is set, 0 if healthy ##### `tile_display_rgbw_update` — Studio ```c void tile_display_rgbw_update(tile_t *tile) ``` Latch pending config/animation register writes (CMD_Update = 0x55). The LP5811 only acts on Dev_Config / animation writes once this is issued. Call after set_aeu / set_animation / set_autonomous and before animate_start. ##### `tile_display_rgbw_animate_start` — Studio ```c void tile_display_rgbw_animate_start(tile_t *tile) ``` Start (or restart) autonomous animation on all enabled channels. ##### `tile_display_rgbw_animate_stop` — Studio ```c void tile_display_rgbw_animate_stop(tile_t *tile) ``` Stop autonomous animation and return to the INITIAL state. ##### `tile_display_rgbw_animate_pause` — Studio ```c void tile_display_rgbw_animate_pause(tile_t *tile) ``` Pause autonomous animation, holding the current output. ##### `tile_display_rgbw_animate_continue` — Studio ```c void tile_display_rgbw_animate_continue(tile_t *tile) ``` Resume autonomous animation after a pause. ##### `tile_display_rgbw_breathe_auto` — Studio ```c void tile_display_rgbw_breathe_auto(tile_t *tile, uint8_t channel, uint8_t peak, uint16_t period_ms, uint8_t repeats) ``` Configure + start an autonomous breathe on one channel. Builds a single-AEU symmetric ramp (0 → peak → 0) on the channel, enables autonomous mode, latches, and starts. Convenience wrapper over the AEU API for the common case. - `channel` — 0-3 (R, W, G, B). - `peak` — Peak brightness 0-255. - `period_ms` — Full breathe period (up+down) in ms. - `repeats` — Whole-pattern repeat: 0-14, 15 = infinite. #### Configuration ##### `tile_display_rgbw_set_max_current` — Studio ```c void tile_display_rgbw_set_max_current(tile_t *tile, disp_rgbw_max_current_t mode) ``` Set the global maximum-current range (MC bit). Selects 25.5 mA or 51 mA full-scale per channel. After changing, the 8-bit per-channel DC codes (set via `set_current()`) re-scale automatically — DC=255 always means full-scale current. Useful when wiring lower-rated LEDs (drop to 25.5 mA to keep DC resolution fine) or when running cooler / saving power. The driver writes `Dev_Config_0` and re-issues the `CMD_Update` latch (0x55) the chip requires for config-register writes to actually take effect. - `mode` — 25.5 mA (0) or 51 mA (1) ##### `tile_display_rgbw_set_short_threshold` — Studio ```c void tile_display_rgbw_set_short_threshold(tile_t *tile, disp_rgbw_lsd_threshold_t threshold) ``` Set the short-circuit detection threshold (fraction of VOUT). Lower thresholds catch milder partial-shorts; higher thresholds tolerate more LED forward-voltage variation without false alarms. Driver default (init) is 0.65 × VOUT — most permissive, least likely to mis-fire on cold LEDs whose Vf hasn't settled. - `threshold` — One of DISP_RGBW_LSD_TH_* ##### `tile_display_rgbw_set_short_shutdown` — Studio ```c void tile_display_rgbw_set_short_shutdown(tile_t *tile, uint8_t enabled) ``` Configure whether a short-circuit fault auto-disables outputs. When enabled, an LSD fault sends the chip into OFAF (one-fail-all- fail) state — every channel turns off until LSD_Clear is written. When disabled (driver default), the chip flags the fault but keeps driving — firmware decides what to do. - `enabled` — 1 = chip auto-shuts-down on LSD, 0 = report only ##### `tile_display_rgbw_set_open_shutdown` — Studio ```c void tile_display_rgbw_set_open_shutdown(tile_t *tile, uint8_t enabled) ``` Configure whether an open-circuit fault auto-disables a sink. When enabled (chip default), a per-channel LOD fault turns off that single current sink. When disabled, the chip flags the fault via `read_faults()` but keeps driving the channel — useful when the open is intermittent (e.g., a flexing wire) and firmware wants to retry rather than relying on the chip to recover. - `enabled` — 1 = chip auto-shuts-down a single sink, 0 = report only ##### `tile_display_rgbw_ms_to_slope` — Studio ```c uint8_t tile_display_rgbw_ms_to_slope(uint16_t ms) ``` Convert a duration in milliseconds to the nearest AEU slope/pause code. - `ms` — Duration in milliseconds (0-8050). **Returns:** 4-bit time code (0-15) for an AEU T-field or Auto_Pause field. ##### `tile_display_rgbw_set_autonomous` — Studio ```c void tile_display_rgbw_set_autonomous(tile_t *tile, uint8_t channel, uint8_t enabled) ``` Put a channel into (or out of) autonomous animation mode. In autonomous mode the on-chip engine runs the channel's AEU program with no MCU intervention; in manual mode the channel follows the PWM registers (set / set_color). Call update() after configuring. - `channel` — 0-3 (R, W, G, B). - `enabled` — 1 = autonomous, 0 = manual. ##### `tile_display_rgbw_set_aeu` — Studio ```c void tile_display_rgbw_set_aeu(tile_t *tile, uint8_t channel, uint8_t aeu, const display_rgbw_aeu_t *prog) ``` Program one AEU sub-engine of a channel. - `channel` — 0-3. - `aeu` — Sub-engine index 1-3. - `prog` — 5-keyframe ramp + slope times + repeat. ##### `tile_display_rgbw_set_animation` — Studio ```c void tile_display_rgbw_set_animation(tile_t *tile, uint8_t channel, uint8_t num_aeu, uint8_t pause_start, uint8_t pause_end, uint8_t repeats) ``` Set a channel's overall autonomous playback. - `channel` — 0-3. - `num_aeu` — How many AEUs to chain, 1-3. - `pause_start` — Start-of-pattern pause code (0-15, see ms_to_slope). - `pause_end` — End-of-pattern pause code (0-15). - `repeats` — Whole-pattern repeat: 0-14, 15 = infinite. ##### `tile_display_rgbw_set_exp_dimming` — Studio ```c void tile_display_rgbw_set_exp_dimming(tile_t *tile, uint8_t channel, uint8_t enabled) ``` Enable/disable exponential PWM dimming for a channel (Dev_Config_5). - `channel` — 0-3. - `enabled` — 1 = exponential curve, 0 = linear. ##### `tile_display_rgbw_set_phase_align` — Studio ```c void tile_display_rgbw_set_phase_align(tile_t *tile, uint8_t channel, uint8_t mode) ``` Set a channel's PWM phase-align method (Dev_Config_7). - `channel` — 0-3. - `mode` — 0/1 = forward, 2 = middle, 3 = backward align. #### Other ##### `tile_display_rgbw_find` ```c uint8_t tile_display_rgbw_find(tiles_pal_t *hal, uint8_t instance) ``` Check if a Disp.RGBW is present on the bus. Probes the instance's independent-mode base address only (0x50 for instance 0). independent: 1 0 1 Bit4 Bit3 RA9 RA8 -> 0x50-0x53 broadcast: 1 1 0 1 1 RA9 RA8 -> 0x6C-0x6F So 0x50-0x53 are the four register pages in independent mode and 0x6C-0x6F are the same pages under the broadcast address. A bus scan showing all eight is one part behaving correctly — not a second device, and not a board fault. Do not go hunting for it; it has cost bring-up time before. - `hal` — Platform abstraction handle - `instance` — Device instance (0 = default address 0x50) **Returns:** 1 if the device ACKs, 0 otherwise **Note:** A healthy Display.RGBW ACKs on EIGHT addresses, and all eight are the same chip. Per datasheet Table 7-4 the 7-bit address carries the chip address AND the top two bits of the 10-bit register address: ##### `tile_display_rgbw_init` ```c void tile_display_rgbw_init(tiles_pal_t *hal, uint8_t instance, tile_t *tile, const disp_rgbw_cfg_t *cfg) ``` Initialize the LP5811 LED driver. Enables the chip, ramps the boost to 4.5 V in 0.1 V committed steps (~1.6 s — avoids the single-step inrush that can brown-out a marginal supply; see version history v2.3.0), sets max current full scale to 51 mA, enables all 4 LED channels, and sets the per-channel current limit to LP5811_DC_DEFAULT — ~1 mA at full PWM, NOT half scale. See that constant for why it is deliberately low and how to raise it. LSD action is left at "no shutdown" (driver-level choice) so a transient short doesn't latch the device into OFAF state without firmware seeing it. Pass cfg=NULL for defaults. - `hal` — Platform abstraction handle - `instance` — Device instance (0 = default address 0x50) - `tile` — Tile handle to populate - `cfg` — Optional config, or NULL for defaults ### Vibe Settings Settings Studio's Vibe interface offers for this tile, generated from the driver's `@studio control` annotations. Each is an argument of the function shown. | Setting | Tier | Values | Default | Function | |---|---|---|---|---| | Red current | basic | 0 to 255 | 5 | `set_current(r)` | | Green current | basic | 0 to 255 | 5 | `set_current(g)` | | Blue current | basic | 0 to 255 | 5 | `set_current(b)` | | White current | basic | 0 to 255 | 5 | `set_current(w)` | | Full-scale current | advanced | 25.5 mA full scale per channel, 51 mA full scale per channel | 51 mA full scale per channel | `set_max_current(mode)` | | Short detection threshold | advanced | 0.35 × VOUT (most sensitive), 0.45 × VOUT, 0.55 × VOUT, 0.65 × VOUT (least sensitive — driver default) | 0.65 × VOUT (least sensitive — driver default) | `set_short_threshold(threshold)` | | Shut down on a short | advanced | on / off | off | `set_short_shutdown(enabled)` | | Shut down an open channel | advanced | on / off | on | `set_open_shutdown(enabled)` | Rules Studio enforces: - Red is set above 5 mA, the on-tile LED's continuous rating. Keep it at or below 5 mA, or lower the full-scale current. - Green is set above 5 mA, the on-tile LED's continuous rating. Keep it at or below 5 mA, or lower the full-scale current. - Blue is set above 5 mA, the on-tile LED's continuous rating. Keep it at or below 5 mA, or lower the full-scale current. - White is set above 5 mA, the on-tile LED's continuous rating. Keep it at or below 5 mA, or lower the full-scale current. ### Driver gaps Chip capabilities this driver does **not** expose. Each is tagged hardware-gated (the tile does not route the pin) or driver-deferred (possible, not yet implemented). Do not assume an API exists for these. #### Multi-address support (0x50/0x54/0x58/0x5C) (niche) Chip-gated. Per datasheet Table 7-4, Address Byte 1 carries the chip address AND the top two bits of the 10-bit register address, so the 7-bit address a bus scan sees is: ### Enums #### `disp_rgbw_max_current_t` Per-channel maximum-current selector (LP5811 MC bit). | Member | Value | Description | | --- | --- | --- | | `DISP_RGBW_MAX_CURRENT_25_5_MA` | `0` | 25.5 mA full scale per channel @studio value=25.5 | | `DISP_RGBW_MAX_CURRENT_51_MA` | `1` | 51 mA full scale per channel @studio value=51 | #### `disp_rgbw_lsd_threshold_t` Short-circuit detection threshold (fraction of VOUT). | Member | Value | Description | | --- | --- | --- | | `DISP_RGBW_LSD_TH_0_35` | `0` | 0.35 × VOUT (most sensitive) | | `DISP_RGBW_LSD_TH_0_45` | `1` | 0.45 × VOUT | | `DISP_RGBW_LSD_TH_0_55` | `2` | 0.55 × VOUT | | `DISP_RGBW_LSD_TH_0_65` | `3` | 0.65 × VOUT (least sensitive — driver default) | ### Structs #### `disp_rgbw_faults_t` LED open / short fault snapshot. | Type | Field | Description | | --- | --- | --- | | `uint8_t` | `open_mask` | Bits 3:0 — channels with open-circuit fault. | | `uint8_t` | `short_mask` | Bits 3:0 — channels with short-circuit fault. | | `uint8_t` | `thermal_shutdown` | 1 = chip in thermal shutdown (TSD). | | `uint8_t` | `config_error` | 1 = configuration error reported by chip. | #### `disp_rgbw_cfg_t` | Type | Field | Description | | --- | --- | --- | | `uint8_t` | `reserved` | Placeholder — no options yet. | #### `display_rgbw_aeu_t` One animation sub-engine (AEU) program for a channel. | Type | Field | Description | | --- | --- | --- | | `uint8_t` | `repeats` | AEU pattern repeat: 0-2, 3 = infinite. | ### Defines | Name | Value | Description | | --- | --- | --- | | `TILE_DISP_RGBW_VERSION_MAJOR` | `2` | | | `TILE_DISP_RGBW_VERSION_MINOR` | `5` | | | `TILE_DISP_RGBW_VERSION_PATCH` | `0` | | | `LP5811_I2C_ADDR_DEFAULT` | `0x50` | | | `LP5811_CONFIG_2_DEFAULT` | `0xE4` | | | `LP5811_BOOST_VOUT_CODE_4V5` | `15` | | | `LP5811_CONFIG_0_MC_51MA` | `0x01` | | | `LP5811_BOOST_RAMP_STEP_MS` | `100` | | | `LP5811_DC_DEFAULT` | `0x05` | | | `LP5811_PWM_FAULT_DETECT_MIN` | `25` | | --- Generated 2026-10-07 from the driver header at tiles@afe442f. Canonical HTML: https://bergsonne.io/docs/tiles/display/rgbw ============================================================================== ## Drive.A.2 driver > Dual-channel audio output driver for the Drive.A.2 tile (DAC63202W smart DAC + 2x TPA2028D1 Class-D amplifiers). Supports I2C and SPI bus access via tiles_pal_t. | | | | --- | --- | | Tile | `Drive.A.2` | | Driver version | `3.3.0` | | Header | `tile_drive_a_2.h` | | Generated from | `tiles@f70bca2` | | Canonical page | https://bergsonne.io/docs/tiles/drive/a2 | ### Overview (DAC63202W smart DAC + 2x TPA2028D1 Class-D amplifiers). Supports I2C and SPI bus access via tiles_pal_t. @version 3.3.0 The Drive.A.2 tile provides two independent audio output channels, each consisting of a 12-bit DAC channel feeding a 3W Class-D amplifier. DAC features (DAC63202W): - 12-bit resolution, dual channel - Voltage output with configurable gain (1x, 1.5x, 2x, 3x, 4x) - Internal 1.21V reference or VDD/external reference - Built-in waveform generation: sine, triangle, sawtooth - I2C (up to 1 MHz) or SPI (up to 50 MHz), auto-detected at boot Amplifier features (TPA2028D1, x2): - 3W into 4 ohm, 880 mW into 8 ohm per channel - I2C-programmable gain: -28 dB to +30 dB in 1 dB steps - AGC with configurable compression, attack/release/hold times - Both amplifiers share I2C address 0x58 — writes affect both Speaker safety: init() leaves the TPA2028D1 output limiter ON at DRIVE_A_2_LIMITER_DEFAULT (3 dBV, 2.0 V peak, 0.25 W into 8 ohm), with AGC compression off. Without it, +30 dB of gain on a full-scale DAC signal drives a clipped square wave at the V+ rail (about 1.4 W into 8 ohm at 3.3 V), enough to burn a small speaker. Raise or disable it with tile_drive_a_2_amp_set_limiter(). In SPI mode, only the DAC is controllable; amplifier functions become no-ops since the I2C bus pins are repurposed for SPI. The DAC's SPI is 3-wire (write-only) until INTERFACE-CONFIG.SDO-EN is set, and a read takes two access cycles (SLASF73A §6.5.1), so the DEVICE-ID check in init and every read-modify-write setter need that 4-wire readback; this driver does not enable it yet. Datasheet (DAC): https://www.ti.com/product/DAC63202W Datasheet (Amp): https://www.ti.com/product/TPA2028D1 ### Examples #### Quick start (I2C — DAC + amplifiers) ```c #include "core_tiles.h" // provides core_tiles_pal() tile_t dac; tiles_pal_t *hal = core_tiles_pal(&core_i2c1); tile_drive_a_2_init(hal, 0, &dac, NULL); if (tile_is_ready(&dac)) { tile_drive_a_2_set(&dac, 0, 2048); // Ch 0 mid-scale tile_drive_a_2_amp_set_gain(&dac, 12); // +12 dB both amps } ``` #### Quick start (SPI — DAC only, no amplifier control) ```c #include "core_tiles.h" tile_t dac; tiles_pal_t *hal = core_tiles_pal(&core_spi1); tile_drive_a_2_init(hal, 0, &dac, NULL); // instance = CS index tile_drive_a_2_set_mv(&dac, 0, 1500); // 1.5 V on channel 0 ``` ### API 36 public functions. Functions marked **Studio** are callable from the Studio behaviour DSL. #### Lifecycle ##### `tile_drive_a_2_sleep` — Studio ```c void tile_drive_a_2_sleep(tile_t *tile) ``` Enter low-power sleep. Powers down both DAC VOUT channels (Hi-Z) and puts the amplifiers into software shutdown. ##### `tile_drive_a_2_wake` — Studio ```c void tile_drive_a_2_wake(tile_t *tile) ``` Wake from sleep. Powers up both VOUT channels, parks them at mid-scale, restores the cached DAC gains, then brings the amplifiers back to the state they were in before sleep: a pair that was muted (mute()) or disabled (amp_disable()) stays in software shutdown; otherwise it is enabled once the DAC has settled. #### Runtime ##### `tile_drive_a_2_set` — Studio ```c void tile_drive_a_2_set(tile_t *tile, uint8_t channel, uint16_t value) ``` Set a DAC channel output by raw 12-bit code (0–4095). - `channel` — 0 or 1 - `value` — 12-bit DAC code (0 = 0V, 4095 = full-scale) ##### `tile_drive_a_2_set_mv` — Studio ```c void tile_drive_a_2_set_mv(tile_t *tile, uint8_t channel, uint16_t mv) ``` Set a DAC channel output in millivolts. Computes the DAC code from this channel's gain: full scale is V+ (set_supply_mv() / cfg->vdd_mv) in the 1x gains (the VDD reference, or the VREF pin, which the tile ties to V+), or 1.21 V times 1.5, 2, 3 or 4 with the internal reference (SLASF73A Eq 1-3). The output cannot exceed V+, so requests above it are clamped to V+. - `channel` — 0 or 1 - `mv` — Desired output in millivolts ##### `tile_drive_a_2_get` — Studio ```c uint16_t tile_drive_a_2_get(tile_t *tile, uint8_t channel) ``` Read back the current DAC code for a channel. - `channel` — 0 or 1 **Returns:** 12-bit DAC code currently loaded ##### `tile_drive_a_2_set_waveform` — Studio ```c void tile_drive_a_2_set_waveform(tile_t *tile, uint8_t channel, drive_a_2_wave_t wave) ``` Configure the waveform shape for a channel. Sets FUNC-CONFIG-X in DAC-X-FUNC-CONFIG. Triangle and sawtooth waves oscillate between DAC-X-MARGIN-LOW and DAC-X-MARGIN-HIGH at the configured slew rate and code step. The sine uses 24 fixed codes (0x19A to 0xE66, about 80 % of full scale) and ignores the margins and code step. Set a non-zero slew rate first, then call start_waveform() to begin output. - `channel` — 0 or 1 - `wave` — Waveform shape ##### `tile_drive_a_2_start_waveform` — Studio ```c void tile_drive_a_2_start_waveform(tile_t *tile, uint8_t channel) ``` Start waveform generation on a channel. Sets START-FUNC-X in COMMON-DAC-TRIG (read-modify-write, so the other channel's generator keeps running). - `channel` — 0 or 1 ##### `tile_drive_a_2_stop_waveform` — Studio ```c void tile_drive_a_2_stop_waveform(tile_t *tile, uint8_t channel) ``` Stop waveform generation on a channel. Clears START-FUNC-X and sets FUNC-CONFIG-X to "disabled". - `channel` — 0 or 1 ##### `tile_drive_a_2_amp_set_gain` — Studio ```c void tile_drive_a_2_amp_set_gain(tile_t *tile, int8_t gain_db) ``` Set the amplifier fixed gain. Both TPA2028D1 amplifiers share I2C address 0x58, so this write affects both channels simultaneously. No-op in SPI mode. - `gain_db` — [-28..30] Gain in dB. Clamped if out of range. **Note:** init() turns AGC compression off (1:1), and with compression off the TPA2028D1 fixed gain is only specified from 0 to +30 dB (SLOS660C Table 10), so negative gains are raised to 0 dB. Negative gains need compression on (amp_set_agc()). The output limiter still caps the output level. ##### `tile_drive_a_2_amp_get_gain` — Studio ```c int8_t tile_drive_a_2_amp_get_gain(tile_t *tile) ``` Read the current amplifier fixed gain. **Returns:** Gain in dB (-28 to +30), or 0 if amp not available ##### `tile_drive_a_2_amp_enable` — Studio ```c void tile_drive_a_2_amp_enable(tile_t *tile) ``` Enable the amplifiers (clear software shutdown). While the tile is asleep this only records the request, and wake() applies it once the DAC has settled at mid-scale. No-op in SPI mode. ##### `tile_drive_a_2_amp_disable` — Studio ```c void tile_drive_a_2_amp_disable(tile_t *tile) ``` Disable the amplifiers (enter software shutdown). The amplifiers stay off across sleep() / wake(). No-op in SPI mode. ##### `tile_drive_a_2_amp_set_agc` — Studio ```c void tile_drive_a_2_amp_set_agc(tile_t *tile, const drive_a_2_agc_cfg_t *cfg) ``` Configure the full AGC/DRC parameters. Writes all AGC registers (attack, release, hold, fixed gain, limiter, compression, noise gate, max gain). Affects both amps. No-op in SPI mode. The compression register is written before the limiter register, because the limiter can only be disabled while compression is 1:1 (SLOS660C Table 11). The limiter stays on unless cfg->limiter_disable is set. The compression ratio also sets the valid fixed-gain range that amp_set_gain() and set_volume_pct() use. - `cfg` — AGC configuration ##### `tile_drive_a_2_amp_read_status` — Studio ```c uint8_t tile_drive_a_2_amp_read_status(tile_t *tile) ``` Read the amplifier status register. Check bit 3 (FAULT) for short-circuit and bit 2 (Thermal) for over-temperature. Write 0 to the respective bit to clear. **Returns:** Raw register 0x01 value, or 0 if amp not available ##### `tile_drive_a_2_read_status` — Studio ```c uint16_t tile_drive_a_2_read_status(tile_t *tile) ``` Read the DAC GENERAL-STATUS register. Contains DEVICE-ID, VERSION-ID, NVM CRC status, and DAC busy flags. **Returns:** 16-bit GENERAL-STATUS value ##### `tile_drive_a_2_play_tone` — Studio ```c void tile_drive_a_2_play_tone(tile_t *tile, drive_a_2_channel_t channel, uint16_t freq_hz, uint16_t ms) ``` Play a sine tone on `channel` for `ms` milliseconds. Configures DAC margins for full-scale swing, sets a sine waveform, and uses the on-chip parametric generator together with a software-tuned slew rate / code step to approximate `freq_hz`. The chip's sine generator runs at 1 / (24 × slew step), so only 15 pitches exist (10417, 5208, 3472, 2315, 1541, 1029, 686, 457, 305, 174, 99.5, 56.9, 32.5, 16.3, 8.1 Hz); the nearest one is played. The sine spans about 80 % of the DAC's range. The amplifier is unmuted for the duration of the tone and restored to its prior mute state on return. - `channel` — DRIVE_A_2_CH_LEFT, _RIGHT, or _BOTH - `freq_hz` — [8..10417] Tone frequency in Hz (rounded to the nearest generator pitch) - `ms` — Duration in milliseconds **Note:** Blocks for `ms` milliseconds via `hal->delay_ms`. ##### `tile_drive_a_2_play_silence` — Studio ```c void tile_drive_a_2_play_silence(tile_t *tile, drive_a_2_channel_t channel, uint16_t ms) ``` Hold the channel at silence (DAC mid-scale) for `ms` ms. Stops any active waveform on `channel`, parks the DAC code at mid-scale (= zero differential at the amp input), and blocks for `ms` milliseconds. Useful to insert a precise gap between tones without toggling the amp shutdown — the amp stays alive but draws minimal idle current with no signal. - `channel` — DRIVE_A_2_CH_LEFT, _RIGHT, or _BOTH - `ms` — Duration in milliseconds **Note:** Blocks for `ms` milliseconds via `hal->delay_ms`. ##### `tile_drive_a_2_play_chirp` — Studio ```c void tile_drive_a_2_play_chirp(tile_t *tile, drive_a_2_channel_t channel, uint16_t start_hz, uint16_t end_hz, uint16_t ms) ``` Linear frequency sweep on `channel` from `start_hz` to `end_hz`. Software sine-LUT chirp delivered through `set` (raw DAC code) at ~8 kHz update rate. Frequency advances linearly across `ms`. For ascending sweeps pass start < end; for descending, start > end. The amp is unmuted for the duration and restored to its prior mute state on return. - `channel` — DRIVE_A_2_CH_LEFT, _RIGHT, or _BOTH - `start_hz` — Initial frequency in Hz - `end_hz` — Final frequency in Hz - `ms` — Sweep duration in milliseconds **Note:** Blocks for `ms` milliseconds via `hal->delay_ms`. ##### `tile_drive_a_2_set_volume_pct` — Studio ```c void tile_drive_a_2_set_volume_pct(tile_t *tile, drive_a_2_channel_t channel, uint8_t pct) ``` Map a 0–100 percent volume to amplifier fixed gain. Linear-in-dB mapping from `pct` onto the fixed-gain range the TPA2028D1 specifies for the active compression ratio (SLOS660C Table 10): compression 1:1 (init's default): gain_db = (pct × 30) / 100 compression 2:1, 4:1 or 8:1: gain_db = -28 + (pct × 58) / 100 With compression off, 0 % → 0 dB (the lowest valid gain, not silence; use mute() for silence), 50 % → +15 dB, 100 % → +30 dB. The output limiter still caps the level. The mapping is intentionally linear-in-dB rather than perceptually weighted; for finer dB control use @ref tile_drive_a_2_amp_set_gain directly. Because both amps share an I²C address, the channel argument is accepted for symmetry with the rest of the tier-2 API but the write lands on both physical amps. - `channel` — DRIVE_A_2_CH_LEFT, _RIGHT, or _BOTH (advisory) - `pct` — [0..100] Percent (clamped if out of range) ##### `tile_drive_a_2_mute` — Studio ```c void tile_drive_a_2_mute(tile_t *tile, drive_a_2_channel_t channel) ``` Mute `channel` by entering amp software shutdown. Stashes the current fixed-gain register so a later @ref tile_drive_a_2_unmute restores it byte-exact, then puts the amp pair into software shutdown (SWS=1). Both physical amps mute regardless of `channel` (shared I²C address). The mute survives sleep() / wake(); only unmute(), amp_enable() or init() clear it. - `channel` — DRIVE_A_2_CH_LEFT, _RIGHT, or _BOTH (advisory) ##### `tile_drive_a_2_unmute` — Studio ```c void tile_drive_a_2_unmute(tile_t *tile, drive_a_2_channel_t channel) ``` Restore audio output after a previous `mute` call. Re-applies the gain that was active at the time of @ref tile_drive_a_2_mute and clears software shutdown (SWS=0). If the channel was never muted by this driver instance the call still wakes the amp using whatever gain is currently in the register. While the tile is asleep the amp is woken by wake(). - `channel` — DRIVE_A_2_CH_LEFT, _RIGHT, or _BOTH (advisory) #### Configuration ##### `tile_drive_a_2_set_gain` — Studio ```c void tile_drive_a_2_set_gain(tile_t *tile, uint8_t channel, drive_a_2_gain_t gain) ``` Set the voltage output gain for a DAC channel. Automatically enables the internal reference when an internal-reference gain is selected, and records the gain for this channel so set_mv() uses the right full scale (each channel keeps its own). - `channel` — 0 or 1 - `gain` — One of the drive_a_2_gain_t values ##### `tile_drive_a_2_set_supply_mv` — Studio ```c void tile_drive_a_2_set_supply_mv(tile_t *tile, uint16_t mv) ``` Tell the driver the tile's V+ supply voltage. The DAC63202W's VDD is the tile's V+ (2.5–5.5 V). In the 1x gains the DAC's full scale is V+, so set_mv() needs it to pick the right code; the internal-reference gains need it to clamp requests above V+. No bus traffic. Default 3300 mV (or cfg->vdd_mv). - `mv` — [2500..5500] V+ in millivolts (clamped). ##### `tile_drive_a_2_set_slew_rate` — Studio ```c void tile_drive_a_2_set_slew_rate(tile_t *tile, uint8_t channel, drive_a_2_slew_t slew) ``` Set the slew rate (time per code step) for a DAC channel. Programs SLEW-RATE-X bits[3:0] in DAC-X-FUNC-CONFIG. Affects both slewed direct-output updates and the on-chip function generator's frequency. The default is no-slew — outputs settle as fast as the analog stage allows. Use a slower slew to soften zero-crossings for capacitive / inductive loads, or to dial in a target waveform frequency together with set_code_step() and set_margins(). - `channel` — 0 or 1 - `slew` — Slew-rate code (drive_a_2_slew_t value 0–15) ##### `tile_drive_a_2_set_code_step` — Studio ```c void tile_drive_a_2_set_code_step(tile_t *tile, uint8_t channel, drive_a_2_step_t step) ``` Set the code step (LSBs per slew tick) for a DAC channel. Programs CODE-STEP-X bits[6:4] in DAC-X-FUNC-CONFIG. Larger steps give faster ramps / higher waveform frequencies at the cost of coarser resolution. - `channel` — 0 or 1 - `step` — Code-step code (drive_a_2_step_t value 0–7) ##### `tile_drive_a_2_set_margins` — Studio ```c void tile_drive_a_2_set_margins(tile_t *tile, uint8_t channel, uint16_t low, uint16_t high) ``` Set the upper / lower bounds for waveform & window-comparator modes. Writes DAC-X-MARGIN-HIGH and DAC-X-MARGIN-LOW. The function generator oscillates between these levels, and they also serve as the thresholds for the chip's window / hysteresis comparator modes. Values are 12-bit DAC codes; high must be > low. - `channel` — 0 or 1 - `low` — 12-bit DAC code for the lower bound (0–4095) - `high` — 12-bit DAC code for the upper bound (0–4095) ##### `tile_drive_a_2_set_phase` — Studio ```c void tile_drive_a_2_set_phase(tile_t *tile, uint8_t channel, drive_a_2_phase_t phase) ``` Set the phase offset for the function generator. Programs PHASE-SEL-X bits[12:11] in DAC-X-FUNC-CONFIG. Take effect the next time start_waveform() is called. Use phase = 90° on one channel and 0° on the other to drive a quadrature pair. - `channel` — 0 or 1 - `phase` — Phase offset (drive_a_2_phase_t) ##### `tile_drive_a_2_set_waveform_params` — Studio ```c void tile_drive_a_2_set_waveform_params(tile_t *tile, uint8_t channel, drive_a_2_wave_t wave, drive_a_2_step_t step, drive_a_2_slew_t slew) ``` Configure the parametric on-chip waveform generator. Convenience helper that sets up the waveform shape, full-scale margins (0 → 4095), code step, and slew rate in one call. The resulting frequency is (SLASF73A Eq 6-8): f_triangle = 1 / (2 × time_step × ceil((margin_high − margin_low) / code_step)) f_sawtooth = 1 / ( time_step × ceil((margin_high − margin_low + 1) / code_step)) f_sine = 1 / (24 × time_step) Call start_waveform() to begin output once configured. For fine-grained control, call set_margins() / set_code_step() / set_slew_rate() / set_waveform() / set_phase() individually. - `channel` — 0 or 1 - `wave` — Waveform shape (drive_a_2_wave_t) - `step` — Code-step (drive_a_2_step_t) - `slew` — Time per step (drive_a_2_slew_t) ##### `tile_drive_a_2_amp_set_limiter` — Studio ```c void tile_drive_a_2_amp_set_limiter(tile_t *tile, uint8_t enabled, uint8_t level) ``` Set, or disable, the amplifiers' output limiter. The limiter caps the amplifier output at level n = -6.5 + 0.5 n dBV (SLOS660C Table 11; power figures are for 8 ohm): 0 = 0.67 V peak, 0.03 W; 19 = 2.0 V peak, 0.25 W (init's default); 26 = 3.0 V peak, 0.56 W (chip default); 31 = 3.99 V peak, 0.99 W. A level whose peak is above V+ cannot be reached, so the output clips first. Read- modify-write of AGC_CTRL1 (the noise-gate bits are kept). Affects both amps. No-op in SPI mode. speaker's rating, is unsafe for small speakers. The chip only honours a disable while AGC compression is 1:1 (init's default); with compression on the driver keeps the limiter enabled and only sets the level. - `enabled` — 1 = limiter on (default), 0 = off (unsafe) - `level` — [0..31] Limiter level (clamped) ##### `tile_drive_a_2_nvm_save` — Studio ```c void tile_drive_a_2_nvm_save(tile_t *tile) ``` Save the DAC's current register state into shadow NVM. Triggers NVM-PROG in COMMON-TRIGGER. The DAC's user-programmable registers (gain, margins, slew, waveform shape, COMMON-CONFIG, etc. — see datasheet "highlighted gray" rows) are committed to non-volatile storage and become the new power-on defaults. Blocking: holds the bus busy for the NVM write cycle. Limited write endurance — TI specs ~1000 cycles. Use only for one-time factory tuning, not for runtime configuration storage. ##### `tile_drive_a_2_nvm_reload` — Studio ```c void tile_drive_a_2_nvm_reload(tile_t *tile) ``` Reload all DAC registers from shadow NVM. Triggers NVM-RELOAD in COMMON-TRIGGER. Restores the saved power-on configuration without a full reset — equivalent to the load that happens automatically on POR. Blocks until the reload completes, then re-reads both channels' VOUT-GAIN so set_mv() follows whatever gain the NVM held. #### Advanced ##### `tile_drive_a_2_read_reg` — Studio ```c uint16_t tile_drive_a_2_read_reg(tile_t *tile, uint8_t reg) ``` Read any 16-bit DAC63202W register. Escape hatch for advanced users wanting to touch registers the driver doesn't expose. Caller is responsible for not bricking the chip — most useful registers have typed setters above. - `reg` — Register address (7-bit) **Returns:** 16-bit register value ##### `tile_drive_a_2_write_reg` — Studio ```c void tile_drive_a_2_write_reg(tile_t *tile, uint8_t reg, uint16_t value) ``` Write any 16-bit DAC63202W register. - `reg` — Register address (7-bit) - `value` — 16-bit value to write (big-endian on the wire) #### Other ##### `tile_drive_a_2_find` ```c uint8_t tile_drive_a_2_find(tiles_pal_t *hal, uint8_t instance) ``` Check whether a DAC63202W is present on the bus. - `hal` — Platform HAL handle - `instance` — Instance index (see mapping table) **Returns:** 1 if device ACKs (I2C) or responds to WHO_AM_I (SPI), 0 otherwise ##### `tile_drive_a_2_init` ```c void tile_drive_a_2_init(tiles_pal_t *hal, uint8_t instance, tile_t *tile, const drive_a_2_cfg_t *cfg) ``` Initialize the Drive.A.2 tile. Verifies DEVICE-ID, powers up both VOUT channels at mid-scale, configures gain, and (in I2C mode) probes and configures the amplifiers: shutdown, fixed gain, compression 1:1, output limiter on at DRIVE_A_2_LIMITER_DEFAULT with the chip-default attack / release / hold times, then wake. Init starts unmuted: it clears the mute state. - `hal` — Platform HAL handle (see core_tiles.h). - `instance` — Instance index (I2C: address variant, SPI: CS index) - `tile` — Pointer to tile handle (populated by this function) - `cfg` — Optional config, or NULL for defaults (1x ext VREF, 6 dB amp) ##### `tile_drive_a_2_reset` ```c void tile_drive_a_2_reset(tile_t *tile) ``` Perform a software reset of the DAC. All DAC registers return to defaults. tile->state becomes TILE_STATE_NONE; call init() again to reconfigure. ### Vibe Settings Settings Studio's Vibe interface offers for this tile, generated from the driver's `@studio control` annotations. Each is an argument of the function shown. | Setting | Tier | Values | Default | Function | |---|---|---|---|---| | Amplifier gain | basic | -28 to 30 | 6 | `amp_set_gain(gain_db)` | | V+ supply | advanced | 2500 to 5500 | 3300 | `set_supply_mv(mv)` | | Output limiter | advanced | on / off | on | `amp_set_limiter(enabled)` | | Limiter level | advanced | 0 to 31 | 19 | `amp_set_limiter(level)` | Rules Studio enforces: - With AGC compression off (init's default) the amplifier's gain is only specified from 0 to +30 dB. Use 0 dB or more, or turn compression on first. - With the output limiter off, full gain on a loud signal drives a clipped square wave at the V+ rail (about 1.4 W into 8 ohm at 3.3 V), which can burn a small speaker. Keep it on unless the speaker is rated for that. ### Driver gaps Chip capabilities this driver does **not** expose. Each is tagged hardware-gated (the tile does not route the pin) or driver-deferred (possible, not yet implemented). Do not assume an API exists for these. #### DAC PWM-output mode (FBx pins) (advanced) The DAC63202W FB0 / FB1 pins (package balls B2 / C2) can be configured as comparator / PWM outputs for driving external power-stage gates. On the Drive.A.2 tile these pins are tied to the on-board TPA2028D1 amplifier inputs (closed-loop voltage- output feedback) and are NOT routed to any tile-connector pad — so even if the driver enabled PWM mode, no Core GPIO could observe or use the output. Closing this gap requires a tile hardware revision that breaks FBx out to a pad. #### Per-amp independent control (niche) The tile carries 2× TPA2028D1 amps wired to a single shared I²C bus, and the TPA2028D1 has a fixed factory I²C address (0x58) — any write at 0x58 is acknowledged by both amps simultaneously. Independent left / right gain, AGC, or shutdown therefore can't be achieved in software; closing this gap requires either an I²C-mux on the tile or amp ICs with selectable addresses. ### Enums #### `drive_a_2_gain_t` DAC output voltage gain setting. | Member | Value | Description | | --- | --- | --- | | `DRIVE_A_2_GAIN_1X_EXT` | `0` | 1x, external VREF pin | | `DRIVE_A_2_GAIN_1X_VDD` | `1` | 1x, VDD as reference (default) | | `DRIVE_A_2_GAIN_1P5X_INT` | `2` | 1.5x, internal 1.21V → 0–1.815V | | `DRIVE_A_2_GAIN_2X_INT` | `3` | 2x, internal 1.21V → 0–2.42V | | `DRIVE_A_2_GAIN_3X_INT` | `4` | 3x, internal 1.21V → 0–3.63V | | `DRIVE_A_2_GAIN_4X_INT` | `5` | 4x, internal 1.21V → 0–4.84V | #### `drive_a_2_wave_t` Built-in waveform shapes for the DAC function generator. | Member | Value | Description | | --- | --- | --- | | `DRIVE_A_2_WAVE_TRIANGLE` | `0` | Triangular wave | | `DRIVE_A_2_WAVE_SAWTOOTH` | `1` | Sawtooth (ramp up) | | `DRIVE_A_2_WAVE_INV_SAW` | `2` | Inverse sawtooth (ramp down) | | `DRIVE_A_2_WAVE_SINE` | `4` | Sine wave | | `DRIVE_A_2_WAVE_OFF` | `7` | Function generation disabled | #### `drive_a_2_slew_t` Slew-rate control (time per code step) for the DAC. | Member | Value | Description | | --- | --- | --- | | `DRIVE_A_2_SLEW_NONE` | `0x0` | No slew (default): immediate update. Invalid for waveforms | | `DRIVE_A_2_SLEW_4_US` | `0x1` | 4 µs/step (sine 10417 Hz) | | `DRIVE_A_2_SLEW_8_US` | `0x2` | 8 µs/step (sine 5208 Hz) | | `DRIVE_A_2_SLEW_12_US` | `0x3` | 12 µs/step (sine 3472 Hz) | | `DRIVE_A_2_SLEW_18_US` | `0x4` | 18 µs/step (sine 2315 Hz) | | `DRIVE_A_2_SLEW_27_US` | `0x5` | 27.04 µs/step (sine 1541 Hz) | | `DRIVE_A_2_SLEW_41_US` | `0x6` | 40.48 µs/step (sine 1029 Hz) | | `DRIVE_A_2_SLEW_61_US` | `0x7` | 60.72 µs/step (sine 686 Hz) | | `DRIVE_A_2_SLEW_91_US` | `0x8` | 91.12 µs/step (sine 457 Hz) | | `DRIVE_A_2_SLEW_137_US` | `0x9` | 136.72 µs/step (sine 305 Hz) | | `DRIVE_A_2_SLEW_239_US` | `0xA` | 239.2 µs/step (sine 174 Hz) | | `DRIVE_A_2_SLEW_419_US` | `0xB` | 418.64 µs/step (sine 99.5 Hz) | | `DRIVE_A_2_SLEW_733_US` | `0xC` | 732.56 µs/step (sine 56.9 Hz) | | `DRIVE_A_2_SLEW_1282_US` | `0xD` | 1282 µs/step (sine 32.5 Hz) | | `DRIVE_A_2_SLEW_2564_US` | `0xE` | 2563.96 µs/step (sine 16.3 Hz) | | `DRIVE_A_2_SLEW_5128_US` | `0xF` | 5127.92 µs/step (sine 8.1 Hz) | #### `drive_a_2_step_t` Code-step size (LSBs per slewed update tick) for the DAC. | Member | Value | Description | | --- | --- | --- | | `DRIVE_A_2_STEP_1_LSB` | `0x0` | 1 LSB per step (default, finest) | | `DRIVE_A_2_STEP_2_LSB` | `0x1` | 2 LSB per step | | `DRIVE_A_2_STEP_3_LSB` | `0x2` | 3 LSB per step | | `DRIVE_A_2_STEP_4_LSB` | `0x3` | 4 LSB per step | | `DRIVE_A_2_STEP_6_LSB` | `0x4` | 6 LSB per step | | `DRIVE_A_2_STEP_8_LSB` | `0x5` | 8 LSB per step | | `DRIVE_A_2_STEP_16_LSB` | `0x6` | 16 LSB per step | | `DRIVE_A_2_STEP_32_LSB` | `0x7` | 32 LSB per step (coarsest) | #### `drive_a_2_phase_t` Phase offset for the function generator (sine / triangle). | Member | Value | Description | | --- | --- | --- | | `DRIVE_A_2_PHASE_0` | `0` | 0° (default) | | `DRIVE_A_2_PHASE_120` | `1` | 120° | | `DRIVE_A_2_PHASE_240` | `2` | 240° | | `DRIVE_A_2_PHASE_90` | `3` | 90° (quadrature) | #### `drive_a_2_comp_t` Amplifier compression ratio. | Member | Value | Description | | --- | --- | --- | | `DRIVE_A_2_COMP_1_1` | `0` | 1:1 — compression off | | `DRIVE_A_2_COMP_2_1` | `1` | 2:1 | | `DRIVE_A_2_COMP_4_1` | `2` | 4:1 (default) | | `DRIVE_A_2_COMP_8_1` | `3` | 8:1 | #### `drive_a_2_channel_t` Logical channel selector for the tier-2 runtime helpers (`play_tone`, `set_volume_pct`, `mute`, …). | Member | Value | Description | | --- | --- | --- | | `DRIVE_A_2_CH_LEFT` | `0` | DAC0 / left amp logically | | `DRIVE_A_2_CH_RIGHT` | `1` | DAC1 / right amp logically | | `DRIVE_A_2_CH_BOTH` | `2` | Both DAC channels (amp writes still hit both) | ### Structs #### `drive_a_2_agc_cfg_t` Full AGC/DRC configuration for the TPA2028D1 amplifiers. | Type | Field | Description | | --- | --- | --- | | `uint8_t` | `compression` | drive_a_2_comp_t. 0 = 1:1 (off). | | `int8_t` | `fixed_gain_db` | -28 to +30 dB (0 to +30 with compression 1:1). Clamped. | | `uint8_t` | `max_gain_db` | 18 to 30 dB. 0 = 18 dB (use 30 for default). | | `uint8_t` | `limiter_level` | 0–31 (0.5 dB steps from -6.5 dBV to 9 dBV; 26 = 6.5 dBV chip default). | | `uint8_t` | `attack` | 0–63 (0.1067 ms per step). 0 = fastest. | | `uint8_t` | `release` | 0–63 (0.0137 s per step). 0 = fastest. | | `uint8_t` | `hold` | 0–63 (0.0137 s per step). 0 = disabled. | | `uint8_t` | `noise_gate` | 0–3 (0=1mV, 1=4mV, 2=10mV, 3=20mV rms). | | `uint8_t` | `limiter_disable` | 1 = limiter off (compression 1:1 only). UNSAFE for small speakers. 0 = on. | #### `drive_a_2_cfg_t` Optional configuration for tile_drive_a_2_init(). | Type | Field | Description | | --- | --- | --- | | `uint8_t` | `gain` | DAC gain (drive_a_2_gain_t). Default: DRIVE_A_2_GAIN_1X_EXT. | | `int8_t` | `amp_gain_db` | Amplifier fixed gain in dB (0 to +30; init turns compression off). 0 = default 6 dB. | | `uint16_t` | `vdd_mv` | Tile V+ in mV (2500–5500), the DAC's reference in the 1x gains. 0 = default 3300. | ### Defines | Name | Value | Description | | --- | --- | --- | | `TILE_DRIVE_A_2_VERSION_MAJOR` | `3` | | | `TILE_DRIVE_A_2_VERSION_MINOR` | `3` | | | `TILE_DRIVE_A_2_VERSION_PATCH` | `0` | | | `DAC63202W_I2C_ADDR_DEFAULT` | `0x49` | A0 → VDD (default) | | `DAC63202W_I2C_ADDR_GND` | `0x48` | A0 → GND | | `DAC63202W_I2C_ADDR_SDA` | `0x4A` | A0 → SDA | | `DAC63202W_I2C_ADDR_SCL` | `0x4B` | A0 → SCL | | `TPA2028D1_I2C_ADDR` | `0x58` | Fixed (both amps) | | `DRIVE_A_2_LIMITER_DEFAULT` | `19` | | | `DRIVE_A_2_LIMITER_MAX` | `31` | Highest limiter level: 31 = 9 dBV, 3.99 V peak, 0.99 W into 8 ohm. | --- Generated 2026-10-07 from the driver header at tiles@f70bca2. Canonical HTML: https://bergsonne.io/docs/tiles/drive/a2 ============================================================================== ## Drive.DC.H driver > H-bridge DC motor driver for the Drive.DC.H tile (rev a). | | | | --- | --- | | Tile | `Drive.DC.H` | | Driver version | `4.3.0` | | Header | `tile_drive_dc_h.h` | | Generated from | `tiles@3edc9ed` | | Canonical page | https://bergsonne.io/docs/tiles/drive/dch | ### Overview Embeds the TI DRV8214, a full H-bridge motor driver with integrated current sensing, stall detection, and voltage/speed regulation. Key specifications: - Output: full H-bridge, 4 A peak / 2 A RMS continuous - Supply: 1.65-11 V motor supply (VM), 1.65-5.5 V logic (VCC) - R_DS(on): 240 mOhm (high + low side total) - Regulation: voltage or speed, via internal PI loop - Current sense: internal current mirror (IPROPI), 3 gain settings - Stall detect: hardware, configurable via IPROPI/VREF threshold - Ripple count: sensorless position/speed via commutation ripples - Protection: UVLO, OCP, OVP, thermal shutdown The tile also exposes EN/PH (or IN1/IN2) control pads (pads 2, 3) for direct GPIO/PWM-based motor control without I²C, and an analog current-proportional output on pad 6 (NPROP/IPROPI) that mirrors the motor winding current scaled by the CS_GAIN_SEL setting. Switch the bridge control source at runtime with set_control_mode(). The DRV8214 integrates a sensorless ripple counting algorithm that counts commutation ripples in the motor current waveform to estimate rotor position and speed without external encoders or Hall sensors. In speed regulation mode (DRIVE_DC_H_MODE_SPEED), the chip's internal PI loop maintains constant motor speed across varying supply voltages and loads. The ripple count and estimated speed can be read at any time via get_ripple_count() / get_speed(). Accurate ripple counting requires motor-specific tuning of the INV_R, KMC, and filter parameters (see DRV8214 datasheet section 9.2.3.1). Datasheet: https://www.bergsonne.io/tiles/drive/dc-h IC datasheet: https://www.ti.com/lit/ds/symlink/drv8214.pdf ### Examples #### Quick start ```c #include "core_tiles.h" tile_t motor; tile_drive_dc_h_init(core_tiles_pal(&core_i2c3), 0, &motor, NULL); if (tile_is_ready(&motor)) { tile_drive_dc_h_forward(&motor); // drive forward core_delay_ms(2000); tile_drive_dc_h_brake(&motor); // slow-decay brake } ``` ### API 32 public functions. Functions marked **Studio** are callable from the Studio behaviour DSL. #### Lifecycle ##### `tile_drive_dc_h_sleep` — Studio ```c void tile_drive_dc_h_sleep(tile_t* tile) ``` Disable the output stage (all FETs Hi-Z). Clears EN_OUT in CONFIG0. The device remains on the I2C bus and registers are accessible, but no current flows through the motor. OVP protection remains active in sleep. ##### `tile_drive_dc_h_wake` — Studio ```c void tile_drive_dc_h_wake(tile_t* tile) ``` Re-enable the output stage. Sets EN_OUT in CONFIG0. The bridge returns to the last commanded state (coast/brake/forward/reverse). #### Runtime ##### `tile_drive_dc_h_forward` — Studio ```c void tile_drive_dc_h_forward(tile_t* tile) ``` Drive motor forward (OUT1=H, OUT2=L). ##### `tile_drive_dc_h_reverse` — Studio ```c void tile_drive_dc_h_reverse(tile_t* tile) ``` Drive motor in reverse (OUT1=L, OUT2=H). ##### `tile_drive_dc_h_brake` — Studio ```c void tile_drive_dc_h_brake(tile_t* tile) ``` Active brake (slow-decay, both low-side FETs on). Motor is actively held. Current recirculates through the low-side FETs, providing strong braking force. ##### `tile_drive_dc_h_coast` — Studio ```c void tile_drive_dc_h_coast(tile_t* tile) ``` Coast (Hi-Z, all FETs off). Motor freewheels. No braking force is applied. This is the initial state after init(). ##### `tile_drive_dc_h_set_target` — Studio ```c void tile_drive_dc_h_set_target(tile_t* tile, uint8_t value) ``` Set the regulation target (WSET_VSET). In voltage mode: sets target motor terminal voltage. With VM_GAIN_SEL=0: value * 15700/255 mV (61.6 mV/bit) With VM_GAIN_SEL=1: value * 3920/255 mV (15.4 mV/bit) In speed mode: sets target ripple speed. Target ripple speed = value * W_SCALE rad/s (W_SCALE 16/32/64/128; shaft RPM = that × 60 / (2π × ripples per rev)). - `value` — Target setpoint (0-255) ##### `tile_drive_dc_h_get_fault` — Studio ```c uint8_t tile_drive_dc_h_get_fault(tile_t* tile) ``` Read the raw FAULT register (0x00). Contains FAULT[7], STALL[5], OCP[4], OVP[3], TSD[2], NPOR[1], CNT_DONE[0]. Use the DRV8214_FAULT_* masks to decode individual bits. **Returns:** Raw fault byte (0 = no faults) ##### `tile_drive_dc_h_clear_fault` — Studio ```c void tile_drive_dc_h_clear_fault(tile_t* tile) ``` Clear all latched faults. Sets CLR_FLT in CONFIG0. Clears FAULT, OCP, OVP, TSD, and NPOR bits. The CLR_FLT bit is self-clearing. ##### `tile_drive_dc_h_is_stalled` — Studio ```c uint8_t tile_drive_dc_h_is_stalled(tile_t* tile) ``` Check if a motor stall condition is active. **Returns:** 1 if STALL bit is set, 0 otherwise ##### `tile_drive_dc_h_get_voltage_mv` — Studio ```c uint16_t tile_drive_dc_h_get_voltage_mv(tile_t* tile) ``` Read motor terminal voltage in millivolts. Reads the VMTR register. The reading is proportional to the voltage across OUT1-OUT2 terminals. Valid while driving. **Returns:** Motor voltage in mV (e.g. 3300 = 3.3 V) ##### `tile_drive_dc_h_get_current_ma` — Studio ```c uint16_t tile_drive_dc_h_get_current_ma(tile_t* tile) ``` Read motor current in milliamps. Reads the IMTR register and converts using the current CS_GAIN_SEL setting. The reading reflects the current flowing through the low-side FETs during drive or brake. **Returns:** Motor current in mA ##### `tile_drive_dc_h_get_speed` — Studio ```c uint8_t tile_drive_dc_h_get_speed(tile_t* tile) ``` Read the ripple speed estimate. Returns the raw SPEED register from the ripple counting algorithm. Value is proportional to motor speed but requires motor-specific calibration for RPM conversion. **Returns:** Raw speed estimate (0-255) ##### `tile_drive_dc_h_get_speed_rpm` — Studio ```c uint32_t tile_drive_dc_h_get_speed_rpm(tile_t* tile) ``` Read the estimated motor speed in RPM. Converts the chip's ripple-speed estimate to shaft RPM using the live W_SCALE setting and the configured ripples-per-rev, making it the exact inverse of the set_speed_rpm() target conversion. Accuracy depends on the motor profile (see set_motor_params()); without INV_R/KMC calibration the estimate is approximate. **Returns:** Estimated shaft speed in revolutions per minute ##### `tile_drive_dc_h_get_ripple_count` — Studio ```c uint16_t tile_drive_dc_h_get_ripple_count(tile_t* tile) ``` Read the 16-bit ripple count. Returns the total number of commutation ripples counted since the last clear. Proportional to rotor position. **Returns:** Ripple count (0-65535) ##### `tile_drive_dc_h_clear_ripple_count` — Studio ```c void tile_drive_dc_h_clear_ripple_count(tile_t* tile) ``` Reset the ripple counter to zero. Sets CLR_CNT in CONFIG0. Also clears CNT_DONE flag. The CLR_CNT bit is self-clearing. ##### `tile_drive_dc_h_set_speed_rpm` — Studio ```c void tile_drive_dc_h_set_speed_rpm(tile_t* tile, uint32_t rpm, drive_dc_h_direction_t direction) ``` Drive at a target speed (RPM) with closed-loop regulation. Switches the chip into speed-regulation mode (REG_CTRL=10b), writes WSET_VSET so the internal PI loop targets `rpm` for the given motor, and engages the bridge in `direction`. Returns immediately — the chip ramps the motor to the target speed autonomously and is_running() / get_speed() report progress. - `rpm` — Target shaft speed in revolutions per minute - `direction` — DRIVE_DC_H_DIR_FORWARD or DRIVE_DC_H_DIR_REVERSE **Note:** The driver picks the finest W_SCALE (16/32/64/128 rad/s, datasheet Table 8-24) whose 8-bit WSET range still reaches the requested speed, so low targets get the best granularity the chip offers. W_SCALE is in ripple rad/s (Eq. 8, 11), so the RPM step is `60 × W_SCALE / (2π × ripples_per_rev)`: ~12.7 RPM at ripples_per_rev=12 for targets up to ~3 250 RPM, ~102 RPM per step at the coarsest scale (to ~26 000 RPM). WSET is clamped at 0xFF for targets beyond the coarsest scale, and a nonzero request never rounds down to a stop. **Note:** The conversion depends on the ripples-per-rev value from the init config (or set_motor_params()), and closed-loop accuracy depends on the ripple-counter calibration (INV_R, KMC) from `motor_mohm` / `kv_uv_per_rpm`. If those aren't set, speed regulation falls back to chip defaults and the RPM target is approximate. Provide a full motor profile — at init or via set_motor_params() — for accurate low-RPM control. ##### `tile_drive_dc_h_move_distance` — Studio ```c void tile_drive_dc_h_move_distance(tile_t* tile, uint16_t ripples, drive_dc_h_direction_t direction) ``` Drive `ripples` commutation ripples in `direction`, then brake. Closed-loop step: programs RC_THR for the requested ripple count, clears the ripple counter, engages the bridge in `direction`, then polls until either CNT_DONE fires or the motor stalls. On completion the bridge is put into slow-decay brake — the motor is actively held at the new position, not coasting. - `ripples` — Number of ripples to advance (0–65472) - `direction` — DRIVE_DC_H_DIR_FORWARD or DRIVE_DC_H_DIR_REVERSE **Note:** This call BLOCKS until the move completes or the motor stalls. Worst-case wall-time depends on motor speed + load; budget accordingly. There is no built-in timeout beyond the chip's own stall detector — use a separate watchdog or @ref tile_drive_dc_h_wait_for_stop with a timeout if you need a hard upper bound. **Note:** On completion the bridge is left in BRAKE (slow decay, both low-side FETs on). Call @ref tile_drive_dc_h_coast afterwards if you'd rather let the motor freewheel. **Note:** Ripple counts are coarse (integer commutation ticks, not encoder counts). Resolution = 1/ripples_per_rev of a revolution — typically 1/12 turn. Don't expect sub-degree positioning. ##### `tile_drive_dc_h_is_running` — Studio ```c uint8_t tile_drive_dc_h_is_running(tile_t* tile) ``` Check whether the bridge is actively driving the motor. Returns 1 when the bridge is in forward or reverse drive (any non-zero, non-brake combination of IN1/IN2 in I²C bridge mode). Returns 0 when braking, coasting, sleeping, in pad-control mode, or not yet ready. **Returns:** 1 if driving, 0 otherwise ##### `tile_drive_dc_h_wait_for_stop` — Studio ```c uint8_t tile_drive_dc_h_wait_for_stop(tile_t* tile, uint32_t timeout_ms) ``` Block until the ripple counter stops advancing or `timeout_ms` elapses. Polls the 16-bit ripple counter at ~10 ms cadence. Returns 1 when two successive samples (≥30 ms apart) report the same count — i.e., the rotor is mechanically stationary. Returns 0 if `timeout_ms` elapses first. Useful after @ref tile_drive_dc_h_brake when you want to know the motor has actually settled rather than just "the bridge has been commanded to brake". - `timeout_ms` — Maximum time to wait, in milliseconds **Returns:** 1 if the rotor settled before timeout, 0 on timeout #### Configuration ##### `tile_drive_dc_h_set_control_mode` — Studio ```c void tile_drive_dc_h_set_control_mode(tile_t* tile, drive_dc_h_control_mode_t mode) ``` Switch the bridge control source between I²C and the EN/PH or IN1/IN2 tile pads. In I²C mode (default after init), forward/reverse/brake/coast control the bridge. In either pad-control mode, the chip ignores I²C bridge bits and tracks pads 2/3 directly: - PAD_PHEN — pad 2 = EN (PWM), pad 3 = PH (direction) - PAD_IN1IN2 — pad 2 = IN1, pad 3 = IN2 (independent half-bridges) Monitoring (voltage, current, fault, ripple count) keeps working over I²C in all modes. Switching to a pad mode disables I²C bridge calls (forward/reverse/etc) — they'll log an error and no-op. - `mode` — Bridge control source (DRIVE_DC_H_CTRL_*) ##### `tile_drive_dc_h_set_regulation_mode` — Studio ```c void tile_drive_dc_h_set_regulation_mode(tile_t* tile, drive_dc_h_reg_mode_t mode) ``` Select the regulation loop (open-loop / current / voltage / speed). Writes REG_CTRL bits in REG_CTRL0. After switching, set_target() is interpreted in the new loop's units (voltage code, speed code, etc.). Speed mode requires ripple counting to be enabled — the driver automatically sets EN_RC=1 when SPEED is selected. - `mode` — Regulation mode (DRIVE_DC_H_REG_*) **Note:** REG_CTRL is only writable with the outputs off (datasheet Table 8-29), so this briefly clears EN_OUT around the write. ##### `tile_drive_dc_h_set_current_regulation_mode` — Studio ```c void tile_drive_dc_h_set_current_regulation_mode(tile_t* tile, drive_dc_h_imode_t mode) ``` Set the current-regulation mode (IMODE in CONFIG3). Selects when the chip's internal current loop folds back to keep motor current under the ITRIP threshold. ITRIP itself is set by the combination of CS_GAIN_SEL (set_current_sense_gain) and the internal 500 mV VREF (INT_VREF=1, fixed by this driver since the external VREF pin isn't routed on the Drive.DC.H tile). - `mode` — Current regulation mode (DRIVE_DC_H_IMODE_*) ##### `tile_drive_dc_h_set_current_sense_gain` — Studio ```c void tile_drive_dc_h_set_current_sense_gain(tile_t* tile, uint8_t code) ``` Set the current-sense gain / max-current range. Programs CS_GAIN_SEL[2:0] in RC_CTRL0. Lower max-current ranges give finer current resolution but lower R_DS(on) headroom; higher ranges support larger motors but coarser get_current_ma() steps. Codes: 0 = 4 A, 1 = 2 A, 2 = 1 A, 3 = 0.5 A, 4 = 0.25 A, 5 = 0.125 A Also affects ITRIP when current regulation is enabled. - `code` — [0..5] CS_GAIN_SEL code ##### `tile_drive_dc_h_set_stall_enabled` — Studio ```c void tile_drive_dc_h_set_stall_enabled(tile_t* tile, uint8_t enabled) ``` Enable or disable hardware stall detection. Toggles EN_STALL in CONFIG0. When disabled, the STALL bit in the fault register won't latch and is_stalled() always returns 0. Useful while tuning the inrush time for a new motor. - `enabled` — 1 = stall detection on, 0 = off ##### `tile_drive_dc_h_set_inrush_time_ms` — Studio ```c void tile_drive_dc_h_set_inrush_time_ms(tile_t* tile, uint16_t ms) ``` Set the inrush blanking time (TINRUSH). Programs CONFIG1/CONFIG2 with a 16-bit count of 102.4 µs ticks, giving up to ~6.7 s. During the blanking window after a drive command, the stall detector ignores motor current — necessary because real motors draw several × steady-state during startup. Tune for the slowest motor you want to drive. - `ms` — [0..6710] ms Inrush blanking time **Note:** With soft-start on (EN_SS=1, init's default) and voltage or speed regulation active, the chip stretches the window to TINRUSH × WSET_VSET (datasheet §8.3.7.3.1): 100 ms at WSET_VSET=255 is a ~25 s ramp. ##### `tile_drive_dc_h_set_stall_recovery` — Studio ```c void tile_drive_dc_h_set_stall_recovery(tile_t* tile, drive_dc_h_stall_recovery_t mode) ``` Set the stall-detection recovery behavior (SMODE in CONFIG3). In LATCH mode, hitting a stall turns the bridge off until either clear_fault() is called or the chip is power-cycled. In REPORT mode the chip flags STALL but keeps driving — useful for haptics or actuators that legitimately stall against an end-stop. - `mode` — DRIVE_DC_H_STALL_LATCH or DRIVE_DC_H_STALL_REPORT ##### `tile_drive_dc_h_set_ripple_threshold` — Studio ```c void tile_drive_dc_h_set_ripple_threshold(tile_t* tile, uint16_t count) ``` Set the ripple-count threshold that fires CNT_DONE. The chip raises CNT_DONE when the running ripple count reaches (count × scale). Useful for "rotate N counts then stop" patterns: watch the CNT_DONE bit in get_fault(), then call coast(). Internal threshold is 10-bit; the driver picks the smallest scale (×2, ×8, ×16, or ×64) that fits `count`. - `count` — Threshold count (0 - 65472, larger = coarser scale) ##### `tile_drive_dc_h_set_ripple_filter_gain` — Studio ```c void tile_drive_dc_h_set_ripple_filter_gain(tile_t* tile, uint8_t code) ``` Set the ripple filter input scaling factor. Programs FLT_GAIN_SEL[1:0] in RC_CTRL0. Scales the magnitude of detected ripples before the counter — increase if get_ripple_count() undercounts, decrease if it spuriously counts noise. Codes: 0 = ×2, 1 = ×4 (default), 2 = ×8, 3 = ×16 - `code` — FLT_GAIN_SEL code (0-3) ##### `tile_drive_dc_h_set_motor_params` — Studio ```c void tile_drive_dc_h_set_motor_params(tile_t* tile, uint16_t motor_mohm, uint16_t ripples_per_rev, uint16_t kv_uv_per_rpm) ``` Program the motor profile for ripple counting and speed regulation. Writes the DRV8214's ripple-counter calibration registers (INV_R, KMC and their scale fields) from physical motor parameters, and caches ripples-per-rev for the set_speed_rpm()/get_speed_rpm() conversions. Equivalent to supplying `motor_mohm`, `ripples_per_rev` and `kv_uv_per_rpm` in the init config — use this when the driver was initialised with defaults (e.g. from Studio) and you want accurate closed-loop speed control. Example (RS PRO 834-7644, direct drive): motor_mohm=6000, ripples_per_rev=12, kv_uv_per_rpm=187. chip defaults (no-op for INV_R/KMC). (poles × brush pairs; common: 3, 5, 6, 7, 12). 0 = default (12); clamped to 255. motor: 100-2000). 0 = skip KMC tuning. - `motor_mohm` — [0..65535] mΩ Winding resistance. 0 = leave - `ripples_per_rev` — [0..255] Commutation ripples per shaft revolution - `kv_uv_per_rpm` — [0..5000] µV/RPM Back-EMF constant (typical small #### Other ##### `tile_drive_dc_h_find` ```c uint8_t tile_drive_dc_h_find(tiles_pal_t* hal, uint8_t instance) ``` Check whether a DRV8214 is present on the I2C bus. - `hal` — Platform HAL handle - `instance` — Instance index (0-8, see mapping table) **Returns:** 1 if device ACKs, 0 otherwise ##### `tile_drive_dc_h_init` ```c void tile_drive_dc_h_init(tiles_pal_t* hal, uint8_t instance, tile_t* tile, const drive_dc_h_cfg_t *cfg) ``` Initialize the DRV8214 motor driver. Verifies device presence, configures I2C bridge control in PWM mode, sets regulation parameters, and enables the output stage in coast state (motor not spinning). Call forward() or reverse() to start the motor. - `hal` — Platform HAL handle - `instance` — Instance index (0-8, see mapping table) - `tile` — Pointer to tile handle (populated by this function) - `cfg` — Optional config, or NULL for defaults ### Vibe Settings Settings Studio's Vibe interface offers for this tile, generated from the driver's `@studio control` annotations. Each is an argument of the function shown. | Setting | Tier | Values | Default | Function | |---|---|---|---|---| | Ripples per revolution | basic | 0 to 255 | 12 | `set_motor_params(ripples_per_rev)` | | Regulation | advanced | 00b: fixed-off-time current reg only (open-loop drive), 01b: cycle-by-cycle current reg, 10b: speed regulation (requires ripple counting), 11b: voltage regulation (default) | 11b: voltage regulation (default) | `set_regulation_mode(mode)` | | Current range (0 = 4 A … 5 = 0.125 A) | advanced | 0 to 5 | 0 | `set_current_sense_gain(code)` | | Stall detection | advanced | on / off | on | `set_stall_enabled(enabled)` | | Inrush time | advanced | 0 to 6710 ms | 100 | `set_inrush_time_ms(ms)` | | Motor winding resistance | advanced | 0 to 65.535 Ω | 0 | `set_motor_params(motor_mohm)` | | Motor Kv (back-EMF) | advanced | 0 to 5000 | 0 | `set_motor_params(kv_uv_per_rpm)` | ### Driver gaps Chip capabilities this driver does **not** expose. Each is tagged hardware-gated (the tile does not route the pin) or driver-deferred (possible, not yet implemented). Do not assume an API exists for these. #### nSLEEP pin (hardware-gated) (advanced) Chip's nSLEEP pin (~100 nA quiescent in sleep, ~1.3 mA active) is not routed to any tile pad in the Drive.DC.H rev-a layout (verify in definitions/Drive-DC-H-a.json — pads 1–10 are GND, EN/IN1, PH/IN2, I²C.CLK, I²C.DAT, NPROP, OUT1, OUT2, VM, V+; nSLEEP is strapped active on the PCB). sleep()/wake() only toggle the I²C-side EN_OUT bit; the chip itself can't reach its true quiescent floor. Closing this gap requires a tile hardware revision that routes nSLEEP to a connector pad. #### Address selection (factory variants) (niche) The DRV8214 derives its 7-bit I²C address from tri-level pins A0/A1, but the Drive.DC.H tile straps these to fixed values per factory variant: A30 (0x30), A31 (0x31), A33 (0x33), A34 (0x34 default). Address is chip-gated at order time, not runtime- settable. The 9-entry instance table in this driver covers the full chip address space, but only those four straps are sold — pick a tile variant per Drive.DC.H tile sharing a bus. ### Enums #### `drive_dc_h_direction_t` Drive direction for the tier-2 helpers. | Member | Value | Description | | --- | --- | --- | | `DRIVE_DC_H_DIR_FORWARD` | `0` | OUT1=H, OUT2=L (matches forward()). | | `DRIVE_DC_H_DIR_REVERSE` | `1` | OUT1=L, OUT2=H (matches reverse()). | ### Structs #### `drive_dc_h_cfg_t` | Type | Field | Description | | --- | --- | --- | | `uint8_t` | `mode` | | | `uint8_t` | `vm_gain` | | | `uint8_t` | `cs_gain` | | | `uint8_t` | `target` | | | `uint16_t` | `motor_mohm` | | | `uint8_t` | `ripples_per_rev` | | | `uint16_t` | `kv_uv_per_rpm` | | ### Defines | Name | Value | Description | | --- | --- | --- | | `TILE_DRIVE_DC_H_VERSION_MAJOR` | `4` | | | `TILE_DRIVE_DC_H_VERSION_MINOR` | `3` | | | `TILE_DRIVE_DC_H_VERSION_PATCH` | `0` | | | `DRV8214_I2C_ADDR_DEFAULT` | `0x34` | | | `DRV8214_CONFIG3_DEFAULT` | `0x63` | Expected CONFIG3 register value at power-on. | | `DRV8214_FAULT_FAULT` | `0x80` | Bit 7 — any fault active | | `DRV8214_FAULT_STALL` | `0x20` | Bit 5 — motor stall | | `DRV8214_FAULT_OCP` | `0x10` | Bit 4 — overcurrent | | `DRV8214_FAULT_OVP` | `0x08` | Bit 3 — overvoltage | | `DRV8214_FAULT_TSD` | `0x04` | Bit 2 — thermal shutdown | | `DRV8214_FAULT_NPOR` | `0x02` | Bit 1 — power-on reset | | `DRV8214_FAULT_CNT_DONE` | `0x01` | Bit 0 — ripple count done | | `DRIVE_DC_H_MODE_VOLTAGE` | `0` | | | `DRIVE_DC_H_MODE_SPEED` | `1` | | | `DRIVE_DC_H_MODE_RIPPLE_COUNT` | `2` | | | `DRIVE_DC_H_MODE_PAD_PHEN` | `3` | | | `DRIVE_DC_H_MODE_PAD_IN1IN2` | `4` | | --- Generated 2026-10-07 from the driver header at tiles@3edc9ed. Canonical HTML: https://bergsonne.io/docs/tiles/drive/dch ============================================================================== ## Drive.H driver > LRA/ERM haptic driver for the Drive.H tile (rev a). | | | | --- | --- | | Tile | `Drive.H` | | Driver version | `4.4.0` | | Header | `tile_drive_h.h` | | Generated from | `tiles@f70bca2` | | Canonical page | https://bergsonne.io/docs/tiles/drive/h | ### Overview Embeds the TI DRV2605 (DEVICE_ID 3 — the non-L part), a haptic driver for LRA (Linear Resonant Actuator) and ERM actuators with a built-in waveform library of 123 effects. The actuator is external, connected via the tile's OUT+ / OUT- pads; V_MOTOR accepts 2.5-5.5 V. The chip regulates output amplitude itself: the supply rail only sets headroom. Full-scale drive level comes from RATED_VOLTAGE / OD_CLAMP (see tile_drive_h_set_actuator_voltage()), NOT from the voltage applied to V_MOTOR. V_MOTOR is the DRV2605's only supply (VDD, 2.5-5.5 V); the output cannot exceed it. The datasheet's minimum load is 8 Ω at VDD = 5.2 V. V+ (pad 10) powers nothing but R2, a 100 kΩ pull-up on EN (pad 3). Power V+ from a logic rail, or drive pad 3 high from a Core pin: with neither, EN floats, the chip may stay in shutdown, and init() fails its device-ID check. Holding pad 3 low draws V+ / 100 kΩ (33 µA at 3.3 V). TRIG (pad 2) has a 100 kΩ pull-down (R1). The tile has no I2C pull-ups. Key specifications: - Output: full-bridge, amplitude regulated per config - Waveform lib: 123 haptic effects (6 libraries) - Smart-loop: automatic resonance tracking for LRA (closed loop) - Modes: Internal trigger, RTP, PWM, audio-to-vibe Datasheet: https://www.bergsonne.io/tiles/drive/h IC datasheet: https://www.ti.com/lit/ds/symlink/drv2605.pdf ### Examples #### Quick start ```c #include "core_tiles.h" tile_t haptic; tile_drive_h_init(core_tiles_pal(&core_i2c1), 0, &haptic, NULL); if (tile_is_ready(&haptic)) { tile_drive_h_play(&haptic, 1, 1); // play effect #1 once } ``` ### API 44 public functions. Functions marked **Studio** are callable from the Studio behaviour DSL. #### Runtime ##### `tile_drive_h_play` — Studio ```c void tile_drive_h_play(tile_t* tile, uint8_t index, uint8_t repeats) ``` Play a waveform effect from the built-in library. Loads the effect index into sequence slot 0, terminates the sequence, and triggers playback. For repeats > 1, re-triggers with a 200ms gap between plays. - `index` — [1..123] Library effect index (see datasheet). - `repeats` — [1..20] Number of times to play (1 = once). ##### `tile_drive_h_play_sequence` — Studio ```c void tile_drive_h_play_sequence(tile_t* tile, const uint8_t *effects, uint8_t count) ``` Play a sequence of up to 8 waveform effects. Loads effects into the waveform sequencer registers (0x04-0x0B), terminates the sequence, and triggers playback. Returns immediately — use tile_drive_h_is_playing() to poll for completion. DSL pads short sequences with 0 (DRV2605 effect 0 is "stop"), so a 3-effect sequence written as `[5, 10, 15, 0, 0, 0, 0, 0]` plays effects 5/10/15 then halts. - `effects` — Array of effect indices (1-123, 0 = stop) - `count` — Number of effects (1-8) ##### `tile_drive_h_load_sequence` — Studio ```c void tile_drive_h_load_sequence(tile_t* tile, const uint8_t *effects, uint8_t count) ``` Load effects into the waveform sequencer without triggering. Identical to tile_drive_h_play_sequence() but does NOT assert the GO bit. Use this to pre-load effects before switching to external trigger mode (edge or level). - `effects` — Array of effect indices (1-123, 0 = stop) - `count` — Number of effects (1-8) ##### `tile_drive_h_set_trigger` — Studio ```c void tile_drive_h_set_trigger(tile_t* tile, drive_h_trigger_t mode) ``` Set the trigger mode. Selects how the waveform sequencer is triggered: - DRIVE_H_TRIG_INTERNAL (default): I2C GO bit, used by play() and play_sequence(). - DRIVE_H_TRIG_EDGE: a rising edge on the IN/TRIG pin sets GO. A second rising edge while playing cancels. Pre-load effects with tile_drive_h_load_sequence() before entering this mode. - DRIVE_H_TRIG_LEVEL: GO follows the IN/TRIG pin level. High = playing, low = idle. Falling edge cancels. Not a Studio setting: rtp_stop(), pwm_input_stop(), audio_stop(), diagnose() and calibrate() all return MODE to internal trigger. - `mode` — Trigger source (drive_h_trigger_t) ##### `tile_drive_h_is_playing` — Studio ```c uint8_t tile_drive_h_is_playing(tile_t* tile) ``` Check whether an effect or sequence is still playing. Reads the GO bit in register 0x0C. The GO bit remains high until playback completes. **Returns:** 1 if playing, 0 if idle ##### `tile_drive_h_stop` — Studio ```c void tile_drive_h_stop(tile_t* tile) ``` Stop any currently playing effect. ##### `tile_drive_h_rtp_start` — Studio ```c void tile_drive_h_rtp_start(tile_t* tile) ``` Enter RTP (Real-Time Playback) mode. Sets DRV2605 MODE register to 0x05 (RTP). The chip drives the LRA at its resonant frequency with amplitude controlled by tile_drive_h_rtp_write(). Call tile_drive_h_rtp_stop() to return to internal trigger mode. ##### `tile_drive_h_rtp_write` — Studio ```c void tile_drive_h_rtp_write(tile_t* tile, uint8_t amplitude) ``` Write an amplitude value in RTP mode. - `amplitude` — [0..127] Amplitude (0 = off, 127 = max). ##### `tile_drive_h_rtp_stop` — Studio ```c void tile_drive_h_rtp_stop(tile_t* tile) ``` Exit RTP mode and return to internal trigger mode. ##### `tile_drive_h_get_status` — Studio ```c uint8_t tile_drive_h_get_status(tile_t* tile) ``` Read the raw STATUS register (0x00). Contains DEVICE_ID[7:5], DIAG_RESULT[3], FB_STS[2], OVER_TEMP[1], OC_DETECT[0]. Status bits clear on read. **Returns:** Raw status byte ##### `tile_drive_h_diagnose` — Studio ```c uint8_t tile_drive_h_diagnose(tile_t* tile) ``` Run actuator diagnostics. Enters diagnostic mode (MODE=6), triggers GO, and polls for completion. The DRV2605 checks whether the actuator is present, open, or shorted. **Returns:** 1 if actuator passed diagnostics, 0 if fault detected ##### `tile_drive_h_calibrate` — Studio ```c uint8_t tile_drive_h_calibrate(tile_t* tile) ``` Run auto-calibration for the connected actuator. Enters calibration mode (MODE=7) with datasheet-recommended parameters, triggers GO, and polls for completion. On success, the DRV2605 stores optimized A_CAL_COMP and A_CAL_BEMF values that improve playback fidelity. **Returns:** 1 if calibration passed, 0 if it failed to converge ##### `tile_drive_h_get_vbat_mv` — Studio ```c uint16_t tile_drive_h_get_vbat_mv(tile_t* tile) ``` Read supply voltage in millivolts. Reads the VBAT register (0x21). The reading is only valid while the device is actively driving a waveform (RTP, library playback, etc.). **Returns:** Battery voltage in mV (e.g. 3300 = 3.3 V), 0 if idle ##### `tile_drive_h_get_resonance_hz` — Studio ```c uint16_t tile_drive_h_get_resonance_hz(tile_t* tile) ``` Read LRA resonant frequency in Hz. Reads the LRA_PERIOD register (0x22). The reading is only valid while the device is actively driving a waveform and must not be polled during braking. **Returns:** Resonant frequency in Hz (e.g. 235), 0 if unavailable ##### `tile_drive_h_standby` — Studio ```c void tile_drive_h_standby(tile_t* tile) ``` Enter low-power standby. Sets the STANDBY bit in the MODE register. The device retains register values and can be woken quickly with tile_drive_h_wake(). ##### `tile_drive_h_wake` — Studio ```c void tile_drive_h_wake(tile_t* tile) ``` Wake from standby. Clears the STANDBY bit in the MODE register. The device returns to the active state, ready for playback. ##### `tile_drive_h_play_click` — Studio ```c void tile_drive_h_play_click(tile_t* tile) ``` Fire a single sharp tactile click. Plays ROM library effect 1 ("Strong Click — 100%") once. Returns immediately; the chip drives the click after the call returns. Use @ref tile_drive_h_is_playing to poll for completion. ##### `tile_drive_h_play_double_tap` — Studio ```c void tile_drive_h_play_double_tap(tile_t* tile) ``` Fire a double tap. Loads the sequencer with ROM library effect 10 ("Double Click — 100%"), a two-click effect, and triggers playback. The chip handles the inter-tap timing internally. Returns immediately; use @ref tile_drive_h_is_playing to poll. ##### `tile_drive_h_play_alert` — Studio ```c void tile_drive_h_play_alert(tile_t* tile) ``` Play an alert pattern. Sequences three ROM-library effects: effect 14 ("Strong Buzz — 100%") → effect 56 ("Pulsing Sharp 1 — 100%") → effect 14. Returns immediately; use @ref tile_drive_h_is_playing to poll. ##### `tile_drive_h_play_buzz` — Studio ```c void tile_drive_h_play_buzz(tile_t* tile, uint16_t ms) ``` Play a sustained vibration for `ms` milliseconds. Switches into RTP (Real-Time Playback) mode, drives a constant full-amplitude signal for `ms` milliseconds, then stops the RTP stream and returns to internal-trigger mode. / @ref tile_drive_h_rtp_stop. - `ms` — [0..65535] Duration in milliseconds **Note:** Blocking call — does not return until the buzz completes. For longer non-blocking patterns, drive RTP directly via ##### `tile_drive_h_is_calibrated` — Studio ```c uint8_t tile_drive_h_is_calibrated(tile_t* tile) ``` Check whether smart-loop calibration converged. Reads the STATUS register's DIAG_RESULT bit, which the auto- calibration engine populates on completion: 0 = converged (calibration produced valid A_CAL_COMP / A_CAL_BEMF values), 1 = failed. Only meaningful after a prior call to @ref tile_drive_h_calibrate. **Returns:** 1 if calibration converged, 0 otherwise **Note:** STATUS bits clear on read, so the result reflects the most recent calibration / diagnostic run. #### Configuration ##### `tile_drive_h_set_brake_factor` — Studio ```c void tile_drive_h_set_brake_factor(tile_t* tile, drive_h_brake_t factor) ``` Set the closed-loop braking strength. Writes FEEDBACK_CTRL FB_BRAKE_FACTOR[6:4]: the feedback gain while braking, relative to the gain while driving. More braking stops the actuator faster (crisper clicks) at the cost of loop stability; the datasheet default (4x) suits most actuators. Only used in closed loop. The datasheet asks for this to be set before auto-calibration. tile_drive_h_calibrate() itself runs at 3x / Medium (TI's recommended calibration values) and restores this setting afterwards. - `factor` — Braking gain (drive_h_brake_t) ##### `tile_drive_h_set_loop_gain` — Studio ```c void tile_drive_h_set_loop_gain(tile_t* tile, drive_h_loop_gain_t gain) ``` Set the closed-loop gain. Writes FEEDBACK_CTRL LOOP_GAIN[3:2]: how fast the loop drives the back-EMF (the actuator's speed) to the requested level. Higher gain settles faster and is less stable; Medium (the datasheet default) suits most actuators. Only used in closed loop. Like the brake factor, set it before calibrating. - `gain` — Loop gain (drive_h_loop_gain_t) ##### `tile_drive_h_set_loop_mode` — Studio ```c void tile_drive_h_set_loop_mode(tile_t* tile, uint8_t closed) ``` Switch between open-loop and closed-loop drive at runtime. Closed loop (recommended for LRAs) enables smart-loop back-EMF feedback: automatic resonance tracking, overdrive, and braking, with full-scale amplitude referenced to RATED_VOLTAGE. Open loop drives blind: amplitude is referenced to OD_CLAMP, RATED_VOLTAGE is ignored, and LRA playback does NOT resonance-track on this part — the commutation frequency comes from DRIVE_TIME (see tile_drive_h_set_resonance_hz()). Applies to whichever actuator type is currently selected (set_library() / FEEDBACK_CTRL N_ERM_LRA). - `closed` — 1 = closed-loop (smart-loop), 0 = open-loop **Note:** Switching to open loop makes OD_CLAMP the full-scale level: with init's 0x8C that is ~2.7 Vrms, stronger than closed loop. ##### `tile_drive_h_set_actuator_voltage` — Studio ```c void tile_drive_h_set_actuator_voltage(tile_t* tile, uint16_t rated_mv, uint16_t overdrive_mv) ``` Set the actuator drive levels in millivolts. The friendly-units version of tile_drive_h_set_actuator_params(): converts voltages to the RATED_VOLTAGE (0x16) and OD_CLAMP (0x17) register values using the DRV2605 datasheet equations (section 7.5.2, Eq. 2-5), honouring the currently selected actuator type (ERM vs LRA) and, for LRAs, the drive frequency currently programmed via tile_drive_h_set_resonance_hz(). - rated_mv: steady-state full-scale level (RMS for LRA, average for ERM). Closed-loop reference. - overdrive_mv: peak ceiling for overdrive/braking; also the full-scale reference in open-loop mode. Must be >= rated_mv; typically 1.3-1.5x for LRAs. Run tile_drive_h_calibrate() afterwards — the datasheet requires recalibration whenever these references change. above its rated voltage for long effects (play_buzz, RTP) heats it and shortens its life. The output never exceeds V_MOTOR, whatever is set here. The control defaults (2230 / 2700 mV) reproduce init's 0x56 / 0x8C at init's 238 Hz DRIVE_TIME. - `rated_mv` — [300..3600] Rated drive level in mV - `overdrive_mv` — [300..5000] Overdrive clamp in mV ##### `tile_drive_h_set_resonance_hz` — Studio ```c void tile_drive_h_set_resonance_hz(tile_t* tile, uint16_t hz) ``` Set the LRA resonant frequency the driver targets. Programs CONTROL1 DRIVE_TIME to half the LRA period — the datasheet-optimal value. In closed-loop mode this seeds the auto-resonance tracker (which then follows the real resonance); in open-loop mode it directly sets the commutation frequency. Read the actuator's actual resonance with tile_drive_h_get_resonance_hz() while driving in closed loop. - `hz` — [125..300] LRA resonant frequency in Hz #### Other ##### `tile_drive_h_find` ```c uint8_t tile_drive_h_find(tiles_pal_t* hal, uint8_t instance) ``` Check whether a DRV2605 is present on the I2C bus. - `hal` — Platform HAL handle - `instance` — Instance index (0 = default, see mapping table) **Returns:** 1 if device ACKs, 0 otherwise ##### `tile_drive_h_init` ```c void tile_drive_h_init(tiles_pal_t* hal, uint8_t instance, tile_t* tile, const drive_h_cfg_t *cfg) ``` Initialize the DRV2605 haptic driver. Verifies the device ID, exits standby, and configures the actuator drive mode. Pass cfg=NULL for defaults (LRA closed-loop, library 6, RATED_VOLTAGE 0x56 / OD_CLAMP 0x8C: see drive_h_cfg_t). Also writes the settings Studio treats as known at power-on, since the chip keeps its registers across an MCU reset: FEEDBACK_CTRL (brake 4x, loop gain Medium), CONTROL2 to its reset value 0xF5 (SAMPLE_TIME 300 µs, bidirectional input), DRIVE_TIME for 238 Hz, and the open/closed-loop bit for BOTH actuator types, so a later set_library() keeps the loop mode. - `hal` — Platform HAL handle - `instance` — Instance index (0 = default, see mapping table) - `tile` — Pointer to tile handle (populated by this function) - `cfg` — Optional config, or NULL for defaults **Note:** Blocks for ~500 ms during init. Call once at startup. ##### `tile_drive_h_set_sequence_wait` — Studio ```c void tile_drive_h_set_sequence_wait(tile_t* tile, uint8_t slot, uint8_t delay_steps) ``` Set a per-slot wait delay in the loaded sequence. The 8 sequencer slots can each be marked as either an effect (MSB=0, low 7 bits = waveform 1..123) or a wait time (MSB=1, low 7 bits × 10 ms = pause). This call rewrites slot @p slot with a wait of @p delay_steps × 10 ms (max 1270 ms). Use it after tile_drive_h_load_sequence() to insert pauses between effects, e.g. play 1, wait 200 ms, play 47. Slot is silently ignored if >= 8; delay_steps is clipped to 0x7F. - `slot` — [0..7] Slot index - `delay_steps` — [0..127] Wait length in 10 ms steps (0 = no wait, 127 = 1.27 s) ##### `tile_drive_h_set_library` — Studio ```c void tile_drive_h_set_library(tile_t* tile, drive_h_library_t library) ``` Switch the active waveform library at runtime. The DRV2605 ships with 6 ROM libraries: 1–5 are TS2200 ERM libraries (A–E), 6 is the LRA library. Library 0 is empty (silence). This call also updates the FEEDBACK_CTRL N_ERM_LRA bit so the chip drives the correct actuator type. The actuator is external (OUT± pads), so this must match what is wired: an LRA (coin / linear) uses DRIVE_H_LIB_LRA, an ERM (spinning mass) motor one of the ERM libraries. - `library` — Library (use DRIVE_H_LIB_* constants) ##### `tile_drive_h_set_actuator_params` — Studio ```c void tile_drive_h_set_actuator_params(tile_t* tile, uint8_t rated_voltage, uint8_t od_clamp, uint8_t fb_brake, uint8_t loop_gain) ``` Tune the actuator drive parameters. Writes the four registers that the auto-calibration engine and smart-loop architecture consume: - rated_voltage (0x16): full-scale RMS drive level (closed-loop ref). - od_clamp (0x17): peak overdrive ceiling (also open-loop ref). - fb_brake (0x1A bits 6:4): feedback brake factor (0=1× .. 6=16×, 7=off). - loop_gain (0x1A bits 3:2): closed-loop gain (0=Low .. 3=Very high). Pass 0 for rated_voltage / od_clamp to leave that register untouched. From C, pass 0xFF for fb_brake / loop_gain to leave them untouched (Studio bounds them to their fields). Run tile_drive_h_calibrate() afterwards to update A_CAL_COMP / A_CAL_BEMF. Register-level escape hatch. For settings prefer tile_drive_h_set_actuator_voltage() (millivolts), tile_drive_h_set_brake_factor() and tile_drive_h_set_loop_gain(). - `rated_voltage` — [0..255] RATED_VOLTAGE byte (0 = no change) - `od_clamp` — [0..255] OD_CLAMP byte (0 = no change) - `fb_brake` — [0..7] Feedback brake factor (drive_h_brake_t codes; 0xFF from C = no change) - `loop_gain` — [0..3] Loop gain (drive_h_loop_gain_t codes; 0xFF from C = no change) ##### `tile_drive_h_set_resonance_params` — Studio ```c void tile_drive_h_set_resonance_params(tile_t* tile, uint8_t sample_time, uint8_t blanking_time, uint8_t idiss_time) ``` Tune the LRA auto-resonance tracker. Writes Control2 (0x1C) bits for sample / blanking / current- dissipation timing. Default values (3 / 1 / 1) work for most coin-type LRAs at 175–235 Hz. Tune only if calibration fails to converge or the actuator runs at the edges of the 125–300 Hz window. init() writes the reset values (3 / 1 / 1). From C, pass 0xFF for any field to leave that register slice untouched (Studio bounds each to its 2-bit field). SAMPLE_TIME also feeds the LRA voltage maths in tile_drive_h_set_actuator_voltage(). - `sample_time` — [0..3] Sample time (0 = 150 µs, 1 = 200, 2 = 250, 3 = 300 µs; 0xFF from C = no change) - `blanking_time` — [0..3] Blanking time code (0xFF from C = no change) - `idiss_time` — [0..3] Current-dissipation time code (0xFF from C = no change) ##### `tile_drive_h_set_waveform_timing` — Studio ```c void tile_drive_h_set_waveform_timing(tile_t* tile, int8_t overdrive, int8_t sustain_pos, int8_t sustain_neg, int8_t brake) ``` Tune the open-loop library waveform timing. Adds signed offsets (in 5 ms steps) to the overdrive, sustain, and brake portions of every library effect. Offsets are 8-bit two's complement, so range is -640..+635 ms per knob. These offsets are only honoured in open-loop mode — closed-loop mode generates them automatically from back-EMF feedback. - `overdrive` — [-128..127] Overdrive Time Offset (0x0D, signed × 5 ms) - `sustain_pos` — [-128..127] Sustain-Time Positive Offset (0x0E, signed × 5 ms) - `sustain_neg` — [-128..127] Sustain-Time Negative Offset (0x0F, signed × 5 ms) - `brake` — [-128..127] Brake Time Offset (0x10, signed × 5 ms) ##### `tile_drive_h_set_rtp_format` — Studio ```c void tile_drive_h_set_rtp_format(tile_t* tile, uint8_t unsigned_, uint8_t bidir) ``` Set the RTP data format. Selects how RTP_INPUT bytes are interpreted: - signed (default for closed-loop bidirectional): 0x80 = full reverse, 0x00 = mid-scale (no drive), 0x7F = full forward. - unsigned (recommended for closed-loop unidirectional and open-loop): 0x00 = no drive, 0xFF = full drive. Also exposes the BIDIR_INPUT bit (CONTROL2[7]) which selects unidirectional vs bidirectional input interpretation; tie this to the same convention as the format flag. - `unsigned_` — [0..1] 1 = unsigned data format, 0 = signed (default) - `bidir` — [0..1] 1 = bidirectional input (default), 0 = unidirectional ##### `tile_drive_h_pwm_input_start` — Studio ```c void tile_drive_h_pwm_input_start(tile_t* tile) ``` Drive the actuator from a PWM signal on pad 2 (TRIG). Sets MODE=3 with N_PWM_ANALOG=0. The chip accepts a 10–250 kHz PWM signal: duty cycle directly modulates output amplitude (50 % = no drive in bidirectional mode; 0 % = no drive in unidirectional mode). Useful for offloading waveform generation to a hardware timer or audio rendering pipeline. Call tile_drive_h_set_rtp_format() first if you need to change the unidirectional / bidirectional interpretation. ##### `tile_drive_h_analog_input_start` — Studio ```c void tile_drive_h_analog_input_start(tile_t* tile) ``` Drive the actuator from an analog voltage on pad 2 (TRIG). Sets MODE=3 with N_PWM_ANALOG=1. Reference voltage is 1.8 V: 0 V → 0 % drive, 0.9 V → 50 %, 1.8 V → 100 %. Useful for waveform synthesis from a DAC or analog signal source. **Note:** Pad 2 (TRIG) is a digital pad on the tile. The chip pin itself accepts 0–1.8 V analog, but the driving Core must output a clean analog level — Drive.H has no input filter or AC-coupling cap on TRIG. ##### `tile_drive_h_pwm_input_stop` — Studio ```c void tile_drive_h_pwm_input_stop(tile_t* tile) ``` Exit PWM / analog input mode. Returns to internal-trigger mode (MODE=0). Same effect as tile_drive_h_audio_stop(); both modes share the IN/TRIG pin. ##### `tile_drive_h_audio_start` — Studio ```c void tile_drive_h_audio_start(tile_t* tile) ``` Drive the actuator from an AC-coupled audio signal. Sets MODE=4 (audio-to-vibe), N_PWM_ANALOG=1 (analog), and AC_COUPLE=1 (0.9 V common-mode bias on IN/TRIG). The chip envelope-detects the audio and drives haptic vibration at matching intensity. **Note:** The DRV2605 expects an AC-coupled line-level audio source (1.8 Vpp full-scale). Drive.H rev a does not include a series capacitor on pad 2 — users wanting audio-to-vibe must add their own external 1 µF AC-coupling cap between the audio source and pad 2. ##### `tile_drive_h_set_audio_params` — Studio ```c void tile_drive_h_set_audio_params(tile_t* tile, uint8_t peak_time, uint8_t filter, uint8_t min_input, uint8_t max_input, uint8_t min_drive, uint8_t max_drive) ``` Tune the audio-to-vibe envelope detector. Configures the four ATV control registers: - ATV_CTRL (0x11): peak-detect time (0=10ms..3=40ms) and low-pass filter cutoff (0=100Hz..3=200Hz). - ATV_MIN_INPUT (0x12): minimum input level below which the envelope is gated (output silent). raw × 1.8/255 V. - ATV_MAX_INPUT (0x13): full-scale input level. raw × 1.8/255 V. - ATV_MIN_DRIVE (0x14): minimum output drive once unmuted. raw / 255 × 100 %. - ATV_MAX_DRIVE (0x15): maximum output drive at full input. raw / 255 × 100 %. Pass 0xFF for any field to leave it untouched. That makes 0xFF, the reset value of ATV_MAX_INPUT and ATV_MAX_DRIVE, unreachable once changed: use tile_drive_h_set_audio_envelope() and tile_drive_h_set_audio_levels() instead, which have no sentinel. - `peak_time` — [0..255] ATH_PEAK_TIME 0..3 (10/20/30/40 ms; 0xFF = no change) - `filter` — [0..255] ATH_FILTER 0..3 (100/125/150/200 Hz; 0xFF = no change) - `min_input` — [0..255] Minimum input gate (0xFF = no change) - `max_input` — [0..255] Full-scale input level (0xFF = no change) - `min_drive` — [0..255] Minimum output drive once active (0xFF = no change) - `max_drive` — [0..255] Full-scale output drive (0xFF = no change) ##### `tile_drive_h_set_audio_envelope` — Studio ```c void tile_drive_h_set_audio_envelope(tile_t* tile, drive_h_atv_peak_t peak_time, drive_h_atv_filter_t filter) ``` Set the audio-to-vibe envelope detector timing. Writes ATV_CTRL (0x11): peak-detection time and low-pass cutoff. Reset values: 20 ms, 125 Hz. - `peak_time` — Peak-detection time (drive_h_atv_peak_t) - `filter` — Low-pass filter cutoff (drive_h_atv_filter_t) ##### `tile_drive_h_set_audio_levels` — Studio ```c void tile_drive_h_set_audio_levels(tile_t* tile, uint8_t min_input, uint8_t max_input, uint8_t min_drive, uint8_t max_drive) ``` Set the audio-to-vibe input window and output drive range. Writes ATV_MIN_INPUT..ATV_MAX_DRIVE (0x12-0x15). Every value is written as given (no sentinel). Input levels are raw × 1.8 V / 255; drive levels raw / 255 × full scale. Reset values: 0x19 / 0xFF / 0x19 / 0xFF. - `min_input` — [0..255] Input level below which the output stays silent - `max_input` — [0..255] Input level that gives full drive - `min_drive` — [0..255] Output drive at the minimum input - `max_drive` — [0..255] Output drive at the full-scale input ##### `tile_drive_h_audio_stop` — Studio ```c void tile_drive_h_audio_stop(tile_t* tile) ``` Exit audio-to-vibe mode. Returns to internal-trigger mode (MODE=0) and clears the AC_COUPLE bit. Same physical effect as tile_drive_h_pwm_input_stop(). ##### `tile_drive_h_program_otp` ```c uint8_t tile_drive_h_program_otp(tile_t* tile) ``` Burn the current calibration into on-chip OTP. Writes the current contents of registers 0x16–0x1A (RATED_VOLTAGE, OD_CLAMP, A_CAL_COMP, A_CAL_BEMF, FEEDBACK_CTRL) to the DRV2605's nonvolatile OTP cells. After this, those values become the power-on defaults — the chip skips run-time calibration on subsequent boots. once per device. A bad programming run permanently mistunes the chip. NOT exposed to Studio by design. Pre-conditions: - Run tile_drive_h_calibrate() first and confirm pass. - VDD must be 4.0–4.4 V at the moment of programming (datasheet section 7.5.7). Lower VDD silently corrupts the cells. Returns 1 if the OTP_STATUS bit reads 1 after programming, 0 if the burn failed or OTP was already programmed. **Returns:** 1 on success, 0 on failure / already-programmed ##### `tile_drive_h_get_otp_status` — Studio ```c uint8_t tile_drive_h_get_otp_status(tile_t* tile) ``` Read the OTP_STATUS bit. Returns 1 if the chip's OTP cells have already been programmed (registers 0x16–0x1A boot from OTP rather than chip defaults). **Returns:** 1 if OTP is programmed, 0 if unprogrammed ### Vibe Settings Settings Studio's Vibe interface offers for this tile, generated from the driver's `@studio control` annotations. Each is an argument of the function shown. | Setting | Tier | Values | Default | Function | |---|---|---|---|---| | Actuator type | basic | ERM motor: TS2200 Library A (fixed open-loop overdrive), ERM motor: TS2200 Library B (no overdrive), ERM motor: TS2200 Library C (no overdrive), ERM motor: TS2200 Library D (no overdrive), ERM motor: TS2200 Library E (no overdrive), LRA (coin / linear actuator): LRA library, auto-resonance | LRA (coin / linear actuator): LRA library, auto-resonance | `set_library(library)` | | Actuator rated voltage | basic | 0.3 to 3.6 V | 2230 | `set_actuator_voltage(rated_mv)` | | Brake strength | advanced | 1x, 2x, 3x, 4x, 6x, 8x, 16x, Braking off | 4x | `set_brake_factor(factor)` | | Loop gain | advanced | Low, Medium, High, Very high | Medium | `set_loop_gain(gain)` | | Closed-loop drive | advanced | on / off | on | `set_loop_mode(closed)` | | Overdrive clamp | advanced | 0.3 to 5 V | 2700 | `set_actuator_voltage(overdrive_mv)` | | LRA resonance | advanced | 125 to 300 | 238 | `set_resonance_hz(hz)` | ### Driver gaps Chip capabilities this driver does **not** expose. Each is tagged hardware-gated (the tile does not route the pin) or driver-deferred (possible, not yet implemented). Do not assume an API exists for these. #### OTP waveform burning (niche) The DRV2605 can burn custom waveforms into one-time-programmable memory. This is implemented in firmware (tile_drive_h_program_otp()) but intentionally NOT exposed to Studio: it permanently and irreversibly modifies the chip. Driver-deferred by policy, not a hardware gap — call it from C if you really need it. #### Minor control bits (niche) Driver-deferred: LRA_DRIVE_MODE (drive once or twice per cycle), SUPPLY_COMP_DIS (supply-voltage compensation), NG_THRESH (PWM / analog noise gate), BRAKE_STABILIZER, STARTUP_BOOST, BEMF_GAIN, LIBRARY_SEL.HI_Z (outputs high-impedance while idle) and MODE.DEV_RESET have no setter. Reachable only by editing the driver; none is hardware-gated. #### Hardware EN control (niche) Hardware-gated in part: the DRV2605 EN pin is on pad 3 with a 100 kΩ pull-up to V+ (pad 10), so the driver cannot toggle it over I2C. Use the STANDBY bit (tile_drive_h_standby()) or drive pad 3 from a Core GPIO. With EN low the chip still ACKs its address but ignores reads and writes (SLOS825E §7.4.1.3), so init fails on the device-ID check; registers are kept. ### Enums #### `drive_h_trigger_t` | Member | Value | Description | | --- | --- | --- | | `DRIVE_H_TRIG_INTERNAL` | `0` | Internal: fired over I2C (the GO bit) | | `DRIVE_H_TRIG_EDGE` | `1` | Rising edge on the TRIG pad | | `DRIVE_H_TRIG_LEVEL` | `2` | Level on the TRIG pad | #### `drive_h_brake_t` | Member | Value | Description | | --- | --- | --- | | `DRIVE_H_BRAKE_1X` | `0` | 1x | | `DRIVE_H_BRAKE_2X` | `1` | 2x | | `DRIVE_H_BRAKE_3X` | `2` | 3x | | `DRIVE_H_BRAKE_4X` | `3` | 4x | | `DRIVE_H_BRAKE_6X` | `4` | 6x | | `DRIVE_H_BRAKE_8X` | `5` | 8x | | `DRIVE_H_BRAKE_16X` | `6` | 16x | | `DRIVE_H_BRAKE_OFF` | `7` | Braking off | #### `drive_h_loop_gain_t` | Member | Value | Description | | --- | --- | --- | | `DRIVE_H_LOOP_GAIN_LOW` | `0` | Low | | `DRIVE_H_LOOP_GAIN_MEDIUM` | `1` | Medium | | `DRIVE_H_LOOP_GAIN_HIGH` | `2` | High | | `DRIVE_H_LOOP_GAIN_VERY_HIGH` | `3` | Very high | #### `drive_h_atv_peak_t` Audio-to-vibe peak-detection time (ATV_CTRL ATH_PEAK_TIME[3:2]). Reset: 20 ms. | Member | Value | Description | | --- | --- | --- | | `DRIVE_H_ATV_PEAK_10MS` | `0` | 10 ms | | `DRIVE_H_ATV_PEAK_20MS` | `1` | 20 ms | | `DRIVE_H_ATV_PEAK_30MS` | `2` | 30 ms | | `DRIVE_H_ATV_PEAK_40MS` | `3` | 40 ms | #### `drive_h_atv_filter_t` Audio-to-vibe low-pass filter (ATV_CTRL ATH_FILTER[1:0]). Reset: 125 Hz. | Member | Value | Description | | --- | --- | --- | | `DRIVE_H_ATV_LPF_100HZ` | `0` | 100 Hz | | `DRIVE_H_ATV_LPF_125HZ` | `1` | 125 Hz | | `DRIVE_H_ATV_LPF_150HZ` | `2` | 150 Hz | | `DRIVE_H_ATV_LPF_200HZ` | `3` | 200 Hz | #### `drive_h_library_t` ROM waveform library, which also sets the actuator type (ERM vs LRA). | Member | Value | Description | | --- | --- | --- | | `DRIVE_H_LIB_EMPTY` | `0` | Empty library (silence). | | `DRIVE_H_LIB_ERM_A` | `1` | ERM motor: TS2200 Library A (fixed open-loop overdrive). | | `DRIVE_H_LIB_ERM_B` | `2` | ERM motor: TS2200 Library B (no overdrive). | | `DRIVE_H_LIB_ERM_C` | `3` | ERM motor: TS2200 Library C (no overdrive). | | `DRIVE_H_LIB_ERM_D` | `4` | ERM motor: TS2200 Library D (no overdrive). | | `DRIVE_H_LIB_ERM_E` | `5` | ERM motor: TS2200 Library E (no overdrive). | | `DRIVE_H_LIB_LRA` | `6` | LRA (coin / linear actuator): LRA library, auto-resonance. | ### Structs #### `drive_h_cfg_t` | Type | Field | Description | | --- | --- | --- | | `uint8_t` | `library` | | | `uint8_t` | `closed_loop` | | | `uint8_t` | `rated_voltage` | | | `uint8_t` | `od_clamp` | | ### Defines | Name | Value | Description | | --- | --- | --- | | `TILE_DRIVE_H_VERSION_MAJOR` | `4` | | | `TILE_DRIVE_H_VERSION_MINOR` | `4` | | | `TILE_DRIVE_H_VERSION_PATCH` | `0` | | | `DRV2605L_I2C_ADDR_DEFAULT` | `0x5A` | | | `DRV2605L_STATUS_DEFAULT` | `0x60` | Expected default STATUS register value (DEVICE_ID = 3). | | `DRV2605L_CONTROL2_DEFAULT` | `0xF5` | | | `DRV2605L_STATUS_DEVICE_ID` | `0xE0` | Bits 7:5 — device ID | | `DRV2605L_STATUS_DIAG_RESULT` | `0x08` | Bit 3 — diagnostic result | | `DRV2605L_STATUS_OVER_TEMP` | `0x02` | Bit 1 — over-temperature | | `DRV2605L_STATUS_OC_DETECT` | `0x01` | Bit 0 — overcurrent detect | | `DRV2605L_MODE_INTERNAL_TRIG` | `0x00` | Internal trigger (I2C GO) | | `DRV2605L_MODE_EXT_EDGE` | `0x01` | External trigger, edge mode | | `DRV2605L_MODE_EXT_LEVEL` | `0x02` | External trigger, level mode | | `DRV2605L_MODE_PWM_ANALOG` | `0x03` | PWM or analog input on TRIG | | `DRV2605L_MODE_AUDIO` | `0x04` | Audio-to-vibe on TRIG | | `DRV2605L_MODE_RTP` | `0x05` | Real-time playback | | `DRV2605L_MODE_DIAGNOSTICS` | `0x06` | Actuator diagnostics | | `DRV2605L_MODE_CALIBRATION` | `0x07` | Auto-calibration | | `DRV2605L_MODE_STANDBY` | `0x40` | STANDBY bit (bit 6) | | `DRV2605L_SEQ_MAX` | `8` | | --- Generated 2026-10-07 from the driver header at tiles@f70bca2. Canonical HTML: https://bergsonne.io/docs/tiles/drive/h ============================================================================== ## Drive.P driver > Piezoelectric haptic driver for the Drive.P tile (rev a). | | | | --- | --- | | Tile | `Drive.P` | | Driver version | `3.5.0` | | Header | `tile_drive_p.h` | | Generated from | `tiles@f70bca2` | | Canonical page | https://bergsonne.io/docs/tiles/drive/p | ### Overview Embeds the Boréas Technologies BOS1921, a piezoelectric driver with integrated high-voltage boost (190 Vpp differential), waveform synthesizer, 1024-sample FIFO, and piezo sensing. Key specifications: - Output: 190 Vpp differential, up to 820 nF capacitive load - Sensing: 7.6 mV resolution (fine), 54.5 mV (coarse) - Play modes: Direct, FIFO, RAM, RAM Synth - Sample rate: 8 ksps – 1024 ksps (configurable) Datasheet: https://www.bergsonne.io/tiles/drive/p IC datasheet: https://mosaic-component-datasheets.s3.eu-north-1.amazonaws.com/5/Bor_as_Technologies-BOS1921.pdf ### Examples #### Quick start ```c #include "core_tiles.h" tile_t piezo; tile_drive_p_init(core_tiles_pal(&core_i2c1), 0, &piezo, NULL); if (tile_is_ready(&piezo)) { tile_drive_p_set_mode(&piezo, DRIVE_P_MODE_PLAY_FIFO); tile_drive_p_write_fifo(&piezo, 0x7FFF); } ``` ### API 28 public functions. Functions marked **Studio** are callable from the Studio behaviour DSL. #### Lifecycle ##### `tile_drive_p_sleep` — Studio ```c void tile_drive_p_sleep(tile_t* tile) ``` Enter low-power sleep mode. Writes CONFIG with DS = 1 and OE = 0 (datasheet §6.2.6): the output stops and the chip drops to SLEEP — ~2.4 µA with retention, ~0.6 µA without (see @ref tile_drive_p_set_sleep_retention). Any I2C traffic wakes the chip, so leave the bus alone until @ref tile_drive_p_wake; set_mode() and the play / sense helpers call wake() for you. ##### `tile_drive_p_wake` — Studio ```c uint8_t tile_drive_p_wake(tile_t* tile) ``` Wake the chip from SLEEP and restore the driver's configuration. Follows the datasheet start-up sequence (§7.4.1 steps 3-6, §7.4.2): a dummy write wakes the chip (its data is ignored, §6.2.6), a 1 ms settle covers the 50 µs SLEEP→IDLE time, then CONFIG (DS = 0, OE = 0, GAINS / GAIND / RET), PARCAP (with UPI), SUP_RISE and COMM (TOUT) are rewritten from the driver's shadow, and CHIP_ID is read back. Rewriting everything covers both retention settings: with retention off (RET = 1) the chip loses its registers in SLEEP; its I2C address survives either way (§6.10.8). RAM contents (RAM-playback / synthesis waveforms) are not restored — re-load them after a no-retention sleep. The tile returns to IDLE mode. Also use it after a COMM.TOUT auto-sleep (@ref tile_drive_p_set_auto_sleep), which leaves the tile READY but the chip asleep. Harmless on an awake chip. 0 otherwise (tile ERROR, or the tile was never initialized) **Returns:** 1 if the chip answered with the expected CHIP_ID (tile READY), #### Runtime ##### `tile_drive_p_set_mode` — Studio ```c void tile_drive_p_set_mode(tile_t* tile, drive_p_mode_t mode) ``` Set the operating mode. - `mode` — One of the drive_p_mode_t values ##### `tile_drive_p_read` — Studio ```c uint16_t tile_drive_p_read(tile_t* tile) ``` Read the current return register value. **Returns:** 16-bit value from the currently selected return register ##### `tile_drive_p_read_sense` — Studio ```c int16_t tile_drive_p_read_sense(tile_t* tile) ``` Read the sensed piezo voltage. Must be in SENSE_FINE or SENSE_COARSE mode. **Returns:** Signed 16-bit sense value (−2048 to +2047) ##### `tile_drive_p_read_status` — Studio ```c uint16_t tile_drive_p_read_status(tile_t* tile) ``` Read the IC status register. **Returns:** 16-bit IC_STATUS value ##### `tile_drive_p_play_click` — Studio ```c void tile_drive_p_play_click(tile_t* tile, uint8_t intensity_pct) ``` Fire a single sharp tactile click. Streams a 2 ms half-sine pulse through the FIFO at 8 ksps in one burst, framed by 0 V samples: two before it (discharges residual piezo charge, §6.2.18) and the pulse itself ends at exactly 0 V, so the output rests at 0 V when the FIFO drains (§6.6). Intensity scales the peak output amplitude in the configured voltage range (default ±95 V; see @ref tile_drive_p_set_output_range to switch to ±13.28 V for low-voltage piezos). Returns when the FIFO has been written; the chip continues playing the click after the call returns. The output is switched off (OE = 0) right after the last sample is queued; the chip finishes the FIFO first (§6.6), then idles. The next play / set_mode call turns it back on. - `intensity_pct` — [0..100] Percent of full-scale output (100 = ±95 V, or ±13.28 V in the low range) ##### `tile_drive_p_play_sine` — Studio ```c void tile_drive_p_play_sine(tile_t* tile, uint16_t freq_hz, uint8_t intensity_pct, uint16_t ms) ``` Play a continuous sine wave for `ms` milliseconds. Generates and streams sine samples at 8 ksps. Frequency is software-quantised to the sample rate (max useful ~3 kHz). The last 2 ms fade linearly to exactly 0 V, so the output never parks at a DC level (§6.6). Streaming paces on FIFO_STATE.FIFO_SPACE and writes 32-sample bursts: the call returns once the last sample is queued, i.e. it blocks for about (ms − 128) ms on long tones and the chip plays out the final ≤ 128 ms after it returns. Gapless output needs a ≥ 400 kHz bus. Stops early if the chip reports ERROR or stops draining for 20 ms. Like play_click(), turns the output off (OE = 0) after queuing the last sample; the chip idles once the FIFO has drained. - `freq_hz` — [1..4000] Sine frequency in Hz (50–3000 useful range) - `intensity_pct` — [0..100] Percent of full-scale output (100 = ±95 V, or ±13.28 V in the low range) - `ms` — Duration in milliseconds ##### `tile_drive_p_play_buzz` — Studio ```c void tile_drive_p_play_buzz(tile_t* tile, uint8_t intensity_pct, uint16_t ms) ``` Play a buzz (sustained mid-frequency vibration). Convenience wrapper for @ref tile_drive_p_play_sine at 150 Hz — a frequency typical small-form-factor piezo actuators feel strongly at. Use `play_sine` directly if you need a specific frequency. - `intensity_pct` — [0..100] Percent of full-scale output (100 = ±95 V, or ±13.28 V in the low range) - `ms` — Duration in milliseconds ##### `tile_drive_p_play_pulse_train` — Studio ```c void tile_drive_p_play_pulse_train(tile_t* tile, uint8_t intensity_pct, uint8_t count, uint16_t gap_ms) ``` Play N clicks separated by gaps. The classic "tick-tick-tick" pattern. Composes @ref tile_drive_p_play_click with `core_delay_ms` between clicks. The output stays on between clicks and goes off (OE = 0) once, after the last click is queued. - `intensity_pct` — [0..100] Percent of full-scale output (100 = ±95 V, or ±13.28 V in the low range) - `count` — [1..255] Number of clicks - `gap_ms` — Milliseconds between successive clicks ##### `tile_drive_p_is_touched` — Studio ```c uint8_t tile_drive_p_is_touched(tile_t* tile, uint16_t threshold_mv) ``` Check whether the piezo is being touched / pressed. Switches into sense mode (fine resolution), reads one sense sample, compares the absolute value against `threshold_mv`, and returns the boolean result. Leaves the chip in sense mode after the call — call `tile_drive_p_set_mode(tile, DRIVE_P_MODE_IDLE)` (or any play mode) to return to driving the actuator. - `threshold_mv` — Absolute sense voltage threshold in mV **Returns:** 1 if sense > threshold (touched), 0 otherwise ##### `tile_drive_p_play_on_touch` — Studio ```c uint8_t tile_drive_p_play_on_touch(tile_t* tile, uint8_t intensity_pct, uint16_t threshold_mv, uint32_t timeout_ms) ``` Block until touch detected, then fire a click. Polls the sense channel until `threshold_mv` is exceeded, then switches into FIFO mode and plays a click via @ref tile_drive_p_play_click. The classic closed-loop tactile-feedback idiom — press the piezo, feel the click. Polling polls every ~1 ms; returns 0 if `timeout_ms` elapses without detection. - `intensity_pct` — [0..100] Percent of full-scale output (100 = ±95 V, or ±13.28 V in the low range) - `threshold_mv` — Touch threshold in mV - `timeout_ms` — Maximum time to wait **Returns:** 1 if a touch fired the click, 0 on timeout ##### `tile_drive_p_play_samples` — Studio ```c void tile_drive_p_play_samples(tile_t* tile, const int16_t* samples, uint16_t count) ``` Stream a buffer of pre-computed samples through the FIFO. Switches into FIFO play mode (if not already there) and writes `count` samples to REFERENCE[11:0] (signed 12-bit, two's complement), 8 ksps. Values beyond ±BOS1921_REFERENCE_MAX (±1743, the ±95 V rated output) are clamped. Start and end the waveform at 0: the last sample stays on the output when the FIFO drains, and the driver does not append one (so back-to-back calls can stream a long waveform in pieces). Paced on FIFO_SPACE with 32-sample burst writes; returns once the last sample is queued (up to 128 ms of it still playing). Stops early if the chip reports ERROR or stops draining for 20 ms. Leaves the output on (OE = 1) so calls chain; call set_mode(DRIVE_P_MODE_IDLE) when done to switch it off. Use this for arbitrary waveforms that don't fit the click / sine / buzz / pulse-train idioms — e.g., recorded waveforms or DSP-generated patterns. - `samples` — Buffer of signed samples, within ±1743 - `count` — Number of samples to write ##### `tile_drive_p_read_sense_samples` — Studio ```c void tile_drive_p_read_sense_samples(tile_t* tile, int16_t* buf, uint16_t count) ``` Read a buffer of sense samples. Switches into fine-resolution sense mode (if not already there) and reads `count` consecutive samples into the caller's buffer. Each sample takes ~125 µs to acquire (8 ksps native). Use for impedance characterisation, multi-touch pattern detection, or piezo-as-mic experiments beyond the simple `is_touched` API. - `buf` — Output buffer for signed 12-bit sense samples (7.6 mV/LSB) - `count` — Number of samples to read #### Configuration ##### `tile_drive_p_set_output_range` — Studio ```c void tile_drive_p_set_output_range(tile_t* tile, drive_p_output_range_t range) ``` Select the output voltage range (CONFIG.GAIND). High-V (±95 V) is the BOS1921 default and suits most piezo actuators. Low-V (±13.28 V) is for low-voltage piezos where the full ±95 V swing would be wasteful or destructive. Use this from IDLE before setting a play mode — the change takes effect on the next OE-enable. - `range` — DRIVE_P_OUTPUT_HIGH_V or DRIVE_P_OUTPUT_LOW_V **Note:** Changing range invalidates the PARCAP / TI_RISE tuning set at init. If accurate output behaviour matters, recompute and rewrite those registers for the new FBratio (see datasheet §7.5). ##### `tile_drive_p_set_sense_gain` — Studio ```c void tile_drive_p_set_sense_gain(tile_t* tile, drive_p_sense_gain_t gain) ``` Select the sense-channel resolution (CONFIG.GAINS). Fine gain (7.6 mV LSB) is the BOS1921 default and gives the highest sensing resolution. Coarse gain (54.5 mV LSB) widens the input range — useful when sensing high-amplitude press events that would otherwise saturate at fine gain. Use from IDLE before entering a sense mode. - `gain` — DRIVE_P_SENSE_FINE_GAIN or DRIVE_P_SENSE_COARSE_GAIN ##### `tile_drive_p_set_sleep_retention` — Studio ```c void tile_drive_p_set_sleep_retention(tile_t* tile, uint8_t retain) ``` Configure register and RAM retention during SLEEP (CONFIG.RET). Default is retain (~2.4 µA quiescent) so that RAM contents and register configuration survive a sleep cycle. Disabling retention (~0.6 µA) is useful for ultra-low-power applications; wake() rewrites the driver's register configuration either way, but RAM contents are lost. Set this before calling sleep(). - `retain` — 1 = retain (default), 0 = clear on sleep ##### `tile_drive_p_set_auto_sleep` — Studio ```c void tile_drive_p_set_auto_sleep(tile_t* tile, uint8_t enabled) ``` Enable or disable the auto-sleep timeout (COMM.TOUT). When enabled, the device drops into SLEEP after 4 ms of bus inactivity during Direct or FIFO playback. Useful for unattended one-shot waveforms; harmful for long streaming playback where a host gap would unexpectedly stop the output. - `enabled` — 1 = auto-sleep on idle, 0 = stay awake **Note:** After a timeout-triggered sleep, PLAY_SRATE is reset to 0x7 (8 ksps) — re-set the sample rate before the next playback if you were using a faster rate. The driver's tile state stays READY; the first I2C write after the timeout only wakes the chip and is discarded, so call @ref tile_drive_p_wake before the next command. ##### `tile_drive_p_set_upi` — Studio ```c void tile_drive_p_set_upi(tile_t* tile, uint8_t enabled) ``` Enable or disable the Unidirectional Power Input (PARCAP.UPI). UPI forces the BOS1921 into sink-only operation: energy recovered from piezo discharge is dumped instead of pushed back into the supply. Useful for battery-powered designs where the supply rail can't safely absorb returned energy. - `enabled` — 1 = sink-only (UPI on), 0 = energy recovery (default) #### Advanced ##### `tile_drive_p_write_fifo` — Studio ```c void tile_drive_p_write_fifo(tile_t* tile, int16_t sample) ``` Write a sample to the FIFO. Raw REFERENCE write, no clamping (the same register takes WFS command words in the RAM modes). In Direct / FIFO mode keep samples within ±BOS1921_REFERENCE_MAX (±1743 = ±95 V). - `sample` — Signed 12-bit waveform sample (REFERENCE[11:0], two's complement) ##### `tile_drive_p_wfs_write` — Studio ```c void tile_drive_p_wfs_write(tile_t* tile, const uint16_t* words, uint16_t count) ``` Write a multi-word WFS command to the BOS1921. - `words` — Array of 16-bit words (big-endian on wire) - `count` — Number of words (max 8) ##### `tile_drive_p_check_and_recover` — Studio ```c uint8_t tile_drive_p_check_and_recover(tile_t* tile, drive_p_mode_t restore_mode) ``` Check status and recover from error/fault states. If IC_STATUS shows STATE = ERROR or any fault bit, resets the chip in the §6.2.8 order (see @ref tile_drive_p_reset; blocks up to ~150 ms while a FIFO drains), then rewrites the full configuration — init's tuned PARCAP (with UPI), SUP_RISE, output range, sense gain, sleep retention and auto-sleep — and re-enters restore_mode. A sleeping tile is woken first. - `restore_mode` — Mode to re-enter after recovery **Returns:** 1 if recovery was performed, 0 if device was healthy #### Other ##### `tile_drive_p_find` ```c uint8_t tile_drive_p_find(tiles_pal_t* hal, uint8_t instance) ``` Check whether a BOS1921 is present on the I2C bus. - `hal` — Platform HAL handle - `instance` — Instance index (0 = default, see mapping table) **Returns:** 1 if device ACKs, 0 otherwise ##### `tile_drive_p_init` ```c void tile_drive_p_init(tiles_pal_t* hal, uint8_t instance, tile_t* tile, const drive_p_cfg_t *cfg) ``` Initialize the BOS1921 piezoelectric driver. Wakes the device, performs a software reset, verifies the chip ID, and configures parasitic capacitance and supply parameters for a 260nF piezo on a 3.7V LiPo supply. The SUP_RISE I2C_ADDR nibble is derived from the instance's address, so a reassigned chip (0x45/0x46) keeps its address — a soft reset doesn't revert it, so init may reset such a chip normally. Pass cfg=NULL for defaults. - `hal` — Platform HAL handle - `instance` — Instance index (0=0x44, 1=0x45, 2=0x46; see mapping table) - `tile` — Pointer to tile handle (populated by this function) - `cfg` — Optional config, or NULL for defaults ##### `tile_drive_p_init_at` ```c void tile_drive_p_init_at(tiles_pal_t* hal, uint8_t addr, tile_t* tile, const drive_p_cfg_t *cfg) ``` Initialize a BOS1921 at an explicit I2C address. The address-explicit sibling of tile_drive_p_init(): identical bring-up, but you name the operating address directly instead of an instance index. This decouples a chip from the fixed instance→address table, which a topology- driven bringup needs — e.g. the v1 Ring runs two Drive.P on separate buses, BOTH at 0x44 (no readdress), which the instance map can't express. Shadow state is keyed per-tile, so two chips at the same address on different buses don't collide. Pass cfg=NULL for defaults. - `hal` — Platform HAL handle (the bus this chip lives on) - `addr` — Operating I2C address (e.g. 0x44 / 0x45 / 0x46) - `tile` — Pointer to tile handle (populated by this function) - `cfg` — Optional config, or NULL for defaults ##### `tile_drive_p_reassign_address` ```c uint8_t tile_drive_p_reassign_address(tiles_pal_t* hal, uint8_t cur_addr, uint8_t new_addr) ``` Reassign one BOS1921's I2C address (GPIO-gated, datasheet §6.3.3). For two BOS1921 sharing a bus (both at 0x44), move each to a unique address. The call wakes the chip(s) still at cur_addr, sets COMM.GPIODIR=1 (GPIO becomes a write-gate), writes the new address into SUP_RISE.I2C_ADDR, and verifies CHIP_ID at new_addr. Only the chip whose GPIO the caller holds low latches the change; the others are untouched. The driver performs the I2C register sequence only; the **caller owns the GPIO** (a board-specific Core pad, not reachable through the tile PAL). Move BOTH chips of a pair off 0x44 (→ 0x45 and 0x46); each call gates a different chip: // chip A → 0x45 : hold A's GPIO low, B's high core_pad_write(GPIO_A, 0); core_pad_write(GPIO_B, 1); tile_drive_p_reassign_address(hal, 0x44, 0x45); // chip B → 0x46 : hold B's GPIO low, A's high (A has left 0x44) core_pad_write(GPIO_A, 1); core_pad_write(GPIO_B, 0); tile_drive_p_reassign_address(hal, 0x44, 0x46); core_pad_input(GPIO_A); core_pad_input(GPIO_B); // release // then init instance 1 (0x45) and 2 (0x46) so it must be in 0x40..0x4F (BOS1921_I2C_ADDR_SECOND/THIRD) - `hal` — Platform HAL handle - `cur_addr` — the chip's current 7-bit address (0x44 at power-up) - `new_addr` — desired 7-bit address; only the low nibble is settable, **Returns:** 1 if the chip answers at new_addr with the correct CHIP_ID, else 0 **Note:** The new address persists until power-on reset; a soft reset will not revert it. Re-running this from 0x44 after a reflash (no power cycle) is a no-op — the chips already left 0x44. ##### `tile_drive_p_reset` ```c void tile_drive_p_reset(tile_t* tile) ``` Perform a software reset. Follows the datasheet's safe order (§6.2.8): output to 0 V (Direct / FIFO modes) and OE = 0, wait for IC_STATUS.STATE to leave RUN (bounded at 150 ms: a full FIFO keeps playing 128 ms after OE = 0), then RST. The chip and the driver's shadow return to power-on defaults (the I2C address is kept) and the tile goes to NONE: re-run init() afterwards. To reset and keep your configuration, use @ref tile_drive_p_check_and_recover. - `tile` — Pointer to tile handle ##### `tile_drive_p_write_reg` ```c void tile_drive_p_write_reg(tile_t* tile, uint8_t reg, uint16_t value) ``` Write a raw 16-bit value to any BOS1921 register. - `tile` — Pointer to tile handle - `reg` — 8-bit register address - `value` — 16-bit value (sent big-endian on the wire) ### Vibe Settings Settings Studio's Vibe interface offers for this tile, generated from the driver's `@studio control` annotations. Each is an argument of the function shown. | Setting | Tier | Values | Default | Function | |---|---|---|---|---| | Output voltage range | basic | ±95V, ±13.28V | ±95V | `set_output_range(range)` | | Touch sensing resolution | advanced | Coarse (54.5 mV per step), Fine (7.6 mV per step) | Fine (7.6 mV per step) | `set_sense_gain(gain)` | | Keep settings while asleep | advanced | on / off | on | `set_sleep_retention(retain)` | | Sleep after 4 ms idle | advanced | on / off | off | `set_auto_sleep(enabled)` | | Never return energy to the supply | advanced | on / off | off | `set_upi(enabled)` | ### Driver gaps Chip capabilities this driver does **not** expose. Each is tagged hardware-gated (the tile does not route the pin) or driver-deferred (possible, not yet implemented). Do not assume an API exists for these. #### Multi-device SYNC pin (advanced) SYNC pin coordinates phase between cascaded BOS1921s (< 2 µs delay). The Drive.P tile has 10 pads (I2C/I3C, OUT±, GPIO, V+, V_DRIVE, GND); the SYNC pin on the IC is not routed to a pad, so multi-tile cascading is hardware-gated to a future tile rev. #### I3C alternate bus mode (advanced) Pads 4/5 are bus-shared between I²C (default) and I3C SDR (≤12.5 Mbps with in-band interrupts). The cores tile-driver framework only ships an I²C PAL; I3C support is an ecosystem-wide gap, not BOS1921-specific. ### Enums #### `drive_p_mode_t` Drive.P operating mode. | Member | Value | Description | | --- | --- | --- | | `DRIVE_P_MODE_IDLE` | `0` | Output disabled, status readback | | `DRIVE_P_MODE_SENSE_FINE` | `1` | Piezo sensing, 7.6 mV/LSB resolution | | `DRIVE_P_MODE_SENSE_COARSE` | `2` | Piezo sensing, 54.5 mV/LSB resolution | | `DRIVE_P_MODE_PLAY_DIRECT` | `3` | Direct waveform output | | `DRIVE_P_MODE_PLAY_FIFO` | `4` | FIFO-buffered playback, 8 ksps | | `DRIVE_P_MODE_PLAY_RAM_SYNTH` | `5` | RAM Synthesis waveform playback | #### `drive_p_output_range_t` Output voltage range (CONFIG.GAIND). | Member | Value | Description | | --- | --- | --- | | `DRIVE_P_OUTPUT_HIGH_V` | `0` | ±95V | | `DRIVE_P_OUTPUT_LOW_V` | `1` | ±13.28V | #### `drive_p_sense_gain_t` Sense-channel resolution (CONFIG.GAINS). | Member | Value | Description | | --- | --- | --- | | `DRIVE_P_SENSE_COARSE_GAIN` | `0` | Coarse (54.5 mV per step) | | `DRIVE_P_SENSE_FINE_GAIN` | `1` | Fine (7.6 mV per step) | ### Structs #### `drive_p_cfg_t` | Type | Field | Description | | --- | --- | --- | | `uint8_t` | `reserved` | Placeholder — no options yet. | ### Defines | Name | Value | Description | | --- | --- | --- | | `TILE_DRIVE_P_VERSION_MAJOR` | `3` | | | `TILE_DRIVE_P_VERSION_MINOR` | `5` | | | `TILE_DRIVE_P_VERSION_PATCH` | `0` | | | `BOS1921_I2C_ADDR_DEFAULT` | `0x44` | | | `BOS1921_I2C_ADDR_SECOND` | `0x45` | Instance 1 — first of a pair | | `BOS1921_I2C_ADDR_THIRD` | `0x46` | Instance 2 — second of a pair | | `BOS1921_CHIP_ID_DEFAULT` | `0x0781` | Expected lower 12 bits of CHIP_ID register. | | `BOS1921_REFERENCE_MAX` | `1743` | | | `BOS_STATUS_STATE_MASK` | `0x0300` | STATE field in IC_STATUS (bits 9:8). | | `BOS_STATUS_STATE_IDLE` | `0x0000` | | | `BOS_STATUS_STATE_CALIB` | `0x0100` | | | `BOS_STATUS_STATE_RUNNING` | `0x0200` | | | `BOS_STATUS_STATE_ERROR` | `0x0300` | | | `BOS_STATUS_FAULT_MASK` | `0x00FC` | Fault bits in IC_STATUS (bits 7:2, excluding FULL and PLAYST). | | `BOS_FIFO_STATE_SPACE_MASK` | `0x03FF` | | | `BOS1921_FIFO_DEPTH` | `1024` | FIFO depth in samples (chip default; the driver never changes it). | | `BOS_SUP_RISE_I2C_ADDR_POS` | `12` | | | `BOS_SUP_RISE_TIMING_DEFAULT` | `0x0967` | default with I2C_ADDR nibble cleared | --- Generated 2026-10-07 from the driver header at tiles@f70bca2. Canonical HTML: https://bergsonne.io/docs/tiles/drive/p ============================================================================== ## Power.L.1N driver > Power.L.1N tile driver — Nordic nPM1300 PMIC. | | | | --- | --- | | Tile | `Power.L.1N` | | Driver version | `1.2.0` | | Header | `tile_power_l_1n.h` | | Generated from | `tiles@c260a08` | | Canonical page | https://bergsonne.io/docs/tiles/power/l1n | ### Overview Battery charger + 2 buck regulators + system supply (VSYS) + 3 indicator LEDs. Platform-agnostic: all bus access goes through tiles_pal_t. The bucks come up at fixed boot voltages chosen by the tile's VSET pulldown resistors (no I2C needed): VSET1 = 47 kΩ → VOUT1 = 1.8 V, VSET2 = 330 kΩ → VOUT2 = 3.3 V. buck_set_mv() switches a buck to software control to override its VSET default at runtime. Tile LEDs. Three discrete 0201 parts (Power-L1-N-a BOM), not an RGB package: U3 = APG015SURKKC-TT 631 nm red U4 = APG015SEKKC-TT 605 nm ORANGE (previously documented as "yellow") U5 = APG015CGKKC-TT 571 nm green Which LED is which (confirmed 2026-09-24): red, orange, green sit left to right on the nPM1300's A1, A2, A3 balls, which are LED0, LED1, LED2 (WLCSP pin table). So LED0 = red (U3), LED1 = orange (U4), LED2 = green (U5). Driver mode assignment made by init() (the chip's own reset assignment): LED0 red → ERROR mode (auto, on for charger faults) LED1 orange → CHARGING mode (auto, on while charging) LED2 green → HOST mode (firmware "ready"/status, via led_set()) Before 1.2.0 init() had this backwards (LED0 HOST, LED2 ERROR, from a misread schematic), so a charger fault lit the green LED and firmware status the red one. @note Prefer conveying state by blink PATTERN rather than by colour. A misstuffed or single-colour tile still communicates correctly that way. Datasheet: Nordic nPM1300 Product Specification v1.1 (4490_483). ### Examples #### Quick start ```c #include "core_tiles.h" tile_t pmic; tile_power_l_1n_init(core_tiles_pal(&core_i2c1), 0, &pmic, NULL); if (tile_is_ready(&pmic)) { tile_power_l_1n_set_charge_current_ma(&pmic, 200); tile_power_l_1n_charger_enable(&pmic, 1); uint16_t vbat = tile_power_l_1n_get_vbat_mv(&pmic); (void)vbat; } ``` ### API 19 public functions. Functions marked **Studio** are callable from the Studio behaviour DSL. #### Runtime ##### `tile_power_l_1n_get_charge_status` — Studio ```c uint8_t tile_power_l_1n_get_charge_status(tile_t* tile) ``` Read the raw charge-status register. **Returns:** BCHGCHARGESTATUS bits (NPM1300_CHG_* mask). ##### `tile_power_l_1n_get_charge_error` — Studio ```c uint8_t tile_power_l_1n_get_charge_error(tile_t* tile) ``` Read the charger error-reason register. **Returns:** BCHGERRREASON bits (0 = no error). ##### `tile_power_l_1n_is_charging` — Studio ```c uint8_t tile_power_l_1n_is_charging(tile_t* tile) ``` Whether the charger is actively charging (trickle / CC / CV). **Returns:** 1 if charging, 0 otherwise. ##### `tile_power_l_1n_is_charge_complete` — Studio ```c uint8_t tile_power_l_1n_is_charge_complete(tile_t* tile) ``` Whether charging has completed (battery full). **Returns:** 1 if charge complete, 0 otherwise. ##### `tile_power_l_1n_battery_present` — Studio ```c uint8_t tile_power_l_1n_battery_present(tile_t* tile) ``` Whether a battery is detected. BATTERYDETECTED in BCHGCHARGESTATUS, a charger-domain bit: the datasheet states "CHARGER waits until a battery is detected before charging", i.e. detection runs as part of the charge cycle. With the charger disabled the bit reads 0 whether or not a cell is fitted — measured on the bench with a battery physically attached (0 before enable, 1 after). Do NOT gate a charge-enable decision on this; that deadlocks. Enable charging first, poll this for a second or two, and disable again if nothing appears. Note also that get_vbat_mv() cannot substitute: an open battery terminal reads a plausible ~3 V once charger current has pushed the VBAT decoupling cap up. **Returns:** 1 if a battery is present, 0 otherwise. ##### `tile_power_l_1n_get_vbat_mv` — Studio ```c uint16_t tile_power_l_1n_get_vbat_mv(tile_t* tile) ``` Measure the battery voltage. **Returns:** VBAT in millivolts (0-5000). ##### `tile_power_l_1n_get_vsys_mv` — Studio ```c uint16_t tile_power_l_1n_get_vsys_mv(tile_t* tile) ``` Measure the system (VSYS) voltage. **Returns:** VSYS in millivolts (0-6375). ##### `tile_power_l_1n_get_die_temp_c` — Studio ```c int16_t tile_power_l_1n_get_die_temp_c(tile_t* tile) ``` Measure the PMIC die temperature. **Returns:** Die temperature in degrees Celsius. ##### `tile_power_l_1n_led_set` — Studio ```c void tile_power_l_1n_led_set(tile_t* tile, uint8_t led, uint8_t on) ``` Turn a host-controlled LED on or off. Only effective when the LED is in HOST mode (see led_set_mode). - `led` — 0, 1 or 2. - `on` — 1 = on, 0 = off. #### Configuration ##### `tile_power_l_1n_charger_enable` — Studio ```c void tile_power_l_1n_charger_enable(tile_t* tile, uint8_t on) ``` Enable or disable battery charging. - `on` — 1 = enable charging, 0 = disable. ##### `tile_power_l_1n_set_vbus_limit_ma` — Studio ```c void tile_power_l_1n_set_vbus_limit_ma(tile_t* tile, uint16_t ma) ``` Set the VBUS (USB) input current limit. The nPM1300 powers up with a 100 mA input limit; init() raises it to 500 mA (what any USB port gives) unless the config says otherwise. The limit caps what is drawn from USB for the system AND the charger together. It does not regulate VSYS: with a battery attached, asking for more than the limit gives cuts the charge back and lets VSYS fall to just above the battery voltage (measured: a 100 mA charge on the 100 mA limit took VSYS from 5.15 V to ~3.65 V; both bucks held, but with only ~350 mV of headroom on 3.3 V, which a load with a start-up surge can dip). With no battery, VSYS simply sags. The chip falls back to its START-UP limit whenever VBUS is removed, which matters for exactly the system that charges: one with a battery keeps running through an unplug, and would otherwise be back at 100 mA on re-plug. So this writes the start-up limit too, and the value holds until the chip is RESET (when it returns to 100 mA, and init() sets it again). Settable 100-1500 mA in 100 mA steps; a request is rounded DOWN to a step (never more than asked) and clamped to that range. 100 and 500 mA are the USB-compliant, accurately trimmed levels; 500 mA is safe for any USB port. - `ma` — [100..1500] mA Input current limit, in milliamps. ##### `tile_power_l_1n_set_charge_current_ma` — Studio ```c void tile_power_l_1n_set_charge_current_ma(tile_t* tile, uint16_t ma) ``` Set the constant-current charge level. Programmable 32-800 mA in 2 mA steps (rounded down); out-of-range values clamp. The chip only accepts a new current while the charger is disabled (datasheet §6.2.4), so if charging is on this pauses it for the write and re-enables it. Keep it at or below 1C for the cell fitted. - `ma` — [32..800] mA Charge current in milliamps. ##### `tile_power_l_1n_set_term_mv` — Studio ```c void tile_power_l_1n_set_term_mv(tile_t* tile, uint16_t mv) ``` Set the charge-termination (full) voltage. Selectable 3.50-3.65 V or 4.00-4.45 V in 50 mV steps; the 3.70-3.95 V gap is not supported and clamps to 3.65 V. - `mv` — [3500..4450] mV Termination voltage in millivolts (e.g. 4200). ##### `tile_power_l_1n_buck_enable` — Studio ```c void tile_power_l_1n_buck_enable(tile_t* tile, uint8_t buck, uint8_t on) ``` Enable or disable a buck regulator. - `buck` — 1 (VOUT1) or 2 (VOUT2). - `on` — 1 = enable, 0 = disable. ##### `tile_power_l_1n_buck_set_mv` — Studio ```c void tile_power_l_1n_buck_set_mv(tile_t* tile, uint8_t buck, uint16_t mv) ``` Set a buck regulator's output voltage. Selects software voltage control for that buck (overriding its VSET resistor) and applies the new target. - `buck` — 1 (VOUT1) or 2 (VOUT2). - `mv` — [1000..3300] Output voltage in mV, 100 mV steps. ##### `tile_power_l_1n_led_set_mode` — Studio ```c void tile_power_l_1n_led_set_mode(tile_t* tile, uint8_t led, power_l_1n_led_mode_t mode) ``` Set an indicator LED's drive mode. In auto modes (ERROR/CHARGING) the charger drives the LED directly; HOST mode hands control to led_set(). LED0 is red, LED1 orange, LED2 green (see the file header). init() leaves LED2 in HOST mode for firmware status. - `led` — 0, 1 or 2. - `mode` — power_l_1n_led_mode_t. #### Advanced ##### `tile_power_l_1n_get_reset_cause` — Studio ```c uint8_t tile_power_l_1n_get_reset_cause(tile_t* tile) ``` Read the reset-cause register (why the PMIC last reset). **Returns:** RSTCAUSE bits. #### Other ##### `tile_power_l_1n_find` ```c uint8_t tile_power_l_1n_find(tiles_pal_t* hal, uint8_t instance) ``` Check whether an nPM1300 is present on the I2C bus. - `hal` — Platform HAL handle - `instance` — Instance index (0 = default, see mapping table) **Returns:** 1 if the device ACKs, 0 otherwise ##### `tile_power_l_1n_init` ```c void tile_power_l_1n_init(tiles_pal_t* hal, uint8_t instance, tile_t* tile, const power_l_1n_cfg_t* cfg) ``` Initialize the nPM1300 PMIC. Sets the indicator LED modes (LED0 red = ERROR, LED1 orange = CHARGING, LED2 green = HOST), disables NTC monitoring (no thermistor on this tile), and applies the charger settings. The buck regulators are left at their boot voltages (1.8 V / 3.3 V, fixed by the VSET resistors) — they are already up. Pass cfg=NULL for defaults (500 mA USB limit, 100 mA, 4.20 V, charging enabled). A zero-initialised cfg is NOT the same as NULL: it leaves charging OFF (enable_charging = 0). 4.20 V and at least ~100 mAh (100 mA is 1C for a 100 mAh cell). They are NOT safe for a LiFePO4 cell (charge to 3.60 V: pass term_mv = 3600) or for a smaller cell (lower charge_current_ma to <= 1C). The chip itself powers up with charging disabled at 3.60 V / 32 mA; init() is what raises it. - `hal` — Platform HAL handle - `instance` — Instance index (0 = default, see mapping table) - `tile` — Pointer to tile handle (populated by this function) - `cfg` — Optional config, or NULL for defaults ### Vibe Settings Settings Studio's Vibe interface offers for this tile, generated from the driver's `@studio control` annotations. Each is an argument of the function shown. | Setting | Tier | Values | Default | Function | |---|---|---|---|---| | Charge the battery | basic | on / off | on | `charger_enable(on)` | | USB input limit | basic | 100 to 1500 mA | 500 | `set_vbus_limit_ma(ma)` | | Charge current | basic | 32 to 800 mA | 100 | `set_charge_current_ma(ma)` | | Charge to | advanced | 3.5 to 4.45 V | 4200 | `set_term_mv(mv)` | ### Driver gaps Chip capabilities this driver does **not** expose. Each is tagged hardware-gated (the tile does not route the pin) or driver-deferred (possible, not yet implemented). Do not assume an API exists for these. #### NTC battery thermistor (advanced) Hardware-gated (not a driver gap). The nPM1300 can sense battery temperature via an NTC thermistor for JEITA charge regulation. NTC is tied to GND on Power-L-1N-a (no thermistor fitted), so the driver disables NTC monitoring (ADCNTCRSEL=0, DISABLENTC). Closing requires a tile hardware revision. #### Load switches / LDOs (LSIN/LSOUT) (niche) Hardware-gated (not a driver gap). The nPM1300's two load-switch/LDO outputs are tied off (LSIN1/2, LSOUT1/2 grounded) on this tile, so there's nothing to control. #### GPIO0-3 (niche) Hardware-gated (not a driver gap). The nPM1300 GPIOs aren't routed to tile pads on Power-L-1N-a, so there's no externally useful GPIO function. #### Ship / hibernate mode (advanced) Driver-deferred. The chip supports ultra-low-power ship (370 nA) and hibernate modes that isolate the battery for storage (datasheet §7.4: TASKENTERSHIPMODE 0x0B02, TASKENTERHIBERNATE 0x0B00). Not yet exposed. The SHPHLD wake button is not routed to a pad on Power-L-1N-a, so the only way back out of ship mode on this tile is applying CHG (VBUS), which needs a bench check before it is offered as a Studio call. #### POF warning + buck retention / forced-PWM (niche) Driver-deferred. Power-fail early-warning (via GPIO) and buck retention-voltage / forced-PWM modes aren't exposed yet. #### USB-C source detection (CC1 / CC2) (advanced) Driver-deferred. The nPM1300 reports what the attached source can supply (USBCDETECTSTATUS: default / 1.5 A / 3 A) and the tile routes CC1 / CC2 to pads 2 / 3, but the driver does not read it. set_vbus_limit_ma() therefore takes the caller's word for what the source can give: 500 mA is safe for any USB port, more is only safe for a supply known to deliver it. ### Events - `charge_complete` - `charging` ### Enums #### `power_l_1n_led_mode_t` LED indicator mode (LEDDRVxMODESEL). | Member | Value | Description | | --- | --- | --- | | `NPM1300_LED_ERROR` | `0` | auto: on for charger error | | `NPM1300_LED_CHARGING` | `1` | auto: on while charging | | `NPM1300_LED_HOST` | `2` | software-controlled via led_set() | | `NPM1300_LED_NOTUSED` | `3` | disabled | ### Defines | Name | Value | Description | | --- | --- | --- | | `TILE_POWER_L_1N_VERSION_MAJOR` | `1` | | | `TILE_POWER_L_1N_VERSION_MINOR` | `2` | | | `TILE_POWER_L_1N_VERSION_PATCH` | `0` | | | `NPM1300_I2C_ADDR` | `0x6B` | | --- Generated 2026-10-07 from the driver header at tiles@c260a08. Canonical HTML: https://bergsonne.io/docs/tiles/power/l1n ============================================================================== ## Power.L.1T driver > Li-Ion charge controller driver for the Power.L.1T tile (rev b). | | | | --- | --- | | Tile | `Power.L.1T` | | Driver version | `3.3.0` | | Header | `tile_power_l_1t.h` | | Generated from | `tiles@afe442f` | | Canonical page | https://bergsonne.io/docs/tiles/power/l1t | ### Overview Embeds the Texas Instruments BQ25150, a single-cell Li-Ion charge controller with programmable 1.8 V LDO output and 12-bit ADC for battery and system monitoring. Key specifications: - Charge input: 3.4–5.5 V (VUVLO / VOVP), input limit 50–600 mA - Battery voltage: 3.6–4.6 V regulation (programmable in 10 mV steps) - LDO output: V+ (pad 10), 1.8 V at power-on, 0.6–3.7 V programmable, 100 mA max (datasheet 7.3 ILDO) - ADC: 12-bit, 6 input channels - Charge current: 1.25–500 mA programmable - No battery NTC: TS is strapped to a fixed 5 kΩ (see below) Tile wiring that matters to firmware (Power-L-1T-b schematic): - I²C only answers on battery when LP (pad 2) is driven > 1.35 V; with LP low (its 900 kΩ default) and no SUPPLY+, the chip is in low-power mode with I²C and the ADC off. With SUPPLY+ present LP has no effect. - GND (pad 1) is a SWITCHED ground: an SI8806 N-FET joins it to BATT-/SUPPLY- (pads 6/9), its gate fed from V+ through 220 kΩ. SW (pad 3) to ground, LDO disabled, PMID off or ship mode all open it. - IMAX = 10 kΩ (no hardware charge-current cap below 500 mA). - /CE, /MR, /PG and INT are not routed (/CE's internal pull-down keeps charging enabled). Datasheet: https://www.bergsonne.io/tiles/power/l1t IC datasheet: https://www.ti.com/lit/ds/symlink/bq25150.pdf ### Examples #### Quick start ```c #include "core_tiles.h" tile_t battery; tile_power_l_1t_init(core_tiles_pal(&core_i2c1), 0, &battery, NULL); if (tile_is_ready(&battery)) { tile_power_l_1t_set_charge_current_ma(&battery, 100); tile_power_l_1t_set_charge_voltage_mv(&battery, 4200); uint16_t vbat = tile_power_l_1t_get_vbat_mv(&battery); if (tile_power_l_1t_is_battery_low(&battery, 10)) { // go to sleep, save the cell } } ``` ### API 38 public functions. Functions marked **Studio** are callable from the Studio behaviour DSL. #### Runtime ##### `tile_power_l_1t_get_vbat_mv` — Studio ```c uint16_t tile_power_l_1t_get_vbat_mv(tile_t* tile) ``` Read battery voltage from the ADC. Replaces the deprecated raw-counts API; performs a 2-byte burst read to avoid torn samples. **Returns:** Battery voltage in mV (0–6000) ##### `tile_power_l_1t_get_vin_mv` — Studio ```c uint16_t tile_power_l_1t_get_vin_mv(tile_t* tile) ``` Read input (VIN) voltage from the ADC. **Returns:** VIN in mV (0–6000) ##### `tile_power_l_1t_get_pmid_mv` — Studio ```c uint16_t tile_power_l_1t_get_pmid_mv(tile_t* tile) ``` Read PMID (system rail) voltage from the ADC. **Returns:** PMID in mV (0–6000) ##### `tile_power_l_1t_get_charge_current_ma` — Studio ```c uint16_t tile_power_l_1t_get_charge_current_ma(tile_t* tile) ``` Read charge current (into the battery) from the ADC. Scaled relative to the configured ICHG fast-charge limit; returns actual mA flowing into the cell. **Returns:** Charge current in mA (0–500) ##### `tile_power_l_1t_get_input_current_ma` — Studio ```c uint16_t tile_power_l_1t_get_input_current_ma(tile_t* tile) ``` Read input current (from VIN) from the ADC. Range scales with ILIMCTRL: ≤150 mA range gives 0–375 mA full scale; >150 mA range gives 0–750 mA full scale. **Returns:** Input current in mA (0–750) ##### `tile_power_l_1t_get_ts_mv` — Studio ```c uint16_t tile_power_l_1t_get_ts_mv(tile_t* tile) ``` Read raw TS pin voltage from the ADC. Returns the millivolt reading on the TS pin (0–1200 mV). On Power-L-1T-b TS is a fixed 5 kΩ to ground (no thermistor), so this reads a constant ~400 mV (5 kΩ x 80 µA bias); it is not a temperature. Not measured in low-power mode. **Returns:** TS voltage in mV (0–1200) ##### `tile_power_l_1t_get_adcin_mv` — Studio ```c uint16_t tile_power_l_1t_get_adcin_mv(tile_t* tile) ``` Read raw ADCIN pin voltage from the ADC. Optional auxiliary input (0–1.2 V range). Useful for monitoring an external sensor (e.g., separate NTC, voltage divider). **Returns:** ADCIN voltage in mV (0–1200) ##### `tile_power_l_1t_get_percent` — Studio ```c uint8_t tile_power_l_1t_get_percent(tile_t* tile) ``` Read battery state-of-charge as a percentage. Derived from VBAT using a linear curve (3000 mV = 0%, 4200 mV = 100%). Coarse — a real fuel gauge would integrate Coulombs. **Returns:** Battery percentage (0–100) ##### `tile_power_l_1t_get_charge_status` — Studio ```c void tile_power_l_1t_get_charge_status(tile_t* tile, power_l_1t_status_t *out) ``` Snapshot the chip's current state + latched faults. Reads STAT0/STAT1 (live state) and FLAG0/FLAG1/FLAG3 (event flags which clear on read) into the supplied struct. Call once per polling cycle — multiple reads will lose flag transitions. - `out` — Caller-allocated status struct (zeroed on entry) ##### `tile_power_l_1t_is_charging` — Studio ```c uint8_t tile_power_l_1t_is_charging(tile_t* tile) ``` Is the cell currently being charged? Convenience over @ref tile_power_l_1t_get_charge_status — returns the `charging` field as a bool: VIN good, not done, charger not disabled over I²C, PMID in AUTO, no UVLO / OVP / TS cold / TS hot. Note this reads (and clears) FLAG3 as a side effect; if you also poll the full status struct, alternate with this rather than calling both per cycle. **Returns:** 1 if charging, 0 otherwise ##### `tile_power_l_1t_is_charge_done` — Studio ```c uint8_t tile_power_l_1t_is_charge_done(tile_t* tile) ``` Has charge termination been reached? Returns the `charge_done` bit from STAT0 — set when the cell reaches VBATREG and current tapers below the termination threshold. **Returns:** 1 if charge cycle has finished, 0 otherwise ##### `tile_power_l_1t_is_battery_low` — Studio ```c uint8_t tile_power_l_1t_is_battery_low(tile_t* tile, uint8_t threshold_pct) ``` Is the battery below `threshold_pct`? The classic "go to sleep" trigger. Equivalent to `get_percent() < threshold_pct`. Quick: one ADC read. - `threshold_pct` — Threshold in percent (0–100) **Returns:** 1 if battery percent is strictly below threshold, 0 otherwise ##### `tile_power_l_1t_is_powered` — Studio ```c uint8_t tile_power_l_1t_is_powered(tile_t* tile) ``` Is external power present (running off VIN)? Returns the `vin_pgood` bit — true when VIN is in the valid operating range (3.4–5.5 V). Use to branch behaviour between "plugged in" and "battery only" modes. **Returns:** 1 if VIN is good, 0 if running off battery only ##### `tile_power_l_1t_wait_for_charge_done` — Studio ```c uint8_t tile_power_l_1t_wait_for_charge_done(tile_t* tile, uint32_t timeout_ms) ``` Block until charge_done is observed, or timeout. Polls the charge_done bit at a 1 s cadence (charge state changes on the order of minutes for a typical cell — faster polling wastes MCU cycles and bus bandwidth without finer-grained answers). Returns 1 immediately if the cycle is already finished. - `timeout_ms` — Maximum time to wait, in milliseconds **Returns:** 1 if charge_done was observed, 0 on timeout ##### `tile_power_l_1t_get_adc_comparators` — Studio ```c uint8_t tile_power_l_1t_get_adc_comparators(tile_t* tile) ``` Read (and clear) the ADC comparator alarm flags. **Returns:** BQ25150_FLAG2_COMPn_ALARM bits that have tripped since last read. #### Configuration ##### `tile_power_l_1t_set_charge_current_ma` — Studio ```c void tile_power_l_1t_set_charge_current_ma(tile_t* tile, uint16_t ma) ``` Set the fast-charge current. Programs ICHG_CTRL with the appropriate ICHARGE_RANGE bit so the resolution scales: ≤318 mA uses 1.25 mA steps, >318 mA uses 2.5 mA steps (rounded down). Values clamp to 500 mA. Crossing 318 mA also doubles the pre-charge step, so the pre-charge current doubles. Keep it at or below 1C for the cell fitted; there is no thermistor. - `ma` — [2..500] mA Target charge current in mA (below 2 the code is 0: no charge). ##### `tile_power_l_1t_set_charge_voltage_mv` — Studio ```c void tile_power_l_1t_set_charge_voltage_mv(tile_t* tile, uint16_t mv) ``` Set the battery regulation voltage. VBATREG = 3600 + code × 10 mV. Values outside 3600–4600 mV clamp to that range. Use 4200 for typical Li-Ion, 3650 for LFP, 4350+ for high-voltage NMC variants. - `mv` — [3600..4600] mV Target battery voltage in millivolts. ##### `tile_power_l_1t_set_pre_charge_ma` — Studio ```c void tile_power_l_1t_set_pre_charge_ma(tile_t* tile, uint8_t ma) ``` Set the pre-charge current (used when VBAT < VLOW). Pre-charge applies to deeply-discharged cells; usually 10–20 % of the fast-charge rate. Values clamp to 1.25–77.5 mA. - `ma` — [2..77] mA Target pre-charge current (38 mA max below 318 mA fast charge). ##### `tile_power_l_1t_set_termination_percent` — Studio ```c void tile_power_l_1t_set_termination_percent(tile_t* tile, uint8_t pct) ``` Set the termination current as a percentage of ICHG. Charging ends when the cell's current draw drops below this threshold during the CV phase. 0 disables termination (charges continuously until VBATREG is reached). Values clamp to 1–31 %. - `pct` — [0..31] % Termination current as % of ICHG (0 = disabled). ##### `tile_power_l_1t_set_input_current_limit_ma` — Studio ```c void tile_power_l_1t_set_input_current_limit_ma(tile_t* tile, uint16_t ma) ``` Set the input (SUPPLY+) current limit. ILIMCTRL is a discrete table: 50, 100, 150, 200, 300, 400, 500 or 600 mA. The largest level not above the request is used (50 mA floor). The chip powers up at 100 mA and init() leaves it there; when the load plus charge current reach the limit, charge current folds back first. The tile is rated for 500 mA on SUPPLY+. - `ma` — [50..600] mA Input current limit. ##### `tile_power_l_1t_set_ts_cold` — Studio ```c void tile_power_l_1t_set_ts_cold(tile_t* tile, uint8_t code) ``` Set the cold-temperature TS threshold (raw register value). The TS register codes are bit-positional (1, 2, 4, 8, 16, ... encode multiples of 4.688 mV up to 600 mV). Datasheet section 8.5.1.49–52 describes the exact mapping. No practical effect on Power-L-1T-b: TS is a fixed 5 kΩ, not a thermistor. - `code` — Raw 8-bit TS_COLD register value ##### `tile_power_l_1t_set_ts_cool` — Studio ```c void tile_power_l_1t_set_ts_cool(tile_t* tile, uint8_t code) ``` Set the cool-temperature TS threshold (raw register value). - `code` — Raw 8-bit TS_COOL register value ##### `tile_power_l_1t_set_ts_warm` — Studio ```c void tile_power_l_1t_set_ts_warm(tile_t* tile, uint8_t code) ``` Set the warm-temperature TS threshold (raw register value). - `code` — Raw 8-bit TS_WARM register value ##### `tile_power_l_1t_set_ts_hot` — Studio ```c void tile_power_l_1t_set_ts_hot(tile_t* tile, uint8_t code) ``` Set the hot-temperature TS threshold (raw register value). - `code` — Raw 8-bit TS_HOT register value ##### `tile_power_l_1t_set_ts_enabled` — Studio ```c void tile_power_l_1t_set_ts_enabled(tile_t* tile, uint8_t enabled) ``` Enable or disable TS-based thermal protection. When disabled, the chip ignores the TS pin for charge control (monitoring continues). Default at init: enabled. No practical effect on Power-L-1T-b: TS is a fixed 5 kΩ, not a thermistor. - `enabled` — 1 = TS thermal protection on, 0 = off ##### `tile_power_l_1t_set_ldo_voltage_mv` — Studio ```c void tile_power_l_1t_set_ldo_voltage_mv(tile_t* tile, uint16_t mv) ``` Set the LDO output voltage. VLDO = 600 + code × 100 mV (rounded down). Values clamp to 600–3700 mV. changed when the EN_LS_LDO ... have disabled the output". This call writes the code without disabling the LDO, so a change made while V+ is on may not apply until the LDO is next disabled and enabled. V+ also drives the pad 1 ground switch gate (SI8806, VGS(th) up to 1.0 V, RDS(on) specified from 1.8 V): below ~1.8 V the downstream ground is not reliably closed. - `mv` — [600..3700] mV Target LDO voltage. ##### `tile_power_l_1t_set_ldo_mode` — Studio ```c void tile_power_l_1t_set_ldo_mode(tile_t* tile, power_l_1t_ldo_mode_t mode) ``` Set the LS/LDO output mode (regulated LDO vs load switch). In LDO mode the chip regulates pad 10 to the configured voltage; in load-switch mode it connects VINLS straight through. VINLS is tied to PMID on this tile, so pad 10 then carries PMID (VIN or VBAT, up to 5.5 V). 100 mA max either way (datasheet 7.3). The datasheet (8.3.5) requires the LDO to be disabled before the mode changes; this call does not do that. - `mode` — LDO mode (POWER_L_1T_LDO_MODE_LDO or _LOAD_SWITCH) ##### `tile_power_l_1t_set_ldo_enabled` — Studio ```c void tile_power_l_1t_set_ldo_enabled(tile_t* tile, uint8_t enabled) ``` Enable or disable the LS/LDO output. When disabled, pad 10 (V+) is pulled down by the chip, and because V+ feeds the pad 1 ground-switch gate, the downstream ground (pad 1) opens too: everything on this tile's output powers off. Default: enabled (chip reset value; init does not touch it). - `enabled` — 1 = output on, 0 = output off (high-Z) ##### `tile_power_l_1t_charger_enable` — Studio ```c void tile_power_l_1t_charger_enable(tile_t* tile, uint8_t on) ``` Enable or disable battery charging over I²C. Sets/clears ICCTRL2.CHARGER_DISABLE. Note the hardware /CE pin still gates charging: when /CE is high, charging is off regardless of this bit; this only takes effect when /CE is held low (the tile's default). - `on` — 1 = allow charging, 0 = disable charging. ##### `tile_power_l_1t_set_battery_uvlo_mv` — Studio ```c void tile_power_l_1t_set_battery_uvlo_mv(tile_t* tile, uint16_t mv) ``` Set the battery under-voltage lockout (discharge cutoff) threshold. BUVLO[2:0]: 3.0 / 2.8 / 2.6 / 2.4 / 2.2 V (nearest below `mv` is chosen); deeper cutoff trades battery life for runtime. Other BUVLO fields (precharge threshold, OCP limit) are preserved. - `mv` — [2200..3000] mV Desired UVLO. ##### `tile_power_l_1t_set_safety_timer` — Studio ```c void tile_power_l_1t_set_safety_timer(tile_t* tile, power_l_1t_safety_timer_t mode) ``` Set the charge safety-timer limit. The timer stops a charge that has not terminated in time; with no battery thermistor on this tile it is one of the few backstops, so prefer not to choose OFF. Changing it while charging restarts it. - `mode` — power_l_1t_safety_timer_t (3h / 6h / 12h / off). ##### `tile_power_l_1t_set_pmid_mode` — Studio ```c void tile_power_l_1t_set_pmid_mode(tile_t* tile, power_l_1t_pmid_mode_t mode) ``` Set the PMID power-path mode (ICCTRL1[1:0]). BAT_ONLY stops charging. FLOAT and PULLDOWN disconnect PMID and turn the LDO off, so V+ (pad 10) and the pad 1 ground go off; the datasheet (8.3.6) says those modes exit only through I²C or an /MR reset, and /MR is not routed here. Do not use them if the Core runs from V+. - `mode` — power_l_1t_pmid_mode_t. ##### `tile_power_l_1t_set_adc_comparator` — Studio ```c void tile_power_l_1t_set_adc_comparator(tile_t* tile, uint8_t comp, power_l_1t_adc_channel_t channel, uint16_t threshold) ``` Configure a programmable ADC comparator (threshold alarm). Routes ADC channel `channel` to comparator `comp` (1-3) and sets its 16-bit threshold (left-justified, same scale as the channel's ADC result). When the measurement crosses the threshold, the matching COMPn_ALARM bit latches in FLAG2 (read via get_adc_comparators()). Pass channel = POWER_L_1T_ADC_CH_DISABLED to turn a comparator off. Note: the chip's above/below polarity bit (ADCALARM_ABOVE) is not exposed by this driver yet — the comparator uses its default sense (flag when the measurement falls below the threshold). - `comp` — [1..3] Comparator index. - `channel` — power_l_1t_adc_channel_t to monitor. - `threshold` — Raw left-justified ADC threshold (top 12 bits used). #### Advanced ##### `tile_power_l_1t_enter_ship_mode` — Studio ```c void tile_power_l_1t_enter_ship_mode(tile_t* tile) ``` Enter ship mode (~10 nA quiescent). Sets EN_SHIP_MODE. If SUPPLY+ is present the chip waits until it is removed, then disconnects the battery (10 nA typ). /MR is not routed on this tile, so the only way out is applying SUPPLY+ again; the output (pad 10, and the pad 1 ground) stays off until then. Registers return to reset values on exit. Use only for storage / shipping. re-insertion) to recover. Marked `section=advanced` for the same posture as other one-way / hardware-gated operations. ##### `tile_power_l_1t_read_status` — Studio ```c uint8_t tile_power_l_1t_read_status(tile_t* tile, uint8_t reg) ``` Read any 8-bit BQ25150 register. - `reg` — Register address **Returns:** 8-bit register value ##### `tile_power_l_1t_write_reg` — Studio ```c void tile_power_l_1t_write_reg(tile_t* tile, uint8_t reg, uint8_t value) ``` Write any 8-bit BQ25150 register. Escape hatch for advanced users wanting to touch registers the driver doesn't expose. Caller is responsible for not bricking the chip — most useful registers have typed setters above. - `reg` — Register address - `value` — Value to write #### Other ##### `tile_power_l_1t_find` ```c uint8_t tile_power_l_1t_find(tiles_pal_t* hal, uint8_t instance) ``` Check whether a BQ25150 is present on the I2C bus. - `hal` — Platform HAL handle - `instance` — Instance index (0 = default, see mapping table) **Returns:** 1 if device ACKs, 0 otherwise ##### `tile_power_l_1t_init` ```c void tile_power_l_1t_init(tiles_pal_t* hal, uint8_t instance, tile_t* tile, const power_l_1t_cfg_t *cfg) ``` Initialize the BQ25150 charge controller. Verifies the device ID, disables the I²C watchdog (the chip otherwise resets all charge parameters every 50 s), enables all 6 ADC channels, configures charge defaults (80 mA fast charge, 18.75 mA pre-charge, 4.20 V regulation, 2.6 V battery UVLO, TS on, 6 h safety timer) and clears the ship-mode request. Input limit, termination, LDO and charger-enable are left as the chip holds them. Pass cfg=NULL for defaults. On battery alone, LP (pad 2) must be high for this to find the chip (I²C is off in low-power mode). - `hal` — Platform HAL handle - `instance` — Instance index (0 = default, see mapping table) - `tile` — Pointer to tile handle (populated by this function) - `cfg` — Optional config, or NULL for defaults ### Vibe Settings Settings Studio's Vibe interface offers for this tile, generated from the driver's `@studio control` annotations. Each is an argument of the function shown. | Setting | Tier | Values | Default | Function | |---|---|---|---|---| | Charge current | basic | 2 to 500 mA | 80 | `set_charge_current_ma(ma)` | | Input current limit | basic | 50 to 600 mA | 100 | `set_input_current_limit_ma(ma)` | | Charge the battery | basic | on / off | on | `charger_enable(on)` | | Charge to | advanced | 3.6 to 4.6 V | 4200 | `set_charge_voltage_mv(mv)` | ### Driver gaps Chip capabilities this driver does **not** expose. Each is tagged hardware-gated (the tile does not route the pin) or driver-deferred (possible, not yet implemented). Do not assume an API exists for these. #### Battery NTC / JEITA (advanced) Hardware-gated (not a driver gap). The BQ25150 can pause or derate charging from a battery thermistor on TS, but Power-L-1T-b straps TS to a fixed 5 kΩ resistor (the datasheet's "TS not used" connection), which reads a constant ~0.40 V: always the normal band. Battery temperature is therefore NOT monitored; set_ts_* and set_ts_enabled have no practical effect. The chip's die thermal foldback (80 °C) still applies. #### MR button + INT pin handling (advanced) The chip's MR (push-button) and INT (interrupt) pins aren't routed to tile pads on the current revision — nothing for a Core GPIO to attach to. Closing this gap requires a tile hardware revision that routes at least INT to a connector pad. Until then, MASK0–3 / MRCTRL register writes have no external effect. ### Enums #### `power_l_1t_safety_timer_t` Charge safety-timer limit (CHARGERCTRL0[2:1]). | Member | Value | Description | | --- | --- | --- | | `POWER_L_1T_SAFETY_3H` | `0` | 3-hour fast-charge timer | | `POWER_L_1T_SAFETY_6H` | `1` | 6-hour (default) | | `POWER_L_1T_SAFETY_12H` | `2` | 12-hour | | `POWER_L_1T_SAFETY_OFF` | `3` | disabled | #### `power_l_1t_pmid_mode_t` PMID power-path mode (ICCTRL1[1:0]). | Member | Value | Description | | --- | --- | --- | | `POWER_L_1T_PMID_AUTO` | `0` | powered from BAT or VIN (default) | | `POWER_L_1T_PMID_BAT_ONLY` | `1` | forced from BAT even when VIN present | | `POWER_L_1T_PMID_FLOAT` | `2` | disconnected, floating | | `POWER_L_1T_PMID_PULLDOWN` | `3` | disconnected, pulled down | #### `power_l_1t_adc_channel_t` ADC channel selector for the programmable comparators (ADC_COMPn). | Member | Value | Description | | --- | --- | --- | | `POWER_L_1T_ADC_CH_DISABLED` | `0` | | | `POWER_L_1T_ADC_CH_ADCIN` | `1` | | | `POWER_L_1T_ADC_CH_TS` | `2` | | | `POWER_L_1T_ADC_CH_VBAT` | `3` | | | `POWER_L_1T_ADC_CH_ICHARGE` | `4` | | | `POWER_L_1T_ADC_CH_VIN` | `5` | | | `POWER_L_1T_ADC_CH_PMID` | `6` | | | `POWER_L_1T_ADC_CH_IIN` | `7` | | #### `power_l_1t_ldo_mode_t` LDO output mode — regulated voltage vs pass-through load switch. | Member | Value | Description | | --- | --- | --- | | `POWER_L_1T_LDO_MODE_LDO` | `0` | Regulated LDO output | | `POWER_L_1T_LDO_MODE_LOAD_SWITCH` | `1` | Pass-through load switch | ### Structs #### `power_l_1t_status_t` Snapshot of the BQ25150's current state and latched faults. | Type | Field | Description | | --- | --- | --- | #### `power_l_1t_cfg_t` | Type | Field | Description | | --- | --- | --- | | `uint8_t` | `reserved` | Placeholder — no options yet. | ### Defines | Name | Value | Description | | --- | --- | --- | | `TILE_POWER_L_1T_VERSION_MAJOR` | `3` | | | `TILE_POWER_L_1T_VERSION_MINOR` | `3` | | | `TILE_POWER_L_1T_VERSION_PATCH` | `0` | | | `BQ25150_I2C_ADDR_DEFAULT` | `0x6B` | | | `BQ25150_FLAG2_ADC_READY` | `0x80` | FLAG2 bit7: ADC conversion completed (clear-on-read) | | `BQ25150_ADCCTRL0_RATE_1S` | `0x80` | ADC_READ_RATE = every 1 s (battery mode) | | `BQ25150_ADCCTRL0_RATE_MANUAL` | `0x00` | ADC_READ_RATE = manual (convert on CONV_START) | | `BQ25150_ADCCTRL0_CONV_START` | `0x20` | bit5: trigger one ADC conversion | | `BQ25150_ADCCTRL0_COMP1_DEFAULT` | `0x02` | ADC_COMP1[2:0] reset value | | `BQ25150_FLAG2_COMP1_ALARM` | `0x40` | Comparator 1 threshold crossed | | `BQ25150_FLAG2_COMP2_ALARM` | `0x20` | Comparator 2 threshold crossed | | `BQ25150_FLAG2_COMP3_ALARM` | `0x10` | Comparator 3 threshold crossed | | `BQ25150_DEVICE_ID_DEFAULT` | `0x20` | Expected DEVICE_ID register value. | --- Generated 2026-10-07 from the driver header at tiles@afe442f. Canonical HTML: https://bergsonne.io/docs/tiles/power/l1t ============================================================================== ## Sense.CAM.P driver > Ultra-low-power global-shutter camera driver for Sense.CAM.P (rev a). | | | | --- | --- | | Tile | `Sense.CAM.P` | | Driver version | `1.0.0` | | Header | `tile_sense_cam_p.h` | | Generated from | `tiles@6af026f` | | Canonical page | https://bergsonne.io/docs/tiles/sense/camp | ### Overview Embeds the PixArt PAG7920J3, a 320 x 240 monochrome global-shutter camera module with an integrated lens in a 2.94 x 2.44 x 2.11 mm cube. Registers are configured over I2C; image data comes out over SPI. The tile does not break out the 8-bit parallel bus, so SPI is the only image path. Key specifications: - Array: 320 x 240 monochrome, 3.0 um pixels, global shutter - Frame rate: 30 fps as configured by the vendor tables, 8-bit RAW. The chip reaches 180 fps only on its parallel bus; over SPI (25 MHz max) QVGA is bounded near 40 fps. - Optics: 71.34 deg H x 53.72 deg V, 64 dB dynamic range - Power: datasheet figures are for parallel QVGA output (0.76 mW at 15 fps, 8.54 mW at 180 fps); SPI output is not characterized. <100 uW in motion detect. - Supply: the tile takes 1.8-3.3 V on pad 10. VDDMA (3.14-3.45 V, <=50 mV ripple) comes from an on-tile TPS610995 boost (3.6 V) and TPS7A2033 LDO; VDDIO is pad 10 itself, so the I/O thresholds follow the host's supply. - Bus: I2C for configuration; SPI mode 3, up to 25 MHz, for images. Datasheet: https://www.bergsonne.io/tiles/sense/cam-p IC datasheet: PixArt PAG7920J3 (V0.8, L1033EN) ### Examples #### Quick start ```c #include "core_tiles.h" static uint8_t frame[160 * 120]; tile_t cam; // One PAL carrying both buses: I2C for configuration, SPI for images. // On a Core the SPI bus's own chip-select pad is used, so spi_cs is 0. sense_cam_p_cfg_t cfg = { .spi_cs = 0, .resolution = SENSE_CAM_P_RES_160x120 }; tile_sense_cam_p_init(core_tiles_pal2(&core_i2c1, &core_spi1), 0, &cam, &cfg); if (tile_is_ready(&cam)) tile_sense_cam_p_capture(&cam, frame, sizeof(frame)); ``` ### API 9 public functions. Functions marked **Studio** are callable from the Studio behaviour DSL. #### Lifecycle ##### `tile_sense_cam_p_part_id` — Studio ```c uint16_t tile_sense_cam_p_part_id(tile_t* tile) ``` Read the 16-bit part identifier over I2C. **Returns:** 0x7920 for a healthy PAG7920J3, 0 on a bus error. ##### `tile_sense_cam_p_spi_id` — Studio ```c uint16_t tile_sense_cam_p_spi_id(tile_t* tile) ``` Read the SPI-side identifier, confirming the SPI link is usable. **Returns:** 0xA55A when the SPI link is up, 0 otherwise. **Note:** Returns 0x0000 whenever the sensor is stopped — the SPI register interface is only alive while R_TG_En = 1. ##### `tile_sense_cam_p_reset` — Studio ```c void tile_sense_cam_p_reset(tile_t* tile) ``` Trigger a software reset (section 5.6.2) and return to defaults. Preferred over a power cycle for repeatable tests: the tile carries ~15.7 uF of bulk and at the camera's microamp idle draw that rail can take seconds to fall below the power-on-reset threshold, so an unplug/replug may not reset the part at all. **Note:** Leaves the sensor unconfigured; call init again afterwards. #### Runtime ##### `tile_sense_cam_p_capture` — Studio ```c uint8_t tile_sense_cam_p_capture(tile_t* tile, uint8_t* dst, uint32_t len) ``` Capture one frame into a caller-supplied buffer. Frame-locks on Frame_Start, then reads the frame in 4800-byte buffers. The caller owns the memory: 19200 bytes at 160x120, 76800 at 320x240. Each buffer must be serviced before the next one fills, or FB_Ovf trips and the frame is void: about 3.4 ms at 320x240 and 6.8 ms at 160x120 with the vendor timing (section 7.3.6: 4800 / width x LineTime / 6 MHz). Anything slow between buffers — a per-byte print, a slow link — will tear the image across several exposures. (no Frame_Start within 1 s, no buffer within 500 ms) or an SPI error. - `dst` — Destination buffer. - `len` — Size of dst; must be >= tile_sense_cam_p_frame_bytes(). **Returns:** 1 on a complete, non-overflowed frame; 0 on overflow, a timeout #### Configuration ##### `tile_sense_cam_p_width` — Studio ```c uint16_t tile_sense_cam_p_width(tile_t* tile) ``` Frame width in pixels for the configured resolution. **Returns:** 160 or 320. ##### `tile_sense_cam_p_height` — Studio ```c uint16_t tile_sense_cam_p_height(tile_t* tile) ``` Frame height in pixels for the configured resolution. **Returns:** 120 or 240. ##### `tile_sense_cam_p_frame_bytes` — Studio ```c uint32_t tile_sense_cam_p_frame_bytes(tile_t* tile) ``` Number of bytes one frame occupies (width x height, 8-bit RAW). **Returns:** 19200 or 76800. #### Other ##### `tile_sense_cam_p_find` ```c uint8_t tile_sense_cam_p_find(tiles_pal_t* hal, uint8_t instance) ``` Check whether a PAG7920J3 is present on the I2C bus. - `hal` — Platform HAL handle - `instance` — Instance index (0 = default, see mapping table) **Returns:** 1 if the device ACKs, 0 otherwise ##### `tile_sense_cam_p_init` ```c void tile_sense_cam_p_init(tiles_pal_t* hal, uint8_t instance, tile_t* tile, const sense_cam_p_cfg_t* cfg) ``` Initialize the camera for SPI image output. Verifies PartID, applies the vendor init tables verbatim for the requested resolution, then confirms the SPI link by reading CheckID. Pass cfg=NULL for defaults (spi_cs 0, 160x120). - `hal` — Platform HAL handle - `instance` — Instance index (0 = default, see mapping table) - `tile` — Pointer to tile handle (populated by this function) - `cfg` — Optional config, or NULL for defaults **Note:** Blocks for ~250 ms. Requires both an I2C bus and an SPI bus on the PAL. Single-instance: the SPI CS and resolution are driver-global. **Note:** Needs a PAL carrying both buses: on a Core, core_tiles_pal2(&i2c, &spi). A plain core_tiles_pal() is I2C-only or SPI-only and fails here with "PAL has no SPI". The SPI link is mode 3, up to 25 MHz. **Note:** Starts from a software reset rather than a power cycle, which section 8.1.3 asks for when changing resolution: the tile has no way to cut its own power, and the reset path is the one proven on the bench. **Note:** The vendor tables leave the sensor free-running at 30 fps (R_Frame_Time = 400000 at 12 MHz, datasheet section 9.1). ### Driver gaps Chip capabilities this driver does **not** expose. Each is tagged hardware-gated (the tile does not route the pin) or driver-deferred (possible, not yet implemented). Do not assume an API exists for these. #### Motion detection (common) The PAG7920J3 has a proprietary on-chip motion-detection mode running at 80x60 under 100 uW, which is the tile's headline always-on feature. Section 8.1.3.8 gives its init table. Driver-deferred, not a hardware gap, and the highest-value thing to add next. #### Manual exposure and gain (common) Auto-exposure and auto-gain run by default and hold mean brightness constant. Section 9.3 defines manual control, which is needed for any machine-vision use wanting repeatable radiometry. Driver-deferred. #### Trigger mode (advanced) Section 8.4 allows one-frame-on-demand capture, with frame rate set by the host's trigger interval. Note it does NOT relax the per-buffer readout deadline — the frame is still emitted in one output cycle at the sensor's own cadence. Driver-deferred. #### Windowing, mirror and flip (advanced) Section 10 covers WOI, mirror/flip, skip and average. Only the fixed 320x240 and 160x120 (2x skip) modes are exposed. Driver-deferred. #### LED strobe control (niche) The sensor can drive an external LED in sync with exposure via GPIO1 (R_expo_LED_* registers). The tile routes GPIO1 to a pad, so this is driver-deferred rather than hardware-gated. #### Multi-sensor frame sync (niche) Section 8.7 supports synchronising several sensors to a shared frame clock. Driver-deferred. #### External clock input (niche) Section 5.5 allows clocking the sensor from GPIO1 instead of its internal oscillator (1/6/12/24/30 MHz). Driver-deferred; the internal clock is used unconditionally. #### 320x240 on a 40 KB Core (common) A QVGA frame is 76800 bytes and must be read in 4800-byte buffers, each within a few ms. It cannot be buffered in a Core.ST.L4's 40 KB of SRAM and tears if streamed over the Core's USB. Use 160x120 (19200 B) there; 320x240 needs a Core with >= 77 KB free (Core.ST.W5, Core.ST.H5) or DMA to a faster sink. Core-gated, not a driver limit. #### Low-power standby (common) R_TG_En = 0 drops the sensor to ~11 uA (Table 4) but also takes the SPI register interface down, and the boost converter has no enable on a pad, so the tile has no true off state. Hardware-gated for the boost; driver-deferred for sensor standby. #### GPIO1 frame interrupt (advanced) Section 8.6 can signal each ready buffer on GPIO1 (pad 2). The driver polls CPU_INT0_Status over SPI instead. GPIO1 is also the LED strobe, frame-sync and external-clock pin, so those features exclude each other. Driver-deferred. #### Frame-rate control (advanced) The vendor tables fix 30 fps (R_Frame_Time = 400000 at 12 MHz, section 9.1). Slower rates would save power; faster ones are bounded by SPI. Driver-deferred. #### 8-bit parallel image output (niche) HARDWARE-GATED. The parallel bus needs PXD0-PXD7 plus PXCLK/HSYNC/VSYNC, and the tile's 10-pad T44 package breaks out only the four SPI signals. Not reachable on this tile at any driver revision. ### Enums #### `sense_cam_p_res_t` @brief Capture resolution. | Member | Value | Description | | --- | --- | --- | | `SENSE_CAM_P_RES_160x120` | `0` | 19200 B/frame, analog 2x skip. | | `SENSE_CAM_P_RES_320x240` | `1` | 76800 B/frame, full array. | ### Structs #### `sense_cam_p_cfg_t` Driver configuration. | Type | Field | Description | | --- | --- | --- | | `uint8_t` | `spi_cs` | CS identifier for the PAL SPI calls. | | `sense_cam_p_res_t` | `resolution` | Capture resolution. | ### Defines | Name | Value | Description | | --- | --- | --- | | `TILE_SENSE_CAM_P_VERSION_MAJOR` | `1` | | | `TILE_SENSE_CAM_P_VERSION_MINOR` | `0` | | | `TILE_SENSE_CAM_P_VERSION_PATCH` | `0` | | | `PAG7920_I2C_ADDR_FLOAT` | `0x35` | GPIO2 floating (tile default). | | `PAG7920_I2C_ADDR_HIGH` | `0x25` | GPIO2 tied to VDDIO. | | `PAG7920_I2C_ADDR_GND` | `0x40` | GPIO2 tied to GND. | | `PAG7920_I2C_ADDR_BROADCAST` | `0x60` | Always live, strap-independent. | | `PAG7920_PART_ID` | `0x7920` | Expected PartID. | | `PAG7920_SPI_RD_BIT` | `0x80` | Bit 7 of byte 0: 1 = read. Addr is 7-bit. | | `PAG7920_SPI_IMG_RD_EN` | `0x02` | 1 = grant access to the output buffer. | | `PAG7920_SPI_INT_STATUS` | `0x03` | CPU_INT0_Status: write 0 to a bit to clear it (0xFE clears FB_Rdy). | | `PAG7920_SPI_CHECKID_L` | `0x06` | 0x5A | | `PAG7920_SPI_CHECKID_H` | `0x07` | 0xA5 | | `PAG7920_SPI_IMG_DATA` | `0x40` | Image burst source. | | `PAG7920_SPI_CHECK_ID` | `0xA55A` | Expected CheckID; 0 unless running. | | `PAG7920_BUFFER_BYTES` | `4800` | Output buffer size. A frame is delivered in units of this. | --- Generated 2026-10-07 from the driver header at tiles@6af026f. Canonical HTML: https://bergsonne.io/docs/tiles/sense/camp ============================================================================== ## Sense.CAP driver > Capacitive trackpad driver for the Sense.CAP tile (IQS7211A). | | | | --- | --- | | Tile | `Sense.CAP` | | Driver version | `1.0.0` | | Header | `tile_sense_cap.h` | | Generated from | `tiles@58298c3` | | Canonical page | https://bergsonne.io/docs/tiles/sense/cap | ### Overview @version 1.0.0 Azoteq IQS7211A mutual-capacitance trackpad controller: up to 2-finger absolute XY tracking, relative XY, per-channel touch status, an on-chip gesture engine (tap, press-and-hold, four-way swipe) and an Alternate Low-Power (ALP) channel for presence wake-up. **Tile and surface.** The driver owns the rules the tile's PCB fixes; the application supplies the electrode surface. The tile routes five chip electrodes to pads: RX3 (pad 2, prox block A), RX6 (pad 9, block B), TX8 (pad 8), TX9 (pad 7) and TX11 (pad 6). RX3 and RX6 are RXn/TXn pins (datasheet Table 2.4) and may also act as Tx. Nothing else is routed, so the largest surface is 2 Rx x 3 Tx = 6 channels (or 1 Rx x 4 Tx with RX6 as a Tx). RX0/TX0 (ball F4) is tied to GND on this layout and is never used; the WLCSP18 ball A1 bonds TX9 and TX10 together (Table 2.2 note i), so the driver only ever selects TX9. A surface is described by sense_cap_surface_t (C) or chosen from the layout presets with tile_sense_cap_set_layout() (Studio). **Baseline.** init() starts writing a tile baseline: main oscillator locked to 14 MHz (18 MHz needs VDD >= 2.2 V, Table 3.2, and the tile cannot know its rail), internal RF filters on (no series resistors on the rev a electrodes), ALP electrodes limited to routed pins with the ALP count filter and debounce on, Comms Request mode, event mode with no event sources (the polled host asks for every window), no trackpad cycles until a surface is set, and the power-on reset acknowledged. init() never software-resets the chip. Set cfg.skip_baseline for a part pre-programmed with GUI settings (datasheet §10.1.1). **Lifecycle (nothing blocks for long).** init() only identifies the chip and returns (see its worst case). The baseline, a layout and its ATI run as one job that surface_poll() or process() advance: each call makes at most one register transaction that may wait for a communication window, and that wait is capped at one Active report period + 2 ms (never more than 20 ms), so no call blocks longer than ~12 ms at the 10 ms Active rate (20 ms at most) plus one I2C transaction. A window that opens later is caught by the next call. The one exception is the first transactions after a chip reset, before the job sets Comms Request EN: the chip clock-stretches each of them up to one report period at its current rate. The job is done when is_surface_ready() returns 1 (surface or not). Steady-state process() makes one read attempt and returns SENSE_CAP_BUSY if no window is open. Blocking helpers (configure_surface(), the setters, the live reads, reset(), hw_reset()) wait within real-time budgets instead. **Communication (polled, Comms Request).** Rev a fits the IQS7211A on an IQS7211E layout: tile pad 3 is the chip's MCLR (active-low reset, 4.7k pull-up plus a capacitor on the tile) and the chip's RDY (ball C3) is not connected. The host therefore polls. The baseline sets Comms Request EN (Config Settings bit 4, Table A.7): instead of clock-stretching an early master, the chip answers 0xEEEE outside a communication window, and the host asks for the next window by writing 0x00 to address 0xFF (§11.9.2). No transaction ever holds the bus waiting for the chip, which matters next to a radio. One window serves one transaction: the STOP ends it (§11.7), and a window the host does not service closes after the I2C timeout (register 0x4A, §11.6). Non-blocking calls (process(), ati_poll()) return SENSE_CAP_BUSY while a window is pending; blocking helpers retry within real-time budgets. A register write lands only inside a window, so every setter reads its register back. **Resets.** process() watches Show Reset (Info Flags bit 7). After a brown-out, a watchdog reset, tile_sense_cap_reset() or tile_sense_cap_hw_reset(), the driver re-applies the baseline and the stored surface on its own and raises SENSE_CAP_EV_RESET. A hardware reset needs a host pin on tile pad 3: pass cfg.mclr (and cfg.reset_on_init to pulse it at every init). Datasheet: Azoteq IQS7211A, Rev v1.3, April 2025 ### Examples #### Polling example (Cores SDK) ```c tile_t pad; sense_cap_cfg_t cfg = { .millis = my_millis, .mclr = my_mclr, .reset_on_init = 1, .layout = SENSE_CAP_LAYOUT_GRID_2X3 }; tile_sense_cap_init(core_tiles_pal(&core_i2c1), 0, &pad, &cfg); // ~0.1 s while (1) { tile_sense_cap_process(&pad); // finishes baseline, layout and ATI first if (tile_sense_cap_get_num_fingers(&pad) > 0) printf("x=%u y=%u\n", tile_sense_cap_get_finger_x(&pad, 0), tile_sense_cap_get_finger_y(&pad, 0)); core_delay_ms(10); } ``` #### Custom surface example (blocking) ```c sense_cap_surface_t s = SENSE_CAP_SURFACE_DEFAULTS; // the 2x3 grid s.tuning.touch_set_mult = 4; s.tuning.touch_clear_mult = 2; s.alp.enable = 0; if (tile_sense_cap_configure_surface(&pad, &s) != SENSE_CAP_OK) { ... } ``` ### API 81 public functions. Functions marked **Studio** are callable from the Studio behaviour DSL. #### Lifecycle ##### `tile_sense_cap_set_layout` — Studio ```c sense_cap_result_t tile_sense_cap_set_layout(tile_t *tile, sense_cap_layout_t layout) ``` Choose the electrode layout (non-blocking). Starts applying a preset: GRID_2X3 is the bench-tuned 2x3 trackpad (X 0-511, Y 0-255); BUTTONS_2X3 is the same wiring read as six channel touch states; SLIDER_1X3 uses RX3 across TX11/TX9/TX8 (X 0-511); SLIDER_1X4 adds RX6 as a fourth Tx (X 0-767). Only GRID_2X3 is bench-tuned. Call process() (or surface_poll()) until is_surface_ready(); the ATI takes a second or two. NONE stops sensing; it is ready once its registers verify. - `layout` — Preset (init: NONE) **Returns:** SENSE_CAP_OK when started, or an error. ##### `tile_sense_cap_surface_poll` — Studio ```c sense_cap_result_t tile_sense_cap_surface_poll(tile_t *tile) ``` Advance the baseline / surface job: registers, then ATI steps. Each call makes at most one register transaction, whose wait for a communication window is capped at one Active report period + 2 ms (20 ms at most): ~12 ms plus the transaction at the 10 ms Active rate. Returns SENSE_CAP_BUSY until the job is done (baseline verified; with a surface, applied and tuned). process() calls it for you while a job runs. Nothing to do: SENSE_CAP_OK. **Returns:** SENSE_CAP_OK when done, SENSE_CAP_BUSY, or the job's error. ##### `tile_sense_cap_is_surface_ready` — Studio ```c uint8_t tile_sense_cap_is_surface_ready(tile_t *tile) ``` Is the tile ready: no job pending, baseline applied, surface tuned? 1 once no baseline / surface job or ATI is pending and the last job verified the register image; with a surface (any layout but NONE) it must also have been applied with the ATI converged. With layout NONE it becomes 1 as soon as the baseline from init() is applied. 0 again while a new job (set_layout(), a setter's re-sync after a chip reset) or an ATI runs, and after a failed job. **Returns:** 1 if ready, 0 otherwise. ##### `tile_sense_cap_ati_start` — Studio ```c sense_cap_result_t tile_sense_cap_ati_start(tile_t *tile, sense_cap_ati_target_t target) ``` Queue auto-tuning (ATI) and return at once. Trackpad ATI runs when the trackpad is next sensed; ALP ATI only runs in LP1/LP2 (§5.6.3), so the driver holds the chip in LP1 under manual control for it and then hands mode control back. Re-run after any mechanical change (cover, enclosure). Advance with ati_poll(). - `target` — Trackpad, ALP or both **Returns:** SENSE_CAP_OK when queued, or SENSE_CAP_ERR_STATE if a job runs. ##### `tile_sense_cap_ati_poll` — Studio ```c sense_cap_result_t tile_sense_cap_ati_poll(tile_t *tile) ``` Advance an ATI started with ati_start(). Stays off the bus for 100 ms after queuing, then checks at most every 50 ms whether the re-ATI bit has cleared (the chip clears it when the algorithm completes, §5.6.3), so it never starves the ATI. The verdict is every sensed channel within 25% of the target; up to three rounds. One register transaction per call, like surface_poll(). **Returns:** SENSE_CAP_OK, SENSE_CAP_BUSY, SENSE_CAP_ERR_ATI or another error. ##### `tile_sense_cap_hw_reset` — Studio ```c sense_cap_result_t tile_sense_cap_hw_reset(tile_t *tile) ``` Hardware-reset the chip through tile pad 3 (MCLR). Pulls MCLR low for 2 ms (at least 250 ns needed, Table 4.2), releases it and waits for the chip to boot (0.1 s, at most ~0.7 s), then starts re-applying the baseline and the stored surface (finish with surface_poll() or process()). Recovers a chip that no longer answers I2C. Needs a tile that init() has seen (READY, or ERROR after a failed init); to reset before init, set cfg.reset_on_init. without cfg.mclr, SENSE_CAP_ERR_STATE before init(), or SENSE_CAP_ERR_NOT_FOUND. **Returns:** SENSE_CAP_OK once the chip is back, SENSE_CAP_ERR_NO_CALLBACK ##### `tile_sense_cap_reset` — Studio ```c sense_cap_result_t tile_sense_cap_reset(tile_t *tile) ``` Software-reset the chip (System Control SW Reset, §9.3.2). The reset takes effect when the window closes. Waits for the reboot, clears the cached data, then starts re-applying the baseline and the stored surface (finish with surface_poll() or process()). **Returns:** SENSE_CAP_OK once the chip is back, or an error. ##### `tile_sense_cap_get_version_major` — Studio ```c uint16_t tile_sense_cap_get_version_major(tile_t *tile) ``` Firmware major version (cached at init). **Returns:** Major version from register 0x01 ##### `tile_sense_cap_get_version_minor` — Studio ```c uint16_t tile_sense_cap_get_version_minor(tile_t *tile) ``` Firmware minor version (cached at init). **Returns:** Minor version from register 0x02 ##### `tile_sense_cap_get_settings_version` — Studio ```c uint16_t tile_sense_cap_get_settings_version(tile_t *tile) ``` Settings-version label of a GUI-programmed part (register 0x74). High byte settings major, low byte minor (§10.1.1). Live read. **Returns:** Raw 16-bit settings version, or 0 on failure #### Runtime ##### `tile_sense_cap_process` — Studio ```c sense_cap_result_t tile_sense_cap_process(tile_t *tile) ``` Read the trackpad data and update state (non-blocking). Call from the main loop, ideally once per report period. One read attempt of 0x10-0x1B: when the window is not open yet it asks for one and returns SENSE_CAP_BUSY without waiting. After a good read it asks for the next window straight away (in the awake modes), so the next call usually lands. Runs the touch recognizers, fires callbacks and handles a chip reset. A Show Reset flag the chip will not clear costs one more capped transaction (a Config Settings check, or a re-ack once a second). While the baseline / surface job or an ATI runs it advances that instead (surface_poll(): one transaction, capped wait) and returns SENSE_CAP_BUSY until it finishes. Worst case per call: ~12 ms plus one I2C transaction at the 10 ms Active rate (window wait capped at 20 ms whatever the rates), except right after a chip reset (see the file header). SENSE_CAP_ERR_RESET (chip reset seen; re-applying), or an error. **Returns:** SENSE_CAP_OK (new data, or a job just finished), SENSE_CAP_BUSY, ##### `tile_sense_cap_on_event` — Studio ```c void tile_sense_cap_on_event(tile_t *tile, sense_cap_event_cb_t cb, void *ctx) ``` Register or change the event callback. - `cb` — Callback (NULL to disable) - `ctx` — User context passed to the callback ##### `tile_sense_cap_get_touch_events` — Studio ```c uint16_t tile_sense_cap_get_touch_events(tile_t *tile) ``` Read and clear the accumulated touch-event bits. **Returns:** Latched SENSE_CAP_EV_* bits since the previous call. ##### `tile_sense_cap_was_tapped` — Studio ```c uint8_t tile_sense_cap_was_tapped(tile_t *tile) ``` Did a tap (single or double) complete since the last check? **Returns:** 1 if a tap completed (consumes the tap bits), 0 otherwise. ##### `tile_sense_cap_get_zone` — Studio ```c int8_t tile_sense_cap_get_zone(tile_t *tile) ``` Which channel (zone) is under the first finger? **Returns:** Channel number (§5.1.1), or -1 with no finger or no surface. ##### `tile_sense_cap_zone_at` — Studio ```c int8_t tile_sense_cap_zone_at(tile_t *tile, uint16_t x, uint16_t y) ``` Which channel (zone) is nearest a position? Electrode centres sit 256 points apart in chip units (§7.4), i.e. at k * res / (n - 1) in the configured resolution; the nearest centre on each axis wins, with Flip X/Y undone (§7.7). Use it in tap callbacks, where the finger has already lifted. - `x` — [0..65535] X position in the configured resolution - `y` — [0..65535] Y position in the configured resolution **Returns:** Channel number, or -1 without a surface. ##### `tile_sense_cap_get_x_pct` — Studio ```c int16_t tile_sense_cap_get_x_pct(tile_t *tile) ``` First finger's X as a percentage of the X range. **Returns:** 0-100 (truncated), or -1 with no finger. ##### `tile_sense_cap_get_y_pct` — Studio ```c int16_t tile_sense_cap_get_y_pct(tile_t *tile) ``` First finger's Y as a percentage of the Y range. **Returns:** 0-100 (truncated), or -1 with no finger or a one-row surface. ##### `tile_sense_cap_wait_for_touch` — Studio ```c uint8_t tile_sense_cap_wait_for_touch(tile_t *tile, uint32_t timeout_ms) ``` Block until a finger touches the surface. Runs process() every 5 ms. Real time comes from cfg.millis when given (otherwise the driver's own delay count). - `timeout_ms` — [0..600000] ms Maximum wait **Returns:** 1 on touch, 0 on timeout. ##### `tile_sense_cap_get_info_flags` — Studio ```c uint16_t tile_sense_cap_get_info_flags(tile_t *tile) ``` Cached Info Flags word (Table A.2). **Returns:** Info Flags (IQS7211A_INFO_* masks) ##### `tile_sense_cap_get_gestures` — Studio ```c uint16_t tile_sense_cap_get_gestures(tile_t *tile) ``` Cached chip gesture bits (Table A.3). Only non-zero with the chip gesture engine selected; each bit lasts one report cycle. **Returns:** SENSE_CAP_GESTURE_* bits from the last read ##### `tile_sense_cap_get_num_fingers` — Studio ```c uint8_t tile_sense_cap_get_num_fingers(tile_t *tile) ``` Number of fingers on the trackpad (0, 1 or 2). **Returns:** Finger count from the cached Info Flags ##### `tile_sense_cap_get_finger_x` — Studio ```c uint16_t tile_sense_cap_get_finger_x(tile_t *tile, uint8_t finger) ``` Absolute X of a finger (§7.2.3). Meaningful only while that slot holds a finger (gate on get_num_fingers); slots are stable while a finger stays down (§7.2.6). An empty slot reads 0xFFFF (observed on hardware); before the first successful process() read, and right after a chip reset, the cache reads 0. - `finger` — [0..1] Finger slot **Returns:** X in the configured resolution, 0xFFFF for an empty slot ##### `tile_sense_cap_get_finger_y` — Studio ```c uint16_t tile_sense_cap_get_finger_y(tile_t *tile, uint8_t finger) ``` Absolute Y of a finger (§7.2.3). An empty slot reads 0xFFFF, as for get_finger_x(). - `finger` — [0..1] Finger slot **Returns:** Y in the configured resolution, 0xFFFF for an empty slot ##### `tile_sense_cap_get_finger_strength` — Studio ```c uint16_t tile_sense_cap_get_finger_strength(tile_t *tile, uint8_t finger) ``` Touch strength of a finger: the sum of its deltas (§7.2.4). - `finger` — [0..1] Finger slot **Returns:** Strength in counts (relative, scales with tuning) ##### `tile_sense_cap_get_finger_area` — Studio ```c uint16_t tile_sense_cap_get_finger_area(tile_t *tile, uint8_t finger) ``` Contact area of a finger, in channels (§7.2.5). - `finger` — [0..1] Finger slot **Returns:** Number of channels associated with the finger ##### `tile_sense_cap_get_relative_x` — Studio ```c int16_t tile_sense_cap_get_relative_x(tile_t *tile) ``` Relative X movement since the previous report (§7.2.2). **Returns:** Signed X delta in the configured resolution ##### `tile_sense_cap_get_relative_y` — Studio ```c int16_t tile_sense_cap_get_relative_y(tile_t *tile) ``` Relative Y movement since the previous report (§7.2.2). **Returns:** Signed Y delta in the configured resolution ##### `tile_sense_cap_get_touch_status` — Studio ```c uint32_t tile_sense_cap_get_touch_status(tile_t *tile) ``` Per-channel touch status CH0..CH31 (Table A.4), live read. Waits for one communication window (at most about one report period). The outputs of the BUTTONS_2X3 layout. **Returns:** Bit n set = channel n touched; 0 on failure ##### `tile_sense_cap_is_touched` — Studio ```c uint8_t tile_sense_cap_is_touched(tile_t *tile, uint8_t channel) ``` Does one channel report touch? (live read) - `channel` — [0..5] Channel number **Returns:** 1 if touched, 0 if not ##### `tile_sense_cap_get_num_channels` — Studio ```c uint8_t tile_sense_cap_get_num_channels(tile_t *tile) ``` Number of channels in the applied surface. **Returns:** n_rx * n_tx, or 0 without a surface. ##### `tile_sense_cap_get_channel_count` — Studio ```c uint16_t tile_sense_cap_get_channel_count(tile_t *tile, uint8_t channel) ``` One channel's raw count (extended map 0xE000, live read). - `channel` — [0..5] Channel number **Returns:** Count value, or 0 on failure / bad channel. ##### `tile_sense_cap_get_channel_delta` — Studio ```c int16_t tile_sense_cap_get_channel_delta(tile_t *tile, uint8_t channel) ``` One channel's signed delta, count minus reference (§5.3.4). - `channel` — [0..5] Channel number **Returns:** Delta value, or 0 on failure / bad channel. ##### `tile_sense_cap_read_channel_counts` — Studio ```c uint8_t tile_sense_cap_read_channel_counts(tile_t *tile, uint16_t *counts, uint8_t n) ``` Read the first n channel counts in one transaction. The extended map auto-increments (§11.4.2), so one window serves the whole surface. - `counts` — Out: n counts - `n` — Channels to read, 1-32 **Returns:** Channels read (n), or 0 on failure (counts untouched). ##### `tile_sense_cap_read_channel_deltas` — Studio ```c uint8_t tile_sense_cap_read_channel_deltas(tile_t *tile, int16_t *deltas, uint8_t n) ``` Read the first n signed channel deltas in one transaction. - `deltas` — Out: n deltas (count - reference, §5.3.4) - `n` — Channels to read, 1-32 **Returns:** Channels read (n), or 0 on failure (deltas untouched). ##### `tile_sense_cap_is_alp_active` — Studio ```c uint8_t tile_sense_cap_is_alp_active(tile_t *tile) ``` Does the ALP channel report presence? (cached Info Flags) **Returns:** 1 if the ALP output bit is set ##### `tile_sense_cap_get_alp_count` — Studio ```c uint16_t tile_sense_cap_get_alp_count(tile_t *tile) ``` Raw ALP count (register 0x23, live read). **Returns:** ALP count, or 0 on failure ##### `tile_sense_cap_get_alp_lta` — Studio ```c uint16_t tile_sense_cap_get_alp_lta(tile_t *tile) ``` ALP long-term average (register 0x24, live read). **Returns:** ALP LTA, or 0 on failure ##### `tile_sense_cap_get_mode` — Studio ```c uint8_t tile_sense_cap_get_mode(tile_t *tile) ``` Current charging mode (cached Info Flags). **Returns:** sense_cap_mode_t value ##### `tile_sense_cap_has_ati_error` — Studio ```c uint8_t tile_sense_cap_has_ati_error(tile_t *tile) ``` Did the last ATI fail? The trackpad ATI error flag, plus the ALP one only while ALP wake is in use: the ALP ATI runs only in LP1/LP2 (§5.6.3), so its flag is stale otherwise. **Returns:** 1 if a relevant ATI error flag is set #### Configuration ##### `tile_sense_cap_set_sensitivity` — Studio ```c uint8_t tile_sense_cap_set_sensitivity(tile_t *tile, sense_cap_sensitivity_t level) ``` Touch sensitivity relative to the surface's tuned multipliers. NORMAL writes the surface's own set/clear multipliers; the other levels scale both by x2, x1.5, x0.75 or x0.5, keeping their ratio. Replaces a set_touch_multipliers() value, and is kept across set_layout(). - `level` — Sensitivity level **Returns:** 1 if written and verified (or queued behind a running job) ##### `tile_sense_cap_set_touch_multipliers` — Studio ```c uint8_t tile_sense_cap_set_touch_multipliers(tile_t *tile, uint8_t set_mult, uint8_t clear_mult) ``` Set the touch set/clear multipliers directly (§5.5.1). A channel reports touch when its count rises above Reference x (1 + set/128) and releases below Reference x (1 + clear/128). Smaller = more sensitive; clear below set gives hysteresis. Replaces the sensitivity level until the next set_sensitivity() or set_layout(). - `set_mult` — [1..255] Touch-set multiplier - `clear_mult` — [0..255] Touch-clear multiplier (clamped to <= set) **Returns:** 1 if written and verified ##### `tile_sense_cap_set_power_profile` — Studio ```c uint8_t tile_sense_cap_set_power_profile(tile_t *tile, sense_cap_power_profile_t profile) ``` Choose a report-rate / timeout bundle for the power modes. Report periods Active / Idle-touch / Idle / LP1 / LP2 (ms), and timeouts Active / Idle-touch / Idle / LP1 (s): - ALWAYS_ACTIVE: 10/10/10/10/10, timeouts never. - RESPONSIVE: 10/20/20/50/100, 10/30/20/60. - BALANCED: 10/50/50/100/200 (the Table 3.4 setup), 5/30/10/30. - LOW_POWER: 20/100/100/160/320, 2/20/3/10. The chip leaves Idle for LP1/LP2 only while ALP wake is on; otherwise the Idle timeout is written as 0 (never, §6.2), because only the ALP can wake it from there. - `profile` — Profile **Returns:** 1 if written and verified ##### `tile_sense_cap_set_power_mode` — Studio ```c uint8_t tile_sense_cap_set_power_mode(tile_t *tile, sense_cap_power_mode_t mode) ``` Automatic power modes, or hold one mode (manual control, §6.3). FORCE_LP1/FORCE_LP2 sense only the ALP and never wake on their own: poll is_alp_active() and switch back. Under manual control the chip does not update trackpad references (§5.4.1): call reseed() when needed. - `mode` — AUTO (init) or a forced mode **Returns:** 1 if written and verified ##### `tile_sense_cap_set_active_rate` — Studio ```c uint8_t tile_sense_cap_set_active_rate(tile_t *tile, uint16_t ms) ``` Active-mode report period. Overrides one field of the power profile. The coordinate report rate tops out at 100 Hz (§1.1). - `ms` — [10..1000] ms Report period in Active mode **Returns:** 1 if written and verified ##### `tile_sense_cap_set_idle_timeout` — Studio ```c uint8_t tile_sense_cap_set_idle_timeout(tile_t *tile, uint16_t seconds) ``` Time in Idle before dropping to LP1 (used while ALP wake is on). Overrides one field of the power profile. 0 = never leave Idle. - `seconds` — [0..3600] s Idle timeout **Returns:** 1 if written and verified ##### `tile_sense_cap_set_report_rate` — Studio ```c uint8_t tile_sense_cap_set_report_rate(tile_t *tile, sense_cap_mode_t mode, uint16_t ms) ``` Report period of one charging mode (§6.1). - `mode` — Which mode's period - `ms` — [1..65535] ms Report period **Returns:** 1 if written and verified ##### `tile_sense_cap_set_mode_timeout` — Studio ```c uint8_t tile_sense_cap_set_mode_timeout(tile_t *tile, sense_cap_mode_t mode, uint16_t seconds) ``` Timeout before the chip steps down from a mode (§6.2). LP2 has no timeout. The Idle timeout is only written while ALP wake is on (see set_power_profile). - `mode` — Active, Idle-touch, Idle or LP1 - `seconds` — [0..65535] s Timeout, 0 = never **Returns:** 1 if written and verified, 0 for LP2 or on failure ##### `tile_sense_cap_set_alp_wake` — Studio ```c uint8_t tile_sense_cap_set_alp_wake(tile_t *tile, uint8_t enable) ``` Allow the chip to sleep in LP1/LP2 and wake on the ALP channel. On: the Idle timeout of the power profile applies, ALP automatic re-ATI is enabled and the ALP counts in has_ati_error(); run ati_start(ALP) once so the ALP is tuned before relying on it. Off (init): the chip never times out of Idle, so it never goes deaf in LP with an untuned ALP. Needs a surface whose alp.enable is set. - `enable` — 1 = allow ALP wake **Returns:** 1 if applied, 0 if the surface has no ALP or on failure ##### `tile_sense_cap_set_alp_threshold` — Studio ```c uint8_t tile_sense_cap_set_alp_threshold(tile_t *tile, uint16_t threshold) ``` ALP output threshold (§5.5.2). The ALP output sets when the ALP count deviates from its LTA by more than this. Lower = wakes on lighter proximity. - `threshold` — [1..1000] Delta counts **Returns:** 1 if written and verified ##### `tile_sense_cap_set_flip_x` — Studio ```c uint8_t tile_sense_cap_set_flip_x(tile_t *tile, uint8_t enable) ``` Mirror X relative to the layout (§7.7). - `enable` — 1 = mirror **Returns:** 1 if written and verified ##### `tile_sense_cap_set_flip_y` — Studio ```c uint8_t tile_sense_cap_set_flip_y(tile_t *tile, uint8_t enable) ``` Mirror Y relative to the layout (§7.7). - `enable` — 1 = mirror **Returns:** 1 if written and verified ##### `tile_sense_cap_set_switch_xy` — Studio ```c uint8_t tile_sense_cap_set_switch_xy(tile_t *tile, uint8_t enable) ``` Swap the X and Y axes relative to the layout (§7.7). Channel numbering is unaffected (§7.7). A resolution set with set_resolution() stays with its axis name. - `enable` — 1 = swap **Returns:** 1 if written and verified ##### `tile_sense_cap_set_resolution` — Studio ```c uint8_t tile_sense_cap_set_resolution(tile_t *tile, uint16_t x_res, uint16_t y_res) ``` XY output range (§7.4). 0 = the layout's own (256 per electrode pitch). Gesture distances scale with the resolution. - `x_res` — [0..65535] X range, 0 = automatic - `y_res` — [0..65535] Y range, 0 = automatic **Returns:** 1 if written and verified ##### `tile_sense_cap_set_tap_time` — Studio ```c uint8_t tile_sense_cap_set_tap_time(tile_t *tile, uint16_t ms) ``` Maximum tap duration (§8.1). Used by both gesture engines: a touch released sooner, without moving beyond the tap distance, is a tap. - `ms` — [50..2000] ms Maximum tap duration **Returns:** 1 if written and verified ##### `tile_sense_cap_set_hold_time` — Studio ```c uint8_t tile_sense_cap_set_hold_time(tile_t *tile, uint16_t ms) ``` Press-and-hold time, counted after the tap time (§8.2). A long press fires at tap time + hold time from touch-down (600 ms with the defaults), in both gesture engines. - `ms` — [0..5000] ms Hold time after the tap time **Returns:** 1 if written and verified ##### `tile_sense_cap_set_swipe_time` — Studio ```c uint8_t tile_sense_cap_set_swipe_time(tile_t *tile, uint16_t ms) ``` Maximum swipe duration for the chip gesture engine (§8.3). - `ms` — [50..2000] ms Swipe must cover its distance within this **Returns:** 1 if written and verified ##### `tile_sense_cap_set_swipe_distance` — Studio ```c uint8_t tile_sense_cap_set_swipe_distance(tile_t *tile, uint16_t px) ``` Minimum swipe travel (§8.3), both axes. 0 = automatic: 48 px per 256 px of electrode pitch. The driver recognizer also needs a release faster than 400 px/s per 256 px pitch. - `px` — [0..65535] Pixels in the configured resolution, 0 = automatic **Returns:** 1 if written and verified ##### `tile_sense_cap_set_swipe_angle` — Studio ```c uint8_t tile_sense_cap_set_swipe_angle(tile_t *tile, uint8_t degrees) ``` Maximum swipe angle from its axis (§8.3). Written as 64 * tan(angle) (register 0x87). 45 accepts every swipe along its dominant axis. - `degrees` — [1..75] deg Largest angle from the X or Y axis **Returns:** 1 if written and verified ##### `tile_sense_cap_set_gesture_source` — Studio ```c uint8_t tile_sense_cap_set_gesture_source(tile_t *tile, sense_cap_gesture_source_t source) ``` Choose the gesture engine. DRIVER (init): the driver's own tap / double tap / hold / swipe recognizers, and the chip's gesture engine is switched off so nothing fires twice. CHIP: the chip's engine (§8) with its gesture event enabled, so a gesture holds its window open until the host reads it. Double tap is paired by the driver either way; drag and pinch are always the driver's. - `source` — Gesture engine **Returns:** 1 if written and verified ##### `tile_sense_cap_enable_gestures` — Studio ```c uint8_t tile_sense_cap_enable_gestures(tile_t *tile, uint16_t mask) ``` Which gestures fire (either engine). - `mask` — [0..63] OR of SENSE_CAP_GESTURE_* bits (init: all) **Returns:** 1 if written and verified ##### `tile_sense_cap_set_max_touches` — Studio ```c uint8_t tile_sense_cap_set_max_touches(tile_t *tile, uint8_t fingers) ``` Maximum simultaneous fingers (§7.3). - `fingers` — [1..2] Fingers tracked **Returns:** 1 if written and verified ##### `tile_sense_cap_set_reference_update` — Studio ```c uint8_t tile_sense_cap_set_reference_update(tile_t *tile, uint16_t seconds) ``` Reference update interval (§5.4.1). Trackpad references refresh from LP1/LP2 under automatic mode control. The datasheet gives a 60 s maximum and does not say what 0 means. - `seconds` — [0..60] s Update interval **Returns:** 1 if written and verified ##### `tile_sense_cap_set_i2c_timeout` — Studio ```c uint8_t tile_sense_cap_set_i2c_timeout(tile_t *tile, uint16_t ms) ``` Unserviced-window timeout (§11.6). How long a requested window waits for the host before the chip moves on (the chip pauses while it waits). Init: 50 ms. Poll process() faster than report period + this, or reads alternate with BUSY. - `ms` — [2..1000] ms Window timeout **Returns:** 1 if written and verified ##### `tile_sense_cap_set_watchdog` — Studio ```c uint8_t tile_sense_cap_set_watchdog(tile_t *tile, uint8_t enable) ``` Enable or disable the chip's watchdog (§10.2). Init: enabled. - `enable` — 1 = enable **Returns:** 1 if written and verified ##### `tile_sense_cap_reseed` — Studio ```c uint8_t tile_sense_cap_reseed(tile_t *tile) ``` Reseed the trackpad references and the ALP LTA (§5.4.3). **Returns:** 1 if the command was sent #### Advanced ##### `tile_sense_cap_read_reg` — Studio ```c uint16_t tile_sense_cap_read_reg(tile_t *tile, uint8_t reg) ``` Read a raw 16-bit register (waits for a window). - `reg` — [0..255] Register address **Returns:** Register value, or 0xEEEE if no window opened ##### `tile_sense_cap_write_reg` — Studio ```c sense_cap_result_t tile_sense_cap_write_reg(tile_t *tile, uint8_t reg, uint16_t value) ``` Write a raw 16-bit register, guarded and verified. Refuses the Tx short test (0x50 bit 15), RX0/TX0 or TX10 in the ALP masks (0x72/0x73) or the Rx/Tx map (0x90-0x95), and the value 0xEEEE. A write to 0x51 updates the driver's comms settings. Not re-applied after a chip reset, and a later driver call may overwrite it. 0x50 is sent as a command without read-back. - `reg` — [0..255] Register address - `value` — [0..65535] Value **Returns:** SENSE_CAP_OK, SENSE_CAP_ERR_FORBIDDEN, or a comms/verify error #### Other ##### `tile_sense_cap_find` ```c uint8_t tile_sense_cap_find(tiles_pal_t *hal, uint8_t instance) ``` Look for an IQS7211A on the bus. Reads the product number, asking for a communication window when the chip answers 0xEEEE (a bare address probe goes unanswered outside a window). Blocks up to ~0.4 s. no-window marker; 0 if nothing answered. - `hal` — Tiles HAL handle (I2C bus) - `instance` — Device instance (only 0 exists) **Returns:** 1 if the chip reported product 763, or answered with the ##### `tile_sense_cap_init` ```c void tile_sense_cap_init(tiles_pal_t *hal, uint8_t instance, tile_t *tile, const sense_cap_cfg_t *cfg) ``` Initialize the trackpad controller and start the tile baseline. Identifies the chip (product 763) and returns: the tile is READY, and the baseline (see the file header) is a job that surface_poll() or process() advance, one register transaction per call, until is_surface_ready() returns 1. With cfg.layout set, the same job also applies and tunes that layout. Setters called meanwhile are queued into the job. A baseline that does not verify is reported by surface_poll() / process() (the error, and TILE_ON_ERROR) and leaves is_surface_ready() at 0; the tile stays READY (only a chip that is not found sets ERROR). Never software-resets the chip. Blocking: the product read, which waits at most ~0.4 s for a communication window (typically one transaction). With cfg.reset_on_init it pulses MCLR first and waits for the boot instead (0.1 s, at most ~0.7 s). Without it, a chip that does not answer gets one MCLR pulse if cfg.mclr is set (worst case ~1.1 s, chip absent). Blocking use: `while (tile_sense_cap_surface_poll(&pad) == SENSE_CAP_BUSY) core_delay_ms(2);` or configure_surface(), which also finishes the baseline. - `hal` — Tiles HAL handle (I2C bus) - `instance` — Device instance (only 0 exists) - `tile` — Tile handle to initialize - `cfg` — Optional config. NULL for defaults. ##### `tile_sense_cap_layout_preset` ```c sense_cap_result_t tile_sense_cap_layout_preset(sense_cap_layout_t layout, sense_cap_surface_t *out) ``` Fill a surface struct with a layout preset. For customising a preset before surface_begin(). SENSE_CAP_LAYOUT_NONE gives the routed 2x3 geometry with every channel disabled. - `layout` — Preset - `out` — Filled with the preset **Returns:** SENSE_CAP_OK, or SENSE_CAP_ERR_ARG for an unknown layout. ##### `tile_sense_cap_surface_begin` ```c sense_cap_result_t tile_sense_cap_surface_begin(tile_t *tile, const sense_cap_surface_t *surf) ``` Validate a surface against the tile and start applying it. Checks the tile rules (routed pins only; never RX0/TX0 or TX10; no pin both Rx and Tx; ALP masks on routed pins; tuning ranges), copies the surface and starts the apply job: register sync (only registers that differ are written, cycles cleared before a geometry change), then trackpad ATI, then ALP ATI if ALP wake is on. Non-blocking: advance it with surface_poll() or process() until is_surface_ready(). Replaces a job still running (the baseline from init included; the new job carries the whole register image). The copy is re-applied after any chip reset. - `tile` — Initialised tile handle - `surf` — Surface description **Returns:** SENSE_CAP_OK when accepted, or the validation error. ##### `tile_sense_cap_configure_surface` ```c sense_cap_result_t tile_sense_cap_configure_surface(tile_t *tile, const sense_cap_surface_t *surf) ``` Apply a surface and wait until it is tuned (blocking). surface_begin() plus surface_poll() until done, bounded by 20 s of real time. Also finishes a baseline still pending from init(). - `tile` — Initialised tile handle - `surf` — Surface description **Returns:** SENSE_CAP_OK when applied and ATI converged; otherwise the error. ##### `tile_sense_cap_on_touch` ```c void tile_sense_cap_on_touch(tile_t *tile, sense_cap_touch_cb_t cb, void *ctx) ``` Register the raw touch-event stream callback (DOWN/MOVED/UP). - `tile` — Tile handle - `cb` — Callback (NULL to disable) - `ctx` — User context ##### `tile_sense_cap_next_touch_event` ```c uint8_t tile_sense_cap_next_touch_event(tile_t *tile, sense_cap_touch_t *ev) ``` Pop the oldest queued touch event (8-deep ring, oldest dropped). - `tile` — Tile handle - `ev` — Filled with the event when one was available **Returns:** 1 if an event was returned, 0 if the queue is empty. ##### `tile_sense_cap_on_tap` ```c void tile_sense_cap_on_tap(tile_t *tile, sense_cap_tap_cb_t cb, void *ctx) ``` Register the tap callback (single and double). - `tile` — Tile handle - `cb` — Callback (NULL to disable) - `ctx` — User context ##### `tile_sense_cap_on_long_press` ```c void tile_sense_cap_on_long_press(tile_t *tile, sense_cap_hold_cb_t cb, void *ctx) ``` Register the long-press callback. - `tile` — Tile handle - `cb` — Callback (NULL to disable) - `ctx` — User context ##### `tile_sense_cap_on_swipe` ```c void tile_sense_cap_on_swipe(tile_t *tile, sense_cap_swipe_cb_t cb, void *ctx) ``` Register the swipe callback. - `tile` — Tile handle - `cb` — Callback (NULL to disable) - `ctx` — User context ##### `tile_sense_cap_on_drag` ```c void tile_sense_cap_on_drag(tile_t *tile, sense_cap_drag_cb_t cb, void *ctx) ``` Register the drag (pan) callback. - `tile` — Tile handle - `cb` — Callback (NULL to disable) - `ctx` — User context ##### `tile_sense_cap_on_pinch` ```c void tile_sense_cap_on_pinch(tile_t *tile, sense_cap_pinch_cb_t cb, void *ctx) ``` Register the two-finger pinch callback. - `tile` — Tile handle - `cb` — Callback (NULL to disable) - `ctx` — User context ##### `tile_sense_cap_read_channels` ```c uint8_t tile_sense_cap_read_channels(tile_t *tile, uint16_t *counts, int16_t *deltas, uint8_t n) ``` Counts and deltas together (two windows). C only. - `tile` — Tile handle - `counts` — Out: n counts, or NULL to skip - `deltas` — Out: n signed deltas, or NULL to skip - `n` — Channels to read, 1-32 **Returns:** 1 if every requested block came back valid, 0 otherwise. ### Vibe Settings Settings Studio's Vibe interface offers for this tile, generated from the driver's `@studio control` annotations. Each is an argument of the function shown. | Setting | Tier | Values | Default | Function | |---|---|---|---|---| | Surface layout | basic | No surface: nothing sensed, 2x3 trackpad (bench-tuned), 2x3 as six buttons, 3-segment slider on RX3, 4-segment slider, RX6 as Tx | No surface: nothing sensed | `set_layout(layout)` | | Touch sensitivity | basic | Firm touches only (multipliers x2), Less sensitive (x1.5), As tuned for the surface, More sensitive (x0.75), Light touches (x0.5) | As tuned for the surface | `set_sensitivity(level)` | | Power profile | basic | Always active (10 ms in every mode), Responsive, Balanced, Low power | Responsive | `set_power_profile(profile)` | | Touch-set multiplier | advanced | 1 to 255 | 8 | `set_touch_multipliers(set_mult)` | | Touch-clear multiplier | advanced | 0 to 255 | 5 | `set_touch_multipliers(clear_mult)` | | Active report period | advanced | 10 to 1000 ms | 10 | `set_active_rate(ms)` | | Idle time before low power | advanced | 0 to 3600 s | 20 | `set_idle_timeout(seconds)` | | Wake on proximity | advanced | on / off | off | `set_alp_wake(enable)` | | Proximity wake threshold | advanced | 1 to 1000 | 20 | `set_alp_threshold(threshold)` | | Mirror X | advanced | on / off | off | `set_flip_x(enable)` | | Mirror Y | advanced | on / off | off | `set_flip_y(enable)` | | Swap X and Y | advanced | on / off | off | `set_switch_xy(enable)` | | X resolution | advanced | 0 to 65535 | 0 | `set_resolution(x_res)` | | Y resolution | advanced | 0 to 65535 | 0 | `set_resolution(y_res)` | | Tap time | advanced | 50 to 2000 ms | 300 | `set_tap_time(ms)` | | Hold time | advanced | 0 to 5000 ms | 300 | `set_hold_time(ms)` | | Swipe distance | advanced | 0 to 65535 | 0 | `set_swipe_distance(px)` | | Swipe angle | advanced | 1 to 75 deg | 45 | `set_swipe_angle(degrees)` | | Gesture engine | advanced | Driver recognizers, Chip gesture engine | Driver recognizers | `set_gesture_source(source)` | ### Driver gaps Chip capabilities this driver does **not** expose. Each is tagged hardware-gated (the tile does not route the pin) or driver-deferred (possible, not yet implemented). Do not assume an API exists for these. #### RDY / event-driven operation (advanced) HW-gated (rev a): the IQS7211A's RDY (ball C3) is not routed, and tile pad 3 carries MCLR instead (the layout was drawn for the IQS7211E, whose A5 ball combines RDY and MCLR). The driver runs polled only: Comms Request mode with event mode on and every event source off, so the chip opens a window only when the host asks. Interrupt-driven wake on touch needs a board revision that routes RDY. #### 18 MHz main oscillator (niche) Driver policy: the baseline locks the main oscillator to 14 MHz (Other Settings bit 4 = 0). 18 MHz needs VDD >= 2.2 V (Table 3.2) and the tile runs from 1.8-3.3 V, which the driver cannot measure. Main Osc Adj and the calibration capacitor (the rest of 0x52) are left as the chip has them. #### Tx short test (niche) Forbidden, HW-gated: RX0/TX0 (ball F4) is tied to GND on this layout, so the Tx short-test configuration (System Control bit 15, Table A.6) would drive a transmitter into ground. The driver never sets it, and write_reg refuses it, along with RX0/TX0 or TX10 in the ALP masks or the Rx/Tx map. #### Analog front-end settings (niche) Driver-deferred: charge-transfer frequency (0x58/0x59, Table A.9), Max Count, projected opamp bias, pool capacitor, NM-in-static, Cs discharge and init delay (0x5A/0x5B, Table A.10) stay at chip values. They are GUI-derived settings for a particular stack-up (§9: "only adjusted under guidance of Azoteq"). The driver only sets the RF filter bit in 0x5A and 0x5B and, when asked, the ALP auto-prox cycles. write_reg reaches the rest. #### Electrodes beyond the routed five (advanced) HW-gated: RX0-RX2, RX4, RX5, RX7 and TX10 are not routed to pads, so the largest surface is 2 Rx x 3 Tx (6 channels) or 1 Rx x 4 Tx with RX6 as a Tx. surface_begin() rejects any other pin. #### Hardware reset from Studio (niche) Driver-deferred: the tile's pad 3 is the chip's MCLR (4.7k pull-up on the tile, chip pull-up 180-240k, Table 4.2). The platform layer has no GPIO write, so a C program passes cfg.mclr (drive low / release) and calls hw_reset() or sets cfg.reset_on_init; a Studio program cannot supply the pin yet, so hw_reset() returns SENSE_CAP_ERR_NO_CALLBACK there. Software reset (reset()) covers every case where the chip still answers I2C. #### Touch-status and XY event sources (niche) Driver policy: with no RDY line, an event window would stall the chip for the whole I2C timeout while nobody services it, so the gesture event is the only source the driver ever enables (and only with the chip gesture engine selected). Touch-status (0x20/0x21) is read live, not cached by process(). #### OTP settings programming (niche) Out of scope: programming a settings image into the part (Azoteq GUI export, §10.1.1). A pre-programmed part is supported: set cfg.skip_baseline and read get_settings_version(). ### Events - `touch_down` - `touch_up` - `tap` - `double_tap` - `long_press` - `swipe` - `drag` - `reset` ### Enums #### `sense_cap_result_t` Result of a lifecycle call, a surface/ATI step or a guarded write. | Member | Value | Description | | --- | --- | --- | | `SENSE_CAP_OK` | `0` | Done | | `SENSE_CAP_BUSY` | `1` | In progress; call the poll function again | | `SENSE_CAP_ERR_STATE` | `2` | Tile not initialised, or another job is running | | `SENSE_CAP_ERR_ARG` | `3` | Argument out of range | | `SENSE_CAP_ERR_PIN` | `4` | Electrode not routed on this tile | | `SENSE_CAP_ERR_FORBIDDEN` | `5` | RX0/TX0, TX10 or the Tx short test | | `SENSE_CAP_ERR_GEOMETRY` | `6` | Duplicate electrode or impossible surface | | `SENSE_CAP_ERR_COMMS` | `7` | No communication window within the budget | | `SENSE_CAP_ERR_VERIFY` | `8` | A register read back differently | | `SENSE_CAP_ERR_ATI` | `9` | ATI did not converge | | `SENSE_CAP_ERR_TIMEOUT` | `10` | Real-time budget exceeded | | `SENSE_CAP_ERR_RESET` | `11` | The chip reset mid-operation (it is being re-applied) | | `SENSE_CAP_ERR_NO_CALLBACK` | `12` | hw_reset() without a cfg.mclr callback | | `SENSE_CAP_ERR_NOT_FOUND` | `13` | No IQS7211A answered | #### `sense_cap_layout_t` Electrode layout presets, all physically valid on the routed pins. | Member | Value | Description | | --- | --- | --- | | `SENSE_CAP_LAYOUT_NONE` | `0` | No surface: nothing sensed | | `SENSE_CAP_LAYOUT_GRID_2X3` | `1` | 2x3 trackpad (bench-tuned) | | `SENSE_CAP_LAYOUT_BUTTONS_2X3` | `2` | 2x3 as six buttons | | `SENSE_CAP_LAYOUT_SLIDER_1X3` | `3` | 3-segment slider on RX3 | | `SENSE_CAP_LAYOUT_SLIDER_1X4` | `4` | 4-segment slider, RX6 as Tx | #### `sense_cap_sensitivity_t` Touch sensitivity, relative to the surface's tuned multipliers. | Member | Value | Description | | --- | --- | --- | | `SENSE_CAP_SENS_LOWEST` | `0` | Firm touches only (multipliers x2) | | `SENSE_CAP_SENS_LOW` | `1` | Less sensitive (x1.5) | | `SENSE_CAP_SENS_NORMAL` | `2` | As tuned for the surface | | `SENSE_CAP_SENS_HIGH` | `3` | More sensitive (x0.75) | | `SENSE_CAP_SENS_HIGHEST` | `4` | Light touches (x0.5) | #### `sense_cap_mode_t` Charging (power) mode, as reported in Info Flags (Table A.2). | Member | Value | Description | | --- | --- | --- | | `SENSE_CAP_MODE_ACTIVE` | `0` | Active | | `SENSE_CAP_MODE_IDLE_TOUCH` | `1` | Idle-touch | | `SENSE_CAP_MODE_IDLE` | `2` | Idle | | `SENSE_CAP_MODE_LP1` | `3` | Low power 1 | | `SENSE_CAP_MODE_LP2` | `4` | Low power 2 | #### `sense_cap_power_mode_t` Who switches the chip's power modes (§6.3). | Member | Value | Description | | --- | --- | --- | | `SENSE_CAP_POWER_AUTO` | `0` | Chip switches modes itself | | `SENSE_CAP_POWER_FORCE_ACTIVE` | `1` | Held in Active (manual control) | | `SENSE_CAP_POWER_FORCE_LP1` | `2` | Held in LP1: ALP only | | `SENSE_CAP_POWER_FORCE_LP2` | `3` | Held in LP2: ALP only | #### `sense_cap_power_profile_t` Report-rate and timeout bundles for the automatic power modes. | Member | Value | Description | | --- | --- | --- | | `SENSE_CAP_POWER_ALWAYS_ACTIVE` | `0` | Always active (10 ms in every mode) | | `SENSE_CAP_POWER_RESPONSIVE` | `1` | Responsive | | `SENSE_CAP_POWER_BALANCED` | `2` | Balanced | | `SENSE_CAP_POWER_LOW_POWER` | `3` | Low power | #### `sense_cap_gesture_source_t` Which engine recognizes tap / hold / swipe. | Member | Value | Description | | --- | --- | --- | | `SENSE_CAP_GESTURES_DRIVER` | `0` | Driver recognizers | | `SENSE_CAP_GESTURES_CHIP` | `1` | Chip gesture engine | #### `sense_cap_ati_target_t` What ati_start() tunes. | Member | Value | Description | | --- | --- | --- | | `SENSE_CAP_ATI_TRACKPAD` | `1` | Trackpad channels | | `SENSE_CAP_ATI_ALP` | `2` | ALP wake channel (runs in LP1) | | `SENSE_CAP_ATI_BOTH` | `3` | Trackpad, then ALP | #### `sense_cap_auto_prox_t` ALP auto-prox cycles per low-power report (0x5B, Table A.10). | Member | Value | Description | | --- | --- | --- | | `SENSE_CAP_AUTOPROX_KEEP` | `0` | Leave the chip's setting | | `SENSE_CAP_AUTOPROX_4` | `1` | 4 cycles | | `SENSE_CAP_AUTOPROX_8` | `2` | 8 cycles | | `SENSE_CAP_AUTOPROX_16` | `3` | 16 cycles | | `SENSE_CAP_AUTOPROX_32` | `4` | 32 cycles | | `SENSE_CAP_AUTOPROX_OFF` | `5` | Auto-prox disabled | #### `sense_cap_finger_t` Finger slot for the absolute-XY getters. | Member | Value | Description | | --- | --- | --- | | `SENSE_CAP_FINGER_1` | `0` | First tracked finger | | `SENSE_CAP_FINGER_2` | `1` | Second tracked finger | #### `sense_cap_phase_t` Phase of a touch event, iOS/Android style. | Member | Value | Description | | --- | --- | --- | | `SENSE_CAP_TOUCH_DOWN` | `0` | Finger arrived | | `SENSE_CAP_TOUCH_MOVED` | `1` | Position changed while down | | `SENSE_CAP_TOUCH_UP` | `2` | Finger left | ### Structs #### `sense_cap_touch_t` | Type | Field | Description | | --- | --- | --- | | `uint8_t` | `phase` | sense_cap_phase_t value. | | `uint8_t` | `finger` | Finger slot, 0 or 1. | | `uint16_t` | `x` | Position in the configured X resolution. | | `uint16_t` | `y` | Position in the configured Y resolution. | | `int16_t` | `dx` | X movement since the previous event. | | `int16_t` | `dy` | Y movement since the previous event. | | `int16_t` | `vx` | X velocity, pixels/second. | | `int16_t` | `vy` | Y velocity, pixels/second. | | `uint16_t` | `strength` | Touch strength at this event. | | `uint32_t` | `t_ms` | Timestamp, driver clock. | #### `sense_cap_geometry_t` | Type | Field | Description | | --- | --- | --- | | `uint8_t` | `n_rx` | Rx electrodes, 1-2. | | `uint8_t` | `n_tx` | Tx electrodes, 1-4. | | `uint8_t` | `disabled_channels` | Bit n = channel n is not sensed (§7.1.4). | | `uint8_t` | `switch_xy` | 1 = X along the Txs, Y along the Rxs (§7.7). | | `uint8_t` | `flip_x` | 1 = invert X (§7.7). | | `uint8_t` | `flip_y` | 1 = invert Y (§7.7). | | `uint16_t` | `x_res` | X output range; 0 = 256 per electrode pitch (§7.4). | | `uint16_t` | `y_res` | Y output range; 0 = 256 per electrode pitch. | | `uint16_t` | `x_trim` | X edge trim (§7.9); 0 = keep the chip's. | | `uint16_t` | `y_trim` | Y edge trim; 0 = keep the chip's. | #### `sense_cap_tuning_t` | Type | Field | Description | | --- | --- | --- | | `uint16_t` | `ati_target` | Trackpad ATI target, counts (0x32, §5.6.3). | | `uint8_t` | `ati_fine_div` | ATI fine divider 1-31 (0x30 [13:9]); fine/mult/div all 0 = keep. | | `uint8_t` | `ati_coarse_mult` | ATI coarse multiplier 1-15 (0x30 [8:5]). | | `uint8_t` | `ati_coarse_div` | ATI coarse divider 1-31 (0x30 [4:0]). | | `uint16_t` | `ati_comp_div` | ATI compensation divider (0x31, §5.6.4). | | `uint8_t` | `touch_set_mult` | Touch-set multiplier 1-255: threshold = ref x (1 + m/128) (§5.5.1). | | `uint8_t` | `touch_clear_mult` | Touch-clear multiplier, <= set (hysteresis). | | `uint8_t` | `filters` | SENSE_CAP_FILTER_* bits (0x60 [5:3]); MAV + IIR recommended (§7.8). | | `uint8_t` | `max_touches` | Fingers tracked, 1-2 (0x61 high byte, §7.3). | | `uint16_t` | `dyn_bottom_speed` | Dynamic IIR bottom speed, px/cycle (0x64). | | `uint16_t` | `dyn_top_speed` | Dynamic IIR top speed, px/cycle (0x65). | | `uint8_t` | `dyn_bottom_beta` | Dynamic IIR bottom beta /256 (0x66 low). | | `uint8_t` | `static_beta` | Static IIR beta /256 (0x66 high). | | `uint16_t` | `ref_drift_limit` | Re-ATI reference drift limit (0x33, §5.7.2). | | `uint16_t` | `min_count_reati` | Minimum-count re-ATI value (0x34, §5.7.2). | | `uint16_t` | `reati_retry_s` | Re-ATI retry time after an ATI error, 1-60 s (0x35). | | `uint16_t` | `ref_update_s` | Reference update time, 1-60 s (0x49, §5.4.1). | | `uint8_t` | `finger_split` | Finger split factor (0x67 high, §7.6). | | `uint8_t` | `stationary_thr` | Stationary-touch movement threshold, px (0x67 low, §7.5). | #### `sense_cap_alp_t` | Type | Field | Description | | --- | --- | --- | | `uint8_t` | `enable` | 1 = tune the ALP and allow ALP wake. | | `uint8_t` | `mutual` | 1 = mutual (Rx + Tx), 0 = self-capacitive (Rx only). | | `uint8_t` | `rx_mask` | Bit n = RXn in the ALP (RX3/RX6 only); 0 = the surface's Rxs. | | `uint16_t` | `tx_mask` | Bit n = TXn in the ALP; 0 = the surface's Txs (mutual only). | | `uint16_t` | `ati_target` | ALP ATI target per prox block, counts (0x38). | | `uint8_t` | `ati_fine_div` | ALP ATI fine divider (0x36); all three 0 = the trackpad's. | | `uint8_t` | `ati_coarse_mult` | ALP ATI coarse multiplier (0x36). | | `uint8_t` | `ati_coarse_div` | ALP ATI coarse divider (0x36). | | `uint16_t` | `ati_comp_div` | ALP ATI compensation divider (0x37). | | `uint16_t` | `lta_drift_limit` | ALP LTA drift limit for re-ATI (0x39). | | `uint16_t` | `threshold` | ALP output threshold, delta counts (0x54, §5.5.2). | | `uint8_t` | `set_debounce` | ALP set debounce (0x56 low). | | `uint8_t` | `clear_debounce` | ALP clear debounce (0x56 high). | | `uint16_t` | `count_beta` | ALP count filter beta /256 (0x70). | | `uint8_t` | `lp1_lta_beta` | LTA beta in LP1, 1/2^x (0x71 low). | | `uint8_t` | `lp2_lta_beta` | LTA beta in LP2, 1/2^x (0x71 high). | | `uint8_t` | `lp1_auto_prox` | sense_cap_auto_prox_t for LP1 (0x5B). | | `uint8_t` | `lp2_auto_prox` | sense_cap_auto_prox_t for LP2 (0x5B). | #### `sense_cap_cfg_t` | Type | Field | Description | | --- | --- | --- | | `sense_cap_event_cb_t` | `on_event` | Event callback (SENSE_CAP_EV_* low byte). NULL = none. | | `time * timeouts and honest` | `velocities` | | | `drain` | `pin` | | | `uint8_t` | `skip_baseline` | 1 = leave a GUI-programmed part's settings alone (§10.1.1). | | `sense_cap_layout_t` | `layout` | Layout to begin after init (process() finishes it). | | `uint8_t` | `reset_on_init` | | #### `sense_cap_surface_t` A complete electrode surface: geometry, trackpad tuning and ALP. | Type | Field | Description | | --- | --- | --- | | `sense_cap_geometry_t` | `geometry` | Which electrodes, XY orientation and range. | | `sense_cap_tuning_t` | `tuning` | ATI, thresholds, filters. | | `sense_cap_alp_t` | `alp` | ALP wake channel. | ### Defines | Name | Value | Description | | --- | --- | --- | | `TILE_SENSE_CAP_VERSION_MAJOR` | `1` | | | `TILE_SENSE_CAP_VERSION_MINOR` | `0` | | | `TILE_SENSE_CAP_VERSION_PATCH` | `0` | | | `IQS7211A_I2C_ADDR` | `0x56` | | | `IQS7211A_PRODUCT_NUMBER` | `763` | | | `IQS7211A_INVALID_RESPONSE` | `0xEEEE` | | | `IQS7211A_CYCLE_PROX_BYTE` | `0x05` | Fixed first byte of every cycle record | | `IQS7211A_CHANNEL_NONE` | `0xFF` | "No channel allocated" (§7.1.2) | | `IQS7211A_INFO_MODE_SHIFT` | `0` | | | `IQS7211A_INFO_NUM_FINGERS_SHIFT` | `8` | | | `SENSE_CAP_GESTURE_ALL` | `0x3F` | | | `IQS7211A_CTRL_MODE_SHIFT` | `0` | | | `IQS7211A_HW_LP2_AUTOPROX_SHIFT` | `5` | 0x5B bits 7-5 | | `IQS7211A_HW_LP1_AUTOPROX_SHIFT` | `2` | 0x5B bits 4-2 | | `SENSE_CAP_PIN_RX3` | `3` | Tile pad 2, prox block A; RX3/TX3 pin, usable as Tx | | `SENSE_CAP_PIN_RX6` | `6` | Tile pad 9, prox block B; RX6/TX6 pin, usable as Tx | | `SENSE_CAP_PIN_TX8` | `8` | Tile pad 8 | | `SENSE_CAP_PIN_TX9` | `9` | Tile pad 7 (ball A1, bonded to TX10: only TX9 is used) | | `SENSE_CAP_PIN_TX11` | `11` | Tile pad 6 | | `SENSE_CAP_MAX_RX` | `2` | Routed Rx pins | | `SENSE_CAP_MAX_TX` | `4` | Routed Tx pins when one Rx pin acts as a Tx | | `SENSE_CAP_MAX_CHANNELS` | `6` | Largest surface: 2 Rx x 3 Tx | | `SENSE_CAP_NUM_FINGERS` | `2` | Finger slots (§7.3) | | `SENSE_CAP_DIR_LEFT` | `0` | Toward -X | | `SENSE_CAP_DIR_RIGHT` | `1` | Toward +X | | `SENSE_CAP_DIR_UP` | `2` | Toward -Y | | `SENSE_CAP_DIR_DOWN` | `3` | Toward +Y | --- Generated 2026-10-07 from the driver header at tiles@58298c3. Canonical HTML: https://bergsonne.io/docs/tiles/sense/cap ============================================================================== ## Sense.HR driver > MAX86174A optical heart-rate and SpO₂ front end (PPG). | | | | --- | --- | | Tile | `Sense.HR` | | Driver version | `1.0.0` | | Header | `tile_sense_hr.h` | | Generated from | `tiles@0adc73d` | | Canonical page | https://bergsonne.io/docs/tiles/sense/hr | ### Overview Platform-agnostic driver for the Analog Devices MAX86174A dual-channel photoplethysmography (PPG) analog front end, paired on Sense.HR with two Vishay VEMD1060X01 silicon PIN photodiodes and four emitters (green ×2, red 645 nm, infrared 940 nm) for reflective optical sensing. Key specifications: - 20-bit PPG ADC, 4 / 8 / 16 / 32 µA full-scale photocurrent ranges - Two independent readout channels (PPG1, PPG2), two photodiode inputs - Six measurement slots per frame, each driving one LED at 0–100 mA (the chip goes to 127.5 mA; the driver caps at the emitters' 100 mA pulse rating) - Frame rates 1 to 2048 fps from an internal 32.768 kHz oscillator - 256-sample FIFO, tagged per measurement slot and channel - Analog ambient-light cancellation plus a 0–28 µA offset DAC - Automatic proximity mode drops to 8 fps until skin is detected - Configurable INTB output (frame ready, FIFO almost full, thresholds) Electrical notes (from Sense-HR-a bring-up, August 2026): - V+ (pad 10) is a 1.8 V rail with a 2.2 V absolute maximum. VLED (pad 9, 2.7–5.5 V) feeds the emitter anodes and must not come up before V+ (datasheet: "Power up VDD before VLED"). Prefer 5 V for VLED: green needs ~3.3 V forward plus driver headroom. - Pads 2 (address) and 3 (I²C/SPI select) are held high by pull-ups (R1, R2) to 1.8 V on the tile. Strap them at power-up; never drive pad 2 above 2.1 V. - INTB (pad 8) is left open-drain, active-low, so the host's own pull-up sets its level (the pin tolerates 6 V). Its push-pull modes drive only to 1.8 V, below many 3.3 V MCUs' input-high threshold. - Emitter-to-detector crosstalk across the tile puts a large DC pedestal on every channel. tile_sense_hr_auto_gain() cancels it with the offset DAC; without that step most settings overflow the ADC. Datasheet: MAX86174A/B, Analog Devices, Rev 2 (7/23) ### Examples #### Quick start (single-wavelength heart rate, polled) ```c #include "core.h" #include "core_tiles.h" #include "tile_sense_hr.h" tile_t hr; tile_sense_hr_init(core_tiles_pal(&core_i2c1), 0, &hr, NULL); tile_sense_hr_auto_gain(&hr, 0, 0); // find LED current + offset for slot 0 (0 = 10 mA cap) tile_sense_hr_start(&hr); while (1) { if (tile_sense_hr_frame_ready(&hr)) { int32_t ppg = tile_sense_hr_read_ppg(&hr, 0); // green, PPG1 // pulsatile AC rides on a large DC — band-pass on the host } } ``` #### Multi-wavelength (green + red + IR interleaved in one frame) ```c sense_hr_cfg_t cfg = { .fps = 128 }; tile_sense_hr_init(core_tiles_pal(&core_i2c1), 0, &hr, &cfg); tile_sense_hr_set_slot(&hr, 0, SENSE_HR_LED_GREEN, 5000); tile_sense_hr_set_slot(&hr, 1, SENSE_HR_LED_RED, 5000); tile_sense_hr_set_slot(&hr, 2, SENSE_HR_LED_IR, 5000); for (uint8_t s = 0; s < 3; s++) tile_sense_hr_auto_gain(&hr, s, 0); tile_sense_hr_start(&hr); int32_t frame[SENSE_HR_FRAME_LEN]; if (tile_sense_hr_read_frame(&hr, frame)) { int32_t green = frame[0], red = frame[2], ir = frame[4]; // PPG1 } ``` ### API 41 public functions. Functions marked **Studio** are callable from the Studio behaviour DSL. #### Lifecycle ##### `tile_sense_hr_sleep` — Studio ```c void tile_sense_hr_sleep(tile_t *tile) ``` Enter shutdown (SHDN = 1): oscillator off, LEDs off, ~1 µA (3 µA max on VDD, 0.5 µA max on VLED). Registers are retained; tile_sense_hr_wake() resumes without reconfiguration. ##### `tile_sense_hr_wake` — Studio ```c void tile_sense_hr_wake(tile_t *tile) ``` Leave shutdown and resume the configured measurements. ##### `tile_sense_hr_reset` — Studio ```c void tile_sense_hr_reset(tile_t *tile) ``` Software-reset the device to power-on defaults. All registers return to defaults and the tile state drops to NONE. Call tile_sense_hr_init() again afterwards. ##### `tile_sense_hr_start` — Studio ```c void tile_sense_hr_start(tile_t *tile) ``` Start the frame engine on every enabled slot. Flushes the FIFO first so the first frame read is fresh. ##### `tile_sense_hr_stop` — Studio ```c void tile_sense_hr_stop(tile_t *tile) ``` Stop all measurements (LEDs off, FIFO stops filling). #### Runtime ##### `tile_sense_hr_auto_gain` — Studio ```c uint8_t tile_sense_hr_auto_gain(tile_t *tile, uint8_t slot, uint32_t max_ua) ``` Find LED current, offset and ADC range for a slot automatically. Blocking, roughly two to three seconds. Runs the slot alone, then: 1. raises LED current toward `max_ua` while the offset DAC can still keep the DC pedestal (crosstalk + tissue return) inside the ADC, backing off current when it cannot; 2. measures the total photocurrent and picks the largest offset that leaves ~0.5 µA of headroom, then the tightest ADC range holding the residual under ~70 % of full scale; 3. verifies, and unwinds one step at a time if that overflowed. Other slots' enables are restored on return; measurements are left stopped. Call tile_sense_hr_start() afterwards. Re-run whenever the optical situation changes (finger placed or removed). - `slot` — Measurement slot 0–5 (must already have an emitter). - `max_ua` — Upper bound for LED current in µA (0 = 10000, a safe default for the 0402 emitters). The search runs in the 32 mA range, so anything above 31875 is capped there. **Returns:** 1 if a valid operating point was found, 0 if the slot never produced samples. ##### `tile_sense_hr_get_status` — Studio ```c uint8_t tile_sense_hr_get_status(tile_t *tile) ``` Read STATUS1 (clears its latched bits and releases INTB). **Returns:** Raw STATUS1 byte (use MAX86174A_STATUS1_* masks). ##### `tile_sense_hr_get_status2` — Studio ```c uint8_t tile_sense_hr_get_status2(tile_t *tile) ``` Read STATUS2: invalid-configuration and LED compliance flags. LEDn_COMPB set means VLED is too low for the programmed current on that pin — raise VLED or lower the current. **Returns:** Raw STATUS2 byte (use MAX86174A_STATUS2_* masks). ##### `tile_sense_hr_frame_ready` — Studio ```c uint8_t tile_sense_hr_frame_ready(tile_t *tile) ``` Check whether at least one full frame is waiting in the FIFO. Non-clearing: compares the FIFO count against the frame length, so it is safe to poll without disturbing STATUS1 or INTB. **Returns:** 1 if a frame can be read, 0 otherwise. ##### `tile_sense_hr_read_frame` — Studio ```c uint8_t tile_sense_hr_read_frame(tile_t *tile, int32_t *frame) ``` Drain the FIFO and decode the most recent complete frame. Reads every waiting sample, sign-extends the 20-bit two's-complement data, and places it by tag: `frame[2·slot + channel]` (channel 0 = PPG1, 1 = PPG2). Entries for disabled slots, powered-down channels, and samples replaced by an overflow tag read SENSE_HR_NO_SAMPLE. Overflow-tagged samples are counted (see get_overflow_count()). Each entry holds the newest sample for its tag, so if the FIFO ends part-way through a frame the result can mix two adjacent frames. The decoded frame is also cached for tile_sense_hr_read_ppg(). - `frame` — Caller buffer of SENSE_HR_FRAME_LEN (12) entries. **Returns:** 1 if a complete frame was decoded, 0 if the FIFO held less than one frame (buffer then holds the previous frame). ##### `tile_sense_hr_read_ppg` — Studio ```c int32_t tile_sense_hr_read_ppg(tile_t *tile, uint8_t slot) ``` Latest PPG1 sample for a slot, in ADC counts. Drains the FIFO if a full frame is waiting (same as read_frame()), then returns the cached channel-1 value for the slot. Negative values are real: the offset DAC can pull the residual below zero. - `slot` — Measurement slot 0–5. **Returns:** Signed 20-bit sample, or SENSE_HR_NO_SAMPLE if none yet. ##### `tile_sense_hr_read_ppg2` — Studio ```c int32_t tile_sense_hr_read_ppg2(tile_t *tile, uint8_t slot) ``` Latest PPG2 sample for a slot, in ADC counts. Only meaningful when channel 2 is powered and a photodiode is routed to it (SENSE_HR_PD_1_2 or SENSE_HR_PD_2_1). - `slot` — Measurement slot 0–5. **Returns:** Signed 20-bit sample, or SENSE_HR_NO_SAMPLE if none yet. ##### `tile_sense_hr_read_photocurrent_na` — Studio ```c int32_t tile_sense_hr_read_photocurrent_na(tile_t *tile, uint8_t slot) ``` Latest PPG1 photocurrent for a slot, in nanoamps. Converts the cached sample with the slot's ADC range (full scale / 524287 counts) and adds the offset-DAC current back, so the result is the photodiode current itself and is comparable across slots that ended up with different gain settings. Integer maths, no float. - `slot` — Measurement slot 0–5. **Returns:** Photocurrent in nA, or SENSE_HR_NO_SAMPLE if no sample yet. ##### `tile_sense_hr_get_overflow_count` — Studio ```c uint16_t tile_sense_hr_get_overflow_count(tile_t *tile) ``` Overflow-tagged samples seen in the last FIFO drain. An overflowing channel produces no data — the chip replaces the sample with an ALC- or exposure-overflow tag. A non-zero count means lower the LED current, raise the offset DAC, or widen the ADC range. **Returns:** Number of overflow-tagged samples in the last read. #### Events ##### `tile_sense_hr_process` — Studio ```c void tile_sense_hr_process(tile_t *tile) ``` Process pending interrupt events. Call from your main loop. In interrupt mode (int_pin set): returns immediately unless INTB has fired since the last call, then reads STATUS1 and fires the callback. In polled mode: reads STATUS1 every call. Reading STATUS1 clears its latched bits and releases INTB. ##### `tile_sense_hr_on_event` — Studio ```c void tile_sense_hr_on_event(tile_t *tile, sense_hr_event_cb_t cb, void *ctx) ``` Register or change the event callback. - `cb` — Callback to fire on STATUS1 activity. NULL to clear. - `ctx` — Opaque user pointer passed to the callback. ##### `tile_sense_hr_set_interrupts` — Studio ```c void tile_sense_hr_set_interrupts(tile_t *tile, uint8_t mask) ``` Choose which STATUS1 sources drive INTB (Interrupt Enable 1). Pass a mask of MAX86174A_STATUS1_* bits, e.g. FRAME_RDY | A_FULL. PWR_RDY is non-maskable. INTB stays open-drain, active-low. - `mask` — STATUS1 bits that should assert INTB. #### Configuration ##### `tile_sense_hr_set_frame_rate` — Studio ```c uint16_t tile_sense_hr_set_frame_rate(tile_t *tile, uint16_t fps) ``` Set the frame rate. Programs FR_CLK_DIV = 32768 / fps, clamped to the chip's 16–32766 divider range (1–2048 fps). If the enabled slots' exposures do not fit in one frame period the chip flags INVALID_CFG in STATUS2. - `fps` — Requested frames per second. **Returns:** Actual frame rate after divider rounding. ##### `tile_sense_hr_get_frame_rate` — Studio ```c uint16_t tile_sense_hr_get_frame_rate(tile_t *tile) ``` Read the configured frame rate. **Returns:** Frames per second after divider rounding. ##### `tile_sense_hr_set_slot` — Studio ```c void tile_sense_hr_set_slot(tile_t *tile, uint8_t slot, sense_hr_led_t led, uint32_t led_ua) ``` Configure and enable a measurement slot with one emitter. Writes the slot's emitter select and LED current, inherits the tile-wide integration / averaging / ADC range / photodiode routing, and enables the slot in SYS_CFG2. Takes effect on the next start() (or immediately if already running). Slot 0 is the one init() set up. - `slot` — Measurement slot 0–5 (MEAS1..MEAS6). - `led` — Emitter to pulse (sense_hr_led_t). - `led_ua` — LED current in µA, 0–100000 (0 disables the LED; above 100 mA is capped). ##### `tile_sense_hr_set_ambient_slot` — Studio ```c void tile_sense_hr_set_ambient_slot(tile_t *tile, uint8_t slot) ``` Configure a slot as a direct ambient-light measurement (no LED). The chip requires an ambient slot to be the LAST enabled slot in the frame. Its sample carries the same tag position as an LED slot. - `slot` — Measurement slot 0–5. ##### `tile_sense_hr_enable_slot` — Studio ```c void tile_sense_hr_enable_slot(tile_t *tile, uint8_t slot, uint8_t enable) ``` Enable or disable a slot without changing its settings. - `slot` — Measurement slot 0–5. - `enable` — 1 = included in each frame, 0 = skipped. ##### `tile_sense_hr_set_led_current` — Studio ```c void tile_sense_hr_set_led_current(tile_t *tile, uint8_t slot, uint32_t led_ua) ``` Set a slot's LED drive current. Picks the smallest LED range (32 / 64 / 96 / 128 mA full scale) that covers the request, then the nearest 8-bit code: 125 / 250 / 375 / 500 µA per step. 0 disables the LED for that slot. - `slot` — Measurement slot 0–5. - `led_ua` — LED current in µA, 0–100000 (above 100 mA is capped). ##### `tile_sense_hr_get_led_current` — Studio ```c uint32_t tile_sense_hr_get_led_current(tile_t *tile, uint8_t slot) ``` Read back a slot's LED drive current after quantisation. - `slot` — Measurement slot 0–5. **Returns:** LED current in µA as programmed (0 = LED off). ##### `tile_sense_hr_set_integration` — Studio ```c void tile_sense_hr_set_integration(tile_t *tile, uint8_t slot, sense_hr_tint_t tint) ``` Set a slot's ADC integration time. Longer integration collects more photocurrent per pulse (more signal, but easier to overflow on crosstalk or ambient light). - `slot` — Measurement slot 0–5. - `tint` — Integration time (sense_hr_tint_t). ##### `tile_sense_hr_set_averaging` — Studio ```c void tile_sense_hr_set_averaging(tile_t *tile, uint8_t slot, sense_hr_aver_t aver) ``` Set how many LED pulses a slot averages into one sample. Averaging improves SNR and ambient rejection at the cost of frame time: each pulse costs one integration period plus settling. - `slot` — Measurement slot 0–5. - `aver` — Pulses per sample (sense_hr_aver_t). ##### `tile_sense_hr_set_adc_range` — Studio ```c void tile_sense_hr_set_adc_range(tile_t *tile, uint8_t slot, sense_hr_adc_range_t range) ``` Set a slot's ADC full-scale photocurrent range (both channels). - `slot` — Measurement slot 0–5. - `range` — Full-scale range (sense_hr_adc_range_t). ##### `tile_sense_hr_set_dac_offset` — Studio ```c void tile_sense_hr_set_dac_offset(tile_t *tile, uint8_t slot, uint8_t offset_ua) ``` Set a slot's offset-DAC cancellation current (both channels). The DAC sources part of the photodiode DC away from the converter so the ADC digitises only what is left. The reported sample is (photocurrent − offset); tile_sense_hr_read_photocurrent_na() adds the offset back. Quantised to 0, 4, 8 … 28 µA. - `slot` — Measurement slot 0–5. - `offset_ua` — Cancellation current in µA, 0–28. ##### `tile_sense_hr_set_photodiodes` — Studio ```c void tile_sense_hr_set_photodiodes(tile_t *tile, uint8_t slot, sense_hr_pdsel_t pdsel) ``` Route the two photodiodes to the readout channels for a slot. Summing both onto PPG1 (default) gives the best single-wavelength SNR. Splitting them (SENSE_HR_PD_1_2) reads each diode on its own channel; PPG2 must then be powered (cfg.ppg2_enable) to see PD2. - `slot` — Measurement slot 0–5. - `pdsel` — Routing (sense_hr_pdsel_t). ##### `tile_sense_hr_set_ppg2_enabled` — Studio ```c void tile_sense_hr_set_ppg2_enabled(tile_t *tile, uint8_t enable) ``` Power readout channel 2 up or down. With both photodiodes summed onto PPG1, channel 2 still emits a near-zero sample per slot per frame; powering it down halves FIFO traffic. Enable it for per-diode readout. - `enable` — 1 = channel 2 powered, 0 = powered down. ##### `tile_sense_hr_set_proximity_mode` — Studio ```c void tile_sense_hr_set_proximity_mode(tile_t *tile, uint8_t enable, uint8_t lower) ``` Enable automatic proximity mode. Reserves slot 5 (MEAS6) as a low-power skin detector: while its PPG1 reading stays below `lower` × 2048 counts the chip runs slot 5 alone at 8 fps; once skin is detected it returns to the programmed frame rate with every enabled slot. Configure slot 5's emitter and a small LED current first (tile_sense_hr_set_slot()). THRESH1_HILO fires on entering proximity mode. - `enable` — 1 = automatic proximity mode, 0 = normal. - `lower` — Skin-detect threshold ÷ 2048 ADC counts. #### FIFO ##### `tile_sense_hr_get_fifo_count` — Studio ```c uint16_t tile_sense_hr_get_fifo_count(tile_t *tile) ``` Number of unread samples in the FIFO (0–256). **Returns:** Samples waiting. ##### `tile_sense_hr_get_fifo_overflow` — Studio ```c uint8_t tile_sense_hr_get_fifo_overflow(tile_t *tile) ``` Samples lost to FIFO overflow since the last FIFO read (0–127). **Returns:** OVF_COUNTER value; saturates at 127. ##### `tile_sense_hr_flush_fifo` — Studio ```c void tile_sense_hr_flush_fifo(tile_t *tile) ``` Discard everything in the FIFO and reset its pointers. ##### `tile_sense_hr_set_fifo_watermark` — Studio ```c void tile_sense_hr_set_fifo_watermark(tile_t *tile, uint16_t samples) ``` Set the almost-full watermark used by the A_FULL flag / event. - `samples` — Number of queued samples that asserts A_FULL, 1–256. ##### `tile_sense_hr_read_fifo_raw` — Studio ```c uint16_t tile_sense_hr_read_fifo_raw(tile_t *tile, uint32_t *buf, uint16_t count) ``` Read raw 24-bit FIFO words (tag in bits 23:20, data in 19:0). For hosts that want to decode the stream themselves. Data is NOT sign-extended here; bit 19 is the sign. - `buf` — Caller buffer for raw words. - `count` — Capacity of `buf`; at most this many samples are read. **Returns:** Number of words actually read. #### Advanced ##### `tile_sense_hr_set_threshold` — Studio ```c void tile_sense_hr_set_threshold(tile_t *tile, uint8_t slot, uint8_t lower, uint8_t upper) ``` Program threshold instance 1 on a slot's PPG1 data. THRESH1_HILO is set (and INTB asserted if enabled) when the sample exceeds `upper` or drops below `lower`. Both limits are in units of 2048 ADC counts (0–255). Upper = 255 disables the upper check; lower = 0 disables the lower check. Negative samples clip to 0 first. (The datasheet's register table says upper 0x00 disables it, its functional description 0xFF; the driver follows the description.) - `slot` — Measurement slot 0–5 to watch. - `lower` — Lower limit ÷ 2048. - `upper` — Upper limit ÷ 2048. ##### `tile_sense_hr_read_reg` — Studio ```c uint8_t tile_sense_hr_read_reg(tile_t *tile, uint8_t reg) ``` Read any register directly. - `reg` — Register address 0x00–0xFF. **Returns:** Register value. ##### `tile_sense_hr_write_reg` — Studio ```c void tile_sense_hr_write_reg(tile_t *tile, uint8_t reg, uint8_t val) ``` Write any register directly. Bypasses the driver's cached configuration; prefer the typed setters. - `reg` — Register address 0x00–0xFF. - `val` — Value to write. #### Other ##### `tile_sense_hr_find` ```c uint8_t tile_sense_hr_find(tiles_pal_t *hal, uint8_t instance) ``` Check if a Sense.HR is present on the bus. - `hal` — Platform HAL handle. - `instance` — 0 = default address (0x6B), 1 = alternate (0x6A). **Returns:** 1 if the device ACKs and PART_ID reads 0x40, 0 otherwise. ##### `tile_sense_hr_init` ```c void tile_sense_hr_init(tiles_pal_t *hal, uint8_t instance, tile_t *tile, const sense_hr_cfg_t *cfg) ``` Initialise a Sense.HR tile. Probes the device, verifies PART_ID, soft-resets, pins the LED4/TRIG pin to LED duty, configures the frame rate and slot 0, leaves INTB open-drain, and sets the tile state to READY. Measurements are NOT running until tile_sense_hr_start(). - `hal` — Platform HAL handle. - `instance` — 0 = default (0x6B), 1 = alternate (0x6A). - `tile` — Tile handle to initialise. - `cfg` — Configuration (NULL for defaults). ### Driver gaps Chip capabilities this driver does **not** expose. Each is tagged hardware-gated (the tile does not route the pin) or driver-deferred (possible, not yet implemented). Do not assume an API exists for these. #### SPI bus mode (advanced) Hardware-gated on Sense-HR-a. The MAX86174A supports 4-wire SPI to 24 MHz (pad 3 to GND selects it), but its SDO/MISO output (pad 2) is push-pull to the 1.8 V VDD (V_OH >= VDD - 0.4 V at 4 mA), below a 3.3 V Core's input-high threshold, and the pad's 2.1 V absolute maximum (VDD + 0.3 V) forbids a pull-up to 3.3 V. SPI needs a level translator on MISO that the tile does not carry. I²C is open-drain and unaffected. #### External frame sync (TRIG input) (niche) Hardware-gated. SYNC_MODE 1/2 take an external frame trigger or frame clock on the LED4_DRV/TRIG pin. On Sense-HR-a that pin drives the second green LED, so the driver pins TRIGLED4_SEL = 1 and runs the internal 32.768 kHz frame oscillator only. #### Dual-LED exposures (driver B) (advanced) Driver-deferred. Each measurement slot can pulse two LEDs at once (MEASx_DRVA + MEASx_DRVB) to sum red and IR into one sample. This driver uses driver A only — one LED per slot — because the SpO₂ ratio needs the wavelengths separated, not summed. Driver B is reachable through tile_sense_hr_write_reg() on MEASx_DRVB_PA. #### Raw exposure/ambient capture (COLLECT_RAW_DATA) (advanced) Driver-deferred. Setting COLLECT_RAW_DATA pushes the LED-on and dark samples to the FIFO separately (DARK tag 0xC) so the host can run its own ambient rejection. The driver's FIFO decoder assumes the chip's computed samples; raw mode changes the frame layout and would need a second decoder. #### Digital low-pass / IIR filter (DLPF_EN) (advanced) Driver-deferred. The chip can average 2–32 adjacent samples (DEC_AVE) or run an IIR low-pass (IIR_CFG selects the cut-off; the IIR supports 100 and 200 fps) before the FIFO. Left disabled: the host-side band-pass a heart-rate algorithm already needs does the same job with fewer constraints. #### Threshold instance 2 and hysteresis (niche) Driver-deferred. tile_sense_hr_set_threshold() programs THRESHOLD1 only, with no time or level hysteresis. THRESHOLD2 (used for a "left proximity mode" interrupt) and TIME_HYST / LEVEL_HYST are reachable through tile_sense_hr_write_reg() on 0x50–0x55. #### FIFO marker, I²C broadcast address, SINC3 / 2nd-order decimation (niche) Driver-deferred. FIFO_MARK pushes a 0xFFFFFE tag to separate data across a configuration change; I2C_BCAST_ADDR lets several parts share one write address; PPG_FILT2_SEL / MEASx_SINC3_SEL swap the ADC decimation filter. None affect a normal measurement; all are reachable through tile_sense_hr_write_reg(). ### Events - `frame_ready` - `fifo_almost_full` - `exposure_overflow` - `proximity` ### Enums #### `sense_hr_led_t` | Member | Value | Description | | --- | --- | --- | | `SENSE_HR_LED_IR` | `0x0` | LED1_DRV — 940 nm infrared | | `SENSE_HR_LED_GREEN` | `0x1` | LED2_DRV — 525 nm green (primary) | | `SENSE_HR_LED_RED` | `0x2` | LED3_DRV — 645 nm red (IN-S42ATR, λP) | | `SENSE_HR_LED_GREEN2` | `0x3` | LED4_DRV — 525 nm green (second) | #### `sense_hr_tint_t` ADC integration time per pulse (MEASx_TINT[1:0], 3rd-order decimation). | Member | Value | Description | | --- | --- | --- | | `SENSE_HR_TINT_14US` | `0x0` | 14.6 µs | | `SENSE_HR_TINT_29US` | `0x1` | 29.2 µs | | `SENSE_HR_TINT_58US` | `0x2` | 58.6 µs | | `SENSE_HR_TINT_117US` | `0x3` | 117.1 µs (default, most signal per exposure) | #### `sense_hr_aver_t` LED pulses averaged per sample (MEASx_AVER[2:0]). | Member | Value | Description | | --- | --- | --- | | `SENSE_HR_AVER_1` | `0x0` | 1 pulse | | `SENSE_HR_AVER_2` | `0x1` | 2 pulses | | `SENSE_HR_AVER_4` | `0x2` | 4 pulses (default) | | `SENSE_HR_AVER_8` | `0x3` | 8 pulses | | `SENSE_HR_AVER_16` | `0x4` | 16 pulses | | `SENSE_HR_AVER_32` | `0x5` | 32 pulses | | `SENSE_HR_AVER_64` | `0x6` | 64 pulses | | `SENSE_HR_AVER_128` | `0x7` | 128 pulses | #### `sense_hr_adc_range_t` PPG ADC full-scale photocurrent (MEASx_PPGy_ADC_RGE[1:0]). | Member | Value | Description | | --- | --- | --- | | `SENSE_HR_ADC_4UA` | `0x0` | 4 µA full scale, 7.6 pA/LSB | | `SENSE_HR_ADC_8UA` | `0x1` | 8 µA full scale, 15.3 pA/LSB | | `SENSE_HR_ADC_16UA` | `0x2` | 16 µA full scale, 30.5 pA/LSB | | `SENSE_HR_ADC_32UA` | `0x3` | 32 µA full scale, 61 pA/LSB (default) | #### `sense_hr_pdsel_t` Photodiode routing to the two readout channels (MEASx_PDSEL[1:0]). | Member | Value | Description | | --- | --- | --- | | `SENSE_HR_PD_BOTH_PPG1` | `0x0` | PD1 + PD2 summed onto PPG1 (default, best SNR) | | `SENSE_HR_PD_2_1` | `0x1` | PD2 → PPG1, PD1 → PPG2 | | `SENSE_HR_PD_1_2` | `0x2` | PD1 → PPG1, PD2 → PPG2 (per-diode readout) | | `SENSE_HR_PD_BOTH_PPG2` | `0x3` | PD1 + PD2 summed onto PPG2 | ### Structs #### `sense_hr_cfg_t` Optional init-time configuration for Sense.HR. | Type | Field | Description | | --- | --- | --- | | `uint16_t` | `fps` | Frame rate, 1–2048 fps. Default 128. | | `uint8_t` | `led` | Slot-0 emitter (sense_hr_led_t). Default green. | | `uint32_t` | `led_ua` | Slot-0 LED current in µA. Default 1000. | | `uint8_t` | `tint` | Integration time (sense_hr_tint_t). 0 = default 117 µs. | | `uint8_t` | `aver` | Pulses averaged (sense_hr_aver_t). 0 = default 4. | | `uint8_t` | `adc_range` | ADC range (sense_hr_adc_range_t). 0 = default 32 µA. | | `uint8_t` | `pdsel` | Photodiode routing (sense_hr_pdsel_t). Default both → PPG1. | | `uint8_t` | `ppg2_enable` | 1 = keep readout channel 2 powered. Default 0 (off). | | `uint8_t` | `int_pin` | Core pad wired to INTB (pad 8). 0 = polled. | | `sense_hr_event_cb_t` | `on_event` | Callback fired by process(). NULL = none. | ### Defines | Name | Value | Description | | --- | --- | --- | | `TILE_SENSE_HR_VERSION_MAJOR` | `1` | | | `TILE_SENSE_HR_VERSION_MINOR` | `0` | | | `TILE_SENSE_HR_VERSION_PATCH` | `0` | | | `MAX86174A_I2C_ADDR_DEFAULT` | `0x6B` | ADDR high (pad 2 open, default) | | `MAX86174A_I2C_ADDR_ALT` | `0x6A` | ADDR low (pad 2 to GND) | | `MAX86174A_MEAS_SEL` | `0` | [6] AMB, [3:2] DRVB, [1:0] DRVA | | `MAX86174A_MEAS_CFG1` | `1` | [7:6] PDSEL, [4:3] TINT, [2:0] AVER | | `MAX86174A_MEAS_CFG2` | `2` | [5:4] LED_RGE, [3:2] PPG2_ADC_RGE, [1:0] PPG1_ADC_RGE | | `MAX86174A_MEAS_CFG3` | `3` | [7:5] PD_SETLNG, [4:3] LED_SETLNG | | `MAX86174A_MEAS_CFG4` | `4` | [6:4] PPG2_DACOFF, [2:0] PPG1_DACOFF | | `MAX86174A_MEAS_DRVA_PA` | `5` | LED driver A current code | | `MAX86174A_MEAS_DRVB_PA` | `6` | LED driver B current code | | `MAX86174A_PART_ID_VALUE` | `0x40` | MAX86174A (dual channel); 0x41 = MAX86174B | | `MAX86174A_FIFO_TAG_SHIFT` | `20` | | | `MAX86174A_FIFO_DATA_MASK` | `0x000FFFFF` | | | `MAX86174A_FIFO_TAG_DARK` | `0xC` | Dark sample (COLLECT_RAW_DATA only) | | `MAX86174A_FIFO_TAG_ALC_OVF` | `0xD` | ALC overflow replaced this sample | | `MAX86174A_FIFO_TAG_EXP_OVF` | `0xE` | Exposure overflow replaced this sample | | `MAX86174A_FIFO_MARKER` | `0xFFFFE` | | | `MAX86174A_FIFO_INVALID` | `0xFFFFF` | | | `SENSE_HR_SLOTS` | `6` | Measurement slots per frame (MEAS1..6) | | `SENSE_HR_CHANNELS` | `2` | Readout channels (PPG1, PPG2) | | `SENSE_HR_FIFO_DEPTH` | `256` | | | `SENSE_HR_ADC_FULL_SCALE` | `524287` | | | `SENSE_HR_FR_CLK_HZ` | `32768` | | | `SENSE_HR_FR_DIV_MIN` | `16` | | | `SENSE_HR_FR_DIV_MAX` | `32766` | | | `SENSE_HR_LED_CHIP_MAX_UA` | `127500` | | | `SENSE_HR_LED_MAX_UA` | `100000` | | | `SENSE_HR_DACOFF_MAX_UA` | `28` | | --- Generated 2026-10-07 from the driver header at tiles@0adc73d. Canonical HTML: https://bergsonne.io/docs/tiles/sense/hr ============================================================================== ## Sense.I.6P6 driver > Complete driver for the Sense.I.6P6 tile (ICM-42686-P). Supports I2C, I3C (Core.ST.H5) and SPI bus access via tiles_pal_t. | | | | --- | --- | | Tile | `Sense.I.6P6` | | Driver version | `1.4.0` | | Header | `tile_sense_i_6p6.h` | | Generated from | `tiles@e538976` | | Canonical page | https://bergsonne.io/docs/tiles/sense/i6p6 | ### Overview Supports I2C, I3C (Core.ST.H5) and SPI bus access via tiles_pal_t. @version 1.4.0 1.4.0: I3C (Core.ST.H5) through the core_tiles_pal I3C bridge; on I2C the datasheet's I2C driver setting (I3C slave off, so the chip's I2C glitch filter is on), which makes it reliable on weak pull-ups. 6-axis IMU with extended measurement range: - Accelerometer: 16-bit, ±2/4/8/16/32 G, up to 32 kHz ODR - Gyroscope: 16-bit, ±31.25 to ±4000 DPS, up to 32 kHz ODR - Temperature: on-chip sensor - FIFO: 2 KB with multiple packet formats - APEX: Pedometer, tilt, wake-on-motion, tap detection Platform-agnostic: uses tiles_pal_t for all bus access. On Core.ST.H5 the tile also runs over I3C: coregen emits core_i3c1 when config.json asks for it (config-json.md §7), and the bridge maps the driver's static address (0x69) to the tile's dynamic address. Datasheet: TDK InvenSense DS-000639, Rev 1.0 ### Examples #### Quick start — I2C (polling) ```c tile_t imu; tile_sense_i_6p6_init(core_tiles_pal(&core_i2c3), 0, &imu, NULL); int16_t accel[3], gyro[3]; tile_sense_i_6p6_get_raw_accels(&imu, accel); tile_sense_i_6p6_get_raw_gyros(&imu, gyro); ``` #### Quick start — I3C (Core.ST.H5) ```c tile_t imu; tile_sense_i_6p6_init(core_tiles_pal(&core_i3c1), 0, &imu, NULL); ``` #### Quick start — SPI ```c tile_t imu; tile_sense_i_6p6_init(core_tiles_pal(&core_spi1), 0, &imu, NULL); int16_t accel[3]; tile_sense_i_6p6_get_raw_accels(&imu, accel); ``` #### Quick start — interrupt-driven with callback ```c void on_data(tile_t *t, uint8_t events, void *ctx) { int16_t buf[7]; tile_sense_i_6p6_get_raw_all(t, buf); } sense_i_6p6_cfg_t cfg = { .on_event = on_data, .int1_pin = 9, // Core pad connected to INT1 }; tile_sense_i_6p6_init(core_tiles_pal(&core_i2c3), 0, &imu, &cfg); tile_sense_i_6p6_int1_data_ready(&imu, 1); while (1) { tile_sense_i_6p6_process(&imu); } ``` ### API 71 public functions. Functions marked **Studio** are callable from the Studio behaviour DSL. #### Lifecycle ##### `tile_sense_i_6p6_sleep` — Studio ```c void tile_sense_i_6p6_sleep(tile_t *tile) ``` Enter sleep mode (accel + gyro off). ~7.5 µA. ##### `tile_sense_i_6p6_wake` — Studio ```c void tile_sense_i_6p6_wake(tile_t *tile) ``` Wake from sleep, restore low-noise mode. Range/ODR preserved. ##### `tile_sense_i_6p6_reset` — Studio ```c void tile_sense_i_6p6_reset(tile_t *tile) ``` Software reset. Blocks ~2 ms. Must call init() again after. #### Runtime ##### `tile_sense_i_6p6_set_accel_range` — Studio ```c void tile_sense_i_6p6_set_accel_range(tile_t *tile, sense_i_6p6_accel_range_t range) ``` Set accelerometer full-scale range. ##### `tile_sense_i_6p6_set_gyro_range` — Studio ```c void tile_sense_i_6p6_set_gyro_range(tile_t *tile, sense_i_6p6_gyro_range_t range) ``` Set gyroscope full-scale range. ##### `tile_sense_i_6p6_set_accel_odr` — Studio ```c void tile_sense_i_6p6_set_accel_odr(tile_t *tile, sense_i_6p6_odr_t odr) ``` Set accelerometer output data rate. ##### `tile_sense_i_6p6_set_gyro_odr` — Studio ```c void tile_sense_i_6p6_set_gyro_odr(tile_t *tile, sense_i_6p6_odr_t odr) ``` Set gyroscope output data rate. ##### `tile_sense_i_6p6_set_power_mode` — Studio ```c void tile_sense_i_6p6_set_power_mode(tile_t *tile, sense_i_6p6_power_mode_t accel, sense_i_6p6_power_mode_t gyro) ``` Set power mode independently for accel and gyro. **Note:** Wait 200 µs after changing mode before accessing other registers. ##### `tile_sense_i_6p6_data_ready` — Studio ```c uint8_t tile_sense_i_6p6_data_ready(tile_t *tile) ``` Check if new sensor data is available. ##### `tile_sense_i_6p6_get_raw_accels` — Studio ```c void tile_sense_i_6p6_get_raw_accels(tile_t *tile, int16_t *buffer) ``` Read raw accelerometer [X, Y, Z]. Convert: g = raw / sensitivity. ##### `tile_sense_i_6p6_get_raw_gyros` — Studio ```c void tile_sense_i_6p6_get_raw_gyros(tile_t *tile, int16_t *buffer) ``` Read raw gyroscope [X, Y, Z]. Convert: dps = raw / sensitivity. ##### `tile_sense_i_6p6_get_raw_6dof` — Studio ```c void tile_sense_i_6p6_get_raw_6dof(tile_t *tile, int16_t *buffer) ``` Burst read accel + gyro [AX, AY, AZ, GX, GY, GZ]. One transaction. ##### `tile_sense_i_6p6_get_temperature` — Studio ```c int16_t tile_sense_i_6p6_get_temperature(tile_t *tile) ``` Read temperature. Convert: degC = raw / 132.48 + 25.0 ##### `tile_sense_i_6p6_get_raw_all` — Studio ```c void tile_sense_i_6p6_get_raw_all(tile_t *tile, int16_t *buffer) ``` Read all 7 channels [Temp, AX, AY, AZ, GX, GY, GZ] in one burst. ##### `tile_sense_i_6p6_is_face_up` — Studio ```c uint8_t tile_sense_i_6p6_is_face_up(tile_t *tile) ``` Detect whether the device is approximately face-up (Z ≈ +1g). Reads the accelerometer once and returns 1 when Z is within ±200 mg of +1 g and X/Y are within ±300 mg of 0. Uses the current ACCEL_CONFIG0 full-scale range to compute thresholds at runtime, so the helper stays correct after `set_accel_range`. Integer math only. **Returns:** 1 if face-up, 0 otherwise. ##### `tile_sense_i_6p6_is_face_down` — Studio ```c uint8_t tile_sense_i_6p6_is_face_down(tile_t *tile) ``` Detect whether the device is approximately face-down (Z ≈ −1g). Mirror of `is_face_up` with an inverted Z target. Same ±200 mg / ±300 mg tolerance bands; same range-aware thresholding. **Returns:** 1 if face-down, 0 otherwise. ##### `tile_sense_i_6p6_is_moving` — Studio ```c uint8_t tile_sense_i_6p6_is_moving(tile_t *tile, uint16_t threshold_mg) ``` Detect motion: |‖a‖ − 1g| > threshold. Reads the accelerometer, computes the squared magnitude in raw counts, and compares against the squared 1-g + threshold band. Squared-form keeps the math integer (no sqrt). Threshold is symmetric around 1 g so the helper triggers on both lift and drop. Range-aware. - `threshold_mg` — [1..32000] Deviation threshold in milli-g (typical: 50–200). **Returns:** 1 if moving, 0 otherwise. ##### `tile_sense_i_6p6_read_tilt_centi_degrees_flat` — Studio ```c uint8_t tile_sense_i_6p6_read_tilt_centi_degrees_flat(tile_t *tile, uint8_t axis, int32_t *out_centi_deg) ``` Elevation of one axis above horizontal, in 0.01° (int32 out). Flat-output variant of read_tilt_centi_degrees(): same computation, but the angle is written through an int32_t so Studio's 32-bit out-scalar locals receive it without a width mismatch. - `tile` — Initialised tile handle. - `axis` — [0..2] 0 = X, 1 = Y, 2 = Z. - `out_centi_deg` — Output: elevation of the axis in 0.01° (−9000..+9000). **Returns:** 1 on success, 0 on bad axis or zero vector. #### Events ##### `tile_sense_i_6p6_process` — Studio ```c void tile_sense_i_6p6_process(tile_t *tile) ``` Process pending interrupt events. Call from your main loop. In interrupt mode: returns immediately if no INT1 interrupt fired; reads INT_STATUS and fires callback only when events are pending. In polled mode: reads INT_STATUS every call, fires callback if set. ##### `tile_sense_i_6p6_on_event` — Studio ```c void tile_sense_i_6p6_on_event(tile_t *tile, sense_i_6p6_event_cb_t cb, void *ctx) ``` Register or change the event callback. - `cb` — Callback to fire on interrupt events. NULL to clear. - `ctx` — Opaque user pointer passed to the callback. #### Configuration ##### `tile_sense_i_6p6_set_filter_bw` — Studio ```c void tile_sense_i_6p6_set_filter_bw(tile_t *tile, sense_i_6p6_filter_bw_t accel_bw, sense_i_6p6_filter_bw_t gyro_bw) ``` Set UI filter bandwidth for both accel and gyro. ##### `tile_sense_i_6p6_set_filter_order` — Studio ```c void tile_sense_i_6p6_set_filter_order(tile_t *tile, sense_i_6p6_filter_order_t accel_order, sense_i_6p6_filter_order_t gyro_order) ``` Set UI filter order for accel and gyro (1st, 2nd, or 3rd). ##### `tile_sense_i_6p6_set_temp_filter` — Studio ```c void tile_sense_i_6p6_set_temp_filter(tile_t *tile, sense_i_6p6_temp_filter_t bw) ``` Set temperature sensor filter bandwidth. ##### `tile_sense_i_6p6_set_temp_enabled` — Studio ```c void tile_sense_i_6p6_set_temp_enabled(tile_t *tile, uint8_t enabled) ``` Enable or disable the temperature sensor. - `enabled` — [0..1] 1 = on, 0 = off. #### FIFO ##### `tile_sense_i_6p6_fifo_config` — Studio ```c void tile_sense_i_6p6_fifo_config(tile_t *tile, sense_i_6p6_fifo_mode_t mode, uint8_t accel, uint8_t gyro, uint8_t temp, uint8_t hires) ``` Configure the FIFO. - `mode` — FIFO operating mode (bypass/stream/stop-on-full) - `accel` — Include accel data in FIFO packets - `gyro` — Include gyro data in FIFO packets - `temp` — Include temperature in FIFO packets - `hires` — Enable 20-bit high-resolution mode (forces ±32g/±4000dps) ##### `tile_sense_i_6p6_fifo_set_watermark` — Studio ```c void tile_sense_i_6p6_fifo_set_watermark(tile_t *tile, uint16_t records) ``` Set the FIFO watermark threshold in records (1–4095). - `records` — [1..4095] Records in the FIFO that raise the watermark interrupt. ##### `tile_sense_i_6p6_fifo_flush` — Studio ```c void tile_sense_i_6p6_fifo_flush(tile_t *tile) ``` Flush the FIFO (discard all data). ##### `tile_sense_i_6p6_fifo_read_packet_flat` — Studio ```c void tile_sense_i_6p6_fifo_read_packet_flat(tile_t *tile, int32_t *out) ``` DSL-friendly flat-output variant of fifo_read_packet(). Drops the struct in favor of a positional int[8] array — the DSL doesn't have a struct ABI yet, so the per-field outputs come back indexed. Layout: out[0..2] = accel x,y,z (raw int16 widened to int32) out[3..5] = gyro x,y,z (raw int16 widened to int32) out[6] = temp (raw int8 widened to int32) out[7] = timestamp (raw uint16) Drops the success bool — call fifo_count() first to know whether there's data. On an empty FIFO, the slots are left untouched. - `tile` — Initialised tile handle. - `out` — Output buffer (8 int32_t slots). ##### `tile_sense_i_6p6_fifo_read_packets_flat` — Studio ```c uint16_t tile_sense_i_6p6_fifo_read_packets_flat(tile_t *tile, int32_t *out, uint16_t cap_ints) ``` DSL-friendly flat-output variant of fifo_read_packets(). Drains up to N packets from the FIFO into the caller's int32 buffer, 8 ints per packet (same layout as fifo_read_packet_flat). Returns the number of *packets* read (not ints), so callers can iterate with `for i in 0..n { let ax = buf[i*8 + 0]; ... }`. The caller's array length is the int-cap (N packets × 8 ints), not the packet count — `cap_ints / 8` packets are attempted. Mirrors the cap-mode array-OUT convention (`cap_param`) other FIFO drivers use. - `tile` — Initialised tile handle. - `out` — Output buffer; must be sized N × 8 ints. - `cap_ints` — Buffer capacity in int slots (= packets × 8). **Returns:** Number of packets actually drained from the FIFO. ##### `tile_sense_i_6p6_fifo_count` — Studio ```c uint16_t tile_sense_i_6p6_fifo_count(tile_t *tile) ``` Read the FIFO record (packet) count. init() sets INTF_CONFIG0.FIFO_COUNT_REC, so FIFO_COUNT is in records. ##### `tile_sense_i_6p6_fifo_lost_count` — Studio ```c uint16_t tile_sense_i_6p6_fifo_lost_count(tile_t *tile) ``` Get the number of lost FIFO packets since last check. #### Interrupts ##### `tile_sense_i_6p6_int1_config` — Studio ```c void tile_sense_i_6p6_int1_config(tile_t *tile, uint8_t config) ``` Configure INT1 pin behavior. - `config` — OR'd flags: ACTIVE_HIGH|PUSH_PULL|LATCHED etc. ##### `tile_sense_i_6p6_int1_data_ready` — Studio ```c void tile_sense_i_6p6_int1_data_ready(tile_t *tile, uint8_t enabled) ``` Route data-ready interrupt to INT1. - `enabled` — 1 to enable, 0 to disable ##### `tile_sense_i_6p6_int1_fifo_ths` — Studio ```c void tile_sense_i_6p6_int1_fifo_ths(tile_t *tile, uint8_t enabled) ``` Route FIFO watermark interrupt to INT1. - `enabled` — 1 to enable, 0 to disable ##### `tile_sense_i_6p6_int1_wom` — Studio ```c void tile_sense_i_6p6_int1_wom(tile_t *tile, uint8_t enabled) ``` Route WOM (wake-on-motion) interrupt to INT1. - `enabled` — 1 to enable, 0 to disable ##### `tile_sense_i_6p6_int2_config` — Studio ```c void tile_sense_i_6p6_int2_config(tile_t *tile, uint8_t config) ``` Configure INT2 pin behavior. Mirrors int1_config for the chip's second interrupt pin (tile pad 8). INT_CONFIG bits [5:3] hold INT2's polarity / drive / mode bits. layout as int1_config; the driver shifts the bits into the INT2 positions internally). - `config` — OR'd flags: ACTIVE_HIGH|PUSH_PULL|LATCHED etc. (same ##### `tile_sense_i_6p6_int2_data_ready` — Studio ```c void tile_sense_i_6p6_int2_data_ready(tile_t *tile, uint8_t enabled) ``` Route data-ready interrupt to INT2. - `enabled` — 1 to enable, 0 to disable ##### `tile_sense_i_6p6_int2_fifo_ths` — Studio ```c void tile_sense_i_6p6_int2_fifo_ths(tile_t *tile, uint8_t enabled) ``` Route FIFO watermark interrupt to INT2. - `enabled` — 1 to enable, 0 to disable ##### `tile_sense_i_6p6_int2_wom` — Studio ```c void tile_sense_i_6p6_int2_wom(tile_t *tile, uint8_t enabled) ``` Route WOM (wake-on-motion) interrupt to INT2. - `enabled` — 1 to enable, 0 to disable ##### `tile_sense_i_6p6_set_int_pulse_duration` — Studio ```c void tile_sense_i_6p6_set_int_pulse_duration(tile_t *tile, sense_i_6p6_int_pulse_t pulse) ``` Set the INT pin pulse-width when configured for pulse mode. Applies to both INT1 and INT2 (it's a chip-wide setting in INT_CONFIG1 bit 6). Only affects pulse-mode interrupts; in latched mode the line stays asserted until the status register is read. - `tile` — Initialised tile handle - `pulse` — Pulse-width selector ##### `tile_sense_i_6p6_get_int_status` — Studio ```c uint8_t tile_sense_i_6p6_get_int_status(tile_t *tile) ``` Read and clear INT_STATUS register. ##### `tile_sense_i_6p6_get_int_status2` — Studio ```c uint8_t tile_sense_i_6p6_get_int_status2(tile_t *tile) ``` Read and clear INT_STATUS2 (WOM/SMD flags). ##### `tile_sense_i_6p6_get_int_status3` — Studio ```c uint8_t tile_sense_i_6p6_get_int_status3(tile_t *tile) ``` Read and clear INT_STATUS3 (APEX: step/tilt/tap flags). #### Advanced ##### `tile_sense_i_6p6_subsystem_reset` — Studio ```c void tile_sense_i_6p6_subsystem_reset(tile_t *tile, sense_i_6p6_subsystem_t which) ``` Reset a single chip subsystem without touching the others. Issues the relevant SIGNAL_PATH_RESET bits. Distinct from the driver's full `reset()` (which re-initialises the whole chip) and from `fifo_flush()` (which targets just the FIFO, also a subsystem reset but already exposed). - APEX: DMP_MEM_RESET_EN, 1 ms, then DMP_INIT_EN (DS-000639 §8.3/§14.33); blocks ~51 ms. Clears the step count. Use after reconfiguring pedometer / tilt / tap features. - TEMP: does nothing. SIGNAL_PATH_RESET has no temperature bit on this part; kept only for source compatibility. - `tile` — Initialised tile handle - `which` — Subsystem to reset ##### `tile_sense_i_6p6_set_gyro_offset` — Studio ```c void tile_sense_i_6p6_set_gyro_offset(tile_t *tile, int16_t x_dps16, int16_t y_dps16, int16_t z_dps16) ``` Set user-programmable gyro offset. - `x_dps` — X offset in dps × 16 (12-bit signed, ±128 dps max, 0.0625 dps resolution) - `y_dps` — Y offset - `z_dps` — Z offset ##### `tile_sense_i_6p6_set_accel_offset` — Studio ```c void tile_sense_i_6p6_set_accel_offset(tile_t *tile, int16_t x_mg, int16_t y_mg, int16_t z_mg) ``` Set user-programmable accel offset. - `x_mg` — X offset in mg (12-bit signed, ±2000 mg max, 1 mg resolution) - `y_mg` — Y offset - `z_mg` — Z offset ##### `tile_sense_i_6p6_self_test` — Studio ```c uint8_t tile_sense_i_6p6_self_test(tile_t *tile, uint8_t *accel_pass, uint8_t *gyro_pass) ``` Perform self-test. Runs the built-in self-test for both accel and gyro. - `accel_pass` — Output: 1 if accel self-test passed, 0 if failed (per-axis OR'd) - `gyro_pass` — Output: 1 if gyro self-test passed, 0 if failed **Returns:** 1 if both passed, 0 if either failed ##### `tile_sense_i_6p6_read_reg` — Studio ```c uint8_t tile_sense_i_6p6_read_reg(tile_t *tile, uint8_t bank, uint8_t reg) ``` Read a register from any bank. Automatically switches to the specified bank and back to bank 0. For advanced/debug use. - `bank` — Bank index (0, 1, 2, or 4). - `reg` — Register address within the bank. ##### `tile_sense_i_6p6_write_reg` — Studio ```c void tile_sense_i_6p6_write_reg(tile_t *tile, uint8_t bank, uint8_t reg, uint8_t val) ``` Write a register in any bank. Automatically switches to the specified bank and back to bank 0. For advanced/debug use. - `bank` — Bank index (0, 1, 2, or 4). - `reg` — Register address within the bank. - `val` — Byte to write. #### Other ##### `tile_sense_i_6p6_find` ```c uint8_t tile_sense_i_6p6_find(tiles_pal_t *hal, uint8_t instance) ``` Check if a Sense.I.6P6 is present on the bus (address probe only). ##### `tile_sense_i_6p6_init` ```c void tile_sense_i_6p6_init(tiles_pal_t *hal, uint8_t instance, tile_t *tile, const sense_i_6p6_cfg_t *cfg) ``` Initialize the ICM-42686P. Performs soft reset, verifies WHO_AM_I, configures sensor ranges and ODR. Pass cfg=NULL for defaults (±8g, ±1000dps, 100Hz, polled). Applies the datasheet's per-bus driver settings (DS-000639 §12.3): on I2C it turns the chip's I3C slave off (INTF_CONFIG6 SDR/DDR = 0, which brings back its I2C glitch filter) and sets both slew rates to 1, retrying until they read back. If they never stick the tile still gets configured, but its state is TILE_STATE_ERROR and on_error is called. On I3C it sets the SDA slew to 3 (4-12 ns); on SPI, SPI_SLEW_RATE 5. **Note:** Blocks for ~12 ms (reset). Call once at startup. ##### `tile_sense_i_6p6_read_tilt_centi_degrees` ```c uint8_t tile_sense_i_6p6_read_tilt_centi_degrees(tile_t *tile, uint8_t axis, int16_t *out_centi_deg) ``` Tilt angle of one axis vs gravity, in 0.01°. Computes `atan2(axis, sqrt(other_a^2 + other_b^2))` for the requested axis using a small integer atan2 approximation: the elevation of that axis above the horizontal plane. Output range: −9000..+9000 (−90.00°..+90.00°; +9000 = axis pointing straight up, e.g. Z when the tile lies face up). Returns 0 and writes 0 to `*out_centi_deg` if the device is in free-fall (all-zero vector). Range-independent (a ratio). - `axis` — 0 = X, 1 = Y, 2 = Z. - `out_centi_deg` — Output tilt in 0.01° (signed, −9000..+9000). **Returns:** 1 on success, 0 on bad axis or zero vector. ##### `tile_sense_i_6p6_wait_for_tap` — Studio ```c uint8_t tile_sense_i_6p6_wait_for_tap(tile_t *tile, uint32_t timeout_ms) ``` Block until a tap interrupt fires or the timeout expires. Polls INT_STATUS3 at ~1 ms cadence (delay_ms(1)). Tap detection must be enabled first via `tile_sense_i_6p6_tap_enable`. The status read is destructive (clear-on-read), so call `get_tap_result` immediately after a positive return to retrieve count/axis/direction. - `timeout_ms` — [1..60000] ms Max time to wait, in ms. Use 0 for a single-shot poll. **Returns:** 1 if a tap was detected, 0 on timeout. ##### `tile_sense_i_6p6_wait_for_motion` — Studio ```c uint8_t tile_sense_i_6p6_wait_for_motion(tile_t *tile, uint32_t timeout_ms) ``` Block until a wake-on-motion interrupt fires or the timeout expires. Polls INT_STATUS2 at ~1 ms cadence. WOM must be configured (thresholds + enable) first. Returns on any-axis WOM. The status read clears the latch. - `timeout_ms` — [1..60000] ms Max time to wait, in ms. Use 0 for a single-shot poll. **Returns:** 1 if motion was detected, 0 on timeout. ##### `tile_sense_i_6p6_fifo_read_packet` ```c uint8_t tile_sense_i_6p6_fifo_read_packet(tile_t *tile, sense_i_6p6_fifo_packet_t *pkt) ``` Read one standard FIFO packet (16 bytes: accel + gyro + temp + timestamp). **Returns:** 1 if valid packet read, 0 if FIFO empty or error. ##### `tile_sense_i_6p6_fifo_read_packets` ```c uint16_t tile_sense_i_6p6_fifo_read_packets(tile_t *tile, sense_i_6p6_fifo_packet_t *packets, uint16_t max_count) ``` Read multiple FIFO packets. - `packets` — Output array - `max_count` — Maximum packets to read **Returns:** Number of packets actually read ##### `tile_sense_i_6p6_wom_config` — Studio ```c void tile_sense_i_6p6_wom_config(tile_t *tile, uint16_t x_mg, uint16_t y_mg, uint16_t z_mg, sense_i_6p6_wom_mode_t mode) ``` Configure Wake-on-Motion thresholds. above ~996 mg clamp to the register maximum 255) - `x_mg` — [0..1000] X-axis threshold in mg (resolution ~3.9 mg; values - `y_mg` — [0..1000] Y-axis threshold in mg - `z_mg` — [0..1000] Z-axis threshold in mg - `mode` — Compare against initial or previous sample **Note:** Requires accel to be running (LN or LP mode). ##### `tile_sense_i_6p6_wom_enable` — Studio ```c void tile_sense_i_6p6_wom_enable(tile_t *tile) ``` Enable WOM. Must configure thresholds first. Keeps the compare mode set by wom_config() and any SMD mode already set. ##### `tile_sense_i_6p6_wom_disable` — Studio ```c void tile_sense_i_6p6_wom_disable(tile_t *tile) ``` Disable WOM (SMD_CONFIG.SMD_MODE = 00). Also stops SMD. Thresholds are kept, so wom_enable() re-arms with the last config. ##### `tile_sense_i_6p6_smd_config` — Studio ```c void tile_sense_i_6p6_smd_config(tile_t *tile, sense_i_6p6_smd_mode_t mode) ``` Configure and enable SMD. WOM thresholds must be set first. ##### `tile_sense_i_6p6_pedometer_enable` — Studio ```c void tile_sense_i_6p6_pedometer_enable(tile_t *tile, sense_i_6p6_dmp_odr_t dmp_odr) ``` Enable the pedometer (step counter + step detector). Requires accel at ≥25 Hz. Initializes the DMP if not already running. - `dmp_odr` — DMP processing rate (25 or 50 Hz) **Note:** APEX_CONFIG0.DMP_POWER_SAVE resets to 1 and the driver leaves it set. Per DS-000639 §8.2 the APEX features then run only after a WOM event (WOM must be enabled). Not yet bench-verified. ##### `tile_sense_i_6p6_pedometer_disable` — Studio ```c void tile_sense_i_6p6_pedometer_disable(tile_t *tile) ``` Disable the pedometer. ##### `tile_sense_i_6p6_get_step_count` — Studio ```c uint16_t tile_sense_i_6p6_get_step_count(tile_t *tile) ``` Read the step count. **Returns:** 16-bit step count (resets on power cycle or DMP reset) ##### `tile_sense_i_6p6_get_step_cadence` — Studio ```c uint8_t tile_sense_i_6p6_get_step_cadence(tile_t *tile) ``` Read the step cadence. **Returns:** Steps per second in u6.2 fixed-point (divide by 4.0 for float) ##### `tile_sense_i_6p6_get_activity` — Studio ```c sense_i_6p6_activity_t tile_sense_i_6p6_get_activity(tile_t *tile) ``` Read the activity classification. **Returns:** 0=unknown, 1=walk, 2=run ##### `tile_sense_i_6p6_tilt_enable` — Studio ```c void tile_sense_i_6p6_tilt_enable(tile_t *tile, uint8_t wait_seconds) ``` Enable tilt detection. Triggers when device tilts >35° for the configured wait time. Requires accel at ≥25 Hz. Initializes DMP if needed. - `wait_seconds` — [0..6] s Time the tilt must be sustained (0, 2, 4, or 6). **Note:** APEX_CONFIG0.DMP_POWER_SAVE resets to 1 and the driver leaves it set. Per DS-000639 §8.2 the APEX features then run only after a WOM event (WOM must be enabled). Not yet bench-verified. ##### `tile_sense_i_6p6_tilt_disable` — Studio ```c void tile_sense_i_6p6_tilt_disable(tile_t *tile) ``` Disable tilt detection. ##### `tile_sense_i_6p6_tap_enable` — Studio ```c void tile_sense_i_6p6_tap_enable(tile_t *tile) ``` Enable tap detection (single + double tap). Requires accel in LN mode at 200 Hz, 500 Hz, or 1 kHz. Initializes DMP if needed. **Note:** APEX_CONFIG0.DMP_POWER_SAVE resets to 1 and the driver leaves it set. Per DS-000639 §8.2 the APEX features then run only after a WOM event (WOM must be enabled). Not yet bench-verified. ##### `tile_sense_i_6p6_tap_disable` — Studio ```c void tile_sense_i_6p6_tap_disable(tile_t *tile) ``` Disable tap detection. ##### `tile_sense_i_6p6_get_tap_result` ```c void tile_sense_i_6p6_get_tap_result(tile_t *tile, sense_i_6p6_tap_result_t *result) ``` Read the last tap detection result. - `result` — Output tap information ##### `tile_sense_i_6p6_get_tap_result_flat` — Studio ```c void tile_sense_i_6p6_get_tap_result_flat(tile_t *tile, int32_t *count, int32_t *axis, int32_t *direction, int32_t *timing) ``` DSL-friendly flat-output variant of get_tap_result(). Same data, four positional out-scalars instead of a struct. Useful for polling-style flows; event-driven flows already get these values via the `Sense.I.6P6.tap` event payload. - `tile` — Initialised tile handle. - `count` — Output: 0 = none, 1 = single tap, 2 = double. - `axis` — Output: 0 = X, 1 = Y, 2 = Z. - `direction` — Output: 0 = positive, 1 = negative. - `timing` — Output: double-tap timing (chip-specific units). ### Vibe Settings Settings Studio's Vibe interface offers for this tile, generated from the driver's `@studio control` annotations. Each is an argument of the function shown. | Setting | Tier | Values | Default | Function | |---|---|---|---|---| | Accel range | basic | ±32 g, ±16 g, ±8 g, ±4 g, ±2 g | ±8 g | `set_accel_range(range)` | | Gyro range | basic | ±4000 deg/s, ±2000 deg/s, ±1000 deg/s, ±500 deg/s, ±250 deg/s, ±125 deg/s, ±62.5 deg/s, ±31.25 deg/s | ±1000 deg/s | `set_gyro_range(range)` | | Accel data rate | basic | 32 kHz, 16 kHz, 8 kHz, 4 kHz, 2 kHz, 1 kHz, 200 Hz, 100 Hz, 50 Hz, 25 Hz, 12.5 Hz, 6.25 Hz (accel LP only), 3.125 Hz (accel LP only), 1.5625 Hz (accel LP only), 500 Hz | 100 Hz | `set_accel_odr(odr)` | | Gyro data rate | basic | 32 kHz, 16 kHz, 8 kHz, 4 kHz, 2 kHz, 1 kHz, 200 Hz, 100 Hz, 50 Hz, 25 Hz, 12.5 Hz, 500 Hz | 100 Hz | `set_gyro_odr(odr)` | | Motion threshold | basic | 1 to 32000 | | `is_moving(threshold_mg)` | | Accel power mode | advanced | Powered off, Accel only: duty-cycled, Low-noise (full performance) | Low-noise (full performance) | `set_power_mode(accel)` | | Gyro power mode | advanced | Powered off, Gyro only: drive on, no output, Low-noise (full performance) | Low-noise (full performance) | `set_power_mode(gyro)` | | Accel filter bandwidth | advanced | ODR/2 (Nyquist), max(400 Hz, ODR)/4 (default), max(400 Hz, ODR)/5, max(400 Hz, ODR)/8, max(400 Hz, ODR)/10, max(400 Hz, ODR)/16, max(400 Hz, ODR)/20, max(400 Hz, ODR)/40 | max(400 Hz, ODR)/4 (default) | `set_filter_bw(accel_bw)` | | Gyro filter bandwidth | advanced | ODR/2 (Nyquist), max(400 Hz, ODR)/4 (default), max(400 Hz, ODR)/5, max(400 Hz, ODR)/8, max(400 Hz, ODR)/10, max(400 Hz, ODR)/16, max(400 Hz, ODR)/20, max(400 Hz, ODR)/40 | max(400 Hz, ODR)/4 (default) | `set_filter_bw(gyro_bw)` | | Accel filter order | advanced | 1st order, 2nd order, 3rd order | 2nd order | `set_filter_order(accel_order)` | | Gyro filter order | advanced | 1st order, 2nd order, 3rd order | 2nd order | `set_filter_order(gyro_order)` | | Temperature filter | advanced | 4000 Hz cutoff, 170 Hz cutoff, 82 Hz cutoff, 40 Hz cutoff, 20 Hz cutoff, 10 Hz cutoff, 5 Hz cutoff | 4000 Hz cutoff | `set_temp_filter(bw)` | | Temperature sensor | advanced | on / off | on | `set_temp_enabled(enabled)` | | Tap wait timeout | advanced | 1 to 60000 ms | | `wait_for_tap(timeout_ms)` | | Motion wait timeout | advanced | 1 to 60000 ms | | `wait_for_motion(timeout_ms)` | | FIFO watermark | advanced | 1 to 4095 | | `fifo_set_watermark(records)` | | Wake threshold X | advanced | 0 to 1000 | | `wom_config(x_mg)` | | Wake threshold Y | advanced | 0 to 1000 | | `wom_config(y_mg)` | | Wake threshold Z | advanced | 0 to 1000 | | `wom_config(z_mg)` | | Wake compare | advanced | Compare against first sample, Compare against previous sample | Compare against previous sample | `wom_config(mode)` | | Significant motion | advanced | Disabled, 1 second WOM window, 3 second WOM window | Disabled | `smd_config(mode)` | | Pedometer rate | advanced | 25 Hz, 50 Hz | | `pedometer_enable(dmp_odr)` | | Tilt delay | advanced | 0 to 6 s | | `enable_tilt(wait_seconds)` | Rules Studio enforces: - Below 12.5 Hz the accelerometer only runs in low-power mode. Switch the accel power mode to low-power first. - In low-power mode the accelerometer tops out at 500 Hz. Switch the accel power mode to low-noise for faster rates. - In low-power mode the accel filter averages instead of low-passing: only the /4 setting (1x averaging) and the /20 setting (16x averaging) are valid. ### Driver gaps Chip capabilities this driver does **not** expose. Each is tagged hardware-gated (the tile does not route the pin) or driver-deferred (possible, not yet implemented). Do not assume an API exists for these. #### External timing pins (FSYNC / CLKIN) (advanced) Hardware-gated (not a driver gap). The ICM-42686-P can take a 31–50 kHz FSYNC input (TIMESTAMP_FSYNC_EN) for sample time-stamping and a ~32 kHz external CLKIN (RTC) for precise ODR. Neither pin is routed to a tile pad on Sense-I-6P6-a, so the capability simply isn't wired out — closing requires a tile hardware revision, not driver work. #### FIFO 20-bit hi-res mode (niche) Design-gated. FIFO_HIRES_EN switches FIFO packets to 20-bit accel + 20-bit gyro (Packet 4 with 3-byte extensions) at higher sensitivity scale factors. Closing properly requires extending sense_i_6p6_fifo_packet_t to represent 20-bit fields and reworking the read_packet parser; toggling the bit alone breaks the existing 16-bit parsing path. Deferred to a future driver pass. #### I3C: HDR-DDR, IBI routing, Studio (niche) I3C SDR works on Core.ST.H5 (driver 1.4.0): core_tiles_pal(&core_i3c1) runs this driver unchanged at up to 12.4 MHz, with dynamic addressing, and in-band interrupts through core_i3c_ibi_enable() + ENEC. What is left: HDR-DDR (the chip has it; the H5 I3C peripheral is SDR-only, so it is hardware-gated on every Core); no driver call routes events to IBI (write INT_SOURCE8/9 in bank 4 with write_reg); and Studio cannot put a bus on I3C yet (config.json can: "i3c": true on the I2C bus). #### APEX raise-to-wake / raise-to-sleep (advanced) Driver-deferred (true driver gap — hardware supports it). The APEX engine detects raise-to-wake / raise-to-sleep gestures (RWR), routable to INT via INT_SOURCE6/7. The driver wraps pedometer / tilt / tap / WOM / SMD but not RWR; no hardware or framework blocker — closing is a driver pass. #### UI notch / anti-alias filter tuning (advanced) Driver-deferred (true driver gap). set_filter_bw / set_filter_order expose the UI filter bandwidth and order, but not the notch-filter centre frequency / Q (Bank-1 GYRO_CFG_STATIC registers) for rejecting a specific vibration band. #### Standalone timestamp (TMSTVAL) read (niche) Driver-deferred (true driver gap). FIFO packets carry a 16-bit timestamp, but the chip's free-running timestamp counter (Bank-1 TMSTVAL0-2) isn't exposed as a direct read. ### Events - `data_ready` - `fifo_watermark` - `tilt` - `tap` - `wake_on_motion` ### Enums #### `sense_i_6p6_accel_range_t` Accelerometer full-scale range. | Member | Value | Description | | --- | --- | --- | | `SENSE_I_6P6_ACCEL_32G` | `0x00` | +/- 32g | | `SENSE_I_6P6_ACCEL_16G` | `0x01` | +/- 16g | | `SENSE_I_6P6_ACCEL_8G` | `0x02` | +/- 8g | | `SENSE_I_6P6_ACCEL_4G` | `0x03` | +/- 4g | | `SENSE_I_6P6_ACCEL_2G` | `0x04` | +/- 2g | #### `sense_i_6p6_gyro_range_t` Gyroscope full-scale range. | Member | Value | Description | | --- | --- | --- | | `SENSE_I_6P6_GYRO_4000DPS` | `0x00` | +/- 4000 deg/s | | `SENSE_I_6P6_GYRO_2000DPS` | `0x01` | +/- 2000 deg/s | | `SENSE_I_6P6_GYRO_1000DPS` | `0x02` | +/- 1000 deg/s | | `SENSE_I_6P6_GYRO_500DPS` | `0x03` | +/- 500 deg/s | | `SENSE_I_6P6_GYRO_250DPS` | `0x04` | +/- 250 deg/s | | `SENSE_I_6P6_GYRO_125DPS` | `0x05` | +/- 125 deg/s | | `SENSE_I_6P6_GYRO_62_5DPS` | `0x06` | +/- 62.5 deg/s | | `SENSE_I_6P6_GYRO_31_25DPS` | `0x07` | +/- 31.25 deg/s | #### `sense_i_6p6_odr_t` Output data rate selection. | Member | Value | Description | | --- | --- | --- | | `SENSE_I_6P6_ODR_32KHZ` | `0x01` | 32 kHz @studio value=32000 | | `SENSE_I_6P6_ODR_16KHZ` | `0x02` | 16 kHz @studio value=16000 | | `SENSE_I_6P6_ODR_8KHZ` | `0x03` | 8 kHz @studio value=8000 | | `SENSE_I_6P6_ODR_4KHZ` | `0x04` | 4 kHz @studio value=4000 | | `SENSE_I_6P6_ODR_2KHZ` | `0x05` | 2 kHz @studio value=2000 | | `SENSE_I_6P6_ODR_1KHZ` | `0x06` | 1 kHz @studio value=1000 | | `SENSE_I_6P6_ODR_200HZ` | `0x07` | 200 Hz @studio value=200 | | `SENSE_I_6P6_ODR_100HZ` | `0x08` | 100 Hz @studio value=100 | | `SENSE_I_6P6_ODR_50HZ` | `0x09` | 50 Hz @studio value=50 | | `SENSE_I_6P6_ODR_25HZ` | `0x0A` | 25 Hz @studio value=25 | | `SENSE_I_6P6_ODR_12_5HZ` | `0x0B` | 12.5 Hz @studio value=12.5 | | `SENSE_I_6P6_ODR_6_25HZ` | `0x0C` | 6.25 Hz (accel LP only) @studio value=6.25 | | `SENSE_I_6P6_ODR_3_125HZ` | `0x0D` | 3.125 Hz (accel LP only) @studio value=3.125 | | `SENSE_I_6P6_ODR_1_5625HZ` | `0x0E` | 1.5625 Hz (accel LP only) @studio value=1.5625 | | `SENSE_I_6P6_ODR_500HZ` | `0x0F` | 500 Hz @studio value=500 | #### `sense_i_6p6_power_mode_t` Power mode for accel/gyro independently. | Member | Value | Description | | --- | --- | --- | | `SENSE_I_6P6_MODE_OFF` | `0x00` | Powered off | | `SENSE_I_6P6_MODE_STANDBY` | `0x01` | Gyro only: drive on, no output | | `SENSE_I_6P6_MODE_LP` | `0x02` | Accel only: duty-cycled | | `SENSE_I_6P6_MODE_LN` | `0x03` | Low-noise (full performance) | #### `sense_i_6p6_filter_bw_t` UI filter bandwidth selection. | Member | Value | Description | | --- | --- | --- | | `SENSE_I_6P6_FILT_BW_ODR_2` | `0x00` | ODR/2 (Nyquist) @studio value=2 | | `SENSE_I_6P6_FILT_BW_ODR_4` | `0x01` | max(400 Hz, ODR)/4 (default) @studio value=4 | | `SENSE_I_6P6_FILT_BW_ODR_5` | `0x02` | max(400 Hz, ODR)/5 @studio value=5 | | `SENSE_I_6P6_FILT_BW_ODR_8` | `0x03` | max(400 Hz, ODR)/8 @studio value=8 | | `SENSE_I_6P6_FILT_BW_ODR_10` | `0x04` | max(400 Hz, ODR)/10 @studio value=10 | | `SENSE_I_6P6_FILT_BW_ODR_16` | `0x05` | max(400 Hz, ODR)/16 @studio value=16 | | `SENSE_I_6P6_FILT_BW_ODR_20` | `0x06` | max(400 Hz, ODR)/20 @studio value=20 | | `SENSE_I_6P6_FILT_BW_ODR_40` | `0x07` | max(400 Hz, ODR)/40 @studio value=40 | | `SENSE_I_6P6_FILT_LP_1X_AVG` | `0x01` | LP mode: 1× averaging (default) | | `SENSE_I_6P6_FILT_LP_16X_AVG` | `0x06` | LP mode: 16× averaging | #### `sense_i_6p6_filter_order_t` UI filter order. | Member | Value | Description | | --- | --- | --- | | `SENSE_I_6P6_FILT_ORDER_1ST` | `0x00` | 1st order | | `SENSE_I_6P6_FILT_ORDER_2ND` | `0x01` | 2nd order | | `SENSE_I_6P6_FILT_ORDER_3RD` | `0x02` | 3rd order | #### `sense_i_6p6_temp_filter_t` Temperature filter bandwidth. | Member | Value | Description | | --- | --- | --- | | `SENSE_I_6P6_TEMP_FILT_4000HZ` | `0x00` | 4000 Hz cutoff @studio value=4000 | | `SENSE_I_6P6_TEMP_FILT_170HZ` | `0x01` | 170 Hz cutoff @studio value=170 | | `SENSE_I_6P6_TEMP_FILT_82HZ` | `0x02` | 82 Hz cutoff @studio value=82 | | `SENSE_I_6P6_TEMP_FILT_40HZ` | `0x03` | 40 Hz cutoff @studio value=40 | | `SENSE_I_6P6_TEMP_FILT_20HZ` | `0x04` | 20 Hz cutoff @studio value=20 | | `SENSE_I_6P6_TEMP_FILT_10HZ` | `0x05` | 10 Hz cutoff @studio value=10 | | `SENSE_I_6P6_TEMP_FILT_5HZ` | `0x06` | 5 Hz cutoff @studio value=5 | #### `sense_i_6p6_fifo_mode_t` FIFO mode. | Member | Value | Description | | --- | --- | --- | | `SENSE_I_6P6_FIFO_BYPASS` | `0x00` | FIFO disabled | | `SENSE_I_6P6_FIFO_STREAM` | `0x01` | Stream mode (oldest data replaced) | | `SENSE_I_6P6_FIFO_STOP_ON_FULL` | `0x02` | Stop on full | #### `sense_i_6p6_dmp_odr_t` APEX DMP output data rate. | Member | Value | Description | | --- | --- | --- | | `SENSE_I_6P6_DMP_ODR_25HZ` | `0x00` | 25 Hz | | `SENSE_I_6P6_DMP_ODR_50HZ` | `0x02` | 50 Hz | #### `sense_i_6p6_wom_mode_t` WOM (Wake-on-Motion) compare mode. | Member | Value | Description | | --- | --- | --- | | `SENSE_I_6P6_WOM_INITIAL` | `0x00` | Compare against first sample | | `SENSE_I_6P6_WOM_PREVIOUS` | `0x01` | Compare against previous sample | #### `sense_i_6p6_smd_mode_t` SMD (Significant Motion Detection) mode. | Member | Value | Description | | --- | --- | --- | | `SENSE_I_6P6_SMD_DISABLED` | `0x00` | Disabled | | `SENSE_I_6P6_SMD_SHORT` | `0x02` | 1 second WOM window | | `SENSE_I_6P6_SMD_LONG` | `0x03` | 3 second WOM window | #### `sense_i_6p6_int_config_t` Interrupt pin configuration. | Member | Value | Description | | --- | --- | --- | | `SENSE_I_6P6_INT_ACTIVE_LOW` | `0x00` | Active low | | `SENSE_I_6P6_INT_ACTIVE_HIGH` | `0x01` | Active high | | `SENSE_I_6P6_INT_OPEN_DRAIN` | `0x00` | Open drain | | `SENSE_I_6P6_INT_PUSH_PULL` | `0x02` | Push-pull | | `SENSE_I_6P6_INT_PULSED` | `0x00` | Pulsed | | `SENSE_I_6P6_INT_LATCHED` | `0x04` | Latched | #### `sense_i_6p6_activity_t` APEX activity classification. | Member | Value | Description | | --- | --- | --- | | `SENSE_I_6P6_ACTIVITY_UNKNOWN` | `0x00` | Unknown | | `SENSE_I_6P6_ACTIVITY_WALK` | `0x01` | Walking | | `SENSE_I_6P6_ACTIVITY_RUN` | `0x02` | Running | #### `sense_i_6p6_int_pulse_t` Pulse-width selector for INT pin pulse mode. | Member | Value | Description | | --- | --- | --- | | `SENSE_I_6P6_INT_PULSE_100US` | `0` | 100 µs pulse (default) | | `SENSE_I_6P6_INT_PULSE_8US` | `1` | 8 µs pulse — for fast hosts | #### `sense_i_6p6_subsystem_t` Subsystem selector for scoped resets. | Member | Value | Description | | --- | --- | --- | | `SENSE_I_6P6_RESET_APEX` | `0` | APEX (DMP) memory + state | | `SENSE_I_6P6_RESET_TEMP` | `1` | No-op: the ICM-42686-P has no temperature-path reset | ### Structs #### `sense_i_6p6_fifo_packet_t` FIFO packet structure (16 bytes, accel + gyro + temp + timestamp). | Type | Field | Description | | --- | --- | --- | | `uint8_t` | `header` | Packet header byte (use SENSE_I_6P6_FIFO_HEADER_* masks). | | `int8_t` | `temp` | 8-bit temperature: degC = temp/2.07 + 25 | | `uint16_t` | `timestamp` | 16-bit timestamp | #### `sense_i_6p6_tap_result_t` Tap detection result. | Type | Field | Description | | --- | --- | --- | | `uint8_t` | `count` | 0=none, 1=single, 2=double | | `uint8_t` | `axis` | 0=X, 1=Y, 2=Z | | `uint8_t` | `direction` | 0=positive, 1=negative | | `uint8_t` | `timing` | Double-tap timing: seconds = timing * 16 / ODR | #### `sense_i_6p6_cfg_t` | Type | Field | Description | | --- | --- | --- | | `uint8_t` | `int1_pin` | Core pad number for INT1. 0 = polled. | | `uint8_t` | `int2_pin` | Core pad number for INT2. 0 = unused. | | `sense_i_6p6_event_cb_t` | `on_event` | Callback. NULL = no callback. | | `uint8_t` | `accel_range` | sense_i_6p6_accel_range_t. 0 = default. | | `uint8_t` | `gyro_range` | sense_i_6p6_gyro_range_t. 0 = default. | | `uint8_t` | `odr` | sense_i_6p6_odr_t. 0 = default (100Hz). | ### Defines | Name | Value | Description | | --- | --- | --- | | `TILE_SENSE_I_6P6_VERSION_MAJOR` | `1` | | | `TILE_SENSE_I_6P6_VERSION_MINOR` | `4` | | | `TILE_SENSE_I_6P6_VERSION_PATCH` | `0` | | | `ICM42686P_I2C_ADDR_DEFAULT` | `0x69` | | | `ICM42686P_I2C_ADDR_ALT` | `0x68` | | | `ICM42686P_B1_SENSOR_CONFIG0` | `0x03` | | | `ICM42686P_B1_GYRO_CFG_STATIC2` | `0x0B` | | | `ICM42686P_B1_GYRO_CFG_STATIC3` | `0x0C` | | | `ICM42686P_B1_GYRO_CFG_STATIC4` | `0x0D` | | | `ICM42686P_B1_GYRO_CFG_STATIC5` | `0x0E` | | | `ICM42686P_B1_GYRO_NF_COSWZ_X` | `0x0F` | | | `ICM42686P_B1_GYRO_NF_COSWZ_Y` | `0x10` | | | `ICM42686P_B1_GYRO_NF_COSWZ_Z` | `0x11` | | | `ICM42686P_B1_GYRO_CFG_STATIC9` | `0x12` | | | `ICM42686P_B1_GYRO_CFG_STATIC10` | `0x13` | | | `ICM42686P_B1_XG_ST_DATA` | `0x5F` | | | `ICM42686P_B1_YG_ST_DATA` | `0x60` | | | `ICM42686P_B1_ZG_ST_DATA` | `0x61` | | | `ICM42686P_B1_TMSTVAL0` | `0x62` | | | `ICM42686P_B1_TMSTVAL1` | `0x63` | | | `ICM42686P_B1_TMSTVAL2` | `0x64` | | | `ICM42686P_B1_INTF_CONFIG4` | `0x7A` | | | `ICM42686P_B1_INTF_CONFIG5` | `0x7B` | | | `ICM42686P_B1_INTF_CONFIG6` | `0x7C` | | | `ICM42686P_INTF_CONFIG6_I2C` | `0x5C` | INTF_CONFIG6 on I2C (DS-000639 §12.3 "I2C driver setting"): reset value 0x5F with I3C_DDR_EN (bit 1) and I3C_SDR_EN (bit 0) cleared | | `ICM42686P_DRIVE_CONFIG_I2C` | `0x09` | DRIVE_CONFIG on I2C (§12.3): I2C_SLEW_RATE (bits 5:3) = 1, SPI_SLEW_RATE (bits 2:0) = 1 | | `ICM42686P_B2_ACCEL_CFG_STATIC2` | `0x03` | | | `ICM42686P_B2_ACCEL_CFG_STATIC3` | `0x04` | | | `ICM42686P_B2_ACCEL_CFG_STATIC4` | `0x05` | | | `ICM42686P_B2_XA_ST_DATA` | `0x3B` | | | `ICM42686P_B2_YA_ST_DATA` | `0x3C` | | | `ICM42686P_B2_ZA_ST_DATA` | `0x3D` | | | `ICM42686P_B4_APEX_CONFIG1` | `0x40` | | | `ICM42686P_B4_APEX_CONFIG2` | `0x41` | | | `ICM42686P_B4_APEX_CONFIG3` | `0x42` | | | `ICM42686P_B4_APEX_CONFIG4` | `0x43` | | | `ICM42686P_B4_APEX_CONFIG7` | `0x46` | | | `ICM42686P_B4_APEX_CONFIG8` | `0x47` | | | `ICM42686P_B4_APEX_CONFIG9` | `0x48` | | | `ICM42686P_B4_WOM_X_THR` | `0x4A` | | | `ICM42686P_B4_WOM_Y_THR` | `0x4B` | | | `ICM42686P_B4_WOM_Z_THR` | `0x4C` | | | `ICM42686P_B4_INT_SOURCE6` | `0x4D` | | | `ICM42686P_B4_INT_SOURCE7` | `0x4E` | | | `ICM42686P_B4_INT_SOURCE8` | `0x4F` | | | `ICM42686P_B4_INT_SOURCE9` | `0x50` | | | `ICM42686P_B4_INT_SOURCE10` | `0x51` | | | `ICM42686P_B4_OFFSET_USER0` | `0x77` | | | `ICM42686P_B4_OFFSET_USER1` | `0x78` | | | `ICM42686P_B4_OFFSET_USER2` | `0x79` | | | `ICM42686P_B4_OFFSET_USER3` | `0x7A` | | | `ICM42686P_B4_OFFSET_USER4` | `0x7B` | | | `ICM42686P_B4_OFFSET_USER5` | `0x7C` | | | `ICM42686P_B4_OFFSET_USER6` | `0x7D` | | | `ICM42686P_B4_OFFSET_USER7` | `0x7E` | | | `ICM42686P_B4_OFFSET_USER8` | `0x7F` | | | `ICM42686P_WHOAMI_DEFAULT` | `0x44` | | --- Generated 2026-10-07 from the driver header at tiles@e538976. Canonical HTML: https://bergsonne.io/docs/tiles/sense/i6p6 ============================================================================== ## Sense.M.3G driver > Triaxial geomagnetic magnetometer driver for Sense.M.3G (BMM350). | | | | --- | --- | | Tile | `Sense.M.3G` | | Driver version | `1.0.0` | | Header | `tile_sense_m_3g.h` | | Generated from | `tiles@cd2d6a4` | | Canonical page | https://bergsonne.io/docs/tiles/sense/m3g | ### Overview @version 1.0.0 Bosch Sensortec BMM350: 3-axis magnetometer, +/-2000 uT range, ODR from 1.5625 Hz to 400 Hz, on-chip magnetic reset, data-ready interrupt, and a 24-bit sensor-time counter. Output is integer nanotesla (nT) and milli-degrees Celsius, fully temperature- and trim-compensated. 1000 nT = 1 uT; Earth's field is roughly 25000-65000 nT depending on where you are. ### Where the compensation math comes from The BMM350 datasheet (BST-BMM350-DS001-27, rev 1.27) deliberately does NOT publish the compensation formulas or the OTP trim-word layout — its chapter 7 is a single link to Bosch's SensorAPI, and every accuracy figure in Table 3 is qualified "after API compensation". The data registers hold *uncompensated* counts. The OTP word map and the compensation chain implemented here are therefore ported from Bosch Sensortec's BMM350_SensorAPI: https://github.com/boschsensortec/BMM350_SensorAPI Copyright (c) 2025 Bosch Sensortec GmbH, BSD-3-Clause Ported, not copied verbatim: the reference implementation is floating-point, and this driver is integer-only per house style, so the chain runs in Q16.16 fixed point on int64 intermediates. Every ported constant is marked `[API]` in the implementation, and everything marked `[DS]` is traceable to the datasheet instead. If you are changing the compensation, check it against the upstream API, not the datasheet — the datasheet cannot tell you whether it is right. ### Power modes Suspend (the boot state, lowest power, settings retained), normal (free-running at the configured ODR), and forced (one conversion on request, then back to suspend). Forced mode is reachable only FROM suspend — a normal-to-forced transition is ignored by the device, so set_mode() routes through suspend for you. ### The dummy-byte rule Every BMM350 I2C read returns TWO dummy bytes before the real data (datasheet §9.2.3), so an n-byte read must fetch n+2 and discard the front. Verified on hardware. Miss it and every register is silently shifted by two — plausible-looking wrong values rather than an obvious failure. All bus access in this driver goes through helpers that handle it; if you add a raw read, do the same. Datasheet: Bosch Sensortec BMM350, BST-BMM350-DS001-27 ### Examples #### Polled example (Cores SDK) ```c tile_t mag; tile_sense_m_3g_init(core_tiles_pal(&core_i2c1), 0, &mag, NULL); tile_sense_m_3g_set_mode(&mag, SENSE_M_3G_MODE_NORMAL); while (1) { if (tile_sense_m_3g_data_ready(&mag)) { tile_sense_m_3g_read(&mag); printf("%ld %ld %ld nT\n", tile_sense_m_3g_get_x_nt(&mag), tile_sense_m_3g_get_y_nt(&mag), tile_sense_m_3g_get_z_nt(&mag)); } core_delay_ms(10); } ``` #### Interrupt-driven example (INT wired to a Core pad) ```c sense_m_3g_cfg_t cfg = { .odr = SENSE_M_3G_ODR_50HZ, .averaging = SENSE_M_3G_AVG_4, .int_pin = 9, // Core pad carrying INT .on_data = on_mag_sample, }; tile_sense_m_3g_init(core_tiles_pal(&core_i2c1), 0, &mag, &cfg); tile_sense_m_3g_set_mode(&mag, SENSE_M_3G_MODE_NORMAL); while (1) { tile_sense_m_3g_process(&mag); } ``` ### API 30 public functions. Functions marked **Studio** are callable from the Studio behaviour DSL. #### Lifecycle ##### `tile_sense_m_3g_set_mode` — Studio ```c void tile_sense_m_3g_set_mode(tile_t *tile, sense_m_3g_mode_t mode) ``` Set the power mode. Forced mode is reachable only from suspend, so a request to enter it from normal mode is routed through suspend first. Each transition blocks for the settling time the device needs, which for suspend to forced depends on the averaging setting. Init leaves the device in suspend, so nothing is measured until this (or wake(), or trigger_measurement()) is called. - `mode` — Target mode (sense_m_3g_mode_t) ##### `tile_sense_m_3g_trigger_measurement` — Studio ```c uint8_t tile_sense_m_3g_trigger_measurement(tile_t *tile) ``` Trigger a single forced-mode conversion and wait for it. Uses the fast forced mode when the configured ODR is 25 Hz or above and the slow one otherwise, per datasheet §5.1.4. The device returns to suspend on its own once the conversion completes. Does not read the sample — call read() after. **Returns:** 1 if a conversion completed, 0 on timeout ##### `tile_sense_m_3g_sleep` — Studio ```c void tile_sense_m_3g_sleep(tile_t *tile) ``` Put the device in suspend mode (lowest power, settings kept). ##### `tile_sense_m_3g_wake` — Studio ```c void tile_sense_m_3g_wake(tile_t *tile) ``` Return the device to normal (free-running) mode. ##### `tile_sense_m_3g_reset` — Studio ```c void tile_sense_m_3g_reset(tile_t *tile) ``` Soft-reset the device and re-run the full init sequence. Re-downloads the OTP trim data and repeats the magnetic reset, so the compensation coefficients survive. Leaves the device in suspend. ##### `tile_sense_m_3g_magnetic_reset` — Studio ```c uint8_t tile_sense_m_3g_magnetic_reset(tile_t *tile) ``` Run a magnetic reset (bit reset, then flip-gain reset). Restores the transducer's magnetic state after exposure to a strong field. The device does this automatically every ODR tick while measuring and once at boot, so it is only needed after the part has sat in suspend through a field event — the sensor cannot detect one while suspended (datasheet §5.1.5). Worth calling after more than a couple of seconds in suspend, which also recharges the CRST capacitor. Must be run from suspend; a device in normal mode is parked, reset, and restored. **Returns:** 1 if both reset phases were acknowledged, 0 otherwise #### Runtime ##### `tile_sense_m_3g_read` — Studio ```c uint8_t tile_sense_m_3g_read(tile_t *tile) ``` Read one sample and apply the full compensation chain. Burst-reads magnetic and temperature data in a single transaction — mandatory, because the device freezes the data registers for the duration of a burst and single reads would tear across an update (datasheet §5.2). **Returns:** 1 on success, 0 if the bus read failed ##### `tile_sense_m_3g_process` — Studio ```c void tile_sense_m_3g_process(tile_t *tile) ``` Poll for a new sample, then read and dispatch it. Call from your main loop. Checks data-ready (or the INT flag when an INT pin is configured), and on a new sample calls read() and fires the callback. Cheap when nothing is ready. ##### `tile_sense_m_3g_on_data` — Studio ```c void tile_sense_m_3g_on_data(tile_t *tile, sense_m_3g_data_cb_t cb, void *ctx) ``` Register or change the new-sample callback. - `cb` — Callback (NULL to disable) - `ctx` — User context passed to the callback ##### `tile_sense_m_3g_get_x_nt` — Studio ```c int32_t tile_sense_m_3g_get_x_nt(tile_t *tile) ``` Compensated X field from the last read, in nanotesla. **Returns:** X in nT (1000 nT = 1 uT) ##### `tile_sense_m_3g_get_y_nt` — Studio ```c int32_t tile_sense_m_3g_get_y_nt(tile_t *tile) ``` Compensated Y field from the last read, in nanotesla. **Returns:** Y in nT ##### `tile_sense_m_3g_get_z_nt` — Studio ```c int32_t tile_sense_m_3g_get_z_nt(tile_t *tile) ``` Compensated Z field from the last read, in nanotesla. **Returns:** Z in nT ##### `tile_sense_m_3g_get_temperature_mc` — Studio ```c int32_t tile_sense_m_3g_get_temperature_mc(tile_t *tile) ``` Die temperature from the last read, in milli-degrees Celsius. This is the compensation chain's own temperature, not an ambient reading — the die sits above ambient by whatever the board is doing. **Returns:** Temperature in m°C (25000 = 25.000 °C) ##### `tile_sense_m_3g_get_magnitude_nt` — Studio ```c uint32_t tile_sense_m_3g_get_magnitude_nt(tile_t *tile) ``` Field magnitude of the last sample, in nanotesla. sqrt(x² + y² + z²), computed with an integer square root. Useful as a sanity check: a quiet indoor spot should land in the 25000-65000 nT band, and a magnet nearby will swamp it. **Returns:** Magnitude in nT ##### `tile_sense_m_3g_data_ready` — Studio ```c uint8_t tile_sense_m_3g_data_ready(tile_t *tile) ``` Check whether the device has a new sample waiting. Reads INT_STATUS, which clears the flag. Init configures the interrupt as PULSED, and in pulsed mode the flag also clears itself ~1.25 ms after the sample (datasheet §5.5), so a loop polling less often than that misses most samples. For polled use, call configure_interrupt(tile, 0, 0, 1, 1) to latch it until read. **Returns:** 1 if INT_STATUS reports data ready ##### `tile_sense_m_3g_get_sensortime` — Studio ```c uint32_t tile_sense_m_3g_get_sensortime(tile_t *tile) ``` Read the 24-bit sensor-time counter. Ticks at 40 µs resolution and marks when the last sample was generated, not when you read it. Only advances in normal mode unless sensor-time always-on is enabled. Wraps at 24 bits without saturating. **Returns:** Raw 24-bit counter value #### Configuration ##### `tile_sense_m_3g_set_odr_averaging` — Studio ```c void tile_sense_m_3g_set_odr_averaging(tile_t *tile, sense_m_3g_odr_t odr, sense_m_3g_avg_t averaging) ``` Set output data rate and averaging together. The pair is constrained: 400 Hz forces no averaging, 200 Hz caps at 2x, 100 Hz at 4x (datasheet Table 5). An illegal combination is clamped to the highest averaging the requested rate allows rather than rejected. Both live in one register and need an update command, which this issues. - `odr` — Output data rate (sense_m_3g_odr_t) - `averaging` — Averaging (sense_m_3g_avg_t) ##### `tile_sense_m_3g_set_axes` — Studio ```c void tile_sense_m_3g_set_axes(tile_t *tile, uint8_t mask) ``` Enable or disable individual measurement axes. A disabled axis stops converting, saving current, and reads back 0. - `mask` — [0..7] OR of BMM350_EN_X (1) / _Y (2) / _Z (4) ##### `tile_sense_m_3g_configure_interrupt` — Studio ```c void tile_sense_m_3g_configure_interrupt(tile_t *tile, uint8_t enable, uint8_t active_high, uint8_t push_pull, uint8_t latched) ``` Configure the data-ready interrupt on the INT pin. Data-ready is always mapped into INT_STATUS. `latched` also decides how long the INT_STATUS flag lives: latched holds it until read, pulsed clears it after ~1.25 ms (datasheet §5.5). Note the datasheet marks INT_CTRL.int_output_en reserved on A-Si silicon. - `enable` — 1 to drive the INT pin, 0 to leave it idle - `active_high` — 1 for active-high, 0 for active-low - `push_pull` — 1 for push-pull, 0 for open-drain (needs a pull-up) - `latched` — 1 to latch until INT_STATUS is read, 0 for a pulse ##### `tile_sense_m_3g_set_pad_drive` — Studio ```c void tile_sense_m_3g_set_pad_drive(tile_t *tile, uint8_t drive) ``` Set the INT/SDA/SCL pad drive strength (0-7, weakest to strongest). - `drive` — [0..7] Drive strength (reset value 7) ##### `tile_sense_m_3g_set_i2c_watchdog` — Studio ```c void tile_sense_m_3g_set_i2c_watchdog(tile_t *tile, uint8_t enable, uint8_t long_wdt) ``` Enable or disable the device's I2C watchdog. Recovers the bus if the host stalls mid-transaction. Independent of the Core's own watchdog. - `enable` — 1 to enable, 0 to disable - `long_wdt` — 1 for the ~40 ms timeout, 0 for ~1.28 ms ##### `tile_sense_m_3g_set_sensortime_always_on` — Studio ```c void tile_sense_m_3g_set_sensortime_always_on(tile_t *tile, uint8_t enable) ``` Keep the sensor-time counter running outside normal mode. - `enable` — 1 to keep counting in suspend and forced mode #### Advanced ##### `tile_sense_m_3g_get_error` — Studio ```c uint8_t tile_sense_m_3g_get_error(tile_t *tile) ``` Read the device's error register. **Returns:** ERR_REG contents; bit 0 set means the last PMU command was rejected ##### `tile_sense_m_3g_get_pmu_status` — Studio ```c uint8_t tile_sense_m_3g_get_pmu_status(tile_t *tile) ``` Read the PMU command status register. overwritten, bit 2 averaging overwritten, bit 3 normal mode, bit 4 illegal command. Bits [7:5] are reserved in the datasheet; Bosch's SensorAPI reads the last command's value there. **Returns:** PMU_CMD_STATUS_0 (datasheet §8.7): bit 0 busy, bit 1 ODR ##### `tile_sense_m_3g_get_otp_word` — Studio ```c uint16_t tile_sense_m_3g_get_otp_word(tile_t *tile, uint8_t word) ``` Read one of the 32 OTP trim words cached at init. The compensation coefficients are derived from these. Exposed for diagnostics — comparing them against a known-good part is the fastest way to tell a compensation bug from a broken OTP download. - `word` — [0..31] Word index **Returns:** The cached 16-bit OTP word, or 0 if the index is out of range ##### `tile_sense_m_3g_get_raw` — Studio ```c int32_t tile_sense_m_3g_get_raw(tile_t *tile, uint8_t axis) ``` Read the uncompensated 21-bit signed counts for one axis. Straight from the data registers with no compensation applied — the input to the compensation chain. For debugging only; these are not field values and are not in spec. - `axis` — [0..3] 0 = X, 1 = Y, 2 = Z, 3 = temperature **Returns:** Signed raw counts from the last read() ##### `tile_sense_m_3g_read_reg` — Studio ```c uint8_t tile_sense_m_3g_read_reg(tile_t *tile, uint8_t reg) ``` Read a raw 8-bit register, dummy bytes handled. - `reg` — [0..127] Register address **Returns:** Register value, or 0 if the tile is not ready ##### `tile_sense_m_3g_write_reg` — Studio ```c void tile_sense_m_3g_write_reg(tile_t *tile, uint8_t reg, uint8_t value) ``` Write a raw 8-bit register. - `reg` — [0..127] Register address - `value` — [0..255] Value to write #### Other ##### `tile_sense_m_3g_find` ```c uint8_t tile_sense_m_3g_find(tiles_pal_t *hal, uint8_t instance) ``` Probe the bus for a BMM350. - `hal` — Tiles HAL handle (I2C bus) - `instance` — 0 for address 0x14 (Sense.M.3G), 1 for 0x15 **Returns:** 1 if a device ACKs and reports CHIP_ID 0x33, 0 otherwise ##### `tile_sense_m_3g_init` ```c void tile_sense_m_3g_init(tiles_pal_t *hal, uint8_t instance, tile_t *tile, const sense_m_3g_cfg_t *cfg) ``` Initialize the magnetometer. Soft-resets the device, verifies CHIP_ID, downloads the 32 OTP trim words and derives the compensation coefficients from them, powers the OTP back down, then runs a full magnetic reset so the transducer starts from a known magnetic state. Leaves the device in suspend — call set_mode() to start measuring. Takes roughly 100 ms, most of it the OTP download and the magnetic reset's bit-reset and flip-gain-reset settling. - `hal` — Tiles HAL handle (I2C bus) - `instance` — 0 for 0x14, 1 for 0x15 - `tile` — Tile handle to initialize - `cfg` — Optional config. NULL for defaults. ### Vibe Settings Settings Studio's Vibe interface offers for this tile, generated from the driver's `@studio control` annotations. Each is an argument of the function shown. | Setting | Tier | Values | Default | Function | |---|---|---|---|---| | Output data rate | basic | 400 Hz (no averaging only), 200 Hz (averaging up to 2x), 100 Hz (averaging up to 4x), 50 Hz, 25 Hz (driver default), 12.5 Hz, 6.25 Hz, 3.125 Hz, 1.5625 Hz | 25 Hz (driver default) | `set_odr_averaging(odr)` | | Averaging | advanced | 1 sample (low power), 2 samples (regular power), 4 samples (low noise, driver default), 8 samples (ultra low noise, 50 Hz or slower) | 4 samples (low noise, driver default) | `set_odr_averaging(averaging)` | ### Driver gaps Chip capabilities this driver does **not** expose. Each is tagged hardware-gated (the tile does not route the pin) or driver-deferred (possible, not yet implemented). Do not assume an API exists for these. #### Self-test (advanced) The BMM350 can inject a ~130 uT internal field to verify the X and Y channels (datasheet §5.1.6). The datasheet describes the concept and the pass criterion but not the register sequence, which lives in the SensorAPI's self_test_config / TMR_SELFTEST_USER handling. Exposing it means porting that sequence and validating it against a known field. Driver-deferred, and the highest-value thing to add next. #### OTP programming (advanced) OTP write commands (DIR_PRGM, DIR_PRGM_1B, EXT_PRGM) are exposed by the chip but are a factory-provisioning facility — programming OTP is irreversible and would destroy the part's trim data. Read access is used internally for compensation and available through get_otp_word. #### I3C interface and in-band interrupts (niche) Hardware-gated: the BMM350 supports I3C with in-band interrupts (IBI), but Sense.M.3G is wired for I2C and the Core's I3C support is not routed to this tile. INT_CTRL_IBI and the I3C error register are consequently not exposed. #### Cross-axis coefficient override (niche) The cross-axis compensation coefficients are read from OTP and applied automatically. There is no API to override them with values from a system-level calibration; hard-iron and soft-iron correction for a specific enclosure belongs above this driver anyway. ### Enums #### `sense_m_3g_odr_t` | Member | Value | Description | | --- | --- | --- | | `SENSE_M_3G_ODR_400HZ` | `0x2` | 400 Hz (no averaging only) @studio value=400 | | `SENSE_M_3G_ODR_200HZ` | `0x3` | 200 Hz (averaging up to 2x) @studio value=200 | | `SENSE_M_3G_ODR_100HZ` | `0x4` | 100 Hz (averaging up to 4x) @studio value=100 | | `SENSE_M_3G_ODR_50HZ` | `0x5` | 50 Hz @studio value=50 | | `SENSE_M_3G_ODR_25HZ` | `0x6` | 25 Hz (driver default) @studio value=25 | | `SENSE_M_3G_ODR_12_5HZ` | `0x7` | 12.5 Hz @studio value=12.5 | | `SENSE_M_3G_ODR_6_25HZ` | `0x8` | 6.25 Hz @studio value=6.25 | | `SENSE_M_3G_ODR_3_125HZ` | `0x9` | 3.125 Hz @studio value=3.125 | | `SENSE_M_3G_ODR_1_5625HZ` | `0xA` | 1.5625 Hz @studio value=1.5625 | #### `sense_m_3g_avg_t` | Member | Value | Description | | --- | --- | --- | | `SENSE_M_3G_AVG_NONE` | `0x0` | 1 sample (low power) | | `SENSE_M_3G_AVG_2` | `0x1` | 2 samples (regular power) | | `SENSE_M_3G_AVG_4` | `0x2` | 4 samples (low noise, driver default) | | `SENSE_M_3G_AVG_8` | `0x3` | 8 samples (ultra low noise, 50 Hz or slower) | #### `sense_m_3g_mode_t` | Member | Value | Description | | --- | --- | --- | | `SENSE_M_3G_MODE_SUSPEND` | `0x00` | Suspend: no conversions, ~1.8 uA, settings kept | | `SENSE_M_3G_MODE_NORMAL` | `0x01` | Normal: free-running at the configured ODR | | `SENSE_M_3G_MODE_FORCED` | `0x03` | One conversion (full CRST recharge), then suspend | | `SENSE_M_3G_MODE_FORCED_FAST` | `0x04` | One conversion (fast CRST recharge; ODR 25 Hz or faster), then suspend | ### Structs #### `sense_m_3g_cfg_t` | Type | Field | Description | | --- | --- | --- | | `sense_m_3g_odr_t` | `odr` | Output data rate. 0 = default 25 Hz. | | `sense_m_3g_avg_t` | `averaging` | Averaging. 0 = AVG_NONE (this is a real value, not "unset"). | | `uint8_t` | `axes` | Axis mask (BMM350_EN_*). 0 = all three. | | `uint8_t` | `int_pin` | Core pad carrying INT. 0 = polled. | | `sense_m_3g_data_cb_t` | `on_data` | Called by process() on a new sample. | ### Defines | Name | Value | Description | | --- | --- | --- | | `TILE_SENSE_M_3G_VERSION_MAJOR` | `1` | | | `TILE_SENSE_M_3G_VERSION_MINOR` | `0` | | | `TILE_SENSE_M_3G_VERSION_PATCH` | `0` | | | `BMM350_I2C_ADDR_LOW` | `0x14` | | | `BMM350_I2C_ADDR_HIGH` | `0x15` | | | `BMM350_CHIP_ID` | `0x33` | | | `BMM350_DUMMY_BYTES` | `2` | | | `BMM350_MAG_TEMP_DATA_LEN` | `12` | | | `BMM350_PMU_CMD_SUS` | `0x00` | Suspend mode | | `BMM350_PMU_CMD_NM` | `0x01` | Normal mode | | `BMM350_PMU_CMD_UPD_OAE` | `0x02` | Apply a new ODR / averaging setting | | `BMM350_PMU_CMD_FM` | `0x03` | Forced mode | | `BMM350_PMU_CMD_FM_FAST` | `0x04` | Forced mode, fast (ODR >= 25 Hz) | | `BMM350_PMU_CMD_FGR` | `0x05` | Flip-gain reset (magnetic reset) | | `BMM350_PMU_CMD_FGR_FAST` | `0x06` | | | `BMM350_PMU_CMD_BR` | `0x07` | Bit reset (magnetic reset) | | `BMM350_PMU_CMD_BR_FAST` | `0x08` | | | `BMM350_CMD_SOFTRESET` | `0xB6` | | | `BMM350_EN_XYZ` | `0x07` | | --- Generated 2026-10-07 from the driver header at tiles@cd2d6a4. Canonical HTML: https://bergsonne.io/docs/tiles/sense/m3g ============================================================================== ## Sense.MIC driver > Complete driver for the Sense.MIC tile (analog MEMS mic + amp + ADC). I2C-only, command-based protocol via tiles_pal_t raw I2C. | | | | --- | --- | | Tile | `Sense.MIC` | | Driver version | `2.4.0` | | Header | `tile_sense_mic.h` | | Generated from | `tiles@8c11e89` | | Canonical page | https://bergsonne.io/docs/tiles/sense/mic | ### Overview I2C-only, command-based protocol via tiles_pal_t raw I2C. @version 2.4.0 Signal chain (production schematic): - Same Sky CMM-2718AT-38164W-TR: analog, omnidirectional, top-port MEMS mic. −38 dBV/Pa (±1 dB), 64 dBA SNR, 128 dB SPL overload, 170 Ω output, 0.75 V DC output, 175 µA. - AC-coupled through C2 (100 nF) into the + input of an Analog Devices AD8605 (rail-to-rail CMOS op-amp, 1 mA). C3 (100 nF) also sits from that input to GND, so C2/C3 halve the audio on the way in. - Non-inverting gain 1 + R4/R3 = 1 + 47k/1k = 48x (C4 100 pF across R4: ~34 kHz low-pass). Mic to ADC: 0.5 x 48 = 24x. - The amp output drives the MAX11645's AIN0 and pad 8. AIN1 is not connected. - Maxim MAX11645: 12-bit, 2-channel, up to 94.4 ksps, I2C to 1.7 MHz, internal 2.048 V reference. Bias: the + input is biased from the MAX11645's REF pin through R2/R1 (330k/330k, with C6 100 nF on REF), and R3 returns to GND with no DC-blocking capacitor, so the bias is amplified 48x as well. REF is driven only by SENSE_MIC_REF_INTERNAL_BUF (Table 6); then the bias is 1.024 V x 48 and the output sits at the V+ rail — no audio. In every other mode REF is not connected, the bias is ~0 V, and the output rests at 0 V: only the positive half-cycles of the sound reach the ADC. The driver therefore defaults to the VDD reference, measures the resting level at init(), and corrects its SPL figure for the half-wave signal. (A board revision with a capacitor in series with R3 would centre the signal on 1.024 V in the REF_INTERNAL_BUF mode.) The MAX11645 uses a command-based I2C protocol (no register addresses). All bus access goes through tiles_pal_t i2c_write_raw / i2c_read_raw. Platform-agnostic: uses tiles_pal_t for all bus access. Datasheet: Maxim MAX11644/MAX11645, 19-4544; Rev 3, 9/09 ### Examples #### Quick start — polling ```c tile_t mic; tile_sense_mic_init(core_tiles_pal(&core_i2c3), 0, &mic, NULL); uint16_t raw = tile_sense_mic_get_raw(&mic); uint16_t mv = tile_sense_mic_get_raw_mv(&mic); ``` #### Quick start — continuous sampling ```c tile_t mic; tile_sense_mic_init(core_tiles_pal(&core_i2c3), 0, &mic, NULL); uint16_t samples[256]; tile_sense_mic_get_samples(&mic, samples, 256); uint16_t dc = tile_sense_mic_dc_level(samples, 256); uint16_t pp = tile_sense_mic_peak_to_peak(samples, 256); ``` #### Quick start — with internal reference for precise mV ```c sense_mic_cfg_t cfg = { .ref = SENSE_MIC_REF_INTERNAL }; tile_t mic; tile_sense_mic_init(core_tiles_pal(&core_i2c3), 0, &mic, &cfg); // Now get_raw_mv() uses 2048 mV full-scale (0.5 mV/LSB) ``` ### API 25 public functions. Functions marked **Studio** are callable from the Studio behaviour DSL. #### Lifecycle ##### `tile_sense_mic_sleep` — Studio ```c void tile_sense_mic_sleep(tile_t *tile) ``` Enter low-power mode (no conversions). The ADC powers down between conversions on its own; sleep also turns an internal reference off (~330 µA). The mic and amp run from V+ and keep drawing ~1.2 mA: the tile has no switch for them. ##### `tile_sense_mic_wake` — Studio ```c void tile_sense_mic_wake(tile_t *tile) ``` Wake from sleep, restore previous configuration. ##### `tile_sense_mic_reset` — Studio ```c void tile_sense_mic_reset(tile_t *tile) ``` Reset the config register to power-on defaults. Must call init() again. #### Runtime ##### `tile_sense_mic_get_vref_mv` — Studio ```c uint16_t tile_sense_mic_get_vref_mv(tile_t *tile) ``` Get the currently configured reference voltage in millivolts. Returns the Vref used for millivolt conversions (3300 for VDD, 2048 for internal, or the user-specified value for external). ##### `tile_sense_mic_get_raw` — Studio ```c uint16_t tile_sense_mic_get_raw(tile_t *tile) ``` Read a single 12-bit ADC sample (0–4095). The MAX11645 auto-converts on every read — no trigger needed. Conversion time is ~3.5 µs (internal clock); the I2C transaction itself dominates the timing (~55 µs at 400 kHz). ##### `tile_sense_mic_get_raw_mv` — Studio ```c uint16_t tile_sense_mic_get_raw_mv(tile_t *tile) ``` Read a single sample and convert to millivolts. Conversion: mv = raw * vref_mv / 4096 ##### `tile_sense_mic_get_audio_sample` — Studio ```c int16_t tile_sense_mic_get_audio_sample(tile_t *tile) ``` Read a single AC-coupled audio sample (signed, relative to DC offset). Returns: (raw - dc_offset) where dc_offset is auto-calibrated during init(). Useful for audio waveform capture where DC bias is removed. **Returns:** Signed 16-bit value. 0 = silence. ##### `tile_sense_mic_get_dc_offset` — Studio ```c uint16_t tile_sense_mic_get_dc_offset(tile_t *tile) ``` Get the auto-calibrated DC offset (mic bias point). Measured during init() by averaging 64 samples. Near 0 on this board (the amp rests at 0 V; see the header), a few counts of amp offset. ##### `tile_sense_mic_get_samples` — Studio ```c void tile_sense_mic_get_samples(tile_t *tile, uint16_t *buf, uint16_t count) ``` Burst-read N samples into a buffer. Reads are back-to-back; effective sample rate depends on I2C bus speed: - 400 kHz: ~12.5 ksps - 1 MHz: ~16 ksps - `buf` — Output buffer (caller-allocated, min count entries) - `count` — Number of samples to read ##### `tile_sense_mic_dc_level` — Studio ```c uint16_t tile_sense_mic_dc_level(tile_t *tile, const uint16_t *samples, uint16_t count) ``` Compute the DC level (mean) of a sample buffer. Useful for determining the resting level (near 0 on this board). - `samples` — Sample buffer (typically from get_samples()). - `count` — Number of samples in the buffer. **Returns:** DC offset in raw ADC counts. ##### `tile_sense_mic_peak_to_peak` — Studio ```c uint16_t tile_sense_mic_peak_to_peak(tile_t *tile, const uint16_t *samples, uint16_t count) ``` Compute peak-to-peak amplitude of a sample buffer (raw counts). Returns max - min across all samples. Silence is a few counts. On this board only the positive half of the signal is captured, so this is the positive peak, not the full swing. - `samples` — Sample buffer. - `count` — Number of samples. **Returns:** Peak-to-peak amplitude in raw ADC counts. ##### `tile_sense_mic_rms` — Studio ```c uint16_t tile_sense_mic_rms(tile_t *tile, const uint16_t *samples, uint16_t count, uint16_t dc_offset) ``` Compute RMS amplitude relative to a DC offset (raw counts). - `samples` — Sample buffer. - `count` — Number of samples. - `dc_offset` — Resting level (get_dc_offset(), or dc_level() of a quiet buffer). **Returns:** RMS of AC component in raw ADC counts. ##### `tile_sense_mic_amplitude_mv` — Studio ```c uint16_t tile_sense_mic_amplitude_mv(tile_t *tile, uint16_t pp_raw) ``` Convert peak-to-peak raw amplitude to millivolts. Uses the configured reference voltage to scale a peak-to-peak raw ADC count into millivolts: mv = pp_raw * vref_mv / 4096. - `pp_raw` — [0..4095] Peak-to-peak amplitude in raw ADC counts. **Returns:** Amplitude in millivolts. ##### `tile_sense_mic_is_loud` — Studio ```c uint8_t tile_sense_mic_is_loud(tile_t *tile, int16_t threshold_db) ``` Quick "is the room loud right now?" check. Captures a short sample buffer (~64 samples / ~5 ms at 12.5 ksps) via @ref tile_sense_mic_get_samples, computes the RMS amplitude relative to the calibrated DC offset, converts it to dB SPL via the signal-chain model above, and compares against `threshold_db` (0.1 dB units, e.g. 700 = 70.0 dB). - `threshold_db` — [300..1100] SPL threshold in 0.1 dB units (e.g. 700 = 70 dB). Readings start at ~45 dB and clip near 112 dB, so thresholds outside that never change. **Returns:** 1 if measured SPL > threshold, 0 otherwise. **Note:** Blocking. Takes ~5 ms while sampling. ##### `tile_sense_mic_read_spl_db` — Studio ```c int16_t tile_sense_mic_read_spl_db(tile_t *tile) ``` Read instantaneous SPL in 0.1 dB units. Captures a short sample buffer (~64 samples), computes the RMS amplitude relative to the calibrated resting level, scales it to 0.1 mV via the configured Vref, then maps to dB SPL: −38 dBV/Pa (1 Pa = 94 dB = 12.59 mV RMS at the mic, ~302 mV RMS at the ADC after the 24x chain), +3 dB for the half-wave signal. Integer-only, with a 32-entry log10 table. Returns dB SPL in 0.1 dB units (e.g. 700 = 70.0 dB). measurement floor. Negative values are not produced. **Returns:** SPL in 0.1 dB units. 300 (30.0 dB) when below the ~45 dB **Note:** Blocking. Takes ~5 ms while sampling. **Note:** Accuracy regime documented in the section comment above. ##### `tile_sense_mic_wait_for_sound` — Studio ```c uint8_t tile_sense_mic_wait_for_sound(tile_t *tile, int16_t threshold_db, uint32_t timeout_ms) ``` Block until ambient SPL crosses a threshold (or timeout). Polls @ref tile_sense_mic_read_spl_db every ~5 ms until the computed SPL (in 0.1 dB units) exceeds `threshold_db`, or the elapsed time exceeds `timeout_ms`. Useful for "wake on sound" patterns — e.g., wait for a door slam, then fire an action. - `threshold_db` — [300..1100] SPL threshold in 0.1 dB units. - `timeout_ms` — [1..60000] ms Maximum wait, in milliseconds. **Returns:** 1 if threshold was crossed, 0 on timeout. **Note:** Blocking until threshold or timeout. ##### `tile_sense_mic_detect_clap` — Studio ```c uint8_t tile_sense_mic_detect_clap(tile_t *tile, uint32_t timeout_ms) ``` Detect a clap pattern (two peaks, quiet brackets). Watches the SPL stream looking for the canonical clap signature: 1. Quiet bracket (≥100 ms below ~50 dB SPL) — establishes baseline 2. First peak (>~70 dB SPL spike, <50 ms wide) 3. Quiet gap (50–500 ms below threshold) 4. Second peak (>~70 dB SPL spike, <50 ms wide) 5. Quiet bracket (≥100 ms below threshold) — ensures it's really a clap-clap Thresholds are integer-fixed: peak ≈ 700 (70.0 dB), quiet floor ≈ 500 (50.0 dB). The pattern detector samples SPL every ~5 ms. - `timeout_ms` — [1..60000] ms Maximum wait, in milliseconds. **Returns:** 1 if a clap pattern was detected, 0 on timeout. **Note:** Blocking until pattern detected or timeout. False positives on door knocks / drawer slams are expected — this is a coarse pattern detector, not a trained classifier. #### Configuration ##### `tile_sense_mic_set_reference` — Studio ```c void tile_sense_mic_set_reference(tile_t *tile, sense_mic_ref_t ref) ``` Change the reference voltage source. - `ref` — One of the sense_mic_ref_t values (VDD, INTERNAL, etc.) **Note:** The internal reference takes 10 ms to wake; this function waits for it. Don't select REF_INTERNAL_BUF or REF_EXTERNAL on this tile (see sense_mic_ref_t). Call calibrate() afterwards: the resting level in counts changes with the reference. ##### `tile_sense_mic_set_channel` — Studio ```c void tile_sense_mic_set_channel(tile_t *tile, sense_mic_channel_t ch) ``` Change the active ADC channel. - `ch` — SENSE_MIC_CH_AIN0 (mic) or SENSE_MIC_CH_AIN1 ##### `tile_sense_mic_set_scan_mode` — Studio ```c void tile_sense_mic_set_scan_mode(tile_t *tile, sense_mic_scan_t scan) ``` Change the scan mode. - `scan` — SENSE_MIC_SCAN_SINGLE, SENSE_MIC_SCAN_UP, or SENSE_MIC_SCAN_8X ##### `tile_sense_mic_set_clock_mode` — Studio ```c void tile_sense_mic_set_clock_mode(tile_t *tile, sense_mic_clock_t clk) ``` Switch the conversion-clock source. Selects whether the MAX11645 clocks conversions from its internal 2.8 MHz oscillator (default) or from the I2C SCL line. In external-clock mode, conversion timing is locked to the host I2C bus, which makes it possible to synchronise multiple Sense.MIC tiles or to align ADC sampling with another time-base. - `clk` — SENSE_MIC_CLOCK_INTERNAL or SENSE_MIC_CLOCK_EXTERNAL **Note:** External-clock mode only takes effect during the next read transaction; SCL must remain active for the full conversion window or the result will be invalid. ##### `tile_sense_mic_set_polarity` — Studio ```c void tile_sense_mic_set_polarity(tile_t *tile, sense_mic_polarity_t pol) ``` Switch the output coding (unipolar vs bipolar). No effect on this tile: bipolar coding applies only to differential inputs, and the driver always converts single-ended (AIN1 is not connected). get_raw() stays 0-4095 straight binary either way. Kept for API compatibility. For signed audio use get_audio_sample(), which subtracts the calibrated resting level. - `pol` — SENSE_MIC_POLARITY_UNIPOLAR or SENSE_MIC_POLARITY_BIPOLAR #### Advanced ##### `tile_sense_mic_calibrate` — Studio ```c void tile_sense_mic_calibrate(tile_t *tile) ``` Re-calibrate the DC offset (e.g., after changing reference). Averages 64 samples to update the stored bias point. **Note:** Call in a quiet environment for best results. #### Other ##### `tile_sense_mic_find` ```c uint8_t tile_sense_mic_find(tiles_pal_t *hal, uint8_t instance) ``` Check if a Sense.MIC is present on the bus (address probe). Lightweight probe — does not configure the device. The MAX11645 has no WHO_AM_I register; this checks for an ACK at 0x36. ##### `tile_sense_mic_init` ```c void tile_sense_mic_init(tiles_pal_t *hal, uint8_t instance, tile_t *tile, const sense_mic_cfg_t *cfg) ``` Initialize the MAX11645 ADC. Sends setup byte (reference, clock, polarity) and configuration byte (scan mode, channel, single-ended). Verifies the device responds by performing a test read. Pass cfg=NULL for defaults: VDD reference, AIN0 (mic), single-ended, unipolar, internal clock, single-channel scan. **Note:** Blocks for ~5 ms (~15 ms with the internal reference, which takes 10 ms to wake). Call once at startup. ### Vibe Settings Settings Studio's Vibe interface offers for this tile, generated from the driver's `@studio control` annotations. Each is an argument of the function shown. | Setting | Tier | Values | Default | Function | |---|---|---|---|---| | Loudness threshold | basic | 30 to 110 dB | 700 | `is_loud(threshold_db)` | | Sound wait threshold | basic | 30 to 110 dB | 700 | `wait_for_sound(threshold_db)` | | Voltage reference | advanced | Supply voltage (V+), Internal 2.048 V | Supply voltage (V+) | `set_reference(ref)` | | Sound wait timeout | advanced | 1 to 60000 ms | 5000 | `wait_for_sound(timeout_ms)` | | Clap wait timeout | advanced | 1 to 60000 ms | 5000 | `detect_clap(timeout_ms)` | ### Driver gaps Chip capabilities this driver does **not** expose. Each is tagged hardware-gated (the tile does not route the pin) or driver-deferred (possible, not yet implemented). Do not assume an API exists for these. #### Full-wave audio (common) Hardware-gated. The amp rests at 0 V in every usable mode (its bias comes from the undriven REF pin and is gained 48x), so only positive half-cycles reach the ADC: fine for levels and events, not for recording a waveform. Needs a board revision (a capacitor in series with R3, then the REF_INTERNAL_BUF mode centres the signal). #### Differential / bipolar input (niche) Hardware-gated: AIN1 is not connected, so the ADC runs single-ended, where the MAX11645 ignores the bipolar setting. #### External reference voltage on REF pin (advanced) Hardware-gated. The MAX11645 accepts an external reference on its REF pin, but on this tile REF only feeds the on-board amp bias divider (R2/R1) and its 100 nF decoupling cap (C6); it is not on any pad, and AIN1 is unconnected. Closing this gap needs a tile revision. Until then SENSE_MIC_REF_EXTERNAL configures the chip but has no usable reference source. ### Enums #### `sense_mic_ref_t` Reference voltage selection. | Member | Value | Description | | --- | --- | --- | | `SENSE_MIC_REF_VDD` | `0x00` | Supply voltage (V+) | | `SENSE_MIC_REF_EXTERNAL` | `0x02` | External reference on the REF pin (not routed on this tile) | | `SENSE_MIC_REF_INTERNAL` | `0x05` | Internal 2.048 V | | `SENSE_MIC_REF_INTERNAL_BUF` | `0x07` | Internal 2.048V, also on the REF pin (saturates this board's amp) | #### `sense_mic_channel_t` ADC channel selection. | Member | Value | Description | | --- | --- | --- | | `SENSE_MIC_CH_AIN0` | `0` | AIN0 — microphone (default) | | `SENSE_MIC_CH_AIN1` | `1` | AIN1 | #### `sense_mic_scan_t` Scan mode. | Member | Value | Description | | --- | --- | --- | | `SENSE_MIC_SCAN_SINGLE` | `0x03` | Single channel conversion (default) | | `SENSE_MIC_SCAN_UP` | `0x00` | Scan from AIN0 up to CS0 | | `SENSE_MIC_SCAN_8X` | `0x01` | Convert CS0 8 times, 8 results per read | #### `sense_mic_clock_t` Conversion-clock source. | Member | Value | Description | | --- | --- | --- | | `SENSE_MIC_CLOCK_INTERNAL` | `0` | Internal 2.8 MHz oscillator (default) | | `SENSE_MIC_CLOCK_EXTERNAL` | `1` | Host-clocked via SCL | #### `sense_mic_polarity_t` Output coding (unipolar vs bipolar). | Member | Value | Description | | --- | --- | --- | | `SENSE_MIC_POLARITY_UNIPOLAR` | `0` | 0 .. VREF, straight binary (default) | | `SENSE_MIC_POLARITY_BIPOLAR` | `1` | ±VREF/2, two's complement | ### Structs #### `sense_mic_cfg_t` | Type | Field | Description | | --- | --- | --- | | `uint8_t` | `ref` | Voltage reference (sense_mic_ref_t). Default: SENSE_MIC_REF_VDD. | | `uint8_t` | `channel` | ADC channel (sense_mic_channel_t). Default: SENSE_MIC_CH_AIN0. | | `uint8_t` | `scan` | Scan mode (sense_mic_scan_t). 0 = default, SENSE_MIC_SCAN_SINGLE. | | `uint8_t` | `clock` | Conversion clock (sense_mic_clock_t). Default: SENSE_MIC_CLOCK_INTERNAL. | | `uint8_t` | `polarity` | Output coding (sense_mic_polarity_t). Default: SENSE_MIC_POLARITY_UNIPOLAR. | | `uint16_t` | `vref_mv` | Reference voltage in mV. 0 = auto (3300 for VDD, 2048 for internal). Set it to the real supply when using the VDD reference off 3.3 V. | ### Defines | Name | Value | Description | | --- | --- | --- | | `TILE_SENSE_MIC_VERSION_MAJOR` | `2` | | | `TILE_SENSE_MIC_VERSION_MINOR` | `4` | | | `TILE_SENSE_MIC_VERSION_PATCH` | `0` | | | `MAX11645_I2C_ADDR` | `0x36` | | | `MAX11645_ADC_BITS` | `12` | | --- Generated 2026-10-07 from the driver header at tiles@8c11e89. Canonical HTML: https://bergsonne.io/docs/tiles/sense/mic ============================================================================== ## Sense.T.C driver > Capacitive touch/proximity driver for the Sense.T.C tile (IQS323). | | | | --- | --- | | Tile | `Sense.T.C` | | Driver version | `1.4.0` | | Header | `tile_sense_t_c.h` | | Generated from | `tiles@4c565e2` | | Canonical page | https://bergsonne.io/docs/tiles/sense/tc | ### Overview @version 1.4.0 Azoteq IQS323 ProxFusion controller with self-capacitive touch sensing. The tile's entire top surface is a touch electrode (CH1/CRx1), and a second channel (CH0/CRx0) is available via pad 8 for external sensors. init() soft-resets the chip and configures it: CH1 on the surface electrode (CRx1/CTx1) and CH0 on pad 8 (CRx0/CTx0), self-capacitance, prox 20 / touch 40, Azoteq's EV-kit filters and report rates (NP 16 ms, LP 60, ULP 160, Halt 3000, auto step-down after 2 s), AUTO power, ATI, then I2C event mode. The chip's own reset values leave every threshold, rate and filter at 0 and point all three channels at CRx0, so none of this is optional. If the chip resets later (brown-out, watchdog), process() sees the reset event and configures it again. Two ways to drive process(): - **Polled mode** (default): call process() from your main loop. No RDY pin wiring required. Each call forces a communication window (about 15 ms). - **RDY mode**: provide a rdy_pin in config. EXTI falling-edge ISR sets a flag; process() only does I2C when the flag is set, which in event mode means a touch or prox change. Lower latency, far less I2C traffic. Event callback: register an on_event callback to be notified of touch, proximity, slider, and gesture changes without polling individual status functions. Slider note: a slider needs at least two channels on the SAME IQS323 (the surface CH1 plus an electrode on pad 8 / CH0; CH2 is not routed), configured through the slider registers (0x90-0x98) with write_reg. Two separate tiles cannot form one slider: each has its own controller. A single tile surface alone cannot produce a slider output. Datasheet: Azoteq IQS323, Rev v1.11, August 2025 ### Examples #### Simple polling example (Cores SDK) ```c tile_t touch; tile_sense_t_c_init(core_tiles_pal(&core_i2c3), 0, &touch, NULL); while (1) { tile_sense_t_c_process(&touch); if (tile_sense_t_c_is_touched(&touch, SENSE_T_C_CH_SURFACE)) led_on(); else led_off(); core_delay_ms(30); } ``` #### Callback example (polled) ```c void on_touch(tile_t *t, uint16_t status, void *ctx) { if (status & SENSE_T_C_SURFACE_TOUCH) led_green(); else led_off(); } sense_t_c_cfg_t cfg = { .on_event = on_touch }; tile_sense_t_c_init(core_tiles_pal(&core_i2c3), 0, &touch, &cfg); while (1) { tile_sense_t_c_process(&touch); core_delay_ms(30); } ``` #### Callback example (RDY interrupt) ```c sense_t_c_cfg_t cfg = { .rdy_pin = 3, .on_event = on_touch, }; tile_sense_t_c_init(core_tiles_pal(&core_i2c3), 0, &touch, &cfg); while (1) { tile_sense_t_c_process(&touch); // ... other work — process() returns fast if no RDY event ... } ``` ### API 37 public functions. Functions marked **Studio** are callable from the Studio behaviour DSL. #### Lifecycle ##### `tile_sense_t_c_sleep` — Studio ```c void tile_sense_t_c_sleep(tile_t *tile) ``` Enter low-power sleep mode (Halt, about 2 µA). The chip stops sensing; process() does nothing until wake(). ##### `tile_sense_t_c_wake` — Studio ```c void tile_sense_t_c_wake(tile_t *tile) ``` Wake from sleep mode. Restores the power mode last chosen with set_power_mode() (init: AUTO). #### Runtime ##### `tile_sense_t_c_get_status` — Studio ```c uint16_t tile_sense_t_c_get_status(tile_t *tile) ``` Read the cached System Status word from the last process() call. ##### `tile_sense_t_c_get_gestures` — Studio ```c uint16_t tile_sense_t_c_get_gestures(tile_t *tile) ``` Read the Gesture Status word (a live read, not cached). ##### `tile_sense_t_c_is_touched` — Studio ```c uint8_t tile_sense_t_c_is_touched(tile_t *tile, uint8_t channel) ``` Check if the given channel is currently touched. Reads the cached System Status from the last process() call. - `channel` — [0..2] 0 = pad 8 electrode, 1 = tile surface, 2 = not routed **Returns:** 1 if touched, 0 otherwise ##### `tile_sense_t_c_is_prox` — Studio ```c uint8_t tile_sense_t_c_is_prox(tile_t *tile, uint8_t channel) ``` Check if the given channel currently reports proximity. Reads the cached System Status from the last process() call. - `channel` — [0..2] 0 = pad 8 electrode, 1 = tile surface, 2 = not routed **Returns:** 1 if in proximity, 0 otherwise ##### `tile_sense_t_c_get_counts` — Studio ```c uint16_t tile_sense_t_c_get_counts(tile_t *tile, uint8_t channel) ``` Read the filtered counts value for a channel (0x13/0x15/0x17). Self-capacitance counts DROP when a finger approaches. - `channel` — [0..2] Channel **Returns:** Filtered counts, or 0 for a bad channel ##### `tile_sense_t_c_get_lta` — Studio ```c uint16_t tile_sense_t_c_get_lta(tile_t *tile, uint8_t channel) ``` Read the long-term average (LTA) for a channel. - `channel` — [0..2] Channel **Returns:** LTA baseline in counts ##### `tile_sense_t_c_get_delta` — Studio ```c int16_t tile_sense_t_c_get_delta(tile_t *tile, uint8_t channel) ``` Read the delta (counts - LTA) for a channel. - `channel` — [0..2] Channel **Returns:** counts - LTA; negative when a finger is near a self-cap channel ##### `tile_sense_t_c_get_slider` — Studio ```c uint16_t tile_sense_t_c_get_slider(tile_t *tile) ``` Read the slider position (if a slider is configured). Raw Slider Position register (0x12): 0..Slider Resolution (0x93). Stays 0 until a slider is configured (see the slider note above). **Returns:** Slider position ##### `tile_sense_t_c_set_thresholds` — Studio ```c void tile_sense_t_c_set_thresholds(tile_t *tile, uint8_t channel, uint8_t prox_thresh, uint8_t touch_thresh) ``` Set proximity and touch thresholds for a channel. Prox is an absolute delta in counts (A.16, written with 4/4 debounce); touch is relative, threshold/256 of the LTA (A.17). A 0 leaves that register unchanged. Azoteq's EV-kit uses prox 20, touch 40. These per-channel values are lost if the chip resets (process() re-applies the all-channel thresholds from init / set_touch_threshold / set_prox_threshold). - `channel` — [0..2] Channel - `prox_thresh` — [0..255] Prox threshold, delta counts (0 = unchanged) - `touch_thresh` — [0..255] Touch threshold, x/256 of LTA (0 = unchanged) ##### `tile_sense_t_c_is_touched_any` — Studio ```c uint8_t tile_sense_t_c_is_touched_any(tile_t *tile) ``` Check whether *any* channel is currently touched. Reflects the cached System Status from the most recent process() call — it does not perform an I²C read itself, so it's safe to call from any rate. Returns 1 if any of CH0/CH1/CH2 currently reports touch, 0 otherwise. Pair with @ref tile_sense_t_c_process in your main loop. **Returns:** 1 if any channel is touched, 0 otherwise ##### `tile_sense_t_c_wait_for_touch` — Studio ```c uint8_t tile_sense_t_c_wait_for_touch(tile_t *tile, uint32_t timeout_ms) ``` Block until any channel is touched (or timeout elapses). Polls process() at ~5 ms cadence and returns as soon as any channel reports touch. If RDY-mode is configured, process() is a no-op until RDY fires, so the poll is effectively interrupt-paced. In polled mode the cadence determines latency. Returns 0 if `timeout_ms` elapses without a touch event. - `timeout_ms` — Maximum time to wait, in milliseconds **Returns:** 1 if a touch was detected, 0 on timeout ##### `tile_sense_t_c_wait_for_gesture` — Studio ```c uint16_t tile_sense_t_c_wait_for_gesture(tile_t *tile, uint32_t timeout_ms) ``` Block until any gesture fires (or timeout elapses). Polls the Gesture Status register at ~5 ms cadence. Returns the raw 16-bit gesture word as soon as any gesture bit is set (IQS323_GESTURE_TAP / SWIPE_POS / SWIPE_NEG / FLICK_POS / FLICK_NEG / HOLD). Returns 0 on timeout. Gestures require the slider/wheel UI to be configured — a single tile surface alone does not produce gesture events. See the datasheet §7.4 for the slider-config requirements. - `timeout_ms` — Maximum time to wait, in milliseconds **Returns:** Raw Gesture Status word (non-zero) on detection, 0 on timeout ##### `tile_sense_t_c_read_slider_pct` — Studio ```c uint8_t tile_sense_t_c_read_slider_pct(tile_t *tile, uint8_t *out_pct) ``` Read the slider position scaled to 0–100 percent. The IQS323 slider position runs 0..Slider Resolution (register 0x93, datasheet §7.4). This helper reads both and integer-scales the position to 0–100 through `out_pct`. Returns 0 if either read returned an invalid (out-of-window) response, or if the resolution is 0 (no slider configured): `out_pct` is untouched in that case so callers can keep the previous value. Like @ref tile_sense_t_c_get_slider, this requires a valid slider configuration on this chip (the surface plus an electrode on pad 8). A single tile surface alone cannot produce a slider output. - `out_pct` — Pointer to receive the percentage (0–100) **Returns:** 1 on success, 0 on read failure (out_pct unmodified) ##### `tile_sense_t_c_reseed` — Studio ```c void tile_sense_t_c_reseed(tile_t *tile) ``` Force the long-term-average baseline to re-snap to current counts. Sets SYSTEM_CONTROL.RESEED (self-clearing). Use after a known environmental change (object placed/removed, cover added) to discard the slow baseline and re-zero the channels immediately, instead of waiting for the LTA filter to drift there. #### Events ##### `tile_sense_t_c_process` — Studio ```c void tile_sense_t_c_process(tile_t *tile) ``` Read pending touch/proximity events and update internal state. Call from your main loop. In polled mode: reads status register, fires callback if events present. In RDY mode: returns immediately if no RDY interrupt; reads + fires callback only when the device has new data. ##### `tile_sense_t_c_on_event` — Studio ```c void tile_sense_t_c_on_event(tile_t *tile, sense_t_c_event_cb_t cb, void *ctx) ``` Register or change the event callback for touch/proximity events. - `cb` — Callback function (NULL to disable) - `ctx` — User context passed to callback #### Configuration ##### `tile_sense_t_c_set_touch_threshold` — Studio ```c void tile_sense_t_c_set_touch_threshold(tile_t *tile, uint8_t threshold) ``` Set the touch threshold on every enabled channel. The touch threshold is relative: a touch registers when the counts drop by threshold/256 of the channel's baseline (A.17). Lower is more sensitive (a touch through thicker covers); higher rejects near misses. Kept by the driver, so it survives a chip reset. 0 is ignored. - `threshold` — [1..255] x/256 of the LTA (default 40, about 16 %) ##### `tile_sense_t_c_set_prox_threshold` — Studio ```c void tile_sense_t_c_set_prox_threshold(tile_t *tile, uint8_t threshold) ``` Set the proximity threshold on every enabled channel. Proximity is an absolute drop in counts (A.16), written with Azoteq's 4/4 debounce. Lower detects a hand from further away. Kept by the driver, so it survives a chip reset. 0 is ignored. - `threshold` — [1..255] delta counts (default 20) ##### `tile_sense_t_c_set_power_mode` — Studio ```c void tile_sense_t_c_set_power_mode(tile_t *tile, sense_t_c_power_mode_t mode) ``` Set the device power mode. AUTO steps from normal to low and ultra-low power after the power timeout without activity, and back on a touch (datasheet §5.3). Typical draw (3 self-cap channels, EV-kit rates): normal 125 µA, low 37.5 µA, ultra-low 4 µA, halt 2 µA. Values above 5 are ignored. - `mode` — Power mode (System Control bits 6-4); init selects AUTO ##### `tile_sense_t_c_enable_events` — Studio ```c void tile_sense_t_c_enable_events(tile_t *tile, uint16_t mask) ``` Set the events-enable mask (which events drive RDY pulses). - `mask` — Event bits (IQS323_STATUS_*_EVENT); init enables touch + prox ##### `tile_sense_t_c_ati` — Studio ```c void tile_sense_t_c_ati(tile_t *tile) ``` Trigger an ATI (auto-tuning) cycle and wait for completion. ##### `tile_sense_t_c_set_ati_setup` — Studio ```c void tile_sense_t_c_set_ati_setup(tile_t *tile, uint8_t channel, uint16_t value) ``` Set the per-channel ATI setup register (0x36 + ch*0x10). Writes the raw 16-bit ATI Setup register (0x36 + ch·0x10) for the channel. Bit layout (datasheet §A.12): - Bits 15-4: ATI Target / Resolution Factor - Bit 3: ATI Band (0 = 1/16, 1 = 1/8) - Bits 2-0: ATI Mode (0 disabled, 1 comp-only, 2 from-comp-divider, 3 partial, 4 full) (The coarse/fine multipliers and dividers live in the separate ATI Multipliers/Dividers register at 0x38 — see IQS323_REG_ATI_MULTI; the compensation divider lives at 0x39 — see set_compensation.) After changing ATI parameters, call @ref tile_sense_t_c_ati to re-run the auto-tuning sequence with the new values. Useful when ambient capacitance shifts (cover-glass thickness, mounting substrate) push the working point off the chip's defaults. - `channel` — 0–2 - `value` — Raw 16-bit ATI Setup register value ##### `tile_sense_t_c_set_counts_filter` — Studio ```c void tile_sense_t_c_set_counts_filter(tile_t *tile, uint16_t beta) ``` Set the global counts-filter beta register (0xB0). Datasheet §A.26. Higher beta = more aggressive smoothing on the raw counts (lower noise, slower response). Default is tuned for typical electrode geometries; tune up for noisy environments, tune down for fast-response applications. - `beta` — Raw 16-bit Counts Filter Betas register value ##### `tile_sense_t_c_set_conversion_freq` — Studio ```c void tile_sense_t_c_set_conversion_freq(tile_t *tile, uint8_t channel, uint16_t value) ``` Set per-channel conversion frequency (0x31 + ch*0x10). Datasheet §A.4. Controls charge-transfer frequency and dead time. Tune for unusual electrode geometries, large capacitance loads, or when self-capacitance interactions push beyond the chip's default Conversion Frequency Fraction. - `channel` — 0–2 - `value` — Raw 16-bit Conversion Frequency register value ##### `tile_sense_t_c_set_channel_mode` — Studio ```c void tile_sense_t_c_set_channel_mode(tile_t *tile, uint8_t channel, sense_t_c_channel_mode_t mode, uint8_t reference_id) ``` Configure a channel's role in the Reference UI. Reference channels measure ambient capacitance (e.g., temperature / humidity drift on the substrate). Follower channels subtract the reference's LTA delta from their own, eliminating common-mode drift. Datasheet §7.3 describes the design pattern. For follower channels, `reference_id` selects which channel to follow (encoded in Channel Setup bits [7:4]). For independent / reference channels, `reference_id` is unused — pass 0. Apply to all participating channels (set the reference channel to REFERENCE first, then set followers). Re-run @ref tile_sense_t_c_ati after changing channel modes. - `channel` — 0–2 - `mode` — Channel role - `reference_id` — Which channel to follow (only used for FOLLOWER) ##### `tile_sense_t_c_set_comm_mode` — Studio ```c void tile_sense_t_c_set_comm_mode(tile_t *tile, sense_t_c_comm_mode_t mode) ``` Switch between event-mode and streaming-mode I²C. Event mode opens a communication window (RDY low) only for an enabled event (touch or prox change, a reset); lowest power, lowest CPU overhead. init() selects event mode. Streaming mode opens one every report cycle; the chip then waits up to the I2C timeout (200 ms) for the host each time, so use it only with the RDY pin and a host that services every window (continuous logging of counts). - `mode` — Event or streaming ##### `tile_sense_t_c_set_report_rate` — Studio ```c void tile_sense_t_c_set_report_rate(tile_t *tile, sense_t_c_power_mode_t mode, uint16_t ms) ``` Set the report rate (conversion interval) for a power mode. Writes the per-mode report-rate register (NP 0xC1 / LP 0xC2 / ULP 0xC3 / HALT 0xC4) in plain milliseconds (clamped 0-3000). This is the tile's headline power/latency knob — faster rate = lower latency, higher current. Only the four reporting modes have a rate; AUTO/AUTO_NO_ULP are ignored. init() writes Azoteq's EV-kit values, NP 16 ms, LP 60 ms, ULP 160 ms, Halt 3000 ms (the chip's reset value is 0 for all four). These are lost if the chip resets; process() restores the defaults. - `mode` — Which power mode's rate to set (NORMAL/LOW/ULTRA_LOW/HALT). - `ms` — Report interval in milliseconds (0-3000). ##### `tile_sense_t_c_set_power_timeout` — Studio ```c void tile_sense_t_c_set_power_timeout(tile_t *tile, uint16_t ms) ``` Set the auto power-mode step-down timeout. Writes POWER_TIMEOUT (0xC5) in milliseconds. In AUTO / AUTO_NO_ULP the chip steps down a power mode after this much inactivity; 0 disables auto step-down. init() writes Azoteq's EV-kit 2000 ms (the chip's reset value is 0). - `ms` — [0..65000] Inactivity timeout in milliseconds (0 = off). #### Advanced ##### `tile_sense_t_c_read_reg` — Studio ```c uint16_t tile_sense_t_c_read_reg(tile_t *tile, uint8_t reg) ``` Read a raw 16-bit IQS323 register. - `reg` — [0..255] Register address (datasheet §9 memory map) **Returns:** Register value, 0xEEEE if no comms window opened ##### `tile_sense_t_c_write_reg` — Studio ```c void tile_sense_t_c_write_reg(tile_t *tile, uint8_t reg, uint16_t value) ``` Write a raw 16-bit IQS323 register. - `reg` — [0..255] Register address (datasheet §9 memory map) - `value` — 16-bit value ##### `tile_sense_t_c_get_compensation` — Studio ```c uint16_t tile_sense_t_c_get_compensation(tile_t *tile, uint8_t channel) ``` Read a channel's compensation (CAPDAC-equivalent offset). Returns the raw Compensation register (0x39 + ch·0x10). The low 10 bits are the compensation value; bits 15-11 are the divider. Normally set by ATI — read it to capture a tuned working point. - `channel` — 0-2. **Returns:** Raw 16-bit compensation register, or 0 on a bad channel. ##### `tile_sense_t_c_set_compensation` — Studio ```c void tile_sense_t_c_set_compensation(tile_t *tile, uint8_t channel, uint16_t value, uint8_t divider) ``` Override a channel's compensation value + divider. Writes the Compensation register (0x39 + ch·0x10): value (0-1023) in bits 9-0, divider (0-31) in bits 15-11. NOTE: a subsequent full ATI overwrites this — to hold a fixed offset, set the channel's ATI Mode (set_ati_setup) to a non-full mode first. - `channel` — 0-2. - `value` — Compensation value (0-1023). - `divider` — Compensation divider (0-31). #### Other ##### `tile_sense_t_c_find` ```c uint8_t tile_sense_t_c_find(tiles_pal_t *hal, uint8_t instance) ``` Probe the bus for an IQS323 touch controller. Instance 0 is 0x44 (order code IQS323-001, this tile); instance 1 is 0x58 (IQS323-002). The address is fixed by the order code, not a strap. - `hal` — Tiles HAL handle (I2C bus) - `instance` — Device instance (0 or 1, selects I2C address) **Returns:** 1 if found, 0 if not ##### `tile_sense_t_c_init` ```c void tile_sense_t_c_init(tiles_pal_t *hal, uint8_t instance, tile_t *tile, const sense_t_c_cfg_t *cfg) ``` Initialize the touch controller. Soft resets, then configures the chip (see the file header): channels, thresholds, filters, report rates, AUTO power, ATI, event mode. Takes roughly 0.5-2 s, most of it ATI and the forced communication windows. - `hal` — Tiles HAL handle (I2C bus) - `instance` — Device instance (0 or 1, selects I2C address) - `tile` — Tile handle to initialize - `cfg` — Optional config (thresholds, RDY pin, callback). NULL for defaults. ##### `tile_sense_t_c_reset` ```c void tile_sense_t_c_reset(tile_t *tile) ``` Soft-reset the IQS323 (System Control bit 1). All registers return to their reset values (thresholds, report rates and filters 0, every channel on CRx0) and the tile leaves the READY state. Call init() again afterwards. ### Vibe Settings Settings Studio's Vibe interface offers for this tile, generated from the driver's `@studio control` annotations. Each is an argument of the function shown. | Setting | Tier | Values | Default | Function | |---|---|---|---|---| | Touch threshold | basic | 1 to 255 | 40 | `set_touch_threshold(threshold)` | | Proximity threshold | advanced | 1 to 255 | 20 | `set_prox_threshold(threshold)` | | Power mode | advanced | Normal power mode, Low power mode, Ultra-low power mode, Halt (lowest power), Auto power mode switching, Auto mode, skip ultra-low power | Auto power mode switching | `set_power_mode(mode)` | | I2C reporting | advanced | Continuous reporting at the configured power-mode rate (the chip's reset default), RDY pulses only on enabled events (what init() selects) | RDY pulses only on enabled events (what init() selects) | `set_comm_mode(mode)` | | Idle time before low power | advanced | 0 to 65000 | 2000 | `set_power_timeout(ms)` | ### Driver gaps Chip capabilities this driver does **not** expose. Each is tagged hardware-gated (the tile does not route the pin) or driver-deferred (possible, not yet implemented). Do not assume an API exists for these. #### Per-gesture threshold tuning (advanced) IQS323 gesture timing (tap touch/release time, hold duration, swipe distance, flick velocity) is packed across several GESTURE_* registers with chip-specific encodings. The driver reports gesture events but doesn't surface a typed API for timing / threshold tuning — closing this properly needs a dedicated pass with Azoteq's gesture-config guide. Raw write_reg works for advanced users in the interim. #### Per-channel timeout / OutA event-indicator (niche) Channel-timeout disable bits (auto-reseed on timeout) and the OutA pin's event-indicator output mode aren't exposed. Most applications don't need them; raw write_reg covers the niche cases that do. OutA is also not routed on this tile (HW-gated). #### Mutual-capacitance / inductive sensing (niche) The chip can run mutual-capacitance (Tx/Rx pairs) and inductive channels. HW-gated: the tile routes one self-cap electrode per channel (surface on CRx1, pad 8 on CRx0) and no TxA; CRx2 is not routed. The driver configures self-capacitance only. #### Hardware reset (MCLR) (niche) Pad 3 is RDY/MCLR: pulling it low outside a window hard-resets the chip. Driver-deferred: the driver uses the soft reset and the pad only as the RDY input. The on-tile 100 nF on the pin slows any host-driven pulse. #### Release UI (long-touch release) (advanced) The tile's IQS323-001 includes the Release UI: an activation LTA that lets a long-held touch or prox release cleanly (registers 0x20-0x25, 0xD4, Sensor Setup bit 6). Driver-deferred; write_reg reaches it. The Movement UI is a different build (IQS323-A01), not on this tile. ### Enums #### `sense_t_c_channel_mode_t` Channel Mode for the Reference UI (datasheet §A.15 bits [3:0]). | Member | Value | Description | | --- | --- | --- | | `SENSE_T_C_CHANNEL_INDEPENDENT` | `0x00` | Stand-alone sensing channel | | `SENSE_T_C_CHANNEL_FOLLOWER` | `0x01` | Subtracts a reference channel's LTA | | `SENSE_T_C_CHANNEL_REFERENCE` | `0x02` | Acts as ambient reference for followers | #### `sense_t_c_comm_mode_t` I²C communication mode (System Control register §A.30 bit 7). | Member | Value | Description | | --- | --- | --- | | `SENSE_T_C_COMM_STREAM` | `0` | Continuous reporting at the configured power-mode rate (the chip's reset default) | | `SENSE_T_C_COMM_EVENT` | `1` | RDY pulses only on enabled events (what init() selects) | ### Structs #### `sense_t_c_cfg_t` | Type | Field | Description | | --- | --- | --- | | `uint8_t` | `rdy_pin` | Platform pin for RDY. 0 = polled mode. | | `sense_t_c_event_cb_t` | `on_event` | Callback. NULL = no callback. | | `uint8_t` | `channels` | Channel enable bitmask (bit 0 = CH0 pad 8, bit 1 = CH1 surface). 0 = both. CH2 is not routed and always off. | | `uint8_t` | `prox_threshold` | Proximity threshold, delta counts (0 = default 20). | | `uint8_t` | `touch_threshold` | Touch threshold, x/256 of the LTA (0 = default 40). | ### Defines | Name | Value | Description | | --- | --- | --- | | `TILE_SENSE_T_C_VERSION_MAJOR` | `1` | | | `TILE_SENSE_T_C_VERSION_MINOR` | `4` | | | `TILE_SENSE_T_C_VERSION_PATCH` | `0` | | | `IQS323_I2C_ADDR_001` | `0x44` | | | `IQS323_I2C_ADDR_002` | `0x58` | | | `SENSE_T_C_CH0` | `0` | CRx0/CTx0 -- pad 8 (external) | | `SENSE_T_C_CH1` | `1` | CRx1/CTx1 -- tile top surface | | `SENSE_T_C_CH2` | `2` | CRx2/CTx2 -- not routed on this tile; always disabled | | `SENSE_T_C_NUM_CHANNELS` | `3` | | | `IQS323_STATUS_POWER_SHIFT` | `14` | | | `IQS323_EVENTS_DEFINED` | `0x005F` | | | `IQS323_INVALID_RESPONSE` | `0xEEEE` | | | `IQS323_CTRL_POWER_SHIFT` | `4` | | | `IQS323_HW_ID_3DD` | `0xF003` | | | `IQS323_HW_ID_3ED` | `0xF004` | | --- Generated 2026-10-07 from the driver header at tiles@4c565e2. Canonical HTML: https://bergsonne.io/docs/tiles/sense/tc ============================================================================== ## Sense.TOF driver > TMF8806 time-of-flight distance sensor. | | | | --- | --- | | Tile | `Sense.TOF` | | Driver version | `1.6.0` | | Header | `tile_sense_tof.h` | | Generated from | `tiles@65a4fd8` | | Canonical page | https://bergsonne.io/docs/tiles/sense/tof | ### Overview Platform-agnostic driver for the AMS/Sciosense TMF8806 direct time-of-flight (dToF) sensor with integrated VCSEL emitter and SPAD detector array. Key specifications: - Distance measurement up to 5000 mm (short-range, 2.5 m, and 5 m modes) - 16-bit distance output in millimeters - Configurable measurement period (30 ms to 2000 ms, or single-shot) - Configurable iteration count for accuracy vs. speed trade-off - 6-bit reliability indicator: 0 = no object; 1 / 10 = short-range algorithm result (uncalibrated / calibrated); other values 2..63 = long-range result, 63 best (datasheet Table 42) - On-chip factory calibration with host-side storage and reload - Algorithm state save/restore for ultra-low-power resume - Embedded 8-bit temperature sensor Datasheet: https://ams.com/tmf8806 ### Examples #### Quick start ```c #include "core.h" #include "core_tiles.h" #include "tile_sense_tof.h" tile_t tof; sense_tof_cfg_t cfg = { .mode = SENSE_TOF_RANGE_2500MM }; tile_sense_tof_init(core_tiles_pal(&core_i2c1), 0, &tof, &cfg); tile_sense_tof_start(&tof); // ... poll or wait for interrupt ... uint16_t dist = tile_sense_tof_get_distance_mm(&tof); // Single-shot convenience: sense_tof_result_t res; tile_sense_tof_measure_single(&tof, &res, 500); ``` ### API 37 public functions. Functions marked **Studio** are callable from the Studio behaviour DSL. #### Lifecycle ##### `tile_sense_tof_sleep` — Studio ```c void tile_sense_tof_sleep(tile_t *tile) ``` Enter standby mode (ENABLE = 0x00). Stops any active measurement and powers down the sensor. Use tile_sense_tof_wake() to resume without full re-initialisation. ##### `tile_sense_tof_wake` — Studio ```c void tile_sense_tof_wake(tile_t *tile) ``` Resume from standby mode. Re-executes the bootloader wake and App0 request sequence. Does not restart measurements — call tile_sense_tof_start() after waking. ##### `tile_sense_tof_reset` — Studio ```c void tile_sense_tof_reset(tile_t *tile) ``` Reset the device via the CPU reset bit in ENABLE. Performs a full CPU reset and re-runs the boot sequence, so the tile is ready again afterwards. Calibration and saved algorithm state are dropped; the measurement configuration (mode, period, iterations, threshold) is kept. Ranging is stopped: call start() to resume. #### Runtime ##### `tile_sense_tof_start` — Studio ```c void tile_sense_tof_start(tile_t *tile) ``` Start continuous or single-shot measurement. Writes the factory calibration data (if loaded), configures the measurement command payload from the current cfg, and issues the measurement command. Results are signaled via the result interrupt. If period_ms == 0x00 in the config, a single measurement is taken. Otherwise measurements repeat at the configured period. ##### `tile_sense_tof_stop` — Studio ```c void tile_sense_tof_stop(tile_t *tile) ``` Stop an active measurement. Sends the stop command and waits for the sensor to acknowledge. No-op if no measurement is active. ##### `tile_sense_tof_get_distance_mm` — Studio ```c uint16_t tile_sense_tof_get_distance_mm(tile_t *tile) ``` Read the distance from the most recent result. Returns the peak distance in millimeters. Does not check whether new data is available — call tile_sense_tof_result_ready() first or use tile_sense_tof_get_result() for full status. When nothing is in range the sensor reports 0; this SATURATES that to tile_sense_tof_max_range_mm() rather than returning 0, so `if (distance < threshold)` behaves correctly with no special case — "out of range" reads as "very far away", which is what it physically means. Use tile_sense_tof_get_result() when you need to tell "no target" from "target at max range"; that reports the raw value. **Returns:** Distance in millimeters, saturating at the configured max range. ##### `tile_sense_tof_max_range_mm` — Studio ```c uint16_t tile_sense_tof_max_range_mm(tile_t *tile) ``` Maximum measurable distance for the configured range mode. 200 mm (short range), 2500 mm, or 5000 mm. This is the value tile_sense_tof_get_distance_mm() saturates to when no object is detected, so `distance >= max_range_mm()` is the explicit "nothing in range" test. **Returns:** Maximum range in millimeters. ##### `tile_sense_tof_get_result_flat` — Studio ```c void tile_sense_tof_get_result_flat(tile_t *tile, int32_t *out) ``` DSL-friendly flat-output variant of get_result(). Drops the struct in favor of a positional int[5] array — the DSL doesn't have a struct ABI yet, so the per-field outputs come back indexed. Layout: out[0]=distance_mm, out[1]=status, out[2]=reliability, out[3]=temperature_c, out[4]=result_number. The temperature is widened from int8_t to int32_t so negative values sign-extend correctly. - `out` — Output buffer (5 int32_t slots). ##### `tile_sense_tof_measure_single_flat` — Studio ```c uint8_t tile_sense_tof_measure_single_flat(tile_t *tile, int32_t *mm, int32_t *status, int32_t *reliability, int32_t *temp_c, int32_t *seq, uint32_t timeout_ms) ``` DSL-friendly flat-output variant of measure_single(). Combines a single-shot measurement with positional int outputs. Same layout convention as get_result_flat — the per-field values land in five out-scalar slots. Returns the chip's success flag. - `mm` — Output: distance in millimetres. - `status` — Output: result status code. - `reliability` — Output: 0–63 reliability score. - `temp_c` — Output: die temperature in degrees Celsius. - `seq` — Output: monotonic result counter. - `timeout_ms` — Maximum wait for the measurement to complete. **Returns:** 1 on a valid result, 0 on timeout / bus error. ##### `tile_sense_tof_result_ready` — Studio ```c uint8_t tile_sense_tof_result_ready(tile_t *tile) ``` Check if a new measurement result is available. Reads the INT_STATUS register and checks the result interrupt bit. Does not clear the interrupt — that is done by get_result() or get_distance_mm(). **Returns:** 1 if a new result is pending, 0 otherwise. ##### `tile_sense_tof_set_distance_mode` — Studio ```c void tile_sense_tof_set_distance_mode(tile_t *tile, sense_tof_distance_mode_t mode) ``` Change the distance mode on the fly. Stops any active measurement, updates the cached mode, and restarts. If no measurement was running, only updates the config for the next start(). - `mode` — New distance mode. ##### `tile_sense_tof_set_period` — Studio ```c void tile_sense_tof_set_period(tile_t *tile, sense_tof_period_t period) ``` Change the measurement repetition period on the fly. Slower rates save most of the power: the laser only fires for the ranging time (~24 ms at the default 900k iterations), and the chip idles at ~140 uA for the rest of each period. Stops any active measurement, updates the cached period, and restarts. If no measurement was running, only updates the config for the next start(). - `period` — New repetition period (sense_tof_period_t, or 1-253 ms). ##### `tile_sense_tof_set_kilo_iters` — Studio ```c void tile_sense_tof_set_kilo_iters(tile_t *tile, uint16_t kilo_iters) ``` Change the per-measurement iteration count on the fly. Iterations (in thousands) trade power for SNR/range: more iterations give a stronger return and longer reach at higher current draw. Typical range 10-4000 (10k-4M); the ranging default is 900. Stops any active measurement, updates the cached value, and restarts. - `kilo_iters` — [10..4000] Iterations in thousands (e.g. 900 = 900k). ##### `tile_sense_tof_set_threshold` — Studio ```c void tile_sense_tof_set_threshold(tile_t *tile, uint8_t threshold) ``` Change the detection threshold on the fly. Sets cmd_data3[5:0] — the minimum confidence for a reported target. Higher values reject weak/spurious returns. 0 is not "everything": the chip uses 6 instead (datasheet §6.9.3). Stops any active measurement, updates the cached value, and restarts. - `threshold` — [0..63] Detection threshold, 0-63. ##### `tile_sense_tof_get_signal_quality_flat` — Studio ```c void tile_sense_tof_get_signal_quality_flat(tile_t *tile, int32_t *reference_hits, int32_t *object_hits, int32_t *crosstalk) ``` Read signal-quality diagnostics into flat out-params (Studio). Reads the most recent result block (one 10-byte burst). Reference and object hits are zero when no object was detected (datasheet §7.3.11 to §7.3.18). Crosstalk is only meaningful with low ambient light and no target within 40 cm (§7.3.19). Call after a result is ready. - `reference_hits` — Output: reference-channel SPAD hit sum (or NULL). - `object_hits` — Output: object-channel SPAD hit sum (or NULL). - `crosstalk` — Output: crosstalk peak value, 0-65535 (or NULL). ##### `tile_sense_tof_get_sys_clock_ticks` — Studio ```c uint32_t tile_sense_tof_get_sys_clock_ticks(tile_t *tile) ``` Read the chip's 32-bit system-clock tick counter. The TMF8806's internal oscillator can drift ±4 % over temperature, which biases distance readings if the host's measurement period doesn't compensate. Reading this register set after each measurement lets the host compute the actual elapsed chip-time vs. its own elapsed wall-time and apply a software correction (HostDriverCommunication §10). Reads SYS_CLOCK_0..3 (0x24–0x27) as a single 4-byte burst. measurement yet). **Returns:** 32-bit system-clock tick count (0 if not in App0 / no ##### `tile_sense_tof_is_object_within` — Studio ```c uint8_t tile_sense_tof_is_object_within(tile_t *tile, uint16_t mm) ``` Single-shot measurement; true if an object is within `mm`. Performs one blocking single-shot measurement (up to 300 ms) and returns 1 iff the reported distance is non-zero, less than or equal to `mm`, and the result counts as a detection: a short-range result (reliability 1 or 10, which is what short-range mode and any object within ~200 mm give) or a long-range result with reliability of at least @ref SENSE_TOF_PRESENCE_RELIABILITY_MIN. If continuous ranging was running it is paused for the shot and resumed afterwards. 0 otherwise (no target, low reliability, or bus timeout). - `mm` — Distance threshold in millimetres (inclusive). **Returns:** 1 if an object is within range with adequate confidence, ##### `tile_sense_tof_wait_for_object` — Studio ```c uint8_t tile_sense_tof_wait_for_object(tile_t *tile, uint16_t mm, uint32_t timeout_ms) ``` Block until an object is detected within `mm`, or timeout. Polls single-shot measurements until one matches the `is_object_within` predicate or `timeout_ms` elapses. Polls every single-shot measurement adds ~30 ms of its own. - `mm` — Distance threshold in millimetres (inclusive). - `timeout_ms` — Maximum wait time in milliseconds. **Returns:** 1 if an object entered range before timeout, 0 otherwise. **Note:** v1 implementation polls — keeps the helper self-contained and avoids the need to wire the chip's INT pin into the tile pad map. A future revision could swap to the chip's threshold-INT (see @ref tile_sense_tof_set_threshold_interrupt) to let a sleeping host stay asleep until proximity wakes it. ##### `tile_sense_tof_read_distance_with_confidence` — Studio ```c uint8_t tile_sense_tof_read_distance_with_confidence(tile_t *tile, uint16_t *mm, uint8_t *confidence_pct) ``` Read distance and confidence in one call. Performs a single-shot measurement and writes the distance (mm) and a 0–100 percent confidence. A long-range result maps its 0–63 reliability as `(reliability * 100) / 63`; the short-range codes are not a scale, so 10 (calibrated) reads as 100 and 1 (uncalibrated) as 50. Integer math, no floats. - `mm` — Output: distance in millimetres (NULL allowed). - `confidence_pct` — Output: 0–100 confidence (NULL allowed). **Returns:** 1 on a successful measurement, 0 on timeout / bus error. #### Configuration ##### `tile_sense_tof_set_threshold_interrupt` — Studio ```c uint8_t tile_sense_tof_set_threshold_interrupt(tile_t *tile, uint8_t persistence, uint16_t low_mm, uint16_t high_mm) ``` Configure threshold-based interrupt suppression. Without this configured, the chip fires INT on every measurement completion. With it: INT only fires when an object is detected in the [low_mm, high_mm] range for `persistence` consecutive measurements. Lets a sleeping host stay asleep until something gets close. Per HostDriverCommunication §8.12 (cmd 0x08 = WR_ADD_CONFIG): - persistence = 0 → interrupt every measurement (default) - persistence = N → require N consecutive in-range hits - low_mm > high_mm → no interrupts (no valid range) - `persistence` — 0–255; 0 = disabled (every-measurement INT). - `low_mm` — Lower bound (inclusive), millimetres. - `high_mm` — Upper bound (inclusive), millimetres. **Returns:** 1 on success, 0 on bus / command-execution timeout. #### Advanced ##### `tile_sense_tof_factory_calibrate` — Studio ```c uint8_t tile_sense_tof_factory_calibrate(tile_t *tile, uint32_t timeout_ms) ``` Run the factory calibration procedure. Performs a calibration measurement using the current mode settings. The sensor must be positioned with a known target or open field per the TMF8806 calibration guidelines. Results are stored internally and can be retrieved with tile_sense_tof_get_calibration(). This is a blocking call that waits for the calibration to complete: about 1.1 s in 2.5 m mode and 2.2 s in 5 m mode (40.96 M iterations), so a timeout below 3000 ms is raised to 3000. Continuous ranging is paused and resumed. The calibration belongs to the current mode's family: short range and 2.5 m share one, 5 m needs its own (datasheet §6.4), and switching across that line drops it. - `timeout_ms` — Maximum wait time in milliseconds. **Returns:** 1 if calibration completed successfully, 0 on timeout or error. ##### `tile_sense_tof_set_calibration` — Studio ```c void tile_sense_tof_set_calibration(tile_t *tile, const uint8_t *data) ``` Load factory calibration data into the driver. Stores a 14-byte calibration dataset that will be written to the sensor before each measurement start. Call this after init() to restore calibration from non-volatile storage. - `data` — Pointer to 14-byte calibration data array. ##### `tile_sense_tof_get_calibration` — Studio ```c void tile_sense_tof_get_calibration(tile_t *tile, uint8_t *data) ``` Read the current factory calibration data from the sensor. Reads 14 bytes of calibration data from the sensor's calibration registers. Typically called after tile_sense_tof_factory_calibrate() to save the data for later reloading via set_calibration(). - `data` — Output buffer for 14 bytes of calibration data. ##### `tile_sense_tof_get_app_version_flat` — Studio ```c void tile_sense_tof_get_app_version_flat(tile_t *tile, int32_t *major, int32_t *minor, int32_t *patch) ``` DSL-friendly flat-output variant of get_app_version(). Drops the struct in favor of three positional out-scalars. - `major` — Output: major version number. - `minor` — Output: minor version number. - `patch` — Output: patch version number. ##### `tile_sense_tof_get_serial_number_flat` — Studio ```c void tile_sense_tof_get_serial_number_flat(tile_t *tile, int32_t *out) ``` DSL-friendly flat-output variant of get_serial_number(). Drops the success bool — on bus error, the buffer comes back as all-zeros, which is invalid as a real serial number so callers can detect failure by checking for a zero serial. Bytes widen to int32 for DSL int compatibility. - `out` — Output buffer (4 int32_t slots). ##### `tile_sense_tof_save_state` — Studio ```c void tile_sense_tof_save_state(tile_t *tile, uint8_t *data) ``` Save the algorithm state from the sensor. Reads 11 bytes of algorithm state data from the sensor (registers 0x28-0x32). This state should be saved before entering sleep in ultra-low-power mode, and restored after wake via restore_state(). Preserving algorithm state across power cycles avoids the ~8 ms re-initialisation penalty and maintains measurement accuracy. - `data` — Output buffer for 11 bytes of state data. ##### `tile_sense_tof_restore_state` — Studio ```c void tile_sense_tof_restore_state(tile_t *tile, const uint8_t *data) ``` Restore algorithm state to the sensor. Writes 11 bytes of previously saved algorithm state to the sensor (registers 0x2E-0x38). Call this after wake() and before start() to resume from the saved algorithm state. The calibration data bitmask in the measurement command (cmd_data7) is automatically updated to include algState when state data has been restored. - `data` — Pointer to 11 bytes of previously saved state data. ##### `tile_sense_tof_get_threshold_interrupt` — Studio ```c uint8_t tile_sense_tof_get_threshold_interrupt(tile_t *tile, uint8_t *persistence, uint16_t *low_mm, uint16_t *high_mm) ``` Read back the current threshold-interrupt configuration. Per HostDriverCommunication §8.12.2 (cmd 0x09 = RD_ADD_CONFIG). - `persistence` — Output (may be NULL). - `low_mm` — Output (may be NULL). - `high_mm` — Output (may be NULL). **Returns:** 1 on success, 0 on timeout. ##### `tile_sense_tof_read_histogram_flat` — Studio ```c void tile_sense_tof_read_histogram_flat(tile_t *tile, uint8_t hist_type, uint32_t timeout_ms, int32_t *out) ``` DSL-friendly flat-output variant of read_histogram(). Drops the success bool — on timeout / bus error the buffer comes back zero-filled. Bytes widen to int32 for DSL int compatibility. Caller passes hist_type and timeout_ms as scalar args; the buffer is the function's output (collapsed into the int[128] return). - `hist_type` — Histogram-type byte (see read_histogram() docs). - `timeout_ms` — Maximum wait for the histogram-ready interrupt. - `out` — Output buffer (128 int32_t slots). #### Other ##### `tile_sense_tof_find` ```c uint8_t tile_sense_tof_find(tiles_pal_t *hal, uint8_t instance) ``` Check if a Sense.TOF tile is present on the bus. Probes the I2C address and reads the ID register. The TMF8806 has a single fixed address (0x41), so only instance 0 is valid. - `hal` — Platform HAL handle. - `instance` — Must be 0 (single-address device). **Returns:** 1 if device ACKs and ID matches (0x09), 0 otherwise. ##### `tile_sense_tof_init` ```c void tile_sense_tof_init(tiles_pal_t *hal, uint8_t instance, tile_t *tile, const sense_tof_cfg_t *cfg) ``` Initialise a Sense.TOF tile. Performs the full TMF8806 boot sequence: 1. Waits for bootloader to enter sleep (ENABLE == 0x00) 2. Wakes the bootloader (PON = 1) 3. Waits for CPU ready (ENABLE == 0x41) 4. Requests App0 measurement application 5. Waits for App0 to start (APPID == 0xC0) 6. Enables result interrupt Does NOT start measurements — call tile_sense_tof_start() after init. Does NOT software-reset to preserve any existing calibration data. - `hal` — Platform HAL handle. - `instance` — Must be 0 (single-address device). - `tile` — Tile handle to initialise. - `cfg` — Configuration (NULL for defaults: 2.5 m, 30 ms, 900k iters). ##### `tile_sense_tof_measure_single` ```c uint8_t tile_sense_tof_measure_single(tile_t *tile, sense_tof_result_t *result, uint32_t timeout_ms) ``` Perform a blocking single-shot measurement. Temporarily overrides the repetition period to single-shot mode, starts a measurement, polls for the result interrupt, reads the result, and restores the original period setting. - `result` — Output: measurement result (may be NULL to discard). - `timeout_ms` — Maximum wait time in milliseconds. **Returns:** 1 if a valid result was obtained, 0 on timeout or error. ##### `tile_sense_tof_get_result` ```c void tile_sense_tof_get_result(tile_t *tile, sense_tof_result_t *result) ``` Read the full measurement result. Reads distance, status, reliability, temperature, and result number from the sensor in a single bus transaction. Clears the result interrupt flag after reading. - `result` — Output struct to populate. ##### `tile_sense_tof_get_app_version` ```c void tile_sense_tof_get_app_version(tile_t *tile, sense_tof_version_t *version) ``` Read the application firmware version. Returns the major, minor, and patch version of the App0 measurement application running on the TMF8806. - `version` — Output struct to populate. ##### `tile_sense_tof_get_serial_number` ```c uint8_t tile_sense_tof_get_serial_number(tile_t *tile, uint8_t *serial) ``` Read the device serial number. Issues the serial number command (0x47) and reads SERIAL_NUMBER_0..3 (0x28-0x2B, datasheet §7.6): a 4-byte unique device identifier. Continuous ranging is paused for the command and resumed afterwards. - `serial` — Output buffer for 4 bytes of serial data. **Returns:** 1 if serial number was read successfully, 0 on error. ##### `tile_sense_tof_get_signal_quality` ```c void tile_sense_tof_get_signal_quality(tile_t *tile, sense_tof_signal_t *sig) ``` Read per-measurement signal-quality diagnostics. Reads the reference/object hit counts and crosstalk from the most recent result block. Useful for assessing return strength and reflectance beyond the reliability byte. Call after a result is ready. - `sig` — Caller-allocated struct, populated on return. ##### `tile_sense_tof_read_histogram` ```c uint8_t tile_sense_tof_read_histogram(tile_t *tile, uint8_t hist_type, uint8_t *buf128, uint32_t timeout_ms) ``` Capture and read a raw histogram from the SPAD/TDC array. Useful for custom multi-target detection, reflectance estimation, or peak-curvature analysis beyond what App0's distance API provides. Per HostDriverCommunication §8.11. Procedure (handled internally): stop any running measurement, configure histogram-readout mode (cmd 0x30), start cyclic measurement, wait for INT_STATUS bit 1, issue 0x80 to start the histogram block read, wait for TID to advance, then read the first 128-byte block from HISTOGRAM_START (0x20, datasheet §7.5.1). That block is TDC0 quarter 0: bins 0..63, each a little-endian 16-bit count (LSB, MSB). The rest of the histogram (further blocks, continued with command 0x32) is not read. Ranging is stopped afterwards: call start() to return to normal distance reads. §8.11 / TMF8806 datasheet for available types; 0x10 = short-range example). - `hist_type` — Histogram type byte (see HostDriverCommunication - `buf128` — Caller-supplied 128-byte buffer. - `timeout_ms` — Maximum wait for the histogram-ready interrupt. **Returns:** 1 on success (buf128 populated), 0 on timeout / bus error. ### Vibe Settings Settings Studio's Vibe interface offers for this tile, generated from the driver's `@studio control` annotations. Each is an argument of the function shown. | Setting | Tier | Values | Default | Function | |---|---|---|---|---| | Distance range | basic | Short range, ~200 mm max, lowest power, Medium range up to 2500 mm (default), Long range up to 5000 mm | Medium range up to 2500 mm (default) | `set_distance_mode(mode)` | | Measurement rate | basic | Every 30 ms, ~33 per second (default), Every 50 ms, 20 per second, Every 100 ms, 10 per second, Every 250 ms, 4 per second, Every second, Every 2 seconds | Every 30 ms, ~33 per second (default) | `set_period(period)` | | Iterations (thousands) | advanced | 10 to 4000 | 900 | `set_kilo_iters(kilo_iters)` | | Detection threshold | advanced | 0 to 63 | 6 | `set_threshold(threshold)` | ### Driver gaps Chip capabilities this driver does **not** expose. Each is tagged hardware-gated (the tile does not route the pin) or driver-deferred (possible, not yet implemented). Do not assume an API exists for these. #### 10 m extended-range mode (common) Deferred to a dedicated session. TMF8806 App0 firmware supports up to 5 m range out of ROM; 10 m mode requires downloading a binary RAM patch from AMS via the bootloader's W_RAM + RAMREMAP_RESET protocol — non-trivial firmware-loading flow not in scope for this driver-coverage pass. #### GPIO0 / GPIO1 runtime control (niche) GPIO0 is routed to tile pad 3 but is consumed as a hardware strap: a 100 k on-board pull-up (R2) to V+ holds GPIO0 high at startup, selecting the 1.8-3.3 V digital-I/O level required by this tile's 2.7-3.5 V rail (TMF8806 datasheet §6.7, Table 4). The chip's EN pin is tied to V+ on the board and is not on a pad, so the sensor cannot be power-cycled from the Core. It is NOT an I2C-address strap (the address is fixed at 0x41, changed only via command 0x49). Driver-deferred: after startup GPIO0 is a normal GPIO (§6.7), and its open-drain object-detect modes (cmd 0x02 cmd_data5 gpio0 = 8 / 9) would work with the on-board pull-up as a second "object present" line on pad 3; push-pull modes would fight R2 and are best avoided. Not exposed yet. GPIO1 is not routed to a pad (held low by a 100 k pull-down, R3). #### I2C address change / several sensors on one bus (niche) Hardware-gated in part. Command 0x49 can move the sensor off 0x41, but giving several sensors their own addresses means releasing them one at a time through EN, and EN is tied to V+ on the tile. A GPIO0-conditioned change through pad 3 is possible but not exposed (driver-deferred). #### Optical stack / cover glass tuning (advanced) Driver-deferred. spadSelect (cmd_data7[7:6]) and the SPAD dead time (cmd_data7[5:3], fixed at the datasheet default of 4) tune the sensor for a cover glass or strong sunlight (datasheet §6.5). #### Spread spectrum, algKeepReady, immediate interrupt (niche) Driver-deferred. The VCSEL / SPAD charge-pump spread spectrum (cmd_data9/8), algKeepReady and algImmediateInterrupt are left off. #### Ultra-low-power shutdown (niche) Hardware-gated. With EN tied to V+ the chip can't be put in its 0.04 µA shutdown; sleep() (PON off) is the floor, about 85 µA standby. #### Oscillator drift correction, calibration per mode (niche) Driver-deferred. get_sys_clock_ticks() gives the raw clock, but the host-side drift correction and re-trim (host-driver note §10 / §11) are not done, and the driver keeps one calibration at a time (switching into or out of 5 m drops it) rather than a set per mode. ### Enums #### `sense_tof_distance_mode_t` Distance mode selection. | Member | Value | Description | | --- | --- | --- | | `SENSE_TOF_SHORT_RANGE` | `0` | Short range, ~200 mm max, lowest power | | `SENSE_TOF_RANGE_2500MM` | `1` | Medium range up to 2500 mm (default) | | `SENSE_TOF_RANGE_5000MM` | `2` | Long range up to 5000 mm | #### `sense_tof_period_t` Measurement repetition period (cmd_data2, `repetitionPeriodMs`). | Member | Value | Description | | --- | --- | --- | | `SENSE_TOF_PERIOD_30MS` | `0x1E` | Every 30 ms, ~33 per second (default) @studio value=33.3 | | `SENSE_TOF_PERIOD_50MS` | `0x32` | Every 50 ms, 20 per second @studio value=20 | | `SENSE_TOF_PERIOD_100MS` | `0x64` | Every 100 ms, 10 per second @studio value=10 | | `SENSE_TOF_PERIOD_250MS` | `0xFA` | Every 250 ms, 4 per second @studio value=4 | | `SENSE_TOF_PERIOD_1S` | `0xFE` | Every second @studio value=1 | | `SENSE_TOF_PERIOD_2S` | `0xFF` | Every 2 seconds @studio value=0.5 | ### Structs #### `sense_tof_cfg_t` Optional init-time configuration for Sense.TOF. | Type | Field | Description | | --- | --- | --- | | `uint8_t` | `mode` | sense_tof_distance_mode_t (default: RANGE_2500MM). | | `uint8_t` | `period_ms` | Repetition period code (default: 0x1E = 30 ms). 0xFE = 1 s, 0xFF = 2 s. 0 here means "default" (30 ms); single shots are measure_single(). | | `uint16_t` | `kilo_iters` | Iterations in thousands (default: 900). Higher values improve SNR at the cost of power. | | `uint8_t` | `threshold` | Detection threshold, 0-63 (default: 6). Lower values increase sensitivity. | #### `sense_tof_result_t` Measurement result from a single ToF capture. | Type | Field | Description | | --- | --- | --- | | `uint16_t` | `distance_mm` | Peak distance in millimeters. 0 if no target. | | `uint8_t` | `status` | Result status. 0x00-0x0F = valid, 0x10+ = error. | | `uint8_t` | `reliability` | 0 = no object; 1 / 10 = short-range algorithm (uncalibrated / calibrated); other values = long-range, 2..63, 63 best. | | `int8_t` | `temperature` | Die temperature in degrees Celsius. | | `uint8_t` | `result_number` | Monotonically incrementing result counter. | #### `sense_tof_version_t` Application firmware version reported by the TMF8806. | Type | Field | Description | | --- | --- | --- | | `uint8_t` | `major` | Major version number. | | `uint8_t` | `minor` | Minor version number. | | `uint8_t` | `patch` | Patch version number. | #### `sense_tof_signal_t` Per-measurement signal-quality diagnostics from the TMF8806. | Type | Field | Description | | --- | --- | --- | | `uint32_t` | `reference_hits` | Reference-channel photon hits (regs 0x34-0x37). | | `uint32_t` | `object_hits` | Object-channel photon hits (regs 0x38-0x3B). | | `uint16_t` | `crosstalk` | Cross-talk count (regs 0x3C-0x3D). | ### Defines | Name | Value | Description | | --- | --- | --- | | `TILE_SENSE_TOF_VERSION_MAJOR` | `1` | | | `TILE_SENSE_TOF_VERSION_MINOR` | `6` | | | `TILE_SENSE_TOF_VERSION_PATCH` | `0` | | | `TMF8806_I2C_ADDR` | `0x41` | Fixed 7-bit I2C address | | `TMF8806_DEVICE_ID` | `0x09` | Expected ID value (bits 5:0 only) | | `TMF8806_ID_MASK` | `0x3F` | Mask for valid ID bits | | `TMF8806_ENABLE_PON` | `0x01` | Power-on bit | | `TMF8806_ENABLE_CPU_READY` | `0x41` | CPU ready + PON | | `TMF8806_ENABLE_CPU_RESET` | `0x80` | CPU reset bit | | `TMF8806_APPID_BOOTLOADER` | `0x80` | Bootloader is running | | `TMF8806_APPID_APP0` | `0xC0` | Measurement application running | | `TMF8806_CMD_MEASURE` | `0x02` | Start measurement | | `TMF8806_CMD_FACTORY_CAL` | `0x0A` | Run factory calibration | | `TMF8806_CMD_STOP` | `0xFF` | Stop measurement | | `TMF8806_CMD_SERIAL` | `0x47` | Read serial number | | `TMF8806_FACTORY_CAL_KITERS` | `0xA000` | | | `TMF8806_INT_RESULT` | `0x01` | Result interrupt flag | | `TMF8806_INT_HISTOGRAM` | `0x02` | Histogram interrupt flag | | `TMF8806_CONTENTS_RESULT` | `0x55` | Result data available | | `TMF8806_CONTENTS_CALIB` | `0x0A` | Calibration data available | | `TMF8806_CONTENTS_SERIAL` | `0x47` | Serial number available | | `TMF8806_CALIB_DATA_LEN` | `14` | Factory calibration data length | | `TMF8806_STATE_DATA_LEN` | `11` | Algorithm state data length | | `TMF8806_BOOT_TIMEOUT_MS` | `500` | Maximum boot sequence wait | | `TMF8806_CMD_TIMEOUT_MS` | `1000` | Maximum command completion wait | | `TMF8806_POLL_INTERVAL_MS` | `2` | Polling interval during boot | | `SENSE_TOF_PRESENCE_RELIABILITY_MIN` | `32` | | | `SENSE_TOF_WAIT_POLL_INTERVAL_MS` | `10` | | --- Generated 2026-10-07 from the driver header at tiles@65a4fd8. Canonical HTML: https://bergsonne.io/docs/tiles/sense/tof ============================================================================== ## Store.O.128 driver > Store.O.128 tile driver — Renesas/Adesto AT25QL128A 128 Mbit QSPI NOR flash. | | | | --- | --- | | Tile | `Store.O.128` | | Driver version | `1.1.0` | | Header | `tile_store_o_128.h` | | Generated from | `tiles@bc1921a` | | Canonical page | https://bergsonne.io/docs/tiles/store/o128 | ### Overview 16 MB serial NOR flash on SPI / Quad-SPI. Standard SPI-NOR command set: JEDEC ID, status, write-enable, page program (256 B pages), 4 KB sector / 64 KB block / chip erase, read, deep power-down. Platform-agnostic: uses the framework's raw SPI transaction primitive (`tiles_pal_t.spi_transfer`) so command + 24-bit address + data fit one CS assertion. A platform that doesn't implement spi_transfer yet leaves it NULL; every call here guards against that (init reports an error; later calls return 0 / do nothing) rather than faulting. Supply is 1.7-2.0 V only (datasheet §9, VCC). VIH max is VCC + 0.4 V, so the SPI lines must be driven at the tile's 1.8 V rail, not 3.3 V. Sidebands: the AT25QL128A ships with QE = 1 (datasheet §7.7, factory default), so WP (pad 6) and HOLD (pad 9) act as IO2/IO3 and their WP/HOLD functions are inactive in single-lane SPI. Neither pin has an internal pull-up (datasheet Table 1); the tile fits a 47 kΩ pull-up on WP only, HOLD is not pulled. Timing (datasheet Table 26, max): tPP 5 ms, tSE 0.4 s, tBE2 2.5 s, tCE 300 s. READ (0x03) is limited to 50 MHz SCK; FAST_READ (0x0B) to 104 MHz. Datasheet: Renesas AT25QL128A. ### Examples #### Quick start ```c #include "core_tiles.h" tile_t flash; uint8_t buf[256]; tile_store_o_128_init(core_tiles_pal(&core_spi1), 0, &flash, NULL); if (tile_is_ready(&flash)) { tile_store_o_128_read(&flash, 0x000000, buf, sizeof buf); } ``` ### API 17 public functions. Functions marked **Studio** are callable from the Studio behaviour DSL. #### Lifecycle ##### `tile_store_o_128_deep_power_down` — Studio ```c void tile_store_o_128_deep_power_down(tile_t* tile) ``` Enter Deep Power-Down (typ 2 µA; every command except release is ignored). ##### `tile_store_o_128_release` — Studio ```c void tile_store_o_128_release(tile_t* tile) ``` Release from Deep Power-Down. #### Runtime ##### `tile_store_o_128_read_jedec_id` — Studio ```c uint32_t tile_store_o_128_read_jedec_id(tile_t* tile) ``` Read the 24-bit JEDEC ID (manufacturer << 16 | type << 8 | capacity). **Returns:** Packed JEDEC ID (e.g. 0x1F4218 for the AT25QL128A). ##### `tile_store_o_128_read_status` — Studio ```c uint8_t tile_store_o_128_read_status(tile_t* tile) ``` Read Status Register 1. **Returns:** SR1 byte (AT25QL128A_SR1_* bits). ##### `tile_store_o_128_is_busy` — Studio ```c uint8_t tile_store_o_128_is_busy(tile_t* tile) ``` Whether a program/erase is in progress (WIP bit). **Returns:** 1 if busy, 0 if idle. ##### `tile_store_o_128_get_capacity` — Studio ```c uint32_t tile_store_o_128_get_capacity(tile_t* tile) ``` Total flash capacity in bytes. **Returns:** 16777216 (16 MB). ##### `tile_store_o_128_read` — Studio ```c void tile_store_o_128_read(tile_t* tile, uint32_t addr, uint8_t* buf, uint16_t len) ``` Read `len` bytes starting at `addr` (READ 0x03). - `addr` — Byte address (0 .. capacity-1). - `buf` — Output buffer. - `len` — Number of bytes. ##### `tile_store_o_128_fast_read` — Studio ```c void tile_store_o_128_fast_read(tile_t* tile, uint32_t addr, uint8_t* buf, uint16_t len) ``` Fast-read `len` bytes from `addr` (FAST_READ 0x0B, 1 dummy byte). Same result as tile_store_o_128_read(); use it when the SPI clock is above the 50 MHz READ (0x03) limit (FAST_READ runs to 104 MHz). - `addr` — Byte address (0 .. capacity-1). - `buf` — Output buffer. - `len` — Number of bytes. ##### `tile_store_o_128_page_program` — Studio ```c void tile_store_o_128_page_program(tile_t* tile, uint32_t addr, const uint8_t* buf, uint16_t len) ``` Program up to one page (≤256 B) within a single page boundary. Issues Write-Enable, programs, and waits for completion (up to 10 ms; tPP max 5 ms). The caller must keep the write inside one 256-byte page: (addr & 0xFF) + len ≤ 256. Bytes past the page end wrap to the start of the same page (chip behavior), so use tile_store_o_128_write() for unaligned spans. Programming only clears bits: erase first. - `addr` — Start address. - `buf` — Data to program. - `len` — Byte count (1..256). ##### `tile_store_o_128_write` — Studio ```c void tile_store_o_128_write(tile_t* tile, uint32_t addr, const uint8_t* buf, uint32_t len) ``` Program an arbitrary-length span, splitting across page boundaries. Programming only clears bits (1 → 0); erase the covering sector(s) first. - `addr` — Start address (0 .. capacity-1). - `buf` — Data to program. - `len` — Byte count. ##### `tile_store_o_128_erase_sector` — Studio ```c void tile_store_o_128_erase_sector(tile_t* tile, uint32_t addr) ``` Erase the 4 KB sector containing `addr` (and wait, up to 0.5 s). - `addr` — Any address inside the sector (low 12 bits ignored). ##### `tile_store_o_128_erase_block` — Studio ```c void tile_store_o_128_erase_block(tile_t* tile, uint32_t addr) ``` Erase the 64 KB block containing `addr` (and wait, up to 3 s). - `addr` — Any address inside the block (low 16 bits ignored). ##### `tile_store_o_128_erase_chip` — Studio ```c void tile_store_o_128_erase_chip(tile_t* tile) ``` Erase the entire chip (and wait; typ 60 s, max 300 s). ##### `tile_store_o_128_wait_ready` — Studio ```c uint8_t tile_store_o_128_wait_ready(tile_t* tile, uint32_t timeout_ms) ``` Block until any in-progress program/erase finishes, or timeout. - `timeout_ms` — Max wait. **Returns:** 1 if ready, 0 if timed out. #### Configuration ##### `tile_store_o_128_write_enable` — Studio ```c void tile_store_o_128_write_enable(tile_t* tile) ``` Issue Write-Enable (sets WEL). Required before any program/erase. #### Other ##### `tile_store_o_128_find` ```c uint8_t tile_store_o_128_find(tiles_pal_t* hal, uint8_t instance) ``` Probe instance `instance`: reads the JEDEC ID, returns 1 if it matches. ##### `tile_store_o_128_init` ```c void tile_store_o_128_init(tiles_pal_t* hal, uint8_t instance, tile_t* tile, const store_o_128_cfg_t* cfg) ``` Initialize: verify JEDEC identity, wake from deep power-down. ### Driver gaps Chip capabilities this driver does **not** expose. Each is tagged hardware-gated (the tile does not route the pin) or driver-deferred (possible, not yet implemented). Do not assume an API exists for these. #### Quad / QPI I/O modes (niche) The AT25QL128A supports Dual/Quad SPI and QPI (≤4 data lines). This driver drives standard single-lane SPI only; quad modes need a quad-capable SPI transfer in the platform layer (driver-deferred, not chip-gated). #### Block protection / status register write (advanced) No Write Status Register (0x01/0x31) support: BP/TB/SEC/CMP block protection, SRP lock modes and the QE bit can't be changed. Factory default is nothing protected, so program/erase work out of the box (driver-deferred). #### 32 KB block erase (niche) The chip also erases 32 KB blocks (0x52); the driver exposes only 4 KB sector, 64 KB block and chip erase (driver-deferred). #### Suspend / resume, reset, OTP, SFDP (niche) Erase/program suspend-resume (0x75/0x7A), software reset (0x66/0x99), the 4 kbit one-time-programmable security register and the SFDP table are not exposed (driver-deferred). ### Structs #### `store_o_128_cfg_t` Optional init config (pass NULL for defaults). | Type | Field | Description | | --- | --- | --- | | `uint8_t` | `reserved` | | ### Defines | Name | Value | Description | | --- | --- | --- | | `TILE_STORE_O_128_VERSION_MAJOR` | `1` | | | `TILE_STORE_O_128_VERSION_MINOR` | `1` | | | `TILE_STORE_O_128_VERSION_PATCH` | `0` | | | `AT25QL128A_CAPACITY` | `0x1000000` | | | `AT25QL128A_PAGE_SIZE` | `256` | | | `AT25QL128A_SECTOR_SIZE` | `4096` | | | `AT25QL128A_BLOCK_SIZE` | `65536` | | | `AT25QL128A_MFR_ID` | `0x1F` | Adesto / Renesas manufacturer | | `AT25QL128A_CAPACITY_ID` | `0x18` | 2^24 bytes density code | | `AT25QL128A_CMD_WREN` | `0x06` | Write Enable | | `AT25QL128A_CMD_WRDI` | `0x04` | Write Disable | | `AT25QL128A_CMD_RDID` | `0x9F` | Read JEDEC ID (3 bytes) | | `AT25QL128A_CMD_RDSR1` | `0x05` | Read Status Register 1 | | `AT25QL128A_CMD_RDSR2` | `0x35` | Read Status Register 2 | | `AT25QL128A_CMD_WRSR` | `0x01` | Write Status Register | | `AT25QL128A_CMD_READ` | `0x03` | Read Data (cmd + 24-bit addr) | | `AT25QL128A_CMD_FAST_READ` | `0x0B` | Fast Read (+1 dummy byte) | | `AT25QL128A_CMD_PP` | `0x02` | Page Program | | `AT25QL128A_CMD_SE` | `0x20` | Sector Erase (4 KB) | | `AT25QL128A_CMD_BE` | `0xD8` | Block Erase (64 KB) | | `AT25QL128A_CMD_CE` | `0xC7` | Chip Erase | | `AT25QL128A_CMD_DP` | `0xB9` | Deep Power-Down | | `AT25QL128A_CMD_RDP` | `0xAB` | Release from Deep Power-Down | | `AT25QL128A_SR1_WIP` | `0x01` | Write In Progress (busy) | | `AT25QL128A_SR1_WEL` | `0x02` | Write Enable Latch | --- Generated 2026-10-07 from the driver header at tiles@bc1921a. Canonical HTML: https://bergsonne.io/docs/tiles/store/o128 ============================================================================== ## Core.ADC — SDK reference > Read analog inputs and drive an analog output: ADC raw/millivolt/temperature reads and DAC output — at the core, hal, or ll layer. | | | | --- | --- | | Category | `adc` | | Core header | `core_adc.h` | | HAL header | `hal_adc.h` | | LL header | `ll_adc.h` | | Generated from | `tiles@6f27e78` | | Canonical page | https://bergsonne.io/docs/sdk/adc | ### Core layer The recommended layer — portable across every Core, default instances, DSL-exposed. #### `core_adc_init` ```c core_adc_init(core_adc_t * adc, uint32_t resolution) ``` Initialise the ADC at the given resolution. - `core_adc_t * adc` - `uint32_t resolution` **Returns:** `hal_status_t` #### `core_adc_add` ```c core_adc_add(core_adc_t * adc, uint8_t pad, uint32_t samp) ``` Register a pad as an ADC input with the given sampling speed. - `core_adc_t * adc` - `uint8_t pad` - `uint32_t samp` **Returns:** `hal_status_t` #### `core_adc_read` ```c core_adc_read(core_adc_t * adc, uint8_t pad) ``` Single-shot read — returns raw ADC count. - `core_adc_t * adc` - `uint8_t pad` **Returns:** `uint16_t` #### `core_adc_read_mv` ```c core_adc_read_mv(core_adc_t * adc, uint8_t pad) ``` Single-shot read — returns calibrated millivolts. - `core_adc_t * adc` - `uint8_t pad` **Returns:** `uint32_t` #### `core_adc_read_pad` — Studio ```c core_adc_read_pad(uint8_t pad) ``` Read a pad as raw ADC counts (0–4095 at 12-bit resolution). - `uint8_t pad` — range 1…64 **Returns:** `int` #### `core_adc_read_mv_pad` — Studio ```c core_adc_read_mv_pad(uint8_t pad) ``` Read a pad as calibrated millivolts (uses VREFINT for per-chip accuracy). - `uint8_t pad` — range 1…64 **Returns:** `int` #### `core_adc_temp_decidegc` — Studio ```c core_adc_temp_decidegc(void) ``` Die temperature in tenths of deg C (e.g. 253 = 25.3 C). Dispatches to the default ADC instance. **Returns:** `int` #### `core_adc_vdd_mv` — Studio ```c core_adc_vdd_mv(void) ``` VDD supply voltage in millivolts (via VREFINT). Dispatches to the default ADC instance. **Returns:** `int` #### `core_adc_temp` ```c core_adc_temp(core_adc_t * adc) ``` Die temperature in tenths of deg C (e.g. 253 = 25.3 C). - `core_adc_t * adc` **Returns:** `int32_t` #### `core_adc_vdd` ```c core_adc_vdd(core_adc_t * adc) ``` Actual VDD supply voltage in millivolts via VREFINT. - `core_adc_t * adc` **Returns:** `uint32_t` #### `core_adc_start_dma` ```c core_adc_start_dma(core_adc_t * adc, uint16_t * buf, uint16_t len, hal_callback_t cb, void * ctx) ``` Start continuous conversion with DMA circular buffer. - `core_adc_t * cb` - `uint16_t * ctx` - `uint16_t len` - `hal_callback_t cb` - `void * ctx` **Returns:** `hal_status_t` #### `core_adc_stop_dma` ```c core_adc_stop_dma(core_adc_t * adc) ``` Stop continuous DMA conversion. - `core_adc_t * adc` **Returns:** `void` #### `core_adc_dma_read` ```c core_adc_dma_read(core_adc_t * adc, uint8_t pad) ``` Read most recent DMA result for a pad. - `core_adc_t * adc` - `uint8_t pad` **Returns:** `uint16_t` #### `core_adc_dma_read_mv` ```c core_adc_dma_read_mv(core_adc_t * adc, uint8_t pad) ``` Read most recent DMA result for a pad, in calibrated millivolts. Prime VDDA before starting DMA (call core_adc_vdd(adc) once), since measuring VREFINT needs the regular sequence that DMA owns. - `core_adc_t * adc` - `uint8_t pad` **Returns:** `uint32_t` #### `core_adc_raw_to_mv` ```c core_adc_raw_to_mv(core_adc_t * adc, uint16_t raw) ``` Convert a raw count from a DMA buffer to millivolts. - `core_adc_t * adc` - `uint16_t raw` **Returns:** `uint32_t` #### `core_adc_set_trigger` ```c core_adc_set_trigger(core_adc_t * adc, uint8_t extsel, uint32_t edge) ``` Pace DMA conversions from a hardware trigger instead of free-running. Call before core_adc_start_dma. See hal_adc_set_trigger for why this matters to any slow output rate: it is what makes a DMA ring span the whole output period, turning the average into a real anti-alias filter instead of a burst average. core_timer_init_freq(&t, TIM2, 800); core_timer_set_trgo(&t, LL_TIM_MMS_UPDATE); core_adc_set_trigger(adc, LL_ADC_L0_TRG_TIM2_TRGO, ADC_TRIG_RISING); core_adc_start_dma(adc, buf, len, 0, 0); core_timer_start(&t); - `core_adc_t * adc` - `uint8_t extsel` - `uint32_t edge` **Returns:** `hal_status_t` #### `core_adc_dma_slot` ```c core_adc_dma_slot(core_adc_t * adc, uint8_t pad) ``` Buffer slot a pad occupies in one DMA scan (-1 if not registered). Use the stride when the buffer holds several scans for averaging. - `core_adc_t * adc` - `uint8_t pad` **Returns:** `int` ### HAL layer Peripheral-level control with explicit handles. #### `hal_adc_init` ```c hal_adc_init(hal_adc_t * adc, ADC_TypeDef * instance, uint32_t sysclk_hz, hal_adc_res_t res) ``` Initialise an ADC instance. Enables the peripheral clock, exits deep power-down (L4/H5), runs self-calibration, and enables the ADC. Must be called before add_channel or read. ADC input pins must already be configured analog (hal_pad_analog). - `hal_adc_t * adc` - `ADC_TypeDef * instance` - `uint32_t sysclk_hz` - `hal_adc_res_t res` **Returns:** `hal_status_t` #### `hal_adc_add_channel` ```c hal_adc_add_channel(hal_adc_t * adc, uint8_t channel, hal_adc_samp_t samp) ``` Add a channel to the conversion sequence. Channels are converted in the order they are added. Call after hal_adc_init, before hal_adc_read or hal_adc_start_dma. - `hal_adc_t * adc` - `uint8_t channel` - `hal_adc_samp_t samp` **Returns:** `hal_status_t` #### `hal_adc_set_oversample` ```c hal_adc_set_oversample(hal_adc_t * adc, hal_adc_oversample_t ratio) ``` Set hardware oversampling ratio for noise reduction (applies to all channels). The right-shift equals log2(N), so the output word stays at the configured resolution. Can be called after init; takes effect on the next conversion. - `hal_adc_t * adc` - `hal_adc_oversample_t ratio` **Returns:** `hal_status_t` #### `hal_adc_set_oversample_ex` ```c hal_adc_set_oversample_ex(hal_adc_t * adc, hal_adc_oversample_t ratio, uint8_t shift) ``` Set hardware oversampling with explicit right-shift for resolution extension. shift controls how many bits the hardware divides the accumulator by before writing to DR. Valid range: 0 – log2(N) where N is the oversampling ratio. shift = log2(N) → same as hal_adc_set_oversample (noise reduction only) shift < log2(N) → output word is wider; effective_bits increases shift = 0 → raw accumulator in DR; max bit depth Example — 12-bit ADC, 256× oversampling, shift = 4: accumulator = up to 20 bits; DR = accumulator >> 4 = 16-bit result effective_bits = 16; hal_adc_read_mv scales against 65535 automatically. - `hal_adc_t * adc` - `hal_adc_oversample_t ratio` - `uint8_t shift` **Returns:** `hal_status_t` #### `hal_adc_read` ```c hal_adc_read(hal_adc_t * adc, uint8_t channel) ``` Single-shot blocking read of one channel. Returns the raw ADC count (range depends on resolution). The channel must have been added via hal_adc_add_channel. - `hal_adc_t * adc` - `uint8_t channel` **Returns:** `uint16_t` #### `hal_adc_read_mv` ```c hal_adc_read_mv(hal_adc_t * adc, uint8_t channel) ``` Read a channel and convert to millivolts. Uses the VREFINT factory calibration to derive the actual VDDA supply voltage, so the result is correct even when VDD ≠ 3.3 V. The VDDA measurement is cached after the first call. - `hal_adc_t * adc` - `uint8_t channel` **Returns:** `uint32_t` #### `hal_adc_raw_to_mv` ```c hal_adc_raw_to_mv(hal_adc_t * adc, uint16_t raw) ``` Convert a raw count to millivolts using the VREFINT-derived VDDA. Does no conversion of its own, so unlike hal_adc_read_mv it is safe to call while continuous DMA owns the regular sequence. That is the point of it: hal_adc_start_dma fills the buffer with raw counts and there is otherwise no supported way to scale them. VDDA is measured on first use, and measuring VREFINT DOES need the regular sequence. So prime it (call this once, or hal_adc_read_vdda_mv) BEFORE hal_adc_start_dma; priming it afterwards returns 0. - `hal_adc_t * adc` - `uint16_t raw` **Returns:** `uint32_t` #### `hal_adc_set_trigger` ```c hal_adc_set_trigger(hal_adc_t * adc, uint8_t extsel, hal_adc_trig_edge_t edge) ``` Pace conversions from a hardware trigger instead of free-running. Call BEFORE hal_adc_start_dma; it only records the setting, and start_dma applies it. With a trigger set, start_dma clears CONT and one scan of the whole channel list runs per trigger edge. This is what lets a slow output rate average over its whole period rather than over one burst. Free-running fills the DMA ring as fast as the ADC can convert, so a ring of N scans only ever holds the last N conversion times worth of signal, however long the period is. That averages noise but does nothing about aliasing, and burns power converting samples nobody reads. Pacing the scans so the ring spans the full output period turns the same average into a boxcar filter, with nulls at the output rate and its multiples. `extsel` is the family-specific trigger encoding. On Core.ST.L0 use the LL_ADC_L0_TRG_* constants (RM0377 Table 58); LL_ADC_L0_TRG_TIM2_TRGO paired with ll_tim_set_trgo(TIM2, LL_TIM_MMS_UPDATE) is the usual pair. - `hal_adc_t * adc` - `uint8_t extsel` - `hal_adc_trig_edge_t edge` **Returns:** `hal_status_t` #### `hal_adc_read_temp_decidegc` ```c hal_adc_read_temp_decidegc(hal_adc_t * adc) ``` Read die temperature in tenths of a degree C. Example: returns 253 for 25.3 °C. Uses TS_CAL1 / TS_CAL2 factory calibration values. Automatically adds the internal temperature sensor channel on first call. - `hal_adc_t * adc` **Returns:** `int32_t` #### `hal_adc_deinit` ```c hal_adc_deinit(hal_adc_t * adc) ``` Disable an ADC initialised with hal_adc_init() and power it down, so a one-off measurement (the status LED's VDD read) leaves no standing current behind. L0 / WBA (ADC4): ADEN and the voltage regulator off (ADC_CR bit 28). L4: also back into deep power-down (RM0394 ADC_CR bit 29). H5: ADEN off only. The peripheral clock stays enabled. - `hal_adc_t * adc` **Returns:** `void` #### `hal_adc_read_vdda_mv` ```c hal_adc_read_vdda_mv(hal_adc_t * adc) ``` Read the actual VDD supply voltage in millivolts via VREFINT. Result is accurate regardless of nominal supply. - `hal_adc_t * adc` **Returns:** `uint32_t` #### `hal_adc_read_all` ```c hal_adc_read_all(hal_adc_t * adc, uint16_t * buf) ``` Read all configured channels into buf[]. Buffer order matches the order channels were added via hal_adc_add_channel. buf must be at least adc->n_channels elements. - `hal_adc_t * adc` - `uint16_t * buf` **Returns:** `void` #### `hal_adc_start_dma` ```c hal_adc_start_dma(hal_adc_t * adc, uint16_t * buf, uint16_t len, hal_callback_t callback, void * ctx) ``` Start continuous ADC conversion with DMA circular buffer. callback is invoked at both half-complete and complete events, supporting a double-buffered processing pattern: - At half-complete: process buf[0 .. len/2 - 1] - At complete: process buf[len/2 .. len - 1] Channels must already be configured via hal_adc_add_channel. DMA and ADC peripheral clocks are enabled automatically. - `hal_adc_t * adc` - `uint16_t * buf` - `uint16_t len` - `hal_callback_t callback` - `void * ctx` **Returns:** `hal_status_t` #### `hal_adc_stop_dma` ```c hal_adc_stop_dma(hal_adc_t * adc) ``` Stop DMA conversion and disable the DMA channel. - `hal_adc_t * adc` **Returns:** `void` #### `hal_adc_init_simple` ```c hal_adc_init_simple(hal_adc_t * adc, ADC_TypeDef * instance) ``` - `hal_adc_t * adc` - `ADC_TypeDef * instance` **Returns:** `hal_status_t` #### `hal_adc_read_pad` ```c hal_adc_read_pad(hal_adc_t * adc, uint8_t pad) ``` - `hal_adc_t * adc` - `uint8_t pad` **Returns:** `uint16_t` #### `hal_adc_read_pad_mv` ```c hal_adc_read_pad_mv(hal_adc_t * adc, uint8_t pad) ``` - `hal_adc_t * adc` - `uint8_t pad` **Returns:** `uint32_t` ### LL layer Register-level access. No safety rails. #### `ll_adc_l0_clock_config` ```c ll_adc_l0_clock_config(ADC_TypeDef * adc, uint32_t pclk_hz) ``` L0 ADC clock (RM0377 §13.3.5). fADC limits per voltage range: 16 / 8 / 4 MHz in range 1 / 2 / 3 (STM32L011 DS Table 54); below 3.5 MHz LFMEN must be set. - HSI16 running: the asynchronous clock (CKMODE = 00), HSI16 / 1 in range 1 and / 2 in range 2 (PRESC, ADC_CCR[21:18]). This is the path the ADC always used, and the only one at the HSI16 clock levels. - HSI16 off (the MSI levels; HSI16 can't even run in range 3): the ADC had no clock at all there and every conversion timed out. It now takes PCLK / 2 (CKMODE = 01, any duty cycle), or PCLK / 4 if / 2 is over the limit. Must be called with the ADC disabled (ADEN = 0), before calibration. - `ADC_TypeDef * adc` - `uint32_t pclk_hz` **Returns:** `void` #### `ll_adc_calibrate` ```c ll_adc_calibrate(ADC_TypeDef * adc) ``` Run ADC self-calibration. Must be called before enabling ADC. ADC must be disabled (ADEN=0). - `ADC_TypeDef * adc` **Returns:** `void` #### `ll_adc_init` ```c ll_adc_init(ADC_TypeDef * adc, uint32_t smpr) ``` Initialize ADC for single-shot conversion, 12-bit resolution. adc: ADC instance smpr: sampling time (LL_ADC_SMPR_* — applies to all channels) Prerequisites: - ADC peripheral clock enabled - ADC pins configured as analog via ll_gpio_config_analog() - `ADC_TypeDef * adc` - `uint32_t smpr` **Returns:** `void` #### `ll_adc_read` ```c ll_adc_read(ADC_TypeDef * adc, uint32_t channel) ``` Read a single ADC channel (blocking). adc: ADC instance channel: ADC channel number (0-18, depends on pin mapping) Returns: 12-bit ADC reading (0-4095) - `ADC_TypeDef * adc` - `uint32_t channel` **Returns:** `uint16_t` #### `ll_adc_read_mv` ```c ll_adc_read_mv(ADC_TypeDef * adc, uint32_t channel, uint32_t vref_mv) ``` Read a channel and convert to millivolts. vref_mv: reference voltage in millivolts (typically 3300 for 3.3V) - `ADC_TypeDef * adc` - `uint32_t channel` - `uint32_t vref_mv` **Returns:** `uint32_t` --- Generated 2026-10-07 from the SDK headers at tiles@6f27e78. Canonical HTML: https://bergsonne.io/docs/sdk/adc ============================================================================== ## Core.Audio — SDK reference > Capture a digital PDM microphone on Core.ST.W5: the SAI clocks the mic and DMAs its 1-bit bitstream, then a software CIC decimator turns it into PCM — at the core, hal, or ll layer. | | | | --- | --- | | Category | `audio` | | Core header | `core_audio.h` | | Core header | `core_audio.h, core_pdm.h` | | HAL header | `hal_sai.h` | | LL header | `ll_sai.h` | | Generated from | `tiles@f70bca2` | | Canonical page | https://bergsonne.io/docs/sdk/audio | ### Core layer The recommended layer — portable across every Core, default instances, DSL-exposed. #### `core_audio_init` ```c core_audio_init(core_audio_t * a, uint32_t kernel_clk_hz, uint32_t pcm_rate_hz, uint8_t data_line, uint8_t clock_line, uint8_t cic_order, uint8_t gain_shift) ``` Configure the mic: SAI PDM block + decimator (capture not started). - `core_audio_t * kernel_clk_hz` - `uint32_t kernel_clk_hz` - `uint32_t pcm_rate_hz` - `uint8_t data_line` - `uint8_t clock_line` - `uint8_t cic_order` - `uint8_t gain_shift` **Returns:** `hal_status_t` #### `core_audio_start` ```c core_audio_start(core_audio_t * a, GPDMA_Channel_TypeDef * ch, uint8_t * pdm_buf, uint32_t pdm_len, int16_t * pcm_buf, core_audio_pcm_cb on_pcm, void * user_ctx) ``` Start continuous capture; `on_pcm` fires per half-buffer with PCM. - `core_audio_t * pdm_buf` - `GPDMA_Channel_TypeDef * pcm_buf` - `uint8_t * pdm_buf` - `uint32_t pdm_len` - `int16_t * pcm_buf` - `core_audio_pcm_cb on_pcm` - `void * user_ctx` **Returns:** `hal_status_t` #### `core_audio_stop` ```c core_audio_stop(core_audio_t * a) ``` Stop capture. - `core_audio_t * a` **Returns:** `void` #### `core_audio_irq` ```c core_audio_irq(core_audio_t * a) ``` Route the capture channel's GPDMA IRQ here. - `core_audio_t * a` **Returns:** `void` #### `core_pdm_init` ```c core_pdm_init(core_pdm_cic_t * st, uint8_t order, uint16_t decimation, uint8_t gain_shift, uint8_t lsb_first) ``` Initialise a CIC decimator. - `core_pdm_cic_t * st` - `uint8_t order` - `uint16_t decimation` - `uint8_t gain_shift` - `uint8_t lsb_first` **Returns:** `void` #### `core_pdm_process` ```c core_pdm_process(core_pdm_cic_t * st, const uint8_t * pdm, uint32_t nbytes, int16_t * out) ``` Decimate a block of packed PDM bits into PCM samples. - `core_pdm_cic_t * st` - `const uint8_t * pdm` - `uint32_t nbytes` - `int16_t * out` **Returns:** `uint32_t` ### HAL layer Peripheral-level control with explicit handles. #### `hal_sai_pdm_init` ```c hal_sai_pdm_init(hal_sai_t * s, SAI_Block_TypeDef * block, const hal_sai_pdm_config_t * cfg) ``` Configure SAI1 block A as a PDM master receiver (block left disabled). - `hal_sai_t * s` - `SAI_Block_TypeDef * block` - `const hal_sai_pdm_config_t * cfg` **Returns:** `hal_status_t` #### `hal_sai_capture_start` ```c hal_sai_capture_start(hal_sai_t * s, GPDMA_Channel_TypeDef * ch, uint8_t * buf, uint32_t len, hal_callback_t callback, void * ctx) ``` Start continuous DMA capture into `buf` (circular). - `hal_sai_t * buf` - `GPDMA_Channel_TypeDef * ch` - `uint8_t * buf` - `uint32_t len` - `hal_callback_t callback` - `void * ctx` **Returns:** `hal_status_t` #### `hal_sai_capture_stop` ```c hal_sai_capture_stop(hal_sai_t * s) ``` Stop capture (SAIEN off, DMA channel disabled). - `hal_sai_t * s` **Returns:** `void` #### `hal_sai_irq_handler` ```c hal_sai_irq_handler(hal_sai_t * s) ``` GPDMA channel ISR body — call from the channel's IRQ handler. - `hal_sai_t * s` **Returns:** `void` ### LL layer Register-level access. No safety rails. #### `ll_sai_block_enable` ```c ll_sai_block_enable(SAI_Block_TypeDef * b) ``` Enable a block — for master RX this immediately starts the bit clock. - `SAI_Block_TypeDef * b` **Returns:** `void` #### `ll_sai_block_disable` ```c ll_sai_block_disable(SAI_Block_TypeDef * b) ``` Disable a block (clear SAIEN) and wait for it to read back 0. - `SAI_Block_TypeDef * b` **Returns:** `void` #### `ll_sai_block_enable_dma` ```c ll_sai_block_enable_dma(SAI_Block_TypeDef * b) ``` Enable peripheral DMA requests (must precede SAIEN). - `SAI_Block_TypeDef * b` **Returns:** `void` #### `ll_sai_flush` ```c ll_sai_flush(SAI_Block_TypeDef * b) ``` Flush the FIFO (one-shot, self-clearing). - `SAI_Block_TypeDef * b` **Returns:** `void` #### `ll_sai_read` ```c ll_sai_read(SAI_Block_TypeDef * b) ``` Read one data word from the FIFO. - `SAI_Block_TypeDef * b` **Returns:** `uint32_t` #### `ll_sai_overrun` ```c ll_sai_overrun(SAI_Block_TypeDef * b) ``` True if an overrun has been latched. - `SAI_Block_TypeDef * b` **Returns:** `int` #### `ll_sai_clear_overrun` ```c ll_sai_clear_overrun(SAI_Block_TypeDef * b) ``` Clear the overrun flag. - `SAI_Block_TypeDef * b` **Returns:** `void` --- Generated 2026-10-07 from the SDK headers at tiles@f70bca2. Canonical HTML: https://bergsonne.io/docs/sdk/audio ============================================================================== ## Core.BLE — SDK reference > Bluetooth Low Energy on Core.ST.W5: advertise, define a GATT server with read/write/notify characteristics, and handle connections — a Core-layer API over the ST radio stack. | | | | --- | --- | | Category | `ble` | | Core header | `core_ble.h` | | Generated from | `tiles@452b1b1` | | Canonical page | https://bergsonne.io/docs/sdk/ble | ### Core layer The recommended layer — portable across every Core, default instances, DSL-exposed. #### `core_ble_set_services` ```c core_ble_set_services(void) ``` Register a service builder function. Called before core_ble_init(). The builder is invoked during init after the GATT server is ready. **Returns:** `void` #### `core_ble_add_services` ```c core_ble_add_services(void) ``` Register an ADDITIONAL service builder, run after the application's own. For SDK modules that bring a GATT service of their own (core_scope's Studio Link) without taking over core_ble_set_services(), which the application — or coregen's generated contract — owns. Call before core_ble_init(). Registering the same builder twice is harmless. Returns 0, or -1 if the slots are full or the stack has already started. **Returns:** `int` #### `core_ble_init` ```c core_ble_init(void) ``` Initialize the BLE stack. Call once after core_init(). **Returns:** `void` #### `core_ble_advertise` ```c core_ble_advertise(const char * name) ``` Start advertising. Call after init + at least one core_ble_process(). - `const char * name` **Returns:** `int` #### `core_ble_stop_advertise` ```c core_ble_stop_advertise(void) ``` Stop advertising. **Returns:** `int` #### `core_ble_process` ```c core_ble_process(void) ``` Process BLE events. Call continuously from main loop. **Returns:** `void` #### `core_ble_add_service` ```c core_ble_add_service(const char * name) ``` Add a GATT service. Returns a handle for adding characteristics. The UUID is auto-assigned SEQUENTIALLY by registration order (0000B000-…, 0000B100-…, …). Convenient, but the UUIDs shift if you reorder or insert services — so for a contract shared with client apps, prefer core_ble_add_service_id() to pin a stable, order-independent UUID. - `const char * name` **Returns:** `core_ble_svc_t` #### `core_ble_add_service_id` ```c core_ble_add_service_id(const char * name, uint16_t id) ``` Add a GATT service with an EXPLICIT 16-bit ID. The full UUID is 0000-8E22-4541-9D4C-21EDAE82ED19. Pinning the ID makes the GATT contract independent of registration order — the recommended path when the service map is a source of truth shared with phone / desktop client apps. - `const char * name` - `uint16_t id` **Returns:** `core_ble_svc_t` #### `core_ble_add_char` ```c core_ble_add_char(core_ble_svc_t svc, const char * name, uint8_t access, uint8_t type, core_ble_write_cb on_write, void * ctx) ``` Add a characteristic to a service. - `core_ble_svc_t svc` - `const char * name` - `uint8_t access` - `uint8_t type` - `core_ble_write_cb on_write` - `void * ctx` **Returns:** `core_ble_char_t` #### `core_ble_add_char_id` ```c core_ble_add_char_id(core_ble_svc_t svc, const char * name, uint16_t id, uint8_t access, uint8_t type, core_ble_write_cb on_write, void * ctx) ``` Add a characteristic with an EXPLICIT 16-bit ID (0000-8E22-…), the order-independent counterpart to core_ble_add_char(). Use for the stable contract shared with client apps. - `core_ble_svc_t svc` - `const char * name` - `uint16_t id` - `uint8_t access` - `uint8_t type` - `core_ble_write_cb on_write` - `void * ctx` **Returns:** `core_ble_char_t` #### `core_ble_add_service_sig` ```c core_ble_add_service_sig(const char * name, uint16_t uuid16) ``` Add a SIG-adopted service by its 16-bit Bluetooth UUID (e.g. 0x180F Battery Service, 0x180A Device Information). Use adopted services for standardised profiles so generic clients and the host OS recognise them; use the _id variants for custom application-specific data. - `const char * name` - `uint16_t uuid16` **Returns:** `core_ble_svc_t` #### `core_ble_add_char_sig` ```c core_ble_add_char_sig(core_ble_svc_t svc, const char * name, uint16_t uuid16, uint8_t access, uint8_t type, core_ble_write_cb on_write, void * ctx) ``` Add a SIG-adopted characteristic by its 16-bit Bluetooth UUID (e.g. 0x2A19 Battery Level, 0x2A29 Manufacturer Name) to a service. - `core_ble_svc_t svc` - `const char * name` - `uint16_t uuid16` - `uint8_t access` - `uint8_t type` - `core_ble_write_cb on_write` - `void * ctx` **Returns:** `core_ble_char_t` #### `core_ble_set_value` ```c core_ble_set_value(core_ble_char_t ch, const void * data, uint16_t len) ``` Update a characteristic's value. For readable characteristics, this is what the central will read. For notify characteristics, call core_ble_notify() after to push the update. - `core_ble_char_t ch` - `const void * data` - `uint16_t len` **Returns:** `int` #### `core_ble_notify` ```c core_ble_notify(core_ble_char_t ch) ``` Send a notification to the connected central. The central must have enabled notifications (CCCD) for this to work. Sends the current value set by core_ble_set_value(). - `core_ble_char_t ch` **Returns:** `int` #### `core_ble_subscribed` ```c core_ble_subscribed(core_ble_char_t ch) ``` Has a client subscribed to notifications on this characteristic? - `core_ble_char_t ch` **Returns:** `int` #### `core_ble_connected` ```c core_ble_connected(void) ``` Returns 1 if a central is connected. **Returns:** `int` #### `core_ble_on_connect` ```c core_ble_on_connect(void * ctx) ``` Set callback for connection events. - `void * ctx` **Returns:** `void` #### `core_ble_on_disconnect` ```c core_ble_on_disconnect(void * ctx) ``` Set callback for disconnection events. - `void * ctx` **Returns:** `void` #### `core_ble_set_tx_power` ```c core_ble_set_tx_power(uint8_t level) ``` TX power: 0=low(-20dBm), 1=medium(0dBm), 2=high(+10dBm). Default: 1. - `uint8_t level` **Returns:** `void` #### `core_ble_set_adv_interval` ```c core_ble_set_adv_interval(uint16_t min_ms, uint16_t max_ms) ``` Advertising interval in ms (20-10240). Default: 100/150. - `uint16_t min_ms` - `uint16_t max_ms` **Returns:** `void` #### `core_ble_set_conn_params` ```c core_ble_set_conn_params(uint16_t min_ms, uint16_t max_ms, uint16_t latency, uint16_t timeout_ms) ``` Request a preferred connection parameter set from the central. The central ultimately owns the connection timing, but a peripheral can ask for parameters that suit its traffic. After each connection the SDK sends an L2CAP update request (retrying until the stack accepts it, since the link is busy with pairing/discovery right after connecting). May also be called while connected to re-request — e.g. tighten the interval while streaming, relax it when idle. For Apple hosts, keep within their guidelines: interval_min >= 15 ms, interval_max >= interval_min + 15 ms, interval_max * (latency + 1) <= 2 s, and timeout in 2000..6000 ms. A short interval lowers latency for high-rate notifications at the cost of power; latency > 0 saves power when the peripheral often has nothing to send. - `uint16_t min_ms` - `uint16_t max_ms` - `uint16_t latency` - `uint16_t timeout_ms` **Returns:** `void` #### `core_ble_enable_pairing` ```c core_ble_enable_pairing(void) ``` Enable OS-level pairing + bonding (Just Works). When enabled, every characteristic is registered behind an encrypted link, so on first access the host runs its pairing flow (a "Pair?" prompt on most OSes; silent for Just Works on macOS). Keys are persisted in flash NVM, so the bond survives resets — the host doesn't re-pair on the next connection and the device stays listed in OS Bluetooth settings. (Reconnect itself is the central's choice; the peripheral simply re-advertises after a drop.) Call before core_ble_init(). Default: disabled (characteristics open, no pairing). Note that once enabled, *all* clients must pair to read/write. **Returns:** `void` --- Generated 2026-10-07 from the SDK headers at tiles@452b1b1. Canonical HTML: https://bergsonne.io/docs/sdk/ble ============================================================================== ## Clocks — SDK reference > How the system clock is set up: a named clock level in config.json (default "medium"), what each level is on each Core, the HSI / HSE / MSI / PLL sources, and the raw ll_rcc path. | | | | --- | --- | | Category | `clocks` | | LL header | `ll_rcc.h` | | Generated from | `tiles@6af026f` | | Canonical page | https://bergsonne.io/docs/sdk/clocks | ### LL layer Register-level access. No safety rails. #### `ll_rcc_hsi16_enable` ```c ll_rcc_hsi16_enable(void) ``` Enable the high-speed internal (HSI16) oscillator. **Returns:** `void` #### `ll_rcc_hse_enable` ```c ll_rcc_hse_enable(void) ``` Enable the high-speed external (HSE) oscillator / crystal. **Returns:** `void` #### `ll_flash_set_latency` ```c ll_flash_set_latency(uint32_t wait_states) ``` Set flash read latency (wait states). Must be set BEFORE increasing SYSCLK, or AFTER decreasing it. See ll_flash_latency_for_range() for the tables. - `uint32_t wait_states` **Returns:** `void` #### `ll_flash_latency_for_mhz` ```c ll_flash_latency_for_mhz(uint32_t mhz) ``` Flash wait states for SYSCLK `mhz` in the highest voltage range (range 1 on L0/L4/WBA; on the H5, the voltage scale currently applied). The WBA and L0 generated clock setup uses ll_flash_latency_for_range(), which also covers their lower ranges. - `uint32_t mhz` **Returns:** `uint32_t` #### `ll_rcc_pll_config` ```c ll_rcc_pll_config(uint32_t src, uint32_t m, uint32_t n, uint32_t r) ``` Configure and enable the main PLL. src: PLL source (LL_RCC_PLLSRC_*) m: input divider (1-based) n: VCO multiplier r: output divider for SYSCLK PLL must be disabled before calling this function. - `uint32_t src` - `uint32_t m` - `uint32_t n` - `uint32_t r` **Returns:** `void` #### `ll_rcc_pll_enable` ```c ll_rcc_pll_enable(void) ``` Enable the main PLL **Returns:** `void` #### `ll_rcc_pll_ready` ```c ll_rcc_pll_ready(void) ``` Check if PLL is locked and ready **Returns:** `int` #### `ll_rcc_set_sysclk` ```c ll_rcc_set_sysclk(uint32_t source) ``` Set the SYSCLK source - `uint32_t source` **Returns:** `void` #### `ll_rcc_wait_sysclk` ```c ll_rcc_wait_sysclk(uint32_t source) ``` Wait until the SYSCLK source has switched - `uint32_t source` **Returns:** `void` #### `ll_rcc_set_ahb_div` ```c ll_rcc_set_ahb_div(uint32_t div) ``` Set AHB prescaler. div: 1, 2, 4, 8, 16, 64, 128, 256, 512 - `uint32_t div` **Returns:** `void` #### `ll_rcc_set_apb1_div` ```c ll_rcc_set_apb1_div(uint32_t div) ``` Set APB1 prescaler. div: 1, 2, 4, 8, 16 - `uint32_t div` **Returns:** `void` #### `ll_rcc_set_apb2_div` ```c ll_rcc_set_apb2_div(uint32_t div) ``` Set APB2 prescaler. div: 1, 2, 4, 8, 16 - `uint32_t div` **Returns:** `void` --- Generated 2026-10-07 from the SDK headers at tiles@6af026f. Canonical HTML: https://bergsonne.io/docs/sdk/clocks ============================================================================== ## Core.Pad — SDK reference > Drive and read tile pads as digital I/O at the layer you want: core_pad by pad number, hal_pad one tier down, or ll_gpio in raw MCU port + pin. | | | | --- | --- | | Category | `pad` | | Core header | `core_pad.h` | | HAL header | `hal_gpio.h, hal_exti.h` | | LL header | `ll_gpio.h, ll_exti.h` | | Generated from | `tiles@f70bca2` | | Canonical page | https://bergsonne.io/docs/sdk/gpio | ### Core layer The recommended layer — portable across every Core, default instances, DSL-exposed. #### `core_pad_output` ```c core_pad_output(uint8_t pad) ``` Configure a pad as push-pull output (default). - `uint8_t pad` **Returns:** `void` #### `core_pad_output_od` ```c core_pad_output_od(uint8_t pad, uint32_t pull) ``` Configure a pad as open-drain output with optional pull resistor. Use PULL_UP for a wired-AND / I2C-style bus, PULL_NONE for external pull. - `uint8_t pad` - `uint32_t pull` **Returns:** `void` #### `core_pad_input` ```c core_pad_input(uint8_t pad, uint32_t pull) ``` Configure a pad as input with pull resistor. - `uint8_t pad` - `uint32_t pull` **Returns:** `void` #### `core_pad_write` — Studio ```c core_pad_write(uint8_t pad, int state) ``` Set a pad high (ON) or low (OFF). - `uint8_t pad` — range 1…64 - `int state` — range 0…1 **Returns:** `void` #### `core_pad_read` — Studio ```c core_pad_read(uint8_t pad) ``` Read a pad. Returns 0 or 1. - `uint8_t pad` — range 1…64 **Returns:** `int` #### `core_pad_toggle` — Studio ```c core_pad_toggle(uint8_t pad) ``` Toggle a pad output. - `uint8_t pad` — range 1…64 **Returns:** `void` #### `core_pad_resolve` ```c core_pad_resolve(uint8_t pad) ``` Resolve a pad to a fast handle once, outside the hot loop. - `uint8_t pad` **Returns:** `core_pad_fast_t` #### `core_pad_fast_set` ```c core_pad_fast_set(core_pad_fast_t h) ``` Drive the resolved pad high — single BSRR write. - `core_pad_fast_t h` **Returns:** `void` #### `core_pad_fast_clear` ```c core_pad_fast_clear(core_pad_fast_t h) ``` Drive the resolved pad low — single BSRR write. - `core_pad_fast_t h` **Returns:** `void` #### `core_pad_fast_write` ```c core_pad_fast_write(core_pad_fast_t h, int state) ``` Drive the resolved pad to `state` (0/1) — single BSRR write. - `core_pad_fast_t h` - `int state` **Returns:** `void` #### `core_pad_fast_read` ```c core_pad_fast_read(core_pad_fast_t h) ``` Read the resolved pad — single IDR load. Returns 0 or 1. - `core_pad_fast_t h` **Returns:** `int` #### `core_pad_bulk_write` ```c core_pad_bulk_write(GPIO_TypeDef * port, uint32_t set_mask, uint32_t clear_mask) ``` Bulk update many pins on ONE port in a single atomic BSRR write: pins in `set_mask` go high, pins in `clear_mask` go low (set wins on overlap, per BSRR). Build the masks from the generated PAD_n_MASK defines. Useful for synchronized buses / LED matrices without per-pad jitter. - `GPIO_TypeDef * port` - `uint32_t set_mask` - `uint32_t clear_mask` **Returns:** `void` #### `core_pad_analog` ```c core_pad_analog(uint8_t pad) ``` Configure a pad as analog (for ADC, DAC, comparator). - `uint8_t pad` **Returns:** `void` #### `core_pad_speed` ```c core_pad_speed(uint8_t pad, uint32_t speed) ``` Set the output speed (slew rate) of a pad. Use SPEED_LOW / SPEED_MED / SPEED_HIGH / SPEED_VHIGH. Only affects outputs — input pads ignore this setting. - `uint8_t pad` - `uint32_t speed` **Returns:** `void` #### `core_pad_on_change` ```c core_pad_on_change(uint8_t pad, uint32_t edge, hal_callback_t cb, void * ctx) ``` Register an edge-triggered callback on a pad. The pad is automatically configured as input with a sensible pull resistor based on edge direction: rising → pull-down (idle low) falling → pull-up (idle high) both → no pull (external bias expected) - `uint8_t pad` - `uint32_t edge` - `hal_callback_t cb` - `void * ctx` **Returns:** `hal_status_t` #### `core_pad_on_change_stop` ```c core_pad_on_change_stop(uint8_t pad) ``` Stop the interrupt on a pad. - `uint8_t pad` **Returns:** `void` ### HAL layer Peripheral-level control with explicit handles. #### `hal_pad_lookup` ```c hal_pad_lookup(uint8_t pad) ``` Look up the GPIO port and pin for a tile pad number. Returns {NULL, 0} for pads with no GPIO (power, reset, etc.). - `uint8_t pad` **Returns:** `hal_pad_gpio_t` #### `hal_pad_output` ```c hal_pad_output(uint8_t pad) ``` Configure a pad as push-pull output. Enables the port clock. - `uint8_t pad` **Returns:** `void` #### `hal_pad_output_od` ```c hal_pad_output_od(uint8_t pad, uint32_t pull) ``` Configure a pad as open-drain output with pull selection. Enables the port clock. - `uint8_t pad` - `uint32_t pull` **Returns:** `void` #### `hal_pad_input` ```c hal_pad_input(uint8_t pad, uint32_t pull) ``` Configure a pad as input with pull selection. - `uint8_t pad` - `uint32_t pull` **Returns:** `void` #### `hal_pad_analog` ```c hal_pad_analog(uint8_t pad) ``` Configure a pad as analog (for ADC, DAC, comparator). - `uint8_t pad` **Returns:** `void` #### `hal_pad_af` ```c hal_pad_af(uint8_t pad, uint32_t af, uint32_t otype, uint32_t speed, uint32_t pull) ``` Configure a pad for alternate function. - `uint8_t pad` - `uint32_t af` - `uint32_t otype` - `uint32_t speed` - `uint32_t pull` **Returns:** `void` #### `hal_pad_write` ```c hal_pad_write(uint8_t pad, int state) ``` Write a pad: 0 = low, non-zero = high - `uint8_t pad` - `int state` **Returns:** `void` #### `hal_pad_read` ```c hal_pad_read(uint8_t pad) ``` Read a pad. Returns 0 or 1. - `uint8_t pad` **Returns:** `int` #### `hal_pad_toggle` ```c hal_pad_toggle(uint8_t pad) ``` Toggle a pad. - `uint8_t pad` **Returns:** `void` #### `hal_exti_enable` ```c hal_exti_enable(uint8_t pad, uint32_t edge, hal_callback_t cb, void * ctx) ``` Enable an edge-triggered interrupt on a tile pad. Configures the pad's GPIO pin as an EXTI source, stores the callback, and enables the NVIC IRQ. The pad must already be configured as an input (see hal_pad_input or tal_exti_enable which does this automatically). - `uint8_t pad` - `uint32_t edge` - `hal_callback_t cb` - `void * ctx` **Returns:** `hal_status_t` #### `hal_exti_disable` ```c hal_exti_disable(uint8_t pad) ``` Disable the EXTI interrupt on a tile pad. Clears the callback and disables the EXTI line. The NVIC IRQ is only disabled when no other lines sharing the same IRQ are active (relevant for grouped IRQs on L0/L4). - `uint8_t pad` **Returns:** `void` ### LL layer Register-level access. No safety rails. #### `ll_gpio_set` ```c ll_gpio_set(GPIO_TypeDef * port, uint32_t pin_mask) ``` Set one or more pins high (write 1s to BSRR lower half) - `GPIO_TypeDef * port` - `uint32_t pin_mask` **Returns:** `void` #### `ll_gpio_clear` ```c ll_gpio_clear(GPIO_TypeDef * port, uint32_t pin_mask) ``` Set one or more pins low (write 1s to BSRR upper half) - `GPIO_TypeDef * port` - `uint32_t pin_mask` **Returns:** `void` #### `ll_gpio_toggle` ```c ll_gpio_toggle(GPIO_TypeDef * port, uint32_t pin_mask) ``` Toggle one or more pins (XOR with ODR) - `GPIO_TypeDef * port` - `uint32_t pin_mask` **Returns:** `void` #### `ll_gpio_read` ```c ll_gpio_read(GPIO_TypeDef * port, uint32_t pin_mask) ``` Read the input state of one or more pins - `GPIO_TypeDef * port` - `uint32_t pin_mask` **Returns:** `uint32_t` #### `ll_gpio_read_output` ```c ll_gpio_read_output(GPIO_TypeDef * port, uint32_t pin_mask) ``` Read the output state of one or more pins - `GPIO_TypeDef * port` - `uint32_t pin_mask` **Returns:** `uint32_t` #### `ll_gpio_set_mode` ```c ll_gpio_set_mode(GPIO_TypeDef * port, uint32_t pin, uint32_t mode) ``` Set the mode for a single pin. mode: LL_GPIO_MODE_INPUT, _OUTPUT, _AF, or _ANALOG - `GPIO_TypeDef * port` - `uint32_t pin` - `uint32_t mode` **Returns:** `void` #### `ll_gpio_set_output_type` ```c ll_gpio_set_output_type(GPIO_TypeDef * port, uint32_t pin, uint32_t otype) ``` Set the output type for a single pin. otype: LL_GPIO_OTYPE_PP (push-pull) or LL_GPIO_OTYPE_OD (open-drain) - `GPIO_TypeDef * port` - `uint32_t pin` - `uint32_t otype` **Returns:** `void` #### `ll_gpio_set_speed` ```c ll_gpio_set_speed(GPIO_TypeDef * port, uint32_t pin, uint32_t speed) ``` Set the output speed for a single pin. speed: LL_GPIO_SPEED_LOW, _MED, _HIGH, or _VHIGH - `GPIO_TypeDef * port` - `uint32_t pin` - `uint32_t speed` **Returns:** `void` #### `ll_gpio_set_pull` ```c ll_gpio_set_pull(GPIO_TypeDef * port, uint32_t pin, uint32_t pull) ``` Set the pull-up/pull-down for a single pin. pull: LL_GPIO_PULL_NONE, _UP, or _DOWN - `GPIO_TypeDef * port` - `uint32_t pin` - `uint32_t pull` **Returns:** `void` #### `ll_gpio_set_af` ```c ll_gpio_set_af(GPIO_TypeDef * port, uint32_t pin, uint32_t af) ``` Set the alternate function for a single pin (0-15). Pins 0-7 use AFR[0] (AFRL), pins 8-15 use AFR[1] (AFRH). - `GPIO_TypeDef * port` - `uint32_t pin` - `uint32_t af` **Returns:** `void` #### `ll_gpio_config_output` ```c ll_gpio_config_output(GPIO_TypeDef * port, uint32_t pin) ``` Configure a pin as general-purpose output (push-pull, no pull, medium speed). - `GPIO_TypeDef * port` - `uint32_t pin` **Returns:** `void` #### `ll_gpio_config_input` ```c ll_gpio_config_input(GPIO_TypeDef * port, uint32_t pin, uint32_t pull) ``` Configure a pin as input with optional pull. - `GPIO_TypeDef * port` - `uint32_t pin` - `uint32_t pull` **Returns:** `void` #### `ll_gpio_config_af` ```c ll_gpio_config_af(GPIO_TypeDef * port, uint32_t pin, uint32_t af, uint32_t otype, uint32_t speed, uint32_t pull) ``` Configure a pin for an alternate function. af: alternate function number (0-15) otype: LL_GPIO_OTYPE_PP or _OD speed: LL_GPIO_SPEED_LOW/MED/HIGH/VHIGH pull: LL_GPIO_PULL_NONE/UP/DOWN - `GPIO_TypeDef * port` - `uint32_t pin` - `uint32_t af` - `uint32_t otype` - `uint32_t speed` - `uint32_t pull` **Returns:** `void` #### `ll_gpio_config_analog` ```c ll_gpio_config_analog(GPIO_TypeDef * port, uint32_t pin) ``` Configure a pin as analog (disconnects from digital logic). - `GPIO_TypeDef * port` - `uint32_t pin` **Returns:** `void` #### `ll_exti_gpio_config` ```c ll_exti_gpio_config(GPIO_TypeDef * port, uint32_t pin, uint32_t edge) ``` Set up a GPIO pin as an interrupt source. port: GPIO port (GPIOA, GPIOB, ...) pin: pin number (0-15) edge: LL_EXTI_RISING, LL_EXTI_FALLING, or LL_EXTI_BOTH After calling this, enable the appropriate NVIC IRQ: ll_nvic_enable_irq(EXTI0_IRQn); Then implement the ISR handler (e.g. EXTI0_IRQHandler). - `GPIO_TypeDef * port` - `uint32_t pin` - `uint32_t edge` **Returns:** `void` #### `ll_exti_config` ```c ll_exti_config(uint32_t line, uint32_t edge) ``` Configure an EXTI line for interrupt generation. line: EXTI line (0-15) edge: LL_EXTI_RISING, LL_EXTI_FALLING, or LL_EXTI_BOTH Prerequisites: - SYSCFG clock enabled - EXTI source selected via ll_exti_set_source() - GPIO pin configured as input - NVIC IRQ enabled after this call - `uint32_t line` - `uint32_t edge` **Returns:** `void` #### `ll_exti_disable` ```c ll_exti_disable(uint32_t line) ``` Disable an EXTI line interrupt - `uint32_t line` **Returns:** `void` #### `ll_exti_pending` ```c ll_exti_pending(uint32_t line) ``` Check if an EXTI line has a pending interrupt - `uint32_t line` **Returns:** `int` #### `ll_exti_clear_pending` ```c ll_exti_clear_pending(uint32_t line) ``` Clear the pending flag for an EXTI line (write 1 to clear) - `uint32_t line` **Returns:** `void` #### `ll_exti_sw_trigger` ```c ll_exti_sw_trigger(uint32_t line) ``` Generate a software interrupt on an EXTI line - `uint32_t line` **Returns:** `void` #### `ll_exti_set_source` ```c ll_exti_set_source(uint32_t line, uint32_t port_index) ``` Select which GPIO port drives a given EXTI line. line: EXTI line number (0-15, matches pin number) port_index: 0=GPIOA, 1=GPIOB, 2=GPIOC, ... Example: ll_exti_set_source(6, 1) → EXTI6 from GPIOB pin 6 - `uint32_t line` - `uint32_t port_index` **Returns:** `void` --- Generated 2026-10-07 from the SDK headers at tiles@f70bca2. Canonical HTML: https://bergsonne.io/docs/sdk/gpio ============================================================================== ## Core.I2C — SDK reference > I2C master on every Core: probe and scan the bus, read and write device registers at 100 kHz / 400 kHz / 1 MHz — at the core_i2c, hal_i2c, or ll_i2c layer. | | | | --- | --- | | Category | `i2c` | | Core header | `core_i2c.h` | | HAL header | `hal_i2c.h` | | LL header | `ll_i2c.h` | | Generated from | `tiles@f70bca2` | | Canonical page | https://bergsonne.io/docs/sdk/i2c | ### Core layer The recommended layer — portable across every Core, default instances, DSL-exposed. #### `_core_i2c_timing` ```c _core_i2c_timing(uint32_t speed_hz) ``` Resolve the TIMINGR value for a given speed using the compile-time I2C kernel clock (I2C_KERNEL_CLK_MHZ from core_config.h). Returns 0 if no pre-computed value exists. - `uint32_t speed_hz` **Returns:** `uint32_t` #### `core_i2c_init` ```c core_i2c_init(core_i2c_t * h, I2C_TypeDef * instance, uint32_t speed_hz) ``` Initialize an I2C bus with automatic timing resolution. core_i2c_t bus; core_i2c_init(&bus, I2C1, I2C_400K); Resolves the TIMINGR value from the compile-time kernel clock. Enables FMP mode automatically when speed is I2C_1M. - `core_i2c_t * h` - `I2C_TypeDef * instance` - `uint32_t speed_hz` **Returns:** `hal_status_t` #### `core_i2c_init_cfg` ```c core_i2c_init_cfg(core_i2c_t * h, I2C_TypeDef * instance, const hal_i2c_config_t * cfg) ``` Initialize I2C with explicit config struct (advanced). For most use cases, prefer core_i2c_init(h, instance, speed_hz). - `core_i2c_t * h` - `I2C_TypeDef * instance` - `const hal_i2c_config_t * cfg` **Returns:** `hal_status_t` #### `core_i2c_write` ```c core_i2c_write(core_i2c_t * h, uint8_t addr, const uint8_t * data, uint32_t len) ``` Write data to a 7-bit address device. - `core_i2c_t * h` - `uint8_t addr` - `const uint8_t * data` - `uint32_t len` **Returns:** `hal_status_t` #### `core_i2c_read` ```c core_i2c_read(core_i2c_t * h, uint8_t addr, uint8_t * buf, uint32_t len) ``` Read data from a 7-bit address device. - `core_i2c_t * h` - `uint8_t addr` - `uint8_t * buf` - `uint32_t len` **Returns:** `hal_status_t` #### `core_i2c_write_reg` ```c core_i2c_write_reg(core_i2c_t * h, uint8_t addr, uint16_t reg, const uint8_t * data, uint32_t len) ``` Write to a device register (8 or 16-bit register address). - `core_i2c_t * h` - `uint8_t addr` - `uint16_t reg` - `const uint8_t * data` - `uint32_t len` **Returns:** `hal_status_t` #### `core_i2c_read_reg` ```c core_i2c_read_reg(core_i2c_t * h, uint8_t addr, uint16_t reg, uint8_t * buf, uint32_t len) ``` Read from a device register (repeated START). - `core_i2c_t * h` - `uint8_t addr` - `uint16_t reg` - `uint8_t * buf` - `uint32_t len` **Returns:** `hal_status_t` #### `core_i2c_write_byte` ```c core_i2c_write_byte(core_i2c_t * h, uint8_t addr, uint16_t reg, uint8_t value) ``` Write a single byte to a register. - `core_i2c_t * h` - `uint8_t addr` - `uint16_t reg` - `uint8_t value` **Returns:** `hal_status_t` #### `core_i2c_read_byte` ```c core_i2c_read_byte(core_i2c_t * h, uint8_t addr, uint16_t reg, uint8_t * value) ``` Read a single byte from a register. - `core_i2c_t * h` - `uint8_t addr` - `uint16_t reg` - `uint8_t * value` **Returns:** `hal_status_t` #### `core_i2c_probe` ```c core_i2c_probe(core_i2c_t * h, uint8_t addr) ``` Check if a device responds at addr. Returns I2C_OK or I2C_NACK. - `core_i2c_t * h` - `uint8_t addr` **Returns:** `hal_status_t` #### `core_i2c_scan` ```c core_i2c_scan(core_i2c_t * h, uint8_t * found, uint8_t * count, uint8_t max_count) ``` Scan the I2C bus (0x08–0x77). Fills found[] with responding addresses. - `core_i2c_t * h` - `uint8_t * found` - `uint8_t * count` - `uint8_t max_count` **Returns:** `void` #### `core_i2c_handle_for_bus` ```c core_i2c_handle_for_bus(uint8_t bus) ``` The I2C handle for a bus id declared in config.json, or NULL if the project doesn't declare that bus. Emitted per project by coregen into core_init.c (when the project declares any I2C bus); the *_bus helpers below resolve their handle through it. Forward-declared here rather than `#include "core_init.h"` so this header compiles without a project (val tests, examples without config.json). The natives-side caller is gated on CORE_HAS_I2C_BUSES, so the linker never asks for the symbol unless the dispatcher exists. - `uint8_t bus` **Returns:** `hal_i2c_t *` #### `core_i2c_write_byte_bus` — Studio ```c core_i2c_write_byte_bus(uint8_t bus, uint8_t addr, uint16_t reg, uint8_t value) ``` Write a single byte to a register on a device on `bus`. Returns I2C_OK on success, I2C_NACK / I2C_ERROR on bus failure, or I2C_ERROR if `bus` isn't declared in config.json. - `uint8_t bus` - `uint8_t addr` - `uint16_t reg` - `uint8_t value` **Returns:** `hal_status_t` #### `core_i2c_read_byte_bus` — Studio ```c core_i2c_read_byte_bus(uint8_t bus, uint8_t addr, uint16_t reg) ``` Read a single byte from a register on a device on `bus`. Returns the byte value (0..255) on success, or -1 on any error (bus undeclared, NACK, timeout). The signed return lets DSL programs branch on `< 0` without an out-pointer. - `uint8_t bus` - `uint8_t addr` - `uint16_t reg` **Returns:** `int` #### `core_i2c_probe_bus` — Studio ```c core_i2c_probe_bus(uint8_t bus, uint8_t addr) ``` Check if a device responds at `addr` on `bus`. Returns 1 if the device ACKs, 0 on NACK / timeout / undeclared bus. - `uint8_t bus` - `uint8_t addr` **Returns:** `int` #### `core_i2c_target_init` ```c 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) ``` Bring up an I2C peripheral as a read-only target at `addr`. - `core_i2c_target_t * addr` - `I2C_TypeDef * speed` - `uint8_t addr` - `uint32_t speed` - `uint8_t * regs` - `uint16_t n_regs` - `hal_i2c_target_cb_t cb` - `void * ctx` **Returns:** `hal_status_t` #### `core_i2c_target_init_rw` ```c 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) ``` As core_i2c_target_init, but accepts host writes to the register range [first, first + count). Everything outside stays read-only. - `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` **Returns:** `hal_status_t` #### `core_i2c_target_deinit` ```c core_i2c_target_deinit(core_i2c_target_t * t) ``` Stop answering on the bus. - `core_i2c_target_t * t` **Returns:** `void` ### HAL layer Peripheral-level control with explicit handles. #### `hal_i2c_init` ```c hal_i2c_init(hal_i2c_t * h, I2C_TypeDef * instance, const hal_i2c_config_t * cfg) ``` Initialize I2C in master mode. The peripheral clock is auto-enabled. SDA/SCL pins must be configured as AF open-drain with pull-up (via tile_init or manually). - `hal_i2c_t * h` - `I2C_TypeDef * instance` - `const hal_i2c_config_t * cfg` **Returns:** `hal_status_t` #### `hal_i2c_recover` ```c hal_i2c_recover(hal_i2c_t * h) ``` Attempt bus recovery when I2C is stuck (SDA held low). Disables the peripheral, bit-bangs SCL as GPIO to clock out the stuck slave, generates a STOP condition, then re-initializes I2C. Returns HAL_OK if SDA was released, HAL_ERROR if still stuck. - `hal_i2c_t * h` **Returns:** `hal_status_t` #### `hal_i2c_write` ```c hal_i2c_write(hal_i2c_t * h, uint8_t addr, const uint8_t * data, uint32_t len) ``` Write data to a 7-bit address device - `hal_i2c_t * h` - `uint8_t addr` - `const uint8_t * data` - `uint32_t len` **Returns:** `hal_status_t` #### `hal_i2c_read` ```c hal_i2c_read(hal_i2c_t * h, uint8_t addr, uint8_t * buf, uint32_t len) ``` Read data from a 7-bit address device - `hal_i2c_t * h` - `uint8_t addr` - `uint8_t * buf` - `uint32_t len` **Returns:** `hal_status_t` #### `hal_i2c_write_reg` ```c hal_i2c_write_reg(hal_i2c_t * h, uint8_t addr, uint16_t reg, const uint8_t * data, uint32_t len) ``` Write to a register on an I2C device. reg is 8-bit or 16-bit: if > 0xFF, two address bytes are sent (MSB first). - `hal_i2c_t * h` - `uint8_t addr` - `uint16_t reg` - `const uint8_t * data` - `uint32_t len` **Returns:** `hal_status_t` #### `hal_i2c_read_reg` ```c hal_i2c_read_reg(hal_i2c_t * h, uint8_t addr, uint16_t reg, uint8_t * buf, uint32_t len) ``` Read from a register on an I2C device. reg is 8-bit or 16-bit: if > 0xFF, two address bytes are sent (MSB first). - `hal_i2c_t * h` - `uint8_t addr` - `uint16_t reg` - `uint8_t * buf` - `uint32_t len` **Returns:** `hal_status_t` #### `hal_i2c_write_byte` ```c hal_i2c_write_byte(hal_i2c_t * h, uint8_t addr, uint16_t reg, uint8_t value) ``` Write a single byte to a register (8-bit or 16-bit reg) - `hal_i2c_t * h` - `uint8_t addr` - `uint16_t reg` - `uint8_t value` **Returns:** `hal_status_t` #### `hal_i2c_read_byte` ```c hal_i2c_read_byte(hal_i2c_t * h, uint8_t addr, uint16_t reg, uint8_t * value) ``` Read a single byte from a register (8-bit or 16-bit reg) - `hal_i2c_t * h` - `uint8_t addr` - `uint16_t reg` - `uint8_t * value` **Returns:** `hal_status_t` #### `hal_i2c_probe` ```c hal_i2c_probe(hal_i2c_t * h, uint8_t addr) ``` Check if a device responds at addr. Returns HAL_OK or HAL_NACK. - `hal_i2c_t * h` - `uint8_t addr` **Returns:** `hal_status_t` #### `hal_i2c_scan` ```c hal_i2c_scan(hal_i2c_t * h, uint8_t * found, uint8_t * count, uint8_t max_count) ``` Scan the I2C bus (0x08–0x77). Fills found[] with responding addresses. Returns actual count via *count. - `hal_i2c_t * h` - `uint8_t * found` - `uint8_t * count` - `uint8_t max_count` **Returns:** `void` #### `hal_i2c_target_init` ```c hal_i2c_target_init(hal_i2c_target_t * t, I2C_TypeDef * instance, const hal_i2c_target_config_t * cfg) ``` Bring up I2C as a target and unmask its interrupt. The peripheral clock is auto-enabled; SDA/SCL must already be AF open-drain (coregen does this from the I2C pads in config.json). Returns HAL_ERROR if regs/n_regs are unset, the writable window falls outside the register file, or the instance is not one this build installs a vector for. - `hal_i2c_target_t * t` - `I2C_TypeDef * instance` - `const hal_i2c_target_config_t * cfg` **Returns:** `hal_status_t` #### `hal_i2c_target_deinit` ```c hal_i2c_target_deinit(hal_i2c_target_t * t) ``` Stop answering, mask the IRQ, and unregister the instance. - `hal_i2c_target_t * t` **Returns:** `void` #### `hal_i2c_target_irq` ```c hal_i2c_target_irq(hal_i2c_target_t * t) ``` Service one I2C interrupt for this target. The SDK already installs the vectors for every I2C instance it knows, so calling this yourself is only needed with a custom vector table. - `hal_i2c_target_t * t` **Returns:** `void` ### LL layer Register-level access. No safety rails. #### `ll_i2c_init` ```c ll_i2c_init(I2C_TypeDef * i2c, uint32_t timing) ``` Initialize I2C in master mode. i2c: I2C instance timing: TIMINGR value (use LL_I2C_TIMING_* defines) fmp: set non-zero to enable Fast-mode Plus (CR1.FMP bit) Only effective on STM32WBA55 and STM32H523. On L422, FMP is enabled via SYSCFG — see ll_i2c_init_fmp(). On L011, FMP is not supported. - `I2C_TypeDef * i2c` - `uint32_t timing` **Returns:** `void` #### `ll_i2c_init_fmp` ```c ll_i2c_init_fmp(I2C_TypeDef * i2c, uint32_t timing) ``` Initialize I2C in Fast-mode Plus (1 MHz), with the Fm+ (20 mA) pad drive. WBA55 / H523: I2C_CR1.FMP (bit 24). L422: I2C_CR1 bits 31:24 are reserved (RM0394 §38.9.1); Fm+ drive is SYSCFG_CFGR1.I2C1_FMP (bit 20) / I2C3_FMP (bit 22), RM0394 §9.2.2, which needs the SYSCFG clock (RCC_APB2ENR.SYSCFGEN, bit 0). - `I2C_TypeDef * i2c` - `uint32_t timing` **Returns:** `void` #### `ll_i2c_write` ```c ll_i2c_write(I2C_TypeDef * i2c, uint8_t addr, const uint8_t * data, uint32_t len) ``` Master write to a 7-bit address device. addr: 7-bit slave address (unshifted, e.g. 0x68 for MPU6050) data: pointer to bytes to send len: number of bytes Returns LL_I2C_OK on success, LL_I2C_NACK, LL_I2C_ERROR, or LL_I2C_TIMEOUT on failure. - `I2C_TypeDef * i2c` - `uint8_t addr` - `const uint8_t * data` - `uint32_t len` **Returns:** `int` #### `ll_i2c_read` ```c ll_i2c_read(I2C_TypeDef * i2c, uint8_t addr, uint8_t * buf, uint32_t len) ``` Master read from a 7-bit address device. addr: 7-bit slave address (unshifted) buf: buffer to store received bytes len: number of bytes to read Returns LL_I2C_OK on success, LL_I2C_NACK, LL_I2C_ERROR, or LL_I2C_TIMEOUT on failure. - `I2C_TypeDef * i2c` - `uint8_t addr` - `uint8_t * buf` - `uint32_t len` **Returns:** `int` #### `ll_i2c_timing_100k` ```c ll_i2c_timing_100k(uint32_t kernel_mhz) ``` Select appropriate timing constant based on kernel clock MHz. Returns a 100kHz (standard mode) timing value. Returns 0 if no pre-computed value exists for the given frequency. - `uint32_t kernel_mhz` **Returns:** `uint32_t` #### `ll_i2c_timing_400k` ```c ll_i2c_timing_400k(uint32_t kernel_mhz) ``` Select appropriate timing constant based on kernel clock MHz. Returns a 400kHz (fast mode) timing value. Minimum kernel clock: 4 MHz. Returns 0 if unsupported. - `uint32_t kernel_mhz` **Returns:** `uint32_t` #### `ll_i2c_timing_1m` ```c ll_i2c_timing_1m(uint32_t kernel_mhz) ``` Select appropriate timing constant based on kernel clock MHz. Returns a 1MHz (fast mode plus) timing value. Minimum kernel clock: 16 MHz. Returns 0 if unsupported. - `uint32_t kernel_mhz` **Returns:** `uint32_t` #### `ll_i2c_target_init` ```c ll_i2c_target_init(I2C_TypeDef * i2c, uint32_t timing, uint8_t addr7) ``` Initialize I2C as a target (device) at a 7-bit own address. i2c: I2C instance timing: TIMINGR value (use LL_I2C_TIMING_* defines). A target does not drive SCL, but TIMINGR still sets SDADEL/SCLDEL, which govern data setup and hold. Use the constant matching the controller's speed. addr7: 7-bit own address, unshifted (e.g. 0x28) Enables address-match, RX, TX, NACK, STOP and error interrupts. The caller still has to unmask the IRQ in the NVIC. - `I2C_TypeDef * i2c` - `uint32_t timing` - `uint8_t addr7` **Returns:** `void` #### `ll_i2c_target_disable` ```c ll_i2c_target_disable(I2C_TypeDef * i2c) ``` Stop answering on the bus and release the own address. - `I2C_TypeDef * i2c` **Returns:** `void` #### `ll_i2c_target_is_read` ```c ll_i2c_target_is_read(I2C_TypeDef * i2c) ``` Direction of the transfer the controller just addressed us for. Returns 1 when the controller intends to READ from us (we transmit), 0 when it intends to write. Only meaningful while ADDR is set. - `I2C_TypeDef * i2c` **Returns:** `uint32_t` #### `ll_i2c_target_addcode` ```c ll_i2c_target_addcode(I2C_TypeDef * i2c) ``` The 7-bit address that actually matched. Worth reading when OAR2 or address masking puts more than one address on the same peripheral. - `I2C_TypeDef * i2c` **Returns:** `uint8_t` #### `ll_i2c_target_flush_tx` ```c ll_i2c_target_flush_tx(I2C_TypeDef * i2c) ``` Discard a byte still sitting in TXDR. TXE is write-1-to-flush. Always do this before answering a read. A controller that abandoned a previous read (or NACKed early) can leave a stale byte loaded, and without a flush that byte is the first thing the next read receives, shifting the whole transfer by one. - `I2C_TypeDef * i2c` **Returns:** `void` #### `ll_i2c_target_clear_addr` ```c ll_i2c_target_clear_addr(I2C_TypeDef * i2c) ``` Clear the address match, which releases the stretched SCL. - `I2C_TypeDef * i2c` **Returns:** `void` --- Generated 2026-10-07 from the SDK headers at tiles@f70bca2. Canonical HTML: https://bergsonne.io/docs/sdk/i2c ============================================================================== ## Core.LED — SDK reference > Drive every Core's onboard status LED at the layer you want — the core_* helpers, or the raw ll_gpio writes underneath. | | | | --- | --- | | Category | `led` | | Core header | `core_led.h` | | LL header | `ll_gpio.h` | | Generated from | `tiles@f356b04` | | Canonical page | https://bergsonne.io/docs/sdk/led | ### Core layer The recommended layer — portable across every Core, default instances, DSL-exposed. #### `core_led_init` ```c core_led_init(void) ``` Enable the GPIO clock and configure the LED pin as a push-pull output. Call once after core_init(). The LED starts in the off state. **Returns:** `void` #### `core_led_on` — Studio ```c core_led_on(void) ``` Turn the onboard LED on. Simplest possible light control — useful alongside blink/heartbeat when the user wants explicit on/off state rather than a timed pattern. **Returns:** `void` #### `core_led_off` — Studio ```c core_led_off(void) ``` Turn the onboard LED off. **Returns:** `void` #### `core_led_toggle` — Studio ```c core_led_toggle(void) ``` Flip the onboard LED's current state. Non-blocking — no delay. Good for driving the LED from an event handler that fires at a rate you've already chosen (e.g., a pad-edge ISR on a button). **Returns:** `void` #### `core_led_blink` — Studio ```c core_led_blink(int n, int on_ms, int off_ms) ``` Blink the LED n times. Each cycle turns the LED on for on_ms milliseconds and off for off_ms milliseconds. Blocking — returns after the last off period. A short on_ms (under 50 ms) is the on-time at 3.3 V: at lower supply voltages it is lengthened so the blink looks as bright (the off-time shrinks to keep the rhythm). See core_led_heartbeat(). - `int n` — range 1…100 - `int on_ms` (ms) — range 1…5000 - `int off_ms` (ms) — range 1…5000 **Returns:** `void` #### `core_led_sos` ```c core_led_sos(void) ``` Blink the SOS pattern (3 short, 3 long, 3 short) in an infinite loop. Use for unrecoverable errors. This function never returns. **Returns:** `void` #### `core_led_heartbeat` — Studio ```c core_led_heartbeat(int period_ms, int on_ms) ``` Start a free-running, asymmetric heartbeat on the onboard LED. Set it up once (e.g. at startup) and it runs on its own — the LED turns on for on_ms, off for the rest of period_ms, repeating forever. Serviced from the 1 ms SysTick interrupt, so it does NOT block your main loop the way a toggle-and-delay would: the loop stays free for tile reads and logic. Call again at any time to change the rhythm; pass period_ms = 0 to stop the heartbeat (the LED is left off). on_ms is clamped to period_ms. Brightness holds across the supply: a short on_ms (under 50 ms, e.g. a 2 ms blip) is the on-time at 3.3 V, and is lengthened as VDD drops (about 32 ms at 1.8 V), because the LED carries far less current there and the eye adds up the light of a short flash. VDD is read through VREFINT when the heartbeat is set (at most once a minute) on the L0, L4 and W5; the H5 uses the on-time as given. Examples: heartbeat(1000, 2) — a 1 Hz 2 ms blip, the same brightness at any VDD. heartbeat(1000, 100) — a 1 Hz "blip": 100 ms on, 900 ms off. heartbeat(500, 250) — a steady 1 Hz, 50%-duty pulse. Defined in core_led.c (not header-only) because it owns persistent state shared with the SysTick handler. - `int period_ms` (ms) — range 0…60000 - `int on_ms` (ms) — range 0…60000 **Returns:** `void` ### LL layer Register-level access. No safety rails. #### `ll_gpio_config_output` ```c ll_gpio_config_output(GPIO_TypeDef * port, uint32_t pin) ``` Configure a pin as general-purpose output (push-pull, no pull, medium speed). - `GPIO_TypeDef * port` - `uint32_t pin` **Returns:** `void` #### `ll_gpio_set` ```c ll_gpio_set(GPIO_TypeDef * port, uint32_t pin_mask) ``` Set one or more pins high (write 1s to BSRR lower half) - `GPIO_TypeDef * port` - `uint32_t pin_mask` **Returns:** `void` #### `ll_gpio_clear` ```c ll_gpio_clear(GPIO_TypeDef * port, uint32_t pin_mask) ``` Set one or more pins low (write 1s to BSRR upper half) - `GPIO_TypeDef * port` - `uint32_t pin_mask` **Returns:** `void` #### `ll_gpio_toggle` ```c ll_gpio_toggle(GPIO_TypeDef * port, uint32_t pin_mask) ``` Toggle one or more pins (XOR with ODR) - `GPIO_TypeDef * port` - `uint32_t pin_mask` **Returns:** `void` #### `ll_gpio_read` ```c ll_gpio_read(GPIO_TypeDef * port, uint32_t pin_mask) ``` Read the input state of one or more pins - `GPIO_TypeDef * port` - `uint32_t pin_mask` **Returns:** `uint32_t` --- Generated 2026-10-07 from the SDK headers at tiles@f356b04. Canonical HTML: https://bergsonne.io/docs/sdk/led ============================================================================== ## Core.Power — SDK reference > Low-power modes and wakeup on every Core: sleep, stop, and standby; RTC / pin / EXTI wakeup; and backup registers that survive reset — at the core_power or ll_pwr layer. | | | | --- | --- | | Category | `power` | | Core header | `core_power.h` | | LL header | `ll_pwr.h` | | Generated from | `tiles@f70bca2` | | Canonical page | https://bergsonne.io/docs/sdk/power | ### Core layer The recommended layer — portable across every Core, default instances, DSL-exposed. #### `core_clock_init` ```c core_clock_init(void) ``` Bring up the clock tree for the project's `clock` level (oscillator, PLL, flash wait states, bus prescalers) and start SysTick. Generated per project by coregen into core_init.c and called by core_init(); call it again only to restore the clocks by hand (core_stop_for already does after a wake). **Returns:** `void` #### `core_sleep` — Studio ```c core_sleep(void) ``` Sleep until any interrupt (CPU stopped, peripherals running). **Returns:** `void` #### `core_stop_for` — Studio ```c core_stop_for(uint32_t seconds) ``` Enter Stop mode for a number of seconds, then wake and restore clocks. Uses the RTC wakeup timer (LSI). Returns after wake with PLL + SysTick restored; core_millis() advances by the time slept, as the RTC measured it. If the watchdog is running, sleeps in chunks of half its timeout and feeds it in between. Core.ST.L4 with a USB host awake on the bus: waits in Sleep mode so the host can still reach it (see the USB note at the top). - `uint32_t seconds` — range 1…86400 **Returns:** `void` #### `core_stop_until_on_change_timeout` ```c core_stop_until_on_change_timeout(uint8_t pad, uint32_t edge, uint32_t timeout_ms) ``` Enter Stop mode until a GPIO edge on the given pad, or until `timeout_ms` has passed (0 = no timeout). Configures the pad as input, arms its EXTI, enters Stop, and restores clocks on wake. If the watchdog is running, wakes every half timeout to feed it and goes back to sleep. Core.ST.L4 with a USB host awake on the bus: waits in Sleep mode (see the USB note at the top). Already at the wake level: EXTI latches only edges that happen after it is armed, so a line that is already at its wake level — a sensor INT held low until serviced, for EDGE_FALLING — would never wake the Core. After arming, this reads the pad and returns 1 at once if it is already low (EDGE_FALLING) or high (EDGE_RISING). EDGE_BOTH always waits for a change. - `uint8_t pad` - `uint32_t edge` - `uint32_t timeout_ms` **Returns:** `int` #### `core_stop_until_on_change` ```c core_stop_until_on_change(uint8_t pad, uint32_t edge) ``` Enter Stop mode until a GPIO edge occurs on the given pad (no timeout). Returns right away if the pad can't take an edge interrupt, or if it is already at the level the edge wakes on (see core_stop_until_on_change_timeout()). If the watchdog is running, wakes every half timeout to feed it and goes back to sleep. - `uint8_t pad` - `uint32_t edge` **Returns:** `void` #### `core_standby_for` ```c core_standby_for(uint32_t seconds) ``` Enter Standby mode with RTC wakeup after the given seconds. On success it does not return — the MCU resets on wake. Check core_woke_from_standby() at the top of main() to detect a Standby wake. Watchdog: the IWDG keeps counting through Standby and nothing feeds it until main() runs again, and Standby can't be split into fed chunks. So while the watchdog is running this refuses — returns HAL_ERROR without sleeping — unless seconds is at most half the watchdog timeout (2 s with the default 5 s watchdog). Use core_stop_for() for longer sleeps. (Core.ST.H5: no check.) - `uint32_t seconds` — range 1…65536 **Returns:** `hal_status_t` #### `core_standby_until_on_change` ```c core_standby_until_on_change(uint8_t pad, uint32_t edge) ``` Enter Standby mode until a GPIO edge on the given pad (via WKUP pin). Does not return — MCU resets on wake. Not all pads support WKUP; returns without entering Standby if the pad has no WKUP capability, or if the watchdog is running (it would reset the Core long before most edges). On Core.ST.L4.2: pad 8 (PA0 = WKUP1) and pad 7 (PA2 = WKUP4). - `uint8_t pad` - `uint32_t edge` **Returns:** `void` #### `core_stop` ```c core_stop(void) ``` Enter Stop mode (caller manages wakeup source + clock recovery). A running watchdog keeps counting and is not fed: wake within its timeout. On the WBA the caller also owns the Stop 1 wait-state/HDIV5 rules (RM0493 §11.7.7). **Returns:** `void` #### `core_standby` ```c core_standby(void) ``` Enter Standby (caller manages wakeup source). Does not return. A running watchdog keeps counting through Standby and resets the Core at its timeout. **Returns:** `void` #### `core_woke_from_standby` — Studio ```c core_woke_from_standby(void) ``` Returns 1 if the MCU woke from Standby. The flag survives later resets until core_clear_standby_flag() — call that once you've handled the wake. **Returns:** `int` #### `core_clear_standby_flag` ```c core_clear_standby_flag(void) ``` Clear the Standby wake flag. Call after core_woke_from_standby() returns 1. **Returns:** `void` #### `core_watchdog_start_seconds` ```c core_watchdog_start_seconds(uint32_t seconds) ``` Start the independent watchdog with a timeout in seconds (convenience). For finer control use core_watchdog_start() from core_watchdog.h which accepts milliseconds. - `uint32_t seconds` **Returns:** `void` ### LL layer Register-level access. No safety rails. #### `ll_pwr_enable_backup_access` ```c ll_pwr_enable_backup_access(void) ``` Enable access to the backup domain (RTC, backup registers). Must be called before writing to RTC or backup registers. **Returns:** `void` #### `ll_pwr_sleep_wfi` ```c ll_pwr_sleep_wfi(void) ``` Enter Sleep mode. CPU clock stopped, all peripherals continue running. Wakes on any enabled interrupt. Use WFI (wait for interrupt) or WFE (wait for event). **Returns:** `void` #### `ll_pwr_stop` ```c ll_pwr_stop(void) ``` Enter Stop mode (lowest power with SRAM retention). All oscillators stopped except LSI/LSE. Wakes via EXTI (GPIO, RTC alarm, etc.). After waking, clock is MSI/HSI — you must reinitialize the PLL if needed. On L4: Stop 0, Stop 1, or Stop 2 available. On WBA/H5: Stop 0 or Stop 1. **Returns:** `void` #### `ll_pwr_standby` ```c ll_pwr_standby(void) ``` Enter Standby mode (lowest power, SRAM lost). Only RTC, IWDG, and wakeup pins remain powered. On wake, the MCU resets (starts from Reset_Handler). WARNING: All SRAM contents are lost. Save state to backup registers or flash before entering standby. **Returns:** `void` #### `ll_pwr_woke_from_standby` ```c ll_pwr_woke_from_standby(void) ``` Check if we woke from standby. The flag is not cleared by a system reset, so it stays set across later resets until ll_pwr_clear_standby_flag(). **Returns:** `int` #### `ll_pwr_clear_standby_flag` ```c ll_pwr_clear_standby_flag(void) ``` Clear the standby flag **Returns:** `void` --- Generated 2026-10-07 from the SDK headers at tiles@f70bca2. Canonical HTML: https://bergsonne.io/docs/sdk/power ============================================================================== ## Core.Scope — SDK reference > Stream variables from a running Core to a live plot in Studio, or to Python, MATLAB or anything else that speaks the protocol, over USB or Bluetooth — core_scope. | | | | --- | --- | | Category | `scope` | | Core header | `core_scope.h` | | Generated from | `tiles@e95f3f8` | | Canonical page | https://bergsonne.io/docs/sdk/scope | ### Core layer The recommended layer — portable across every Core, default instances, DSL-exposed. #### `core_scope_init` ```c core_scope_init(void) ``` Start the module on the Core's default link. **Returns:** `void` #### `core_scope_set_link` ```c core_scope_set_link(const core_scope_link_t * link) ``` Stream over a different link than the default. - `const core_scope_link_t * link` **Returns:** `void` #### `core_scope_add` ```c core_scope_add(const char * name, const volatile void * ptr, core_scope_type_t type) ``` Register one variable as a channel. - `const char * name` - `const volatile void * ptr` - `core_scope_type_t type` **Returns:** `int` #### `core_scope_add_array` ```c core_scope_add_array(const char * name, const volatile void * ptr, core_scope_type_t type, uint8_t count) ``` Register a fixed array as channels `name[0]` .. `name[count-1]`. - `const char * name` - `const volatile void * ptr` - `core_scope_type_t type` - `uint8_t count` **Returns:** `int` #### `core_scope_set_interval_ms` ```c core_scope_set_interval_ms(uint32_t ms) ``` Limit core_scope_update() to one sample per `ms` milliseconds. - `uint32_t ms` — range 0…60000 **Returns:** `void` #### `core_scope_declare_rate_hz` ```c core_scope_declare_rate_hz(uint32_t hz) ``` Promise that samples are taken at exactly this rate. - `uint32_t hz` — range 0…4000000 **Returns:** `void` #### `core_scope_declare_rate_millihertz` ```c core_scope_declare_rate_millihertz(uint32_t millihertz) ``` As core_scope_declare_rate_hz(), in millihertz — for rates that are not a whole number of hertz (an IMU at 416.667 Hz = 416667). - `uint32_t millihertz` **Returns:** `void` #### `core_scope_sample` ```c core_scope_sample(void) ``` Snapshot the registered variables. ISR-safe. **Returns:** `void` #### `core_scope_pump` ```c core_scope_pump(void) ``` Frame the pending samples and hand them to the link. Main loop. **Returns:** `void` #### `core_scope_update` ```c core_scope_update(void) ``` core_scope_sample() + core_scope_pump(), honoring the interval gate. The one call a simple main loop needs. **Returns:** `void` #### `core_scope_enable` ```c core_scope_enable(int on) ``` Pause or resume at run time (a debug button, a BLE command). - `int on` **Returns:** `void` #### `core_scope_active` ```c core_scope_active(void) ``` Is a host listening right now? **Returns:** `int` #### `core_scope_dropped` ```c core_scope_dropped(void) ``` Samples dropped since boot because the ring was full. **Returns:** `uint32_t` --- Generated 2026-10-07 from the SDK headers at tiles@e95f3f8. Canonical HTML: https://bergsonne.io/docs/sdk/scope ============================================================================== ## Core.SPI — SDK reference > SPI master on the Cores that have it: full-duplex byte transfers with software chip-select, plus non-blocking DMA — at the core_spi, hal_spi, or ll_spi layer. | | | | --- | --- | | Category | `spi` | | Core header | `core_spi.h` | | HAL header | `hal_spi.h` | | LL header | `ll_spi.h` | | Generated from | `tiles@003821d` | | Canonical page | https://bergsonne.io/docs/sdk/spi | ### Core layer The recommended layer — portable across every Core, default instances, DSL-exposed. #### `core_spi_init` ```c core_spi_init(hal_spi_t * h, SPI_TypeDef * instance, const hal_spi_config_t * cfg) ``` Initialize SPI in master mode (8-bit frames). - `hal_spi_t * h` - `SPI_TypeDef * instance` - `const hal_spi_config_t * cfg` **Returns:** `hal_status_t` #### `core_spi_configure` ```c core_spi_configure(hal_spi_t * h, const hal_spi_config_t * cfg) ``` Change prescaler, SPI mode or bit order, keeping the CS pad. - `hal_spi_t * h` - `const hal_spi_config_t * cfg` **Returns:** `hal_status_t` #### `core_spi_sck_hz` ```c core_spi_sck_hz(const hal_spi_t * h) ``` The SCK frequency the handle is set to, in Hz. - `const hal_spi_t * h` **Returns:** `uint32_t` #### `core_spi_set_cs` ```c core_spi_set_cs(hal_spi_t * h, uint8_t pad) ``` Assign a CS pin by tile pad number. - `hal_spi_t * h` - `uint8_t pad` **Returns:** `void` #### `core_spi_select` ```c core_spi_select(hal_spi_t * h) ``` Assert CS. Everything until core_spi_deselect() is one transaction. - `hal_spi_t * h` **Returns:** `void` #### `core_spi_deselect` ```c core_spi_deselect(hal_spi_t * h) ``` Deassert CS, ending the transaction. - `hal_spi_t * h` **Returns:** `void` #### `core_spi_transfer` ```c core_spi_transfer(hal_spi_t * h, uint8_t tx) ``` Full-duplex single byte. CS untouched. - `hal_spi_t * h` - `uint8_t tx` **Returns:** `uint8_t` #### `core_spi_exchange` ```c core_spi_exchange(hal_spi_t * h, const uint8_t * tx, uint8_t * rx, uint32_t len) ``` Full-duplex transfer of `len` bytes. CS untouched. - `hal_spi_t * h` - `const uint8_t * tx` - `uint8_t * rx` - `uint32_t len` **Returns:** `hal_status_t` #### `core_spi_write` ```c core_spi_write(hal_spi_t * h, const uint8_t * data, uint32_t len) ``` Write-only: send `len` bytes, discard what comes back. CS untouched. - `hal_spi_t * h` - `const uint8_t * data` - `uint32_t len` **Returns:** `hal_status_t` #### `core_spi_read` ```c core_spi_read(hal_spi_t * h, uint8_t * buf, uint32_t len) ``` Read-only: clock out the fill byte (0xFF) and capture `len` bytes. CS untouched. - `hal_spi_t * h` - `uint8_t * buf` - `uint32_t len` **Returns:** `hal_status_t` #### `core_spi_xfer` ```c core_spi_xfer(hal_spi_t * h, const uint8_t * tx, uint8_t * rx, uint32_t len) ``` One CS-framed full-duplex transaction: select, exchange, deselect. - `hal_spi_t * h` - `const uint8_t * tx` - `uint8_t * rx` - `uint32_t len` **Returns:** `hal_status_t` #### `core_spi_write_read` ```c core_spi_write_read(hal_spi_t * h, const uint8_t * tx, uint32_t tx_len, uint8_t * rx, uint32_t rx_len) ``` One CS-framed command + data transaction. - `hal_spi_t * h` - `const uint8_t * tx` - `uint32_t tx_len` - `uint8_t * rx` - `uint32_t rx_len` **Returns:** `hal_status_t` #### `core_spi_exchange_dma` ```c core_spi_exchange_dma(hal_spi_t * h, const uint8_t * tx, uint8_t * rx, uint32_t len) ``` Full-duplex DMA transfer, blocking until the last frame is off the bus. CS untouched. - `hal_spi_t * h` - `const uint8_t * tx` - `uint8_t * rx` - `uint32_t len` **Returns:** `hal_status_t` #### `core_spi_xfer_dma` ```c core_spi_xfer_dma(hal_spi_t * h, const uint8_t * tx, uint8_t * rx, uint32_t len, hal_callback_t cb, void * ctx) ``` Start a full-duplex DMA transfer and return at once. - `hal_spi_t * h` - `const uint8_t * tx` - `uint8_t * rx` - `uint32_t len` - `hal_callback_t cb` - `void * ctx` **Returns:** `hal_status_t` #### `core_spi_dma_wait` ```c core_spi_dma_wait(hal_spi_t * h) ``` Wait for a core_spi_xfer_dma() transfer to finish (bounded). - `hal_spi_t * h` **Returns:** `hal_status_t` #### `core_spi_busy` ```c core_spi_busy(hal_spi_t * h) ``` Whether a DMA transfer is in progress on this handle. - `hal_spi_t * h` **Returns:** `int` #### `core_spi_handle_for_bus` ```c core_spi_handle_for_bus(uint8_t bus) ``` The SPI handle for a bus id declared in config.json, or NULL if the project doesn't declare that bus. Emitted per project by coregen into core_init.c (when the project declares any SPI bus); the *_bus helpers below resolve their handle through it. Forward-declared here rather than `#include "core_init.h"` so this header compiles without a project (val tests, examples without config.json). The natives-side caller is gated on CORE_HAS_SPI_BUSES, so the linker never asks for the symbol unless the dispatcher exists. - `uint8_t bus` **Returns:** `hal_spi_t *` #### `core_spi_xfer_byte_bus` — Studio ```c core_spi_xfer_byte_bus(uint8_t bus, uint8_t cs_pad, uint8_t tx) ``` Single-byte full-duplex transfer over `bus`, with CS auto-managed around the call (asserted before, deasserted after). Returns the received byte (0..255) on success or -1 on any error (bus undeclared, cs_pad undefined, transfer timed out). The signed return lets DSL programs branch on `< 0` without an out-pointer. Most chip protocols pair two of these (write a register address, then read or write the value). Multi-byte sequences that need CS held across them — display init streams, audio frame transfers — still need Tier 1 with manual select/deselect. - `uint8_t bus` - `uint8_t cs_pad` - `uint8_t tx` **Returns:** `int` ### HAL layer Peripheral-level control with explicit handles. #### `hal_spi_init` ```c hal_spi_init(hal_spi_t * h, SPI_TypeDef * instance, const hal_spi_config_t * cfg) ``` Initialize SPI in master mode, 8-bit frames. The peripheral clock is auto-enabled and the peripheral reset first. SCK/MOSI/MISO pins must be configured for AF (via coregen or manually). - `hal_spi_t * h` - `SPI_TypeDef * instance` - `const hal_spi_config_t * cfg` **Returns:** `hal_status_t` #### `hal_spi_configure` ```c hal_spi_configure(hal_spi_t * h, const hal_spi_config_t * cfg) ``` Change prescaler, mode or bit order on an initialized handle. Keeps the CS pin. Waits (bounded) for the bus to go idle first. - `hal_spi_t * h` - `const hal_spi_config_t * cfg` **Returns:** `hal_status_t` #### `hal_spi_deinit` ```c hal_spi_deinit(hal_spi_t * h) ``` Disable the SPI peripheral and release the handle. - `hal_spi_t * h` **Returns:** `void` #### `hal_spi_set_cs` ```c hal_spi_set_cs(hal_spi_t * h, GPIO_TypeDef * port, uint32_t pin) ``` Assign a CS GPIO pin for automatic chip-select management. The pin is configured as push-pull output and deasserted (high). - `hal_spi_t * h` - `GPIO_TypeDef * port` - `uint32_t pin` **Returns:** `void` #### `hal_spi_set_cs_map` ```c hal_spi_set_cs_map(hal_spi_t * h, const hal_spi_cs_t * map, uint8_t n) ``` Attach a chip-select map for several devices on one bus, one pin per device. Every pin is deasserted, then configured as a push-pull output. The table is kept by pointer, so it must outlive the handle (static const). Set cs_active_low first if the devices use active-high selects. hal_spi_select()/deselect() keep driving only the pin from hal_spi_set_cs() (none, when coregen built a map); use hal_spi_select_id() per device. - `hal_spi_t * h` - `const hal_spi_cs_t * map` - `uint8_t n` **Returns:** `void` #### `hal_spi_select` ```c hal_spi_select(hal_spi_t * h) ``` Assert CS (drive active — low by default). - `hal_spi_t * h` **Returns:** `void` #### `hal_spi_deselect` ```c hal_spi_deselect(hal_spi_t * h) ``` Deassert CS (drive inactive — high by default). - `hal_spi_t * h` **Returns:** `void` #### `hal_spi_select_id` ```c hal_spi_select_id(hal_spi_t * h, uint8_t id) ``` Assert the chip select of device `id`. With a chip-select map, only the entry whose id matches is driven; an id that is not in the map drives nothing and returns -1 (never some other device's pin). Without a map the bus has one device, and this asserts the hal_spi_set_cs() pin (if any) for any id. - `hal_spi_t * h` - `uint8_t id` **Returns:** `int` #### `hal_spi_deselect_id` ```c hal_spi_deselect_id(hal_spi_t * h, uint8_t id) ``` Deassert the chip select that hal_spi_select_id(h, id) asserted. - `hal_spi_t * h` - `uint8_t id` **Returns:** `void` #### `hal_spi_sck_hz` ```c hal_spi_sck_hz(const hal_spi_t * h) ``` The SCK frequency the handle's prescaler gives, in Hz: the SPI kernel clock (SYSCLK at every coregen clock level, read from the SysTick reload) divided by 2..256. - `const hal_spi_t * h` **Returns:** `uint32_t` #### `hal_spi_exchange` ```c hal_spi_exchange(hal_spi_t * h, const uint8_t * tx, uint8_t * rx, uint32_t len) ``` Full-duplex transfer of `len` bytes without touching CS. NULL tx clocks out the handle's fill byte (0xFF); NULL rx discards what comes back. tx and rx may be the same buffer. - `hal_spi_t * h` - `const uint8_t * tx` - `uint8_t * rx` - `uint32_t len` **Returns:** `hal_status_t` #### `hal_spi_transfer` ```c hal_spi_transfer(hal_spi_t * h, uint8_t tx) ``` Full-duplex single byte: send tx, return received byte (0xFF on error). - `hal_spi_t * h` - `uint8_t tx` **Returns:** `uint8_t` #### `hal_spi_transfer_buf` ```c hal_spi_transfer_buf(hal_spi_t * h, uint8_t * buf, uint32_t len) ``` Full-duplex buffer transfer (in-place: rx overwrites buf). - `hal_spi_t * h` - `uint8_t * buf` - `uint32_t len` **Returns:** `void` #### `hal_spi_write` ```c hal_spi_write(hal_spi_t * h, const uint8_t * data, uint32_t len) ``` Write-only: send data, discard received bytes. CS untouched. - `hal_spi_t * h` - `const uint8_t * data` - `uint32_t len` **Returns:** `hal_status_t` #### `hal_spi_read` ```c hal_spi_read(hal_spi_t * h, uint8_t * buf, uint32_t len) ``` Read-only: clock out the fill byte (0xFF), capture received bytes. CS untouched. - `hal_spi_t * h` - `uint8_t * buf` - `uint32_t len` **Returns:** `hal_status_t` #### `hal_spi_xfer` ```c hal_spi_xfer(hal_spi_t * h, const uint8_t * tx, uint8_t * rx, uint32_t len) ``` One CS-framed full-duplex transaction: select → exchange → deselect. tx and rx can be the same buffer. Either can be NULL. - `hal_spi_t * h` - `const uint8_t * tx` - `uint8_t * rx` - `uint32_t len` **Returns:** `hal_status_t` #### `hal_spi_write_read` ```c hal_spi_write_read(hal_spi_t * h, const uint8_t * tx, uint32_t tx_len, uint8_t * rx, uint32_t rx_len) ``` One CS-framed command + data transaction: select, send tx_len bytes (received bytes discarded), then clock in rx_len bytes (sending the fill byte), deselect. CS stays asserted across both phases (SPI-NOR reads: command + 3-byte address + data). - `hal_spi_t * h` - `const uint8_t * tx` - `uint32_t tx_len` - `uint8_t * rx` - `uint32_t rx_len` **Returns:** `hal_status_t` #### `hal_spi_exchange_dma` ```c hal_spi_exchange_dma(hal_spi_t * h, const uint8_t * tx, uint8_t * rx, uint32_t len) ``` Full-duplex DMA transfer, blocking until the last frame is off the bus. CS untouched. Same NULL conventions as hal_spi_exchange(). Bounded: a stall (no DMA progress for the stall budget) aborts both channels, resets the peripheral and returns HAL_TIMEOUT. - `hal_spi_t * h` - `const uint8_t * tx` - `uint8_t * rx` - `uint32_t len` **Returns:** `hal_status_t` #### `hal_spi_xfer_dma` ```c hal_spi_xfer_dma(hal_spi_t * h, const uint8_t * tx, uint8_t * rx, uint32_t len, hal_callback_t cb, void * ctx) ``` Start a full-duplex DMA transfer (non-blocking). - `hal_spi_t * h` - `const uint8_t * tx` - `uint8_t * rx` - `uint32_t len` - `hal_callback_t cb` - `void * ctx` **Returns:** `hal_status_t` #### `hal_spi_dma_wait` ```c hal_spi_dma_wait(hal_spi_t * h) ``` Wait (bounded) for an async DMA transfer started with hal_spi_xfer_dma(). On a stall it aborts the transfer and resets the peripheral. - `hal_spi_t * h` **Returns:** `hal_status_t` #### `hal_spi_busy` ```c hal_spi_busy(hal_spi_t * h) ``` Returns 1 if a DMA transfer is in progress. - `hal_spi_t * h` **Returns:** `int` ### LL layer Register-level access. No safety rails. #### `ll_spi_stall_cycles` ```c ll_spi_stall_cycles(SPI_TypeDef * spi) ``` Stall budget, in CPU cycles, for the SPI's current baud rate. - `SPI_TypeDef * spi` **Returns:** `uint32_t` #### `ll_spi_config` ```c ll_spi_config(SPI_TypeDef * spi, uint32_t prescaler, uint32_t cpol, uint32_t cpha, uint32_t lsb_first) ``` Configure SPI as an 8-bit master with software CS. - `SPI_TypeDef * spi` - `uint32_t prescaler` - `uint32_t cpol` - `uint32_t cpha` - `uint32_t lsb_first` **Returns:** `void` #### `ll_spi_init` ```c ll_spi_init(SPI_TypeDef * spi, uint32_t prescaler, uint32_t cpol, uint32_t cpha) ``` Initialize SPI in master mode, 8-bit, MSB-first. spi: SPI instance prescaler: LL_SPI_PRESCALER_* (clock divider) cpol: LL_SPI_CPOL_LOW or _HIGH cpha: LL_SPI_CPHA_1EDGE or _2EDGE Prerequisites: - Peripheral clock enabled via ll_rcc_apb1/2_clk_enable() - SCK/MOSI/MISO pins configured for AF via ll_gpio_config_af() - CS managed manually via GPIO (not hardware NSS) - `SPI_TypeDef * spi` - `uint32_t prescaler` - `uint32_t cpol` - `uint32_t cpha` **Returns:** `void` #### `ll_spi_xfer_poll` ```c ll_spi_xfer_poll(SPI_TypeDef * spi, const uint8_t * tx, uint8_t * rx, uint32_t len, uint8_t fill, uint32_t stall) ``` Bounded full-duplex transfer of `len` bytes (polled, FIFO-pipelined). - `SPI_TypeDef * spi` - `const uint8_t * tx` - `uint8_t * rx` - `uint32_t len` - `uint8_t fill` - `uint32_t stall` **Returns:** `int` #### `ll_spi_transfer` ```c ll_spi_transfer(SPI_TypeDef * spi, uint8_t tx_data) ``` Transfer a single byte (full-duplex): send tx_data, return received byte. Bounded: returns 0xFF if the transfer stalls (use ll_spi_xfer_poll to see the error). - `SPI_TypeDef * spi` - `uint8_t tx_data` **Returns:** `uint8_t` #### `ll_spi_transfer_buf` ```c ll_spi_transfer_buf(SPI_TypeDef * spi, uint8_t * buf, uint32_t len) ``` Transfer a buffer (full-duplex, in-place). tx_buf is sent; received data overwrites tx_buf. Pass NULL for tx_buf to clock out 0xFF and discard what comes back. - `SPI_TypeDef * spi` - `uint8_t * buf` - `uint32_t len` **Returns:** `void` #### `ll_spi_write` ```c ll_spi_write(SPI_TypeDef * spi, uint8_t data) ``` Write-only transfer (discard received data). - `SPI_TypeDef * spi` - `uint8_t data` **Returns:** `void` #### `ll_spi_read` ```c ll_spi_read(SPI_TypeDef * spi) ``` Read-only transfer (send 0xFF, return received byte). - `SPI_TypeDef * spi` **Returns:** `uint8_t` #### `ll_spi_disable` ```c ll_spi_disable(SPI_TypeDef * spi) ``` Disable the SPI after the bus goes idle (bounded wait: FTLVL=0, BSY=0 on the L4, RM0394 §41.4.9; a clean suspend-then-disable on SPI v2). - `SPI_TypeDef * spi` **Returns:** `void` #### `ll_spi_enable_dma_rx` ```c ll_spi_enable_dma_rx(SPI_TypeDef * spi) ``` Enable DMA requests for RX - `SPI_TypeDef * spi` **Returns:** `void` #### `ll_spi_disable_dma_rx` ```c ll_spi_disable_dma_rx(SPI_TypeDef * spi) ``` Disable DMA requests for RX - `SPI_TypeDef * spi` **Returns:** `void` #### `ll_spi_enable_dma_tx` ```c ll_spi_enable_dma_tx(SPI_TypeDef * spi) ``` Enable DMA requests for TX - `SPI_TypeDef * spi` **Returns:** `void` #### `ll_spi_disable_dma_tx` ```c ll_spi_disable_dma_tx(SPI_TypeDef * spi) ``` Disable DMA requests for TX - `SPI_TypeDef * spi` **Returns:** `void` #### `ll_spi_init_halfduplex` ```c ll_spi_init_halfduplex(SPI_TypeDef * spi, uint32_t prescaler, uint32_t cpol, uint32_t cpha) ``` Initialize SPI v2 as a half-duplex (1-line) master, 8-bit, MSB-first, starting in transmit direction. Leaves SPE disabled. Pin/clock prerequisites are the same as ll_spi_init(). - `SPI_TypeDef * spi` - `uint32_t prescaler` - `uint32_t cpol` - `uint32_t cpha` **Returns:** `void` #### `ll_spi_halfduplex_set_dir` ```c ll_spi_halfduplex_set_dir(SPI_TypeDef * spi, int tx) ``` Select half-duplex data direction (1 = transmit, 0 = receive). Must be called with SPE=0 (HDDIR is config-protected while enabled). - `SPI_TypeDef * spi` - `int tx` **Returns:** `void` #### `ll_spi_halfduplex_write` ```c ll_spi_halfduplex_write(SPI_TypeDef * spi, const uint8_t * data, uint32_t len) ``` Polled half-duplex master transmit of `len` bytes. Programs TSIZE (with SPE disabled), enables the peripheral, starts the master clock, feeds the FIFO, and waits for end-of-transfer. Leaves SPE disabled so the next call can reprogram TSIZE. Returns 1 on success, 0 on overrun/timeout. - `SPI_TypeDef * spi` - `const uint8_t * data` - `uint32_t len` **Returns:** `int` #### `ll_spi_dma_stop` ```c ll_spi_dma_stop(SPI_TypeDef * spi) ``` Stop a DMA transfer: disable the peripheral + DMA request generation, clear flags. - `SPI_TypeDef * spi` **Returns:** `void` --- Generated 2026-10-07 from the SDK headers at tiles@003821d. Canonical HTML: https://bergsonne.io/docs/sdk/spi ============================================================================== ## Core.Stepper — SDK reference > Drive any STEP/DIR stepper driver from two pads and a timer: ramped position and velocity moves with a signed microstep count — core_stepper. | | | | --- | --- | | Category | `stepper` | | Core header | `core_stepper.h` | | Generated from | `tiles@8e95159` | | Canonical page | https://bergsonne.io/docs/sdk/stepper | ### Core layer The recommended layer — portable across every Core, default instances, DSL-exposed. #### `core_stepper_init` ```c core_stepper_init(core_stepper_t * h, uint8_t step_pad, uint8_t dir_pad) ``` Bind a stepper axis to a STEP pad (timer output) and a DIR pad. The timer and channel come from the pad's TIMx.y assignment in config.json, which is also what routes the pin to the timer. Loads conservative defaults: 1000 steps/s cruise, 100 steps/s start, 4000 steps/s^2 ramp, 8 us pulses. The timer is left stopped until a move. Do not share the timer with a PWM output — the module rewrites its period on every step. - `core_stepper_t * h` - `uint8_t step_pad` — range 1…64 - `uint8_t dir_pad` — range 1…64 **Returns:** `hal_status_t` #### `core_stepper_init_ch` ```c core_stepper_init_ch(core_stepper_t * h, TIM_TypeDef * instance, uint8_t channel, uint8_t step_pad, uint8_t dir_pad) ``` Bind an axis with an explicit timer instance and channel (no coregen lookup). The caller must have configured the STEP pad's alternate function for that channel. Everything else as core_stepper_init(). - `core_stepper_t * h` - `TIM_TypeDef * instance` - `uint8_t channel` — range 1…4 - `uint8_t step_pad` — range 1…64 - `uint8_t dir_pad` — range 1…64 **Returns:** `hal_status_t` #### `core_stepper_set_enable_pad` ```c core_stepper_set_enable_pad(core_stepper_t * h, uint8_t en_pad, bool active_low) ``` Add an ENABLE output. The pad is driven to its disabled level immediately; call core_stepper_enable() to power the driver. Pass pad 0 to remove it. - `core_stepper_t * h` - `uint8_t en_pad` — range 0…64 - `bool active_low` **Returns:** `void` #### `core_stepper_enable` ```c core_stepper_enable(core_stepper_t * h, bool on) ``` Drive the ENABLE pad. No effect without core_stepper_set_enable_pad(). Motion is not interlocked: halt before disabling if you care where the rotor ends up. - `core_stepper_t * h` - `bool on` **Returns:** `void` #### `core_stepper_set_polarity` ```c core_stepper_set_polarity(core_stepper_t * h, bool step_active_low, bool dir_invert) ``` Set signal polarities. Call while idle. - `core_stepper_t * h` - `bool step_active_low` - `bool dir_invert` **Returns:** `void` #### `core_stepper_set_pulse_us` ```c core_stepper_set_pulse_us(core_stepper_t * h, uint32_t pulse_us) ``` Set the STEP pulse width; the low time between pulses is kept at least as long, which also bounds DIR setup. 8 us (default) satisfies the DM320T's 7.5 us; a DRV8428 is happy with 1 us. The maximum step rate is 1 / (2 * pulse_us). - `core_stepper_t * h` - `uint32_t pulse_us` (us) — range 1…1000 **Returns:** `void` #### `core_stepper_set_speed` ```c core_stepper_set_speed(core_stepper_t * h, uint32_t steps_per_s) ``` Set the cruise rate for position moves and the cap for run(). Takes effect at the next step; a move in flight ramps to the new value. - `core_stepper_t * h` - `uint32_t steps_per_s` (hz) — range 1…500000 **Returns:** `void` #### `core_stepper_set_accel` ```c core_stepper_set_accel(core_stepper_t * h, uint32_t steps_per_s2) ``` Set the acceleration used for every ramp, or 0 to switch instantly between the start rate and full speed (fine for small, unloaded motors at low rates; expect stalls otherwise). - `core_stepper_t * h` - `uint32_t steps_per_s2` — range 0…1000000 **Returns:** `void` #### `core_stepper_set_min_speed` ```c core_stepper_set_min_speed(core_stepper_t * h, uint32_t steps_per_s) ``` Set the rate a ramp starts from and stops at. Steppers can start instantly at a few hundred full steps per second; starting from a crawl only wastes time. Clamped to at least 1 (16 on a 16-bit timer). - `core_stepper_t * h` - `uint32_t steps_per_s` (hz) — range 1…500000 **Returns:** `void` #### `core_stepper_move` ```c core_stepper_move(core_stepper_t * h, int32_t steps) ``` Move a signed number of microsteps relative to the current target (so two quick calls of +160 travel 320). Retargets a move in flight; a reversal decelerates to the start rate before flipping DIR. - `core_stepper_t * h` - `int32_t steps` **Returns:** `void` #### `core_stepper_move_to` ```c core_stepper_move_to(core_stepper_t * h, int32_t position) ``` Move to an absolute microstep position. - `core_stepper_t * h` - `int32_t position` **Returns:** `void` #### `core_stepper_run` ```c core_stepper_run(core_stepper_t * h, int32_t steps_per_s) ``` Run continuously at a signed rate, ramping from the current speed. Rate 0 decelerates to a stop (same as core_stepper_stop()). The magnitude is capped at the cruise rate from core_stepper_set_speed(). - `core_stepper_t * h` - `int32_t steps_per_s` **Returns:** `void` #### `core_stepper_stop` ```c core_stepper_stop(core_stepper_t * h) ``` Decelerate to a stop using the configured acceleration. Position continues to count during the ramp. Idempotent. - `core_stepper_t * h` **Returns:** `void` #### `core_stepper_halt` ```c core_stepper_halt(core_stepper_t * h) ``` Stop instantly: timer off, STEP forced to idle. A pulse whose rising edge the driver has already seen is counted. A loaded motor at speed will lose steps; the position counter reflects the pulses sent. - `core_stepper_t * h` **Returns:** `void` #### `core_stepper_busy` ```c core_stepper_busy(const core_stepper_t * h) ``` true while pulses are being generated (including a ramp to zero). - `const core_stepper_t * h` **Returns:** `bool` #### `core_stepper_state` ```c core_stepper_state(const core_stepper_t * h) ``` Current motion state. - `const core_stepper_t * h` **Returns:** `core_stepper_state_t` #### `core_stepper_position` ```c core_stepper_position(const core_stepper_t * h) ``` Current signed microstep position (pulses actually issued). - `const core_stepper_t * h` **Returns:** `int32_t` #### `core_stepper_set_position` ```c core_stepper_set_position(core_stepper_t * h, int32_t position) ``` Redefine the current position without moving (homing). Only while idle; the target follows so the next move() is relative to it. - `core_stepper_t * h` - `int32_t position` **Returns:** `void` #### `core_stepper_target` ```c core_stepper_target(const core_stepper_t * h) ``` Target position of the current or last move. - `const core_stepper_t * h` **Returns:** `int32_t` #### `core_stepper_speed` ```c core_stepper_speed(const core_stepper_t * h) ``` Current signed step rate. - `const core_stepper_t * h` **Returns:** `int32_t` #### `core_stepper_stop_distance` ```c core_stepper_stop_distance(const core_stepper_t * h, uint32_t steps_per_s) ``` Microsteps needed to decelerate from `steps_per_s` to the start rate at the configured acceleration. Useful for planning where a run() will stop, e.g. to land on a pocket boundary. - `const core_stepper_t * h` - `uint32_t steps_per_s` **Returns:** `uint32_t` --- Generated 2026-10-07 from the SDK headers at tiles@8e95159. Canonical HTML: https://bergsonne.io/docs/sdk/stepper ============================================================================== ## Core.NVM — SDK reference > Keeping data and identity on a Core: NVM (EEPROM on Core.ST.L0; not yet on the L4, W5 and H5), write-once OTP identity on Core.ST.W5, and the factory unique ID on every Core. | | | | --- | --- | | Category | `nvm` | | Core header | `core_nvm.h` | | LL header | `ll_flash.h` | | Generated from | `tiles@044f8f3` | | Canonical page | https://bergsonne.io/docs/sdk/storage | ### Core layer The recommended layer — portable across every Core, default instances, DSL-exposed. #### `core_nvm_read` ```c core_nvm_read(uint32_t offset, void * buf, uint32_t len) ``` Read bytes from NVM. Bytes never written read as CORE_NVM_ERASED (0xFF on Core.ST.L4 / W5 / H5, 0 on Core.ST.L0). - `uint32_t offset` - `void * buf` - `uint32_t len` **Returns:** `int` #### `core_nvm_write` ```c core_nvm_write(uint32_t offset, const void * data, uint32_t len) ``` Write bytes to NVM. On Core.ST.L4 / W5 / H5 (1 KB, flash-emulated) a write is atomic: after a reset or power loss the range reads all old or all new, and the data survives reflashing. Most writes take well under a millisecond; about one small write in a hundred (L4; one in 500 on the W5, one in 350 on the H5) compacts the store, which erases a flash page and stalls the Core.ST.L4 for ~22 ms. Core.ST.L0 writes its EEPROM byte by byte (~3.2 ms each). - `uint32_t offset` - `const void * data` - `uint32_t len` **Returns:** `int` #### `core_nvm_erase_all` ```c core_nvm_erase_all(void) ``` Erase the whole NVM region: every byte reads CORE_NVM_ERASED afterwards. Core.ST.L4 / W5 / H5: one page erase, or nothing if the region is already empty. Core.ST.L0: clears the EEPROM word by word (up to ~0.4 s; words already clear are skipped). **Returns:** `int` #### `core_nvm_size` — Studio ```c core_nvm_size(void) ``` Returns the total NVM size in bytes for this Core. **Returns:** `uint32_t` #### `core_nvm_read_byte` — Studio ```c core_nvm_read_byte(uint32_t offset) ``` Read a single byte from NVM at `offset`. Returns the byte (0..255) on success or -1 on any error (offset out of range, NVM disabled). A byte never written reads 255 (0xFF) on Core.ST.L4 / W5 / H5 and 0 on Core.ST.L0. The signed return lets DSL programs branch on `< 0` without an out-pointer. - `uint32_t offset` — range 0…1023 **Returns:** `int` #### `core_nvm_write_byte` — Studio ```c core_nvm_write_byte(uint32_t offset, uint8_t value) ``` Write a single byte to NVM at `offset`. Returns 1 on success or -1 on any error (offset out of range, flash error, no NVM on this Core). Each call is one flash record on Core.ST.L4 / W5 / H5: fine for a setting or a boot counter, wasteful in a tight loop (see core_nvm.h on wear). - `uint32_t offset` — range 0…1023 - `uint8_t value` — range 0…255 **Returns:** `int` ### LL layer Register-level access. No safety rails. #### `ll_flash_unlock` ```c ll_flash_unlock(void) ``` Unlock flash for erase/program. **Returns:** `void` #### `ll_flash_lock` ```c ll_flash_lock(void) ``` Lock flash (re-enable write protection). **Returns:** `void` #### `ll_flash_erase_page` ```c ll_flash_erase_page(uint32_t page) ``` Erase a single flash page. Returns 0 on success, -1 on error. - `uint32_t page` **Returns:** `int` #### `ll_flash_program_dword` ```c ll_flash_program_dword(uint32_t addr, uint32_t word0, uint32_t word1) ``` Program a double-word (8 bytes) at an 8-byte aligned address. Returns 0 on success, -1 on error. - `uint32_t addr` - `uint32_t word0` - `uint32_t word1` **Returns:** `int` #### `ll_flash_program_qword` ```c ll_flash_program_qword(uint32_t addr, const uint32_t w[4]) ``` Program a quad-word (128-bit / 16 bytes) at a 16-byte aligned address. WBA55 and H5 program in 128-bit lines: the flash controller only completes (and clears BSY) once all four 32-bit words have been written, so all four must be issued before the busy wait — a partial write never completes. This is also the native granularity of the OTP region, so it serves both main flash and OTP (same PG bit + NSKEYR unlock; only the target address differs). Returns 0 on success, -1 on error. - `uint32_t addr` - `const uint32_t[4] w` **Returns:** `int` #### `ll_flash_wait_bsy` ```c ll_flash_wait_bsy(void) ``` Spin until flash is not busy. **Returns:** `void` #### `ll_flash_clear_errors` ```c ll_flash_clear_errors(void) ``` Clear all error flags. **Returns:** `void` --- Generated 2026-10-07 from the SDK headers at tiles@044f8f3. Canonical HTML: https://bergsonne.io/docs/sdk/storage ============================================================================== ## Core.Timer — SDK reference > Hardware timers on every Core: PWM output, input capture, and periodic callbacks — at the core_timer / core_pwm, hal_timer, or ll_tim layer. | | | | --- | --- | | Category | `timer` | | Core header | `core_timer.h` | | HAL header | `hal_timer.h` | | LL header | `ll_tim.h` | | Generated from | `tiles@f70bca2` | | Canonical page | https://bergsonne.io/docs/sdk/timers | ### Core layer The recommended layer — portable across every Core, default instances, DSL-exposed. #### `core_timer_init_freq` ```c core_timer_init_freq(core_timer_t * h, TIM_TypeDef * instance, uint32_t freq_hz) ``` Initialize a timer at a given overflow frequency. Use for PWM output and periodic tick — the frequency is how often the counter wraps (= the PWM frequency). core_timer_init_freq(&t, TIM2, 1000); // overflows at 1 kHz - `core_timer_t * h` - `TIM_TypeDef * instance` - `uint32_t freq_hz` **Returns:** `hal_status_t` #### `core_timer_set_trgo` ```c core_timer_set_trgo(core_timer_t * h, uint32_t mms) ``` Route an internal timer event to TRGO so another peripheral can be paced by it, with no pin, no channel and no interrupt involved. LL_TIM_MMS_UPDATE is the periodic-pacer case: every counter overflow emits a trigger. Pair it with core_adc_set_trigger() to run the ADC at an exact rate instead of free-running. core_timer_init_freq(&t, TIM2, 800); core_timer_set_trgo(&t, LL_TIM_MMS_UPDATE); core_timer_start(&t); - `core_timer_t * h` - `uint32_t mms` **Returns:** `void` #### `core_timer_init_tick` ```c core_timer_init_tick(core_timer_t * h, TIM_TypeDef * instance, uint32_t tick_hz) ``` Initialize a timer at a given tick rate, free-running to max count. Use for input capture — the tick rate sets the measurement resolution, and the counter runs as long as possible before wrapping (0xFFFF for 16-bit, 0xFFFFFFFF for 32-bit TIM2). core_timer_init_tick(&t, TIM2, 1000000); // 1 us per tick core_timer_capture_init(&t, 1); core_timer_start(&t); - `core_timer_t * h` - `TIM_TypeDef * instance` - `uint32_t tick_hz` **Returns:** `hal_status_t` #### `core_timer_start` ```c core_timer_start(core_timer_t * h) ``` Start the timer counter. - `core_timer_t * h` **Returns:** `void` #### `core_timer_stop` ```c core_timer_stop(core_timer_t * h) ``` Stop the timer counter. - `core_timer_t * h` **Returns:** `void` #### `core_timer_set_freq` ```c core_timer_set_freq(core_timer_t * h, uint32_t freq_hz) ``` Change the timer frequency (recalculates PSC/ARR). - `core_timer_t * h` - `uint32_t freq_hz` **Returns:** `void` #### `core_timer_pwm_set` ```c core_timer_pwm_set(core_timer_t * h, uint8_t channel, uint16_t duty_permil) ``` Set PWM duty cycle for a channel. channel: 1–4 duty_permil: 0–1000 (0 = off, 500 = 50%, 1000 = always on) - `core_timer_t * h` - `uint8_t channel` - `uint16_t duty_permil` **Returns:** `void` #### `core_timer_pwm_set_pad` ```c core_timer_pwm_set_pad(core_timer_t * h, uint8_t pad, uint16_t duty_permil) ``` Set PWM duty cycle by pad number (requires coregen: only defined when the project's config.json binds a timer to a pad). Resolves the channel from the pad's timer assignment; pads without a timer are ignored. - `core_timer_t * h` - `uint8_t pad` - `uint16_t duty_permil` **Returns:** `void` #### `core_timer_capture_init` ```c core_timer_capture_init(core_timer_t * h, uint8_t channel) ``` Configure a channel for input capture (rising edge). The timer must already be initialized with core_timer_init(). - `core_timer_t * h` - `uint8_t channel` **Returns:** `void` #### `core_timer_capture_read` ```c core_timer_capture_read(core_timer_t * h, uint8_t channel) ``` Read the last captured value from a channel. - `core_timer_t * h` - `uint8_t channel` **Returns:** `uint32_t` #### `core_timer_enable_tick` ```c core_timer_enable_tick(core_timer_t * h, core_callback_t cb, void * ctx) ``` Enable periodic tick on an already-initialized timer. Does NOT touch PSC/ARR — the timer keeps its existing timebase. The callback fires on each counter overflow at the timer's frequency. Typical pattern: core_timer_init(&t, TIM2, 1000); // 1 kHz timebase core_timer_pwm_set(&t, 1, 500); // CH1 = 50% PWM (permil) core_timer_enable_tick(&t, on_tick, NULL); // also fire ISR at 1 kHz core_timer_start(&t); - `core_timer_t * h` - `core_callback_t cb` - `void * ctx` **Returns:** `hal_status_t` #### `core_timer_disable_tick` ```c core_timer_disable_tick(core_timer_t * h) ``` Disable the tick callback (clears UIE, keeps timer running). - `core_timer_t * h` **Returns:** `void` #### `core_tick_init` ```c core_tick_init(core_timer_t * h, TIM_TypeDef * instance, uint32_t period_us, core_callback_t cb, void * ctx) ``` Convenience: initialize a timer as a tick-only source. Sets up the timebase AND enables the update interrupt. Use this when the timer's only job is a periodic callback. core_tick_init(&t, TIM3, 500000, on_tick, NULL); // 2 Hz core_timer_start(&t); - `core_timer_t * h` - `TIM_TypeDef * instance` - `uint32_t period_us` - `core_callback_t cb` - `void * ctx` **Returns:** `hal_status_t` ### HAL layer Peripheral-level control with explicit handles. #### `hal_timer_pwm_init` ```c hal_timer_pwm_init(hal_timer_t * h, TIM_TypeDef * instance, uint32_t pclk_hz, uint32_t freq_hz) ``` Initialize a timer for PWM output at the given frequency. Auto-calculates PSC and ARR from pclk_hz and freq_hz. For advanced timers (TIM1), also enables MOE. - `hal_timer_t * h` - `TIM_TypeDef * instance` - `uint32_t pclk_hz` - `uint32_t freq_hz` **Returns:** `hal_status_t` #### `hal_timer_pwm_set_duty` ```c hal_timer_pwm_set_duty(hal_timer_t * h, uint8_t channel, uint16_t duty_permil) ``` Set PWM duty cycle for a channel. channel: 1–4 duty_permil: 0–1000 (0 = off, 500 = 50%, 1000 = 100%) - `hal_timer_t * h` - `uint8_t channel` - `uint16_t duty_permil` **Returns:** `void` #### `hal_timer_pwm_set_freq` ```c hal_timer_pwm_set_freq(hal_timer_t * h, uint32_t freq_hz) ``` Change PWM frequency (recalculates PSC/ARR, resets all channel duties) - `hal_timer_t * h` - `uint32_t freq_hz` **Returns:** `void` #### `hal_timer_pwm_start` ```c hal_timer_pwm_start(hal_timer_t * h) ``` Start the PWM timer - `hal_timer_t * h` **Returns:** `void` #### `hal_timer_pwm_stop` ```c hal_timer_pwm_stop(hal_timer_t * h) ``` Stop the PWM timer - `hal_timer_t * h` **Returns:** `void` #### `hal_timer_tick_init` ```c hal_timer_tick_init(hal_timer_t * h, TIM_TypeDef * instance, uint32_t pclk_hz, uint32_t period_us, hal_callback_t cb, void * ctx) ``` Configure a timer to call a callback at a fixed interval. period_us: interval in microseconds (1 – 1000000) cb: callback function (called from ISR context!) ctx: user context passed to callback - `hal_timer_t * h` - `TIM_TypeDef * instance` - `uint32_t pclk_hz` - `uint32_t period_us` - `hal_callback_t cb` - `void * ctx` **Returns:** `hal_status_t` #### `hal_timer_tick_enable` ```c hal_timer_tick_enable(hal_timer_t * h, hal_callback_t cb, void * ctx) ``` Enable periodic tick on an already-initialized timer. Does NOT touch PSC/ARR — the timer keeps its existing timebase. Use this to add a tick callback to a timer that's already set up for PWM or capture via hal_timer_pwm_init(). The callback fires on each counter overflow (update event) at the timer's current frequency. - `hal_timer_t * h` - `hal_callback_t cb` - `void * ctx` **Returns:** `hal_status_t` #### `hal_timer_tick_disable` ```c hal_timer_tick_disable(hal_timer_t * h) ``` Disable the tick callback (clears UIE, keeps timer running). - `hal_timer_t * h` **Returns:** `void` ### LL layer Register-level access. No safety rails. #### `ll_tim_set_trgo` ```c ll_tim_set_trgo(TIM_TypeDef * tim, uint32_t mms) ``` Route an internal timer event to TRGO, where another peripheral can consume it. LL_TIM_MMS_UPDATE turns the timer into a periodic pacer: every overflow emits a trigger, with no CPU involvement and no jitter from interrupt latency. No pin is involved and no channel is consumed, which is what makes this affordable on a part with every pad already committed. - `TIM_TypeDef * tim` - `uint32_t mms` **Returns:** `void` #### `ll_tim_start` ```c ll_tim_start(TIM_TypeDef * tim) ``` Start the timer counter - `TIM_TypeDef * tim` **Returns:** `void` #### `ll_tim_stop` ```c ll_tim_stop(TIM_TypeDef * tim) ``` Stop the timer counter - `TIM_TypeDef * tim` **Returns:** `void` #### `ll_tim_get_counter` ```c ll_tim_get_counter(TIM_TypeDef * tim) ``` Read the current counter value - `TIM_TypeDef * tim` **Returns:** `uint32_t` #### `ll_tim_set_counter` ```c ll_tim_set_counter(TIM_TypeDef * tim, uint32_t value) ``` Set the counter value - `TIM_TypeDef * tim` - `uint32_t value` **Returns:** `void` #### `ll_tim_update_flag` ```c ll_tim_update_flag(TIM_TypeDef * tim) ``` Check and clear the update (overflow) flag - `TIM_TypeDef * tim` **Returns:** `int` #### `ll_tim_pwm_config` ```c ll_tim_pwm_config(TIM_TypeDef * tim, uint32_t channel, uint32_t duty) ``` Configure a channel for PWM output (mode 1: active while CNT < CCR). tim: timer instance channel: 1, 2, 3, or 4 duty: compare value (0 = always off, ARR = always on) For advanced timers (TIM1), you must also call ll_tim_enable_moe(). - `TIM_TypeDef * tim` - `uint32_t channel` - `uint32_t duty` **Returns:** `void` #### `ll_tim_set_duty` ```c ll_tim_set_duty(TIM_TypeDef * tim, uint32_t channel, uint32_t duty) ``` Set the duty cycle (compare value) for a channel - `TIM_TypeDef * tim` - `uint32_t channel` - `uint32_t duty` **Returns:** `void` #### `ll_tim_set_duty_pct` ```c ll_tim_set_duty_pct(TIM_TypeDef * tim, uint32_t channel, uint32_t pct) ``` Set PWM duty as a percentage (0-100) of the current ARR period - `TIM_TypeDef * tim` - `uint32_t channel` - `uint32_t pct` **Returns:** `void` #### `ll_tim_enable_moe` ```c ll_tim_enable_moe(TIM_TypeDef * tim) ``` Enable the main output enable (MOE) bit on advanced timers (TIM1). Required for TIM1 PWM outputs to actually drive the pins. - `TIM_TypeDef * tim` **Returns:** `void` #### `ll_tim_ic_config` ```c ll_tim_ic_config(TIM_TypeDef * tim, uint32_t channel) ``` Configure a channel for input capture on rising edge. tim: timer instance channel: 1, 2, 3, or 4 - `TIM_TypeDef * tim` - `uint32_t channel` **Returns:** `void` #### `ll_tim_ic_read` ```c ll_tim_ic_read(TIM_TypeDef * tim, uint32_t channel) ``` Read the captured value for a channel - `TIM_TypeDef * tim` - `uint32_t channel` **Returns:** `uint32_t` #### `ll_tim_cc_flag` ```c ll_tim_cc_flag(TIM_TypeDef * tim, uint32_t channel) ``` Check and clear the capture/compare flag for a channel - `TIM_TypeDef * tim` - `uint32_t channel` **Returns:** `int` --- Generated 2026-10-07 from the SDK headers at tiles@f70bca2. Canonical HTML: https://bergsonne.io/docs/sdk/timers ============================================================================== ## Core.Serial — SDK reference > Serial UART on every Core: blocking TX with printf and polled or ring-buffered RX, at the core_serial, hal_uart, or ll_uart layer. DMA transmit is not implemented yet. | | | | --- | --- | | Category | `serial` | | Core header | `core_serial.h` | | HAL header | `hal_uart.h` | | LL header | `ll_uart.h` | | Generated from | `tiles@f70bca2` | | Canonical page | https://bergsonne.io/docs/sdk/uart | ### Core layer The recommended layer — portable across every Core, default instances, DSL-exposed. #### `core_serial_init` ```c core_serial_init(hal_uart_t * h, USART_TypeDef * instance, const hal_uart_config_t * cfg) ``` Initialize a UART instance. Clock is auto-resolved from PCLK1_HZ (core_config.h). - `hal_uart_t * h` - `USART_TypeDef * instance` - `const hal_uart_config_t * cfg` **Returns:** `hal_status_t` #### `core_serial_init_clk` ```c core_serial_init_clk(hal_uart_t * h, USART_TypeDef * instance, uint32_t pclk_hz, const hal_uart_config_t * cfg) ``` - `hal_uart_t * h` - `USART_TypeDef * instance` - `uint32_t pclk_hz` - `const hal_uart_config_t * cfg` **Returns:** `hal_status_t` #### `core_serial_write` ```c core_serial_write(hal_uart_t * h, const uint8_t * data, uint32_t len) ``` Transmit a buffer (blocking). - `hal_uart_t * h` - `const uint8_t * data` - `uint32_t len` **Returns:** `void` #### `core_serial_print` ```c core_serial_print(hal_uart_t * h, const char * str) ``` Transmit a null-terminated string (blocking). - `hal_uart_t * h` - `const char * str` **Returns:** `void` #### `core_serial_putc` ```c core_serial_putc(hal_uart_t * h, uint8_t byte) ``` Transmit a single byte (blocking). - `hal_uart_t * h` - `uint8_t byte` **Returns:** `void` #### `core_serial_available` ```c core_serial_available(hal_uart_t * h) ``` Number of bytes available in the RX buffer. - `hal_uart_t * h` **Returns:** `uint16_t` #### `core_serial_getc` ```c core_serial_getc(hal_uart_t * h) ``` Receive a single byte (blocking -- waits for data). - `hal_uart_t * h` **Returns:** `uint8_t` #### `core_serial_read` ```c core_serial_read(hal_uart_t * h, uint8_t * buf, uint16_t max_len) ``` Read available bytes from the RX ring buffer (non-blocking). Returns number of bytes read. - `hal_uart_t * h` - `uint8_t * buf` - `uint16_t max_len` **Returns:** `uint16_t` #### `core_serial_handle_for_bus` ```c core_serial_handle_for_bus(uint8_t bus) ``` The UART handle for a bus id declared in config.json, or NULL if the project doesn't declare that bus. Emitted per project by coregen into core_init.c (when the project declares any USART); the *_bus helpers below resolve their handle through it. Forward-declared here rather than `#include "core_init.h"` so this header compiles without a project (val tests, examples without config.json). The natives-side caller is gated on CORE_HAS_SERIAL_BUSES, so the linker never asks for the symbol unless the dispatcher exists. - `uint8_t bus` **Returns:** `hal_uart_t *` #### `core_serial_print_bus` — Studio ```c core_serial_print_bus(uint8_t bus, const char * str) ``` Write a null-terminated string to `bus` (blocking). Returns 0 on success or -1 on undeclared bus. The most-common DSL pattern — "drop a debug line on UART2" — is one line: serial.print(2, "ready\\n"); - `uint8_t bus` - `const char * str` **Returns:** `int` #### `core_serial_putc_bus` — Studio ```c core_serial_putc_bus(uint8_t bus, uint8_t byte) ``` Write a single byte to `bus` (blocking). Returns 0 on success or -1 on undeclared bus. - `uint8_t bus` - `uint8_t byte` **Returns:** `int` ### HAL layer Peripheral-level control with explicit handles. #### `hal_uart_init` ```c hal_uart_init(hal_uart_t * h, USART_TypeDef * instance, uint32_t pclk_hz, const hal_uart_config_t * cfg) ``` Initialize a UART instance. h: handle (caller provides storage) instance: USART peripheral (USART1, USART2, LPUART1) pclk_hz: peripheral clock frequency feeding this USART cfg: configuration (baud rate, interrupt RX enable) The peripheral clock is auto-enabled. TX/RX pins must be configured for AF (via tile_init or manually). - `hal_uart_t * h` - `USART_TypeDef * instance` - `uint32_t pclk_hz` - `const hal_uart_config_t * cfg` **Returns:** `hal_status_t` #### `hal_uart_deinit` ```c hal_uart_deinit(hal_uart_t * h) ``` Disable the UART and its interrupts - `hal_uart_t * h` **Returns:** `void` #### `hal_uart_putc` ```c hal_uart_putc(hal_uart_t * h, uint8_t byte) ``` Transmit a single byte (blocking) - `hal_uart_t * h` - `uint8_t byte` **Returns:** `void` #### `hal_uart_tx` ```c hal_uart_tx(hal_uart_t * h, const uint8_t * data, uint32_t len) ``` Transmit a buffer (blocking) - `hal_uart_t * h` - `const uint8_t * data` - `uint32_t len` **Returns:** `void` #### `hal_uart_tx_str` ```c hal_uart_tx_str(hal_uart_t * h, const char * str) ``` Transmit a null-terminated string (blocking) - `hal_uart_t * h` - `const char * str` **Returns:** `void` #### `hal_uart_printf` ```c hal_uart_printf(hal_uart_t * h, const char * fmt, ...) ``` Printf over UART (blocking, uses vsnprintf from newlib-nano) - `hal_uart_t * h` - `const char * fmt` **Returns:** `int` #### `hal_uart_rx_ready` ```c hal_uart_rx_ready(hal_uart_t * h) ``` Check if data is available (works for both polling and interrupt mode) - `hal_uart_t * h` **Returns:** `int` #### `hal_uart_rx` ```c hal_uart_rx(hal_uart_t * h) ``` Receive a single byte (blocking — waits for data) - `hal_uart_t * h` **Returns:** `uint8_t` #### `hal_uart_rx_try` ```c hal_uart_rx_try(hal_uart_t * h, uint8_t * byte) ``` Try to receive a byte without blocking. Returns 1 if got one. - `hal_uart_t * h` - `uint8_t * byte` **Returns:** `int` #### `hal_uart_read` ```c hal_uart_read(hal_uart_t * h, uint8_t * buf, uint16_t max_len) ``` Read available bytes from the RX ring buffer (interrupt mode). Non-blocking — returns immediately with however many bytes are available. Returns number of bytes read. - `hal_uart_t * h` - `uint8_t * buf` - `uint16_t max_len` **Returns:** `uint16_t` #### `hal_uart_available` ```c hal_uart_available(hal_uart_t * h) ``` Number of bytes available in the RX ring buffer - `hal_uart_t * h` **Returns:** `uint16_t` #### `hal_uart_tx_dma` ```c hal_uart_tx_dma(hal_uart_t * h, const uint8_t * data, uint32_t len, hal_callback_t cb, void * ctx) ``` Transmit a buffer via DMA (non-blocking). Not implemented yet: returns HAL_ERROR on every Core and never calls the callback. It needs a DMA channel assignment from coregen and per-family DMA setup. Use hal_uart_tx() (polled) until then. - `hal_uart_t * h` - `const uint8_t * data` - `uint32_t len` - `hal_callback_t cb` - `void * ctx` **Returns:** `hal_status_t` #### `hal_uart_tx_busy` ```c hal_uart_tx_busy(hal_uart_t * h) ``` Check if a DMA TX is in progress (always 0 while DMA TX is unimplemented). - `hal_uart_t * h` **Returns:** `int` ### LL layer Register-level access. No safety rails. #### `ll_uart_init` ```c ll_uart_init(USART_TypeDef * uart, uint32_t pclk_hz, uint32_t baud) ``` Initialize a USART for 8N1 at the given baud rate. uart: USART instance (USART1, USART2, etc.) pclk_hz: peripheral clock feeding this USART (APB1 or APB2) baud: desired baud rate Prerequisites: - Peripheral clock enabled via ll_rcc_apb1/2_clk_enable() - TX/RX pins configured for AF via ll_gpio_config_af() - `USART_TypeDef * uart` - `uint32_t pclk_hz` - `uint32_t baud` **Returns:** `void` #### `ll_lpuart_init` ```c ll_lpuart_init(USART_TypeDef * uart, uint32_t pclk_hz, uint32_t baud) ``` Initialize an LPUART for 8N1. LPUART BRR calculation differs: BRR = 256 * pclk / baud - `USART_TypeDef * uart` - `uint32_t pclk_hz` - `uint32_t baud` **Returns:** `void` #### `ll_uart_wait_txe` ```c ll_uart_wait_txe(USART_TypeDef * uart) ``` Wait until transmit data register is empty - `USART_TypeDef * uart` **Returns:** `void` #### `ll_uart_wait_tc` ```c ll_uart_wait_tc(USART_TypeDef * uart) ``` Wait until transmission is complete (last byte fully shifted out) - `USART_TypeDef * uart` **Returns:** `void` #### `ll_uart_tx` ```c ll_uart_tx(USART_TypeDef * uart, uint8_t data) ``` Transmit a single byte (blocking) - `USART_TypeDef * uart` - `uint8_t data` **Returns:** `void` #### `ll_uart_tx_buf` ```c ll_uart_tx_buf(USART_TypeDef * uart, const uint8_t * data, uint32_t len) ``` Transmit a buffer (blocking) - `USART_TypeDef * uart` - `const uint8_t * data` - `uint32_t len` **Returns:** `void` #### `ll_uart_tx_str` ```c ll_uart_tx_str(USART_TypeDef * uart, const char * str) ``` Transmit a null-terminated string (blocking) - `USART_TypeDef * uart` - `const char * str` **Returns:** `void` #### `ll_uart_rx_ready` ```c ll_uart_rx_ready(USART_TypeDef * uart) ``` Check if received data is available - `USART_TypeDef * uart` **Returns:** `int` #### `ll_uart_rx` ```c ll_uart_rx(USART_TypeDef * uart) ``` Receive a single byte (blocking — waits until data arrives) - `USART_TypeDef * uart` **Returns:** `uint8_t` #### `ll_uart_rx_try` ```c ll_uart_rx_try(USART_TypeDef * uart, uint8_t * data) ``` Try to receive a byte without blocking. Returns 1 if a byte was read (stored in *data), 0 if nothing available. - `USART_TypeDef * uart` - `uint8_t * data` **Returns:** `int` #### `ll_uart_clear_errors` ```c ll_uart_clear_errors(USART_TypeDef * uart) ``` Clear all error flags (PE, FE, NE, ORE) - `USART_TypeDef * uart` **Returns:** `void` #### `ll_uart_errors` ```c ll_uart_errors(USART_TypeDef * uart) ``` Check for any error flags - `USART_TypeDef * uart` **Returns:** `uint32_t` --- Generated 2026-10-07 from the SDK headers at tiles@f70bca2. Canonical HTML: https://bergsonne.io/docs/sdk/uart ============================================================================== ## Core.USB — SDK reference > USB device on the Cores that have it: a CDC virtual serial port with printf and buffered RX, plus vendor HID reports — at the core_usb or hal_usb_cdc layer. | | | | --- | --- | | Category | `usb` | | Core header | `core_usb.h` | | Core header | `core_usb.h, core_usb_hid.h` | | HAL header | `hal_usb_cdc.h` | | LL header | `ll_usb.h, ll_usb_drd.h` | | Generated from | `tiles@6af026f` | | Canonical page | https://bergsonne.io/docs/sdk/usb | ### Core layer The recommended layer — portable across every Core, default instances, DSL-exposed. #### `core_usb_init` ```c core_usb_init(void) ``` Initialize USB CDC. Device appears as /dev/tty.usbmodem* on the host. **Returns:** `void` #### `core_usb_connected` ```c core_usb_connected(void) ``` Returns 1 if a host terminal is connected (DTR set). **Returns:** `int` #### `core_usb_wait_host` ```c core_usb_wait_host(uint32_t timeout_ms) ``` Wait up to `timeout_ms` for a host terminal to open the port (DTR set). Feeds the watchdog while it waits. Not needed just to see early output: on Core.ST.L4 text printed before the port opens is kept (up to HAL_USB_CDC_TX_QUEUE_SIZE bytes) and sent when it does. - `uint32_t timeout_ms` **Returns:** `int` #### `core_usb_write` ```c core_usb_write(const uint8_t * buf, uint16_t len) ``` Transmit data: queued, never waits long (see hal_usb_cdc_write); returns bytes queued, -1 before init. - `const uint8_t * buf` - `uint16_t len` **Returns:** `int` #### `core_usb_try_write` ```c core_usb_try_write(const uint8_t * buf, uint16_t len) ``` Transmit without waiting: queues the whole buffer or none of it. Returns len if queued, 0 if there is no room right now (offer it again later), -1 if no terminal is connected. Main loop only. - `const uint8_t * buf` - `uint16_t len` **Returns:** `int` #### `core_usb_print` — Studio ```c core_usb_print(const char * s) ``` Print a string followed by a newline. Thin wrapper for DSL-style callers. - `const char * text` **Returns:** `void` #### `core_usb_print_int` — Studio ```c core_usb_print_int(int v) ``` Print a signed integer followed by a newline. - `int value` **Returns:** `void` #### `core_usb_print_float` — Studio ```c core_usb_print_float(double v) ``` Print a double with a newline. %g trims trailing zeros for readability. - `double value` **Returns:** `void` #### `core_usb_print_bool` — Studio ```c core_usb_print_bool(int v) ``` Print "true" / "false" followed by a newline. - `int value` **Returns:** `void` #### `core_usb_on_receive` ```c core_usb_on_receive(hal_usb_cdc_rx_cb_t cb, void * ctx) ``` Set a callback for received data. Called from USB ISR. When set, data is NOT buffered for polling reads. - `hal_usb_cdc_rx_cb_t cb` - `void * ctx` **Returns:** `void` #### `core_usb_available` ```c core_usb_available(void) ``` Returns the number of bytes available to read (ring buffer mode). **Returns:** `uint16_t` #### `core_usb_getc` ```c core_usb_getc(void) ``` Read a single byte (blocking — waits for data). **Returns:** `uint8_t` #### `core_usb_read` ```c core_usb_read(uint8_t * buf, uint16_t max) ``` Read available bytes into buf (non-blocking). Returns bytes read. - `uint8_t * buf` - `uint16_t max` **Returns:** `uint16_t` #### `core_usb_try_read` ```c core_usb_try_read(uint8_t * byte) ``` Non-blocking single byte read. Returns 1 if a byte was read, 0 if empty. - `uint8_t * byte` **Returns:** `int` #### `core_usb_hid_send` ```c core_usb_hid_send(const uint8_t * buf, uint16_t len) ``` Send a HID report (up to 64 bytes, zero-padded automatically). Blocking — waits for the previous report to be read by the host. Returns bytes of user data sent, or -1 if USB not configured. - `const uint8_t * buf` - `uint16_t len` **Returns:** `int` #### `core_usb_hid_set_rx_callback` ```c core_usb_hid_set_rx_callback(hal_usb_hid_rx_cb_t cb, void * ctx) ``` Register a callback for inbound HID reports (host -> device). Fires from the USB ISR with one report per call (up to 64 bytes), delivered via the EP3 OUT interrupt endpoint or HID SET_REPORT. Pass NULL to disable; unhandled reports are dropped. - `hal_usb_hid_rx_cb_t cb` - `void * ctx` **Returns:** `void` ### HAL layer Peripheral-level control with explicit handles. #### `hal_usb_cdc_init` ```c hal_usb_cdc_init(void) ``` Initialize USB CDC. This function: - Enables HSI48 + CRS (crystal-less 48MHz for USB) - Selects HSI48 as USB clock source - Enables VDDUSB power supply - Configures PA11/PA12 as AF10 - Enables USB peripheral clock - Configures USB device and endpoints - Enables USB interrupt - Connects DP pull-up (host sees device) core_init() already calls it; later calls do nothing. **Returns:** `void` #### `hal_usb_cdc_connected` ```c hal_usb_cdc_connected(void) ``` Check if the host has opened the virtual COM port. Returns 1 if configured and DTR is set (terminal connected). **Returns:** `int` #### `hal_usb_cdc_write` ```c hal_usb_cdc_write(const uint8_t * buf, uint16_t len) ``` Transmit data over USB CDC. Queues into the TX FIFO (HAL_USB_CDC_TX_QUEUE_SIZE) and returns once the bytes are queued. Before a terminal opens the port (DTR) the bytes wait in the FIFO and go out when it does; what doesn't fit is dropped (the oldest text is kept). With a terminal that isn't reading, it waits at most HAL_USB_CDC_TX_TIMEOUT_MS, then drops. Never waits in an interrupt handler. Returns the number of bytes queued (short = the rest was dropped), or -1 before hal_usb_cdc_init(). (Core.ST.H5 until 2026-09-26: blocking, and -1 with no terminal.) - `const uint8_t * buf` - `uint16_t len` **Returns:** `int` #### `hal_usb_cdc_printf` ```c hal_usb_cdc_printf(const char * fmt, ...) ``` Printf over USB CDC (blocking). Returns number of characters written (from vsnprintf). - `const char * fmt` **Returns:** `int` #### `hal_usb_cdc_set_rx_callback` ```c hal_usb_cdc_set_rx_callback(hal_usb_cdc_rx_cb_t cb, void * ctx) ``` Set a callback for received data. Called from the USB ISR with a pointer to data copied from PMA. If set, data is NOT stored in the ring buffer. If not set, data goes into the ring buffer for polling reads. - `hal_usb_cdc_rx_cb_t cb` - `void * ctx` **Returns:** `void` #### `hal_usb_cdc_rx_ready` ```c hal_usb_cdc_rx_ready(void) ``` Check if data is available to read (ring buffer mode). **Returns:** `int` #### `hal_usb_cdc_getc` ```c hal_usb_cdc_getc(void) ``` Read a single byte (blocking — waits for data). **Returns:** `uint8_t` #### `hal_usb_cdc_rx_try` ```c hal_usb_cdc_rx_try(uint8_t * byte) ``` Try to read a byte without blocking. Returns 1 if a byte was read, 0 if no data available. - `uint8_t * byte` **Returns:** `int` #### `hal_usb_cdc_read` ```c hal_usb_cdc_read(uint8_t * buf, uint16_t max_len) ``` Read available bytes into buffer (non-blocking). Returns number of bytes actually read. - `uint8_t * buf` - `uint16_t max_len` **Returns:** `uint16_t` #### `hal_usb_cdc_available` ```c hal_usb_cdc_available(void) ``` Number of bytes available in the RX buffer. **Returns:** `uint16_t` #### `hal_usb_cdc_poll` ```c hal_usb_cdc_poll(void) ``` Poll USB events (call from main loop). Use this as an alternative to interrupt-driven operation. **Returns:** `void` #### `hal_usb_hid_send_report` ```c hal_usb_hid_send_report(const uint8_t * buf, uint16_t len) ``` Send a HID report (up to 64 bytes) via EP3 interrupt IN. Waits for the previous report to complete (at most HAL_USB_HID_TX_TIMEOUT_MS, never in an interrupt handler or while the bus is suspended; a report that can't go is dropped). Returns bytes sent, or -1 if not configured or dropped. - `const uint8_t * buf` - `uint16_t len` **Returns:** `int` #### `hal_usb_hid_set_rx_callback` ```c hal_usb_hid_set_rx_callback(hal_usb_hid_rx_cb_t cb, void * ctx) ``` Set a callback for HID OUT reports (host -> device). Called from the USB ISR with one report per call, copied from PMA. Reception arrives via the EP3 OUT interrupt endpoint and via the HID SET_REPORT control request. If no callback is set, OUT reports are dropped (the endpoint is still re-armed). - `hal_usb_hid_rx_cb_t cb` - `void * ctx` **Returns:** `void` ### LL layer Register-level access. No safety rails. #### `ll_usb_power_on` ```c ll_usb_power_on(void) ``` Power-on and reset the USB peripheral. Call after enabling the USB clock (ll_rcc_apb1_clk_enable(LL_APB1_USB)). **Returns:** `void` #### `ll_usb_connect` ```c ll_usb_connect(void) ``` Enable the DP pull-up resistor (signals device connection to host). **Returns:** `void` #### `ll_usb_disconnect` ```c ll_usb_disconnect(void) ``` Disable the DP pull-up (disconnect from host). **Returns:** `void` #### `ll_usb_set_address` ```c ll_usb_set_address(uint8_t addr) ``` Set the USB device address and enable function. - `uint8_t addr` **Returns:** `void` #### `ll_usb_ep_config` ```c ll_usb_ep_config(uint8_t ep, uint16_t type, uint8_t addr) ``` Configure an endpoint: type and address, EP_KIND clear, both data toggles DATA0 and both directions NAK, in one write. A USB reset already zeroes the toggles; SET_CONFIGURATION without one does not, and RM0394 §46.6.2 makes initializing DTOG mandatory for non-control endpoints. Stale CTR flags are cleared (written 0). - `uint8_t ep` - `uint16_t type` - `uint8_t addr` **Returns:** `void` #### `ll_usb_pma_write` ```c ll_usb_pma_write(uint16_t pma_offset, const uint8_t * buf, uint16_t len) ``` Copy data from user buffer to PMA. pma_offset: byte offset in PMA (must be even) buf: source data len: number of bytes to copy - `uint16_t pma_offset` - `const uint8_t * buf` - `uint16_t len` **Returns:** `void` #### `ll_usb_pma_read` ```c ll_usb_pma_read(uint16_t pma_offset, uint8_t * buf, uint16_t len) ``` Copy data from PMA to user buffer. - `uint16_t pma_offset` - `uint8_t * buf` - `uint16_t len` **Returns:** `void` #### `ll_usb_drd_power_on` ```c ll_usb_drd_power_on(void) ``` Power-on and reset the USB peripheral. Call after enabling the USB clock. **Returns:** `void` #### `ll_usb_drd_connect` ```c ll_usb_drd_connect(void) ``` Enable the DP pull-up resistor (signals device connection to host). **Returns:** `void` #### `ll_usb_drd_disconnect` ```c ll_usb_drd_disconnect(void) ``` Disable the DP pull-up (disconnect from host). **Returns:** `void` #### `ll_usb_drd_set_address` ```c ll_usb_drd_set_address(uint8_t addr) ``` Set the USB device address and enable function. - `uint8_t addr` **Returns:** `void` #### `ll_usb_drd_chep_config` ```c ll_usb_drd_chep_config(uint8_t ep, uint32_t type, uint8_t addr) ``` Configure an endpoint: type and address, EPKIND clear, both data toggles DATA0 and both directions NAK, in one write; stale VTRX/VTTX are cleared (written 0) and the host-mode upper half is written 0. A USB reset zeroes the toggles, SET_CONFIGURATION without one does not, and RM0481 §55.6.7 makes initializing DTOG mandatory for non-control endpoints. (This used to leave the toggles as they were: a re-configure, or an app started after the ROM bootloader, could lose the first packet as a "duplicate".) - `uint8_t ep` - `uint32_t type` - `uint8_t addr` **Returns:** `void` #### `ll_usb_drd_pma_write` ```c ll_usb_drd_pma_write(uint32_t pma_offset, const uint8_t * buf, uint16_t len) ``` Copy data from user buffer to PMA. pma_offset: byte offset in PMA (should be 4-byte aligned) buf: source data len: number of bytes to copy - `uint32_t pma_offset` - `const uint8_t * buf` - `uint16_t len` **Returns:** `void` #### `ll_usb_drd_pma_read` ```c ll_usb_drd_pma_read(uint32_t pma_offset, uint8_t * buf, uint16_t len) ``` Copy data from PMA to user buffer. - `uint32_t pma_offset` - `uint8_t * buf` - `uint16_t len` **Returns:** `void` --- Generated 2026-10-07 from the SDK headers at tiles@6af026f. Canonical HTML: https://bergsonne.io/docs/sdk/usb ============================================================================== ## Bergsonne Tile I²C addresses > Default and alternate 7-bit I²C addresses for every Tile that exposes a fixed address. Core Tiles are omitted — they are programmable, so their addresses are defined per project. Generated from the canonical tile definitions (the same source the hardware is built from), so this tracks the parts rather than being maintained alongside them. Canonical page: https://bergsonne.io/resources/i2c-addresses ### Sense | Tile | Device | Default | Alternates | | --- | --- | --- | --- | | `Sense.CAM.P` | I2C | `0x35` | `0x25`, `0x40` | | `Sense.CAP` | I2C | `0x56` | — | | `Sense.HR` | I2C | `0x6B` | `0x6A` | | `Sense.I.6P6` | I2C | `0x69` | `0x68` | | `Sense.M.3G` | I2C | `0x14` | — | | `Sense.MIC` | I2C | `0x36` | — | | `Sense.T.C` | I2C | `0x44` | — | | `Sense.TOF` | I2C | `0x41` | — | ### Drive | Tile | Device | Default | Alternates | | --- | --- | --- | --- | | `Drive.A.2` | I2C | `0x49` | `0x48`, `0x4A`, `0x4B` | | `Drive.DC.H` | I2C | `0x34` | `0x30`, `0x31`, `0x33` | | `Drive.H` | I2C | `0x5A` | — | | `Drive.P` | I2C | `0x44` | — | ### Power | Tile | Device | Default | Alternates | | --- | --- | --- | --- | | `Power.L.1N` | I2C | `0x6B` | — | | `Power.L.1T` | I2C | `0x6B` | — | ### Display | Tile | Device | Default | Alternates | | --- | --- | --- | --- | | `Display.RGBW` | I2C | `0x50` | — | --- Generated 2026-10-07. Canonical HTML: https://bergsonne.io/resources/i2c-addresses ============================================================================== ## Bergsonne Tile pad assignments > The default function of each pad on every Tile, with its alternates. Core Tiles are omitted — their pads are configured per project. Generated from the canonical tile definitions (the same source the hardware is built from), so this tracks the parts rather than being maintained alongside them. Canonical page: https://bergsonne.io/resources/pad-assignments ### Sense #### Sense.CAM.P monochrome QVGA camera | Pad | Default function | Type | Alternates | | --- | --- | --- | --- | | 1 | `GND` | power | — | | 2 | `GPIO1` | digital | — | | 3 | `GPIO2` | digital | — | | 4 | `I2C.CLK` | digital | — | | 5 | `I2C.DAT` | digital | — | | 6 | `SPI.MISO` | digital | — | | 7 | `SPI.CLK` | digital | — | | 8 | `SPI.CS` | digital | — | | 9 | `SPI.MOSI` | digital | — | | 10 | `V+` | power | — | #### Sense.CAP capacitive touch controller | Pad | Default function | Type | Alternates | | --- | --- | --- | --- | | 1 | `GND` | power | — | | 2 | `RX3` | analog | — | | 3 | `/MCLR` | digital | — | | 4 | `I2C.CLK` | digital | — | | 5 | `I2C.DAT` | digital | — | | 6 | `TX11` | analog | — | | 7 | `TX9/TX10` | analog | — | | 8 | `TX8` | analog | — | | 9 | `RX6` | analog | — | | 10 | `V+` | power | — | #### Sense.HR heart rate, pulse oximetry, and hydration | Pad | Default function | Type | Alternates | | --- | --- | --- | --- | | 1 | `GND` | power | — | | 2 | `I2C.AD0` | digital | `SPI.MISO` (digital) | | 3 | `I2C.EN` | digital | `SPI.CS` (digital) | | 4 | `I2C.CLK` | digital | `SPI.CLK` (digital) | | 5 | `I2C.DAT` | digital | `SPI.MOSI` (digital) | | 8 | `INTB` | digital | — | | 9 | `VLED` | power | — | | 10 | `V+` | power | — | #### Sense.I.6P6 extended-range 6-DOF IMU | Pad | Default function | Type | Alternates | | --- | --- | --- | --- | | 1 | `GND` | power | — | | 2 | `I2C.AD0` | digital | `SPI.MISO` (digital) | | 3 | `I2C.EN` | digital | `SPI.CS` (digital) | | 4 | `I2C.CLK` | digital | `SPI.CLK` (digital), `I3C.CLK` (digital) | | 5 | `I2C.DAT` | digital | `SPI.MOSI` (digital), `I3C.DAT` (digital) | | 8 | `INT2` | digital | — | | 9 | `INT1` | digital | — | | 10 | `V+` | power | — | #### Sense.M.3G triaxial geomagnetic magnetometer | Pad | Default function | Type | Alternates | | --- | --- | --- | --- | | 1 | `GND` | power | — | | 4 | `I2C.CLK` | digital | — | | 5 | `I2C.DAT` | digital | — | | 9 | `INT` | digital | — | | 10 | `V+` | power | — | #### Sense.MIC I2C-output MEMS microphone | Pad | Default function | Type | Alternates | | --- | --- | --- | --- | | 1 | `GND` | power | — | | 4 | `I2C.CLK` | digital | — | | 5 | `I2C.DAT` | digital | — | | 8 | `AOUT` | analog | — | | 10 | `V+` | power | — | #### Sense.T.C capacitive touch | Pad | Default function | Type | Alternates | | --- | --- | --- | --- | | 1 | `GND` | power | — | | 3 | `RDY` | digital | — | | 4 | `I2C.CLK` | digital | — | | 5 | `I2C.DAT` | digital | — | | 8 | `C0` | analog | — | | 10 | `V+` | power | — | #### Sense.TOF time-of-flight distance | Pad | Default function | Type | Alternates | | --- | --- | --- | --- | | 1 | `GND` | power | — | | 3 | `GPIO` | digital | — | | 4 | `I2C.CLK` | digital | — | | 5 | `I2C.DAT` | digital | — | | 9 | `INT` | digital | — | | 10 | `V+` | power | — | ### Drive #### Drive.A.2 I2C/SPI-input dual 3W Class-D amplifier | Pad | Default function | Type | Alternates | | --- | --- | --- | --- | | 1 | `GND` | power | — | | 2 | `GPIO` | digital | `SPI.MISO` (digital) | | 3 | `A0` | digital | `SPI.MOSI` (digital) | | 4 | `I2C.CLK` | digital | `SPI.CS` (digital) | | 5 | `I2C.DAT` | digital | `SPI.CLK` (digital) | | 6 | `OUT.0-` | drive | — | | 7 | `OUT.0+` | drive | — | | 8 | `OUT.1-` | drive | — | | 9 | `OUT.1+` | drive | — | | 10 | `V+` | power | — | #### Drive.DC.H 11V/2A H-bridge DC-motor driver | Pad | Default function | Type | Alternates | | --- | --- | --- | --- | | 1 | `GND` | power | — | | 2 | `EN` | digital | `IN1` (digital) | | 3 | `PH` | digital | `IN2` (digital) | | 4 | `I2C.CLK` | digital | — | | 5 | `I2C.DAT` | digital | — | | 6 | `NPROP` | analog | — | | 7 | `OUT2` | drive | — | | 8 | `OUT1` | drive | — | | 9 | `VM` | power | — | | 10 | `V+` | power | — | #### Drive.H haptic ERM/LRA driver | Pad | Default function | Type | Alternates | | --- | --- | --- | --- | | 1 | `GND` | power | — | | 2 | `TRIG` | digital | — | | 3 | `EN` | digital | — | | 4 | `I2C.CLK` | digital | — | | 5 | `I2C.DAT` | digital | — | | 7 | `OUT+` | drive | — | | 8 | `OUT-` | drive | — | | 9 | `V_MOTOR` | power | — | | 10 | `V+` | power | — | #### Drive.P piezoelectric sensor/actuator driver | Pad | Default function | Type | Alternates | | --- | --- | --- | --- | | 1 | `GND` | power | — | | 3 | `GPIO` | digital | — | | 4 | `I2C.CLK` | digital | `I3C.CLK` (digital) | | 5 | `I2C.DAT` | digital | `I3C.DAT` (digital) | | 7 | `OUT+` | drive | — | | 8 | `OUT-` | drive | — | | 9 | `V_DRIVE` | power | — | | 10 | `V+` | power | — | ### Power #### Power.BB multi-output buck-boost regulator | Pad | Default function | Type | Alternates | | --- | --- | --- | --- | | 1 | `GND` | power | — | | 2 | `` | | — | | 3 | `VIN` | power | — | | 6 | `` | | — | | 7 | `` | | — | | 8 | `5V0` | power | — | | 9 | `3V3` | power | — | | 10 | `1V8` | power | — | #### Power.L.1N 1C Li-Ion charge manager w/ 1.8 & 3.3V out | Pad | Default function | Type | Alternates | | --- | --- | --- | --- | | 1 | `GND` | power | — | | 2 | `CC1` | other | — | | 3 | `CC2` | other | — | | 4 | `I2C.CLK` | digital | — | | 5 | `I2C.DAT` | digital | — | | 6 | `CHG` | power | — | | 7 | `BATT` | power | — | | 8 | `VSYS` | power | — | | 9 | `3V3` | power | — | | 10 | `1V8` | power | — | #### Power.L.1T single-cell Li-Ion charge controller with adjustable LDO output | Pad | Default function | Type | Alternates | | --- | --- | --- | --- | | 1 | `GND` | power | — | | 2 | `LP` | digital | — | | 3 | `SW` | digital | — | | 4 | `I2C.CLK` | digital | — | | 5 | `I2C.DAT` | digital | — | | 6 | `BATT-` | power | — | | 7 | `BATT+` | power | — | | 8 | `SUPPLY+` | power | — | | 9 | `SUPPLY-` | power | — | | 10 | `V+` | power | — | ### Display #### Display.RGBW Smart RGBW LED | Pad | Default function | Type | Alternates | | --- | --- | --- | --- | | 1 | `GND` | power | — | | 2 | `` | other | — | | 3 | `` | other | — | | 4 | `I2C.CLK` | digital | — | | 5 | `I2C.DAT` | digital | — | | 6 | `` | other | — | | 7 | `` | other | — | | 8 | `EN` | digital | — | | 9 | `` | other | — | | 10 | `V+` | power | — | ### Store #### Store.O.128 128Mbit NOR flash | Pad | Default function | Type | Alternates | | --- | --- | --- | --- | | 1 | `GND` | power | — | | 2 | `SPI.MOSI` | digital | `QSPI.IO0` (digital) | | 3 | `SPI.CS` | digital | `QSPI.CS` (digital) | | 4 | `SPI.CLK` | digital | `QSPI.CLK` (digital) | | 5 | `SPI.MISO` | digital | `QSPI.IO1` (digital) | | 6 | `WP` | digital | `QSPI.IO2` (digital) | | 9 | `HOLD` | digital | `QSPI.IO3` (digital) | | 10 | `V+` | power | — | --- Generated 2026-10-07. Canonical HTML: https://bergsonne.io/resources/pad-assignments ============================================================================== End of corpus — 55 documents, generated 2026-10-07.