BERGSONNE

Timers & PWM

The general-purpose timers drive three everyday jobs: PWM output for LEDs and motors, input capture for measuring incoming signals, and periodic callbacks at a fixed rate. core_pwm / core_timer sit on hal_timer and ll_tim. Use the Core / HAL / LL toggle at the top of the sidebar to switch.

Overview

A timer counts a prescaled clock up to a reload value. From that one mechanism you get a PWM frequency (the reload) and duty (a compare value), a capture timebase, or a periodic interrupt. You pick the timer instance (TIM1, TIM2, TIM15, …); the clock is resolved for you.

Duty is permil, not percent
Everywhere a duty cycle appears it’s 0–1000 (parts per mille): 500 is 50%. Easy to misread as percent.

Two Core surfaces share the hardware: core_pwm (including a Tier 2 core_pwm_duty(pad, …) that resolves the timer from config.json) and the lower-level core_timer for capture and explicit control.

PWM output

Init a timer at the PWM frequency, set a channel’s duty, start. The Tier 2 core_pwm_duty is even shorter when the pad is declared in config.json:

#include "core.h"

core_timer_t pwm;
core_pwm_init(&pwm, TIM2, 2000);   // 2 kHz
core_pwm_set(&pwm, 1, 750);        // channel 1, 75% (permil)
core_pwm_start(&pwm);

// Tier 2 — timer/channel resolved from config.json:
core_pwm_duty(7, 500);             // pad 7, 50%

Input capture

Capture latches the counter on each rising edge of an input — measure a pulse’s period or frequency. Init a timebase, arm a channel, read the latched value:

core_timer_t cap;
core_timer_init_freq(&cap, TIM2, 1000);   // 1 kHz timebase
core_timer_capture_init(&cap, 1);
core_timer_start(&cap);

uint32_t edge = core_timer_capture_read(&cap, 1);

Periodic callbacks

For a fixed-rate ISR — sampling, control loops — use the “every” helper or core_tick_init. The callback fires from the timer interrupt at the period you set:

static void on_tick(void *ctx) { /* runs every 1 ms */ }

core_timer_t t;
core_every_us(&t, TIM6, 1000, on_tick, NULL);
core_every_start(&t);

Cross-architecture support

The timebase and PWM are the verified baseline across the family; capture, encoder, and low-power-timer modes vary. The core_timer / core_pwm contract is the same everywhere:

L0M0+L4M4W5M33H5M33WCH (RISC-V) · Nordic (nRF54) — in development

See the implementation status for the full matrix.

API reference

PWM (core_pwm)

Default-instance · Tier 2
void core_pwm_duty(uint8_t pad, uint16_t duty_permil);
Set PWM duty cycle on a pad. Timer handle resolved from config.json.
Lower-level · Tier 1
hal_status_t core_pwm_init(core_timer_t * h, TIM_TypeDef * instance, uint32_t freq_hz);
Initialize a timer for PWM output at the given frequency. Clock is auto-resolved from SYSCLK_HZ (core_config.h).
hal_status_t core_pwm_init_clk(core_timer_t * h, TIM_TypeDef * instance, uint32_t pclk_hz, uint32_t freq_hz);
void core_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 = 100%)
void core_pwm_set_freq(core_timer_t * h, uint32_t freq_hz);
Change PWM frequency (recalculates PSC/ARR, resets all channel duties).
void core_pwm_start(core_timer_t * h);
Start the PWM timer.
void core_pwm_stop(core_timer_t * h);
Stop the PWM timer.
hal_status_t core_pwm_init_pad(core_timer_t * h, uint8_t pad, uint32_t freq_hz);
Initialize PWM on a pad. Timer instance resolved from config.json.
void core_pwm_set_pad(core_timer_t * h, uint8_t pad, uint16_t duty_permil);
Set PWM duty on a pad (0–1000 permil).
hal_status_t core_every_us(core_timer_t * h, TIM_TypeDef * instance, uint32_t period_us, hal_callback_t cb, void * ctx);
Configure a periodic 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 Clock is auto-resolved from SYSCLK_HZ (core_config.h).
hal_status_t core_every_us_clk(core_timer_t * h, TIM_TypeDef * instance, uint32_t pclk_hz, uint32_t period_us, hal_callback_t cb, void * ctx);
void core_every_start(core_timer_t * h);
Start the periodic timer.
void core_every_stop(core_timer_t * h);
Stop the periodic timer.

Generated from core_pwm.htiles@6bbf0d8.

Timer (core_timer)

Lower-level · Tier 1
hal_status_t 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
hal_status_t 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);
void core_timer_start(core_timer_t * h);
Start the timer counter.
void core_timer_stop(core_timer_t * h);
Stop the timer counter.
void core_timer_set_freq(core_timer_t * h, uint32_t freq_hz);
Change the timer frequency (recalculates PSC/ARR).
void 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)
void 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().
uint32_t core_timer_capture_read(core_timer_t * h, uint8_t channel);
Read the last captured value from a channel.
hal_status_t 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);
void core_timer_disable_tick(core_timer_t * h);
Disable the tick callback (clears UIE, keeps timer running).
hal_status_t 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);

Generated from core_timer.htiles@6bbf0d8.