Micro-toolkit

microtimer

Main loops need bounded software timers without allocation or a scheduler.

A fixed-capacity manager fires caller callbacks from explicit tick calls.

DevelopmentMITC99
Last verified
2026-09-18 · README, API, design and verification reviewed
Platform scope
Portable C99; integration depends on caller-provided adapters
01 / TICK-DRIVEN TIMER MODEL

The application owns time and calls the tick

microtimer is a fixed-capacity C99 software timer manager for a serialized execution context. It does not create a background thread, own a clock source, allocate a scheduler or hide synchronization. The caller supplies a millisecond clock function and invokes mtimer_tick() from its chosen loop.

CALLERProvide uint32 ms clocknon-blocking callback
→
MANAGERInspect fixed timer slotsone captured now per tick
→
DUE TIMERInvoke callback at most onceper successful tick
→
APPLICATIONPerform the workscheduler remains caller policy
02 / FIXED SLOT CAPACITY

Storage size is part of the public manager layout

The manager contains a fixed array sized by MTIMER_MAX_TIMERS. The configuration header rejects values below 1 or above 255. Creation returns a nonnegative slot ID or a negative error; destroying a timer invalidates that ID immediately, and a later create may reuse its slot.

Names are optional. A non-null name remains caller-owned and must stay valid, immutable and unique within the manager for the timer's lifetime. The timer stores a pointer, not a managed copy.

SLOT LIFETIME
create → slot index assigned
↓
start / pause / resume / stop
↓
destroy → ID invalid immediately
↓
future create may reuse slot
do not retain a destroyed ID as identity
03 / PERIOD AND WRAP SEMANTICS

Missed periods are skipped, not replayed in a burst

Clock values are milliseconds on an unsigned 32-bit modulo counter. Natural wrap is supported when the clock is not reset while timers are active and operations remain within a full counter cycle. Each tick captures one clock value and checks timers in ascending slot order.

A periodic timer fires at most once per successful tick. If one interval is due, phase is retained. If several intervals were missed, intermediate callbacks are skipped and the start position is deterministically advanced to account for elapsed whole periods. This bounds callback work after a delayed loop; it does not provide catch-up semantics.

elapsed < interval→not due·one interval due→one callback·many missed→skip backlog
04 / CALLBACK OWNERSHIP

Callbacks run synchronously inside tick

The callback receives a timer slot ID and caller context pointer. Since callbacks execute as part of the tick path, their latency and blocking behavior are charged to the caller's loop. Mutation of the same manager during a callback returns MTIMER_ERR_BUSY.

The default ownership model is one execution context per manager. Shared use requires external serialization around every API call. ISR use has stricter requirements: exclusive ownership or external protection, an ISR-safe bounded clock callback, and callbacks that are ISR-safe, bounded, non-blocking and avoid prohibited same-manager mutations.

No hidden synchronization. The library contains no locks and does not claim general thread safety. A host test does not validate interrupt timing or behavior on a target MCU.
05 / ABI AND QUERIES

Initialization checks caller and library layout

The public manager structure contains the timer array and counters; its layout is part of the ABI. mtimer_init_sized() accepts the caller-visible manager size and returns MTIMER_ERR_ABI when size or resolved configuration disagrees. The inline mtimer_init() passes sizeof(*tm) automatically for a consistently built consumer.

Query APIs use explicit status-plus-output patterns for state, remaining time and fire counts. mtimer_tick(NULL) returns MTIMER_ERR_NULL; callers must not interpret a positive/zero success convention from a different API generation.

C99 · MINIMAL SETUP
mtimer_t tm;
if (mtimer_init(&tm, platform_millis) != MTIMER_OK) return 1;
int id = mtimer_create(&tm, "blink", 500u,
                       MTIMER_PERIODIC, blink_cb, ctx);
if (id < 0) return 1;
if (mtimer_start(&tm, (uint8_t)id) != MTIMER_OK) return 1;
for (;;) {
    if (mtimer_tick(&tm) < 0) return 1;
}
06 / REPOSITORY VERIFICATION STATE

Test fixtures exist; the verification record has not run them

The repository contains runtime tests, compile-fail configuration tests, C/C++ consumers, CMake and Make integration, and install-package consumer fixtures. Its checked-in verification status explicitly lists local compilation, runtime tests, compile-fail execution, sanitizers, static analysis, ARM cross-compilation and installed consumer execution as not verified in that record.

CI and release workflows exist, but their presence is configuration evidence. The repository's verification notes do not establish a passing workflow or release for the currently reviewed state.

Design rationale · API reference · Verification status

07 / TIMING LIMITS

A software timer table, not a real-time scheduler

  • The application must call tick regularly; callback timing follows that cadence and the work in the tick path.
  • Missed periodic intervals are skipped, so this is not a queued catch-up timer.
  • Timer capacity is compile-time bounded, and IDs are reusable slot indexes.
  • Manager access must be serialized by the application; no hidden locks exist.
  • ISR usage is conditional and requires bounded ISR-safe callbacks and clock source.
  • Longjmp or abnormal exit during a callback has no cleanup guarantee and may leave the active-tick guard set.
  • Current verification documentation lists execution and hardware/toolchain checks as unverified.

Micro Toolkit family · All products