BERGSONNE
Get started

SDK layers & API tiers

Two different ideas share this page. Layers are how the SDK is built — registers at the bottom, the friendly core_* API on top. Tiers are two styles of that top API. You can write good firmware knowing only the top of each; the rest is here for when you go deeper.

The SDK layers

The Cores SDK is stacked. Each layer leans on the one below and presents something simpler to the one above. From the top down:

Corecore_*

The user-facing API you write firmware against — core_pad_write, core_pwm_duty, …

TALTile Abstraction Layer

Resolves a pad to the peripheral behind it — which timer, ADC channel, or EXTI line.

HALHardware Abstraction Layer

Portable, handle-based peripheral operations over the registers. Not STM32Cube HAL.

LLLow Level

Direct register access, wrapping CMSIS. The thin floor everything else stands on.

The order is meaningful: a layer implies everything beneath it. If a Core supports a feature at the Core layer, the HAL and LL pieces it depends on are there too. The SDK’s implementation-status tables use exactly these labels — LL, HAL, TAL, Core — to say how far up the stack a given peripheral has been brought on each Core.

You mostly live at the top
Day-to-day firmware is written against the Core layer. You drop to HAL or LL only for something the core_* API doesn’t expose — a peripheral mode, a register bit, tight timing. The lower layers are always there; you just rarely need them.

Separate from this stack is the PAL, which ports the tile drivers to a host. The layers here are about the Core’s own firmware API; the PAL is about making tile drivers portable. They’re different axes.

The two API tiers

Within that top core_* layer, most peripherals expose two tiers of function. Same hardware, same state underneath — two ergonomics.

Tier 1 — handle-based

The classic embedded pattern. You own a handle, name the peripheral instance, and call init / set / start against it. Maximum control, more verbose:

core_timer_t pwm;
core_pwm_init(&pwm, TIM2, 1000);   // handle, instance, frequency
core_pwm_set(&pwm, 3, 500);        // handle, channel, duty
core_pwm_start(&pwm);

Tier 2 — default-instance

When a config.json describes the project, coregen allocates the handles for you and emits a dispatcher that maps a pad to the right one. You call a thin wrapper with only the arguments you care about — no handle, no instance:

core_pwm_duty(7, 500);   // pad, duty — handle resolved automatically

Initialization happens inside core_init(). Tier 2 functions are static inline wrappers over Tier 1, so there’s zero runtime cost — and the two share the same handle and state. They’re sugar, not a separate world.

Which tier should I use?

Reach for Tier 2 by default — it’s less to get wrong, and it’s exactly what Studio emits in its generated C. Drop to Tier 1 when you need:

  • Multiple instances of one peripheral with different settings.
  • A peripheral the project doesn’t declare in config.json.
  • Fine-grained control the wrapper doesn’t expose — input capture, a mid-flight frequency change, per-channel state.
Escaping from Studio to C
Studio always generates Tier 2. Because the tiers share state, escape-to-C code can freely mix them — keep the Tier 2 calls Studio emitted and reach into Tier 1 only where you need the extra control.

How to tell them apart

  • Tier 2 takes a pad / pin / channel as its first argument — never a core_*_t * handle.
  • Tier 1 reads like verbsinit, set, start. Tier 2 reads like user vocabulary — duty, read_mv, heartbeat.
  • The prefix names the docs page: core_pwm_duty lives under pwm, core_led_blink under led.