BERGSONNE

Writing a tile driver

A tile driver is a small, self-contained C library — one header, one implementation — written against the PAL so it runs on any Core. Get the conventions and annotations right and the rest is free: the driver page, its Studio palette entry, and its coverage gaps are all derived from the header you write.

Overview

The job of a driver is to turn a chip’s register map into a clean, integer-only API that reads like the device’s datasheet headings — and to do it once, portably. You write two files in the SDK’s drivers/: tile_<family>_<variant>.h (the public API + docs) and its .c (register access, init sequencing, conversions). Everything the docs site shows comes from the header.

Anatomy of a driver

The header declares the handle, config, and the public functions, following the shared conventionstiles_pal_t * plus an instance index in / out, integer units, a find and an init:

// tile_sense_x.h
uint8_t tile_sense_x_find(tiles_pal_t *hal, uint8_t instance);
void    tile_sense_x_init(tiles_pal_t *hal, uint8_t instance,
                          tile_t *tile, const sense_x_cfg_t *cfg);
int32_t tile_sense_x_get_value_milli(tile_t *tile);

The implementation does the chip work — but stays within a few house rules that keep drivers consistent and Core-portable:

  • Integer-only conversions (milli-/centi-units); never pull in floating point.
  • Don’t software-reset in init — it wipes NVM calibration; bring the chip up from a known register state instead.
  • Enable block-data-update; disable unused engines at init for lower power.
  • All bus I/O goes through the tiles_pal_t * — no direct core_i2c_* / core_spi_* calls.

@studio annotations

A handful of @studio tags in the header’s doc comments drive both the Studio palette and this docs site:

  • @studio tile label=Sense.X icon=◇ — a file-level tag that opts the driver into the Studio palette (and flips its Studio-ready badge).
  • @studio expose on a function marks it callable from the DSL palette (a Tier 2 verb). An optional section=<bucket> groups it on the driver page.
  • @studio event name=<id> declares an event the tile can raise (data-ready, motion, tap, …).
  • @studio unsupported severity=<common|advanced|niche> category="…" records a chip capability the driver doesn’t cover, with a short rationale on the following lines. These become the driver page’s coverage-gap rows.
/**
 * @brief Read a FIFO batch of samples.
 * @studio expose section=fifo
 */
uint16_t tile_sense_x_fifo_read(tile_t *tile, sample_t *out, uint16_t max);

/**
 * @studio unsupported severity=advanced category="Alternate bus modes (SPI / I3C)"
 *   I²C-only today. The chip supports 3/4-wire SPI and I3C SDR; adding them
 *   needs the driver framework to plumb a second bus abstraction.
 */
Declare gaps as you go
Walk the datasheet’s capability list and annotate every feature you defer — alternate buses, embedded engines, FIFO modes, power states. Honest, derived coverage beats a hand-curated table that drifts.

Documenting (the docs are derived)

Standard Doxygen on each public function is what populates its entry on the driver page — @brief, @param, @return, @note. A file-level @brief + description becomes the page intro, and a @code … @endcode block becomes a quick-start example. Enums, structs, and user-facing #defines are pulled in too.

/**
 * @brief Read pressure in milli-hectopascals (integer, no float).
 * @param tile  Initialized tile handle.
 * @return Pressure in mhPa.
 * @note  Block-data-update is on, so reads are coherent.
 */
int32_t tile_sense_x_get_pressure_mhpa(tile_t *tile);

Testing across Cores

Two test tiers keep a driver honest. A validation test calls every public function and builds cleanly for all four Core variants (it doesn’t need hardware — it proves the API compiles everywhere). A hardware test exercises the tile on a real Core with pass/fail output over USB CDC. Multi-address tiles should try each address.

Build all Cores
A driver isn’t done until it compiles on every Core (M0+ through M33). The validation test is what enforces that — a function that only builds on one Core is a portability bug.

Publishing

Docs publish themselves. Once the driver is in the SDK and listed as active, the generator parses its header into a manifest, the site syncs it, and the driver’s page — API reference, Studio split, and coverage gaps — appears under Tile Drivers automatically. No hand-written page, no coverage table to maintain.

A drift check in CI fails the build if a header changes without its manifest being regenerated — so the page can never silently fall behind the code.