BERGSONNE

Coregen

Coregen turns a project’s declarative config.json into the C headers and init code that wire your chosen Core’s pins, clocks, buses, and tiles — so you write application logic, not boilerplate.

Overview

Coregen (tools/coregen/coregen.py) reads two inputs — a Core’s canonical definition (the hardware description, from definitions/) and your project’s config.json — and emits the headers and init source that configure GPIO, clocks, peripherals (I²C / SPI / USART / timers / ADC / DAC), and tile drivers.

config.json is the single source of truth for a project’s hardware setup. You never hand-edit generated files — change the config and re-run. Coregen runs automatically during make (or on its own with make generate), validating the config against the target Core’s capabilities first and exiting with an actionable error — bad pad, unavailable function, unreachable clock speed — before any compile.

One generator, every Core
MCU capabilities (PLL ranges, APB clock routing, peripheral↔bus mapping) live in MCU_DB inside coregen.py. The core field takes a public name — Core.ST.L0.1, Core.ST.L4.1, Core.ST.L4.2, Core.ST.W5, Core.ST.H5.1 — which resolves to the matching definition file.

What it generates

Three baseline headers come from the Core definition alone; the rest are emitted when you pass a config.json (the normal build path), and the tile outputs only when the project declares tiles:

  • core_pads.hPAD_n_PORT / PAD_n_PIN (and timer-pad info) for every pad.
  • core_board.h — board-level defines (LED pad, power rails, debug).
  • core_interfaces.h — per-signal alternate-function constants (e.g. I2C1_CLK_AF).
  • core_config.hSYSCLK_MHZ, PLL M/N/R, and the resolved pad assignments.
  • core_init.c / core_init.h core_clock_init(), core_pads_init(), and per-peripheral + tile init.
  • core.h — the master include (pulls in all of the above); written next to your main.c.
  • tile_handles.h + core_drivers.mk — tile handle variables and the driver source list (only with tiles).
Build artifacts — don't edit, don't commit
Generated files live in the project’s generated output and are rebuilt every run. tile_handles.h is smart-merged — anything you add outside its coregen:begin/coregen:end markers is preserved.

config.json

Every project carries a config.json in its directory (the filename is literal; the project’s name comes from the directory). A representative config:

Full field reference
This is a tour. For the field-by-field reference — every key, allowed values, what coregen validates, and the keys it silently ignores — see Project Config.
{
  "core": "Core.ST.L4.2",          // public Core name (resolves to a definition)
  "clock": "max",                  // "default" or a tile clock option (low/medium/high/max)

  "pads": {                        // pad number → function from the Core's definition
    "4": "I2C1.CLK",
    "5": "I2C1.DAT",
    "9": "GPIO.OUT",
    "12": "ADC7",
    "13": "TIM2.1"
  },

  "gpio": {                        // optional per-pad overrides
    "9": { "default": "high", "output_type": "push-pull" },
    "5": { "pull": "up", "exti": "falling" }
  },

  "interfaces": {                  // tuning for the buses your pads imply
    "I2C1": { "speed": 400000, "pullups": true },
    "TIM2": { "freq": 1000 }
  },

  "tiles": [                       // tile drivers bound to a configured bus
    { "tile": "Sense.BP", "bus": "I2C1", "instance": 0 }
  ],

  "bootloader": "none"             // "none" | "custom" | "rom"
}

The fields

  • core — the public Core name (resolves to a definitions/<stem>.json).
  • clock — a performance level the Core defines ("default" picks its schema default); coregen solves the PLL to hit it and applies any required voltage scaling.
  • pads — pad number → a function the Core’s definition exposes, plus the synthetic GPIO.OUT / GPIO.IN. Coregen infers which peripherals are in use from these.
  • gpio — optional per-pad overrides (pull, output_type, speed, exti, default).
  • interfaces — tuning per bus: I2Cn.speed/pullups, SPIn.mode/prescaler, USARTn.baud, TIMn.freq.
  • tiles — each entry binds a tile (tile) to an enabled bus with an instance (SPI tiles also take cs_pad).
  • bootloader"none" (SWD) / "custom" / "rom"; drives the flash path. See Bootloading.
Core.ST.W5 I²C ceiling
On the wireless Core the I²C kernel clock is hardware-routed to HSI16 (16 MHz) regardless of SYSCLK, so its maximum I²C speed is 400 kHz — coregen errors if a config asks for more.

Build flow

Coregen is step one of the build:

  • coregen validates config.json against the Core and writes the generated files (exits non-zero on any error).
  • compiler builds main.c + SDK HAL + core_init.c + the tile drivers from core_drivers.mk.
  • linker uses the MCU-specific linker script from sdk/device/.
make generate                            # run coregen only, no compile
make                                     # full build for the default Core
make TILE=Core.ST.H5.1 PROJECT=my-proj   # specific Core + project

Extending coregen

Coregen is one Python file plus Jinja2 templates. The pieces you touch most:

  • MCU_DB — add a new MCU (define, family, CPU flags, PLL ranges).
  • the build_*_config() functions — one per config section (pads, clock, I²C/SPI/USART/timer/ADC, tiles) + validate_project_config().
  • TILE_DRIVER_MAP — register a tile driver so a tiles[].tile value resolves to its header/source.
  • templates in tools/coregen/templates/ — the context comes from the ctx dict in generate().

To expose a driver’s functions to Studio and the docs, annotate its header — see the @studio annotation reference.