Skip to content

Commit 657fe76

Browse files
committed
chore: initial project bootstrap
Bootstrap the microtimer repository with the core C99 timer manager implementation, public API header, unit tests, documentation, MIT license, and GitHub CI workflow.
0 parents  commit 657fe76

File tree

11 files changed

+1077
-0
lines changed

11 files changed

+1077
-0
lines changed

.github/workflows/ci.yml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [master]
6+
pull_request:
7+
branches: [master]
8+
9+
jobs:
10+
test:
11+
name: Build and Test (${{ matrix.cc }})
12+
runs-on: ubuntu-latest
13+
strategy:
14+
fail-fast: false
15+
matrix:
16+
cc: [gcc, clang]
17+
18+
steps:
19+
- name: Checkout
20+
uses: actions/checkout@v4
21+
22+
- name: Clone microtest dependency
23+
run: git clone --depth 1 https://github.com/Vanderhell/microtest.git ../microtest
24+
25+
- name: Build and run tests
26+
run: make -C tests CC=${{ matrix.cc }}

.gitignore

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Build artifacts
2+
/build/
3+
/dist/
4+
/out/
5+
/bin/
6+
/obj/
7+
*.o
8+
*.obj
9+
*.a
10+
*.lib
11+
*.so
12+
*.dylib
13+
*.dll
14+
*.exe
15+
16+
# Test artifacts
17+
/tests/test_all
18+
/tests/*.exe
19+
20+
# Local dependency clone used for tests
21+
/microtest/
22+
23+
# Logs and temporary files
24+
*.log
25+
*.tmp
26+
*.temp
27+
28+
# Editor and OS files
29+
.vscode/
30+
.idea/
31+
*.swp
32+
*.swo
33+
.DS_Store
34+
Thumbs.db
35+
git.txt

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Changelog
2+
3+
## [1.0.0] - 2026-03-21
4+
5+
### Added
6+
- Oneshot and periodic software timers.
7+
- Drift-corrected periodic timing.
8+
- Pause/resume with remaining-time preservation.
9+
- Dynamic interval change.
10+
- Slot reuse via destroy/create.
11+
- Named timer lookup.
12+
- Per-timer and global fire counters.
13+
- 25 tests (69 assertions) using the microtest framework.

CONTRIBUTING.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Contributing
2+
In scope: bug fixes, docs, tests. Out of scope: dynamic allocation, HW timer abstraction.
3+
By contributing, you agree to the MIT License.

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2026 Vanderhell
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
# microtimer
2+
3+
[![CI](https://github.com/Vanderhell/microtimer/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/Vanderhell/microtimer/actions/workflows/ci.yml)
4+
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
5+
[![C Standard](https://img.shields.io/badge/C-C99-blue.svg)](https://en.wikipedia.org/wiki/C99)
6+
7+
Software timer manager for embedded systems.
8+
9+
C99 | Zero dependencies | Zero allocations | Oneshot + Periodic | Portable
10+
11+
## Why microtimer?
12+
13+
Embedded main loops often contain repeated timing checks:
14+
15+
```c
16+
if (now - last_blink > 500) { toggle_led(); last_blink = now; }
17+
if (now - last_send > 5000) { send_telemetry(); last_send = now; }
18+
if (now - last_check > 1000) { check_sensors(); last_check = now; }
19+
```
20+
21+
`microtimer` replaces this with registered timers and a single tick path:
22+
23+
```c
24+
mtimer_create(&tm, "blink", 500, MTIMER_PERIODIC, on_blink, NULL);
25+
mtimer_create(&tm, "send", 5000, MTIMER_PERIODIC, on_send, NULL);
26+
mtimer_create(&tm, "timeout", 3000, MTIMER_ONESHOT, on_timeout, NULL);
27+
28+
while (1) {
29+
mtimer_tick(&tm);
30+
}
31+
```
32+
33+
## Features
34+
35+
- Oneshot timers that auto-stop after firing.
36+
- Periodic timers with drift correction.
37+
- Pause/resume with remaining-time preservation.
38+
- Dynamic interval changes at runtime.
39+
- Slot reuse through destroy/create.
40+
- Named timer lookup for diagnostics and shell commands.
41+
- Per-timer and global fire counters.
42+
43+
## Build and Test
44+
45+
Requirements:
46+
- C99 compiler (`gcc` or `clang`)
47+
- `make`
48+
49+
Run tests:
50+
51+
```bash
52+
# clone microtest next to this repository root
53+
# expected path: ../microtest/include
54+
make -C tests
55+
```
56+
57+
## Public API
58+
59+
Key functions:
60+
- `mtimer_init`
61+
- `mtimer_create`, `mtimer_destroy`
62+
- `mtimer_start`, `mtimer_stop`, `mtimer_pause`, `mtimer_resume`
63+
- `mtimer_set_interval`
64+
- `mtimer_tick`
65+
- `mtimer_count`, `mtimer_find`, `mtimer_remaining`
66+
67+
See [`include/mtimer.h`](include/mtimer.h) for full API details.
68+
69+
## Repository Layout
70+
71+
- `include/mtimer.h` - public API
72+
- `src/mtimer.c` - implementation
73+
- `tests/test_all.c` - unit tests
74+
- `docs/DESIGN.md` - design rationale
75+
76+
## Ecosystem
77+
78+
- [microhealth](https://github.com/Vanderhell/microhealth)
79+
- [microwdt](https://github.com/Vanderhell/microwdt)
80+
- [microres](https://github.com/Vanderhell/microres)
81+
- [microsh](https://github.com/Vanderhell/microsh)
82+
83+
## Configuration
84+
85+
- `MTIMER_MAX_TIMERS` (default: `8`)
86+
87+
## Contributing
88+
89+
See [CONTRIBUTING.md](CONTRIBUTING.md).
90+
91+
## Changelog
92+
93+
See [CHANGELOG.md](CHANGELOG.md).
94+
95+
## License
96+
97+
MIT - see [LICENSE](LICENSE).

docs/DESIGN.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Design Rationale
2+
3+
## 1. Slot-based allocation
4+
Timers live in a fixed array. create() finds a free slot, destroy() frees it. No heap, no linked lists.
5+
6+
## 2. Drift-corrected periodic
7+
Periodic timers anchor to start_ms + interval, not "now." This prevents drift accumulation over long runs. If we're way behind (missed multiple periods), we snap to now.
8+
9+
## 3. Pause saves remaining
10+
Pause captures how much time is left. Resume restores from that point, regardless of how long the pause lasted.
11+
12+
## 4. Oneshot auto-stops
13+
No need to manually stop a oneshot timer. It transitions to STOPPED after firing.
14+
15+
| Decision | Gains | Costs |
16+
|----------|-------|-------|
17+
| Fixed array | Zero alloc, O(1) access | Max timer count |
18+
| Drift correction | Accurate long-term timing | Slightly complex tick |
19+
| Pause/resume | Useful for UI, power save | Extra state per timer |
20+
| Auto-stop oneshot | Clean semantics | Can't restart without explicit start |

include/mtimer.h

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
/*
2+
* microtimer — Software timer manager for embedded systems.
3+
*
4+
* Register oneshot and periodic timers, tick from SysTick or main loop.
5+
* Replaces scattered `if (now - last > interval)` patterns.
6+
*
7+
* C99 · Zero dependencies · Zero allocations · Portable
8+
*
9+
* SPDX-License-Identifier: MIT
10+
* https://github.com/Vanderhell/microtimer
11+
*/
12+
13+
#ifndef MTIMER_H
14+
#define MTIMER_H
15+
16+
#ifdef __cplusplus
17+
extern "C" {
18+
#endif
19+
20+
#include <stdint.h>
21+
#include <stdbool.h>
22+
#include <stddef.h>
23+
24+
/* ── Configuration ─────────────────────────────────────────────────────── */
25+
26+
#ifndef MTIMER_MAX_TIMERS
27+
#define MTIMER_MAX_TIMERS 8
28+
#endif
29+
30+
/* ── Error codes ───────────────────────────────────────────────────────── */
31+
32+
typedef enum {
33+
MTIMER_OK = 0,
34+
MTIMER_ERR_NULL = -1,
35+
MTIMER_ERR_FULL = -2,
36+
MTIMER_ERR_NOT_FOUND = -3,
37+
MTIMER_ERR_INVALID = -4,
38+
} mtimer_err_t;
39+
40+
const char *mtimer_err_str(mtimer_err_t err);
41+
42+
/* ── Timer mode ────────────────────────────────────────────────────────── */
43+
44+
typedef enum {
45+
MTIMER_ONESHOT = 0, /**< Fires once, then auto-stops. */
46+
MTIMER_PERIODIC = 1, /**< Fires repeatedly at interval. */
47+
} mtimer_mode_t;
48+
49+
/* ── Timer state ───────────────────────────────────────────────────────── */
50+
51+
typedef enum {
52+
MTIMER_STOPPED = 0,
53+
MTIMER_RUNNING = 1,
54+
MTIMER_PAUSED = 2,
55+
} mtimer_state_t;
56+
57+
const char *mtimer_state_str(mtimer_state_t state);
58+
59+
/* ── Platform callback ─────────────────────────────────────────────────── */
60+
61+
typedef uint32_t (*mtimer_clock_fn)(void);
62+
63+
/* ── Timer callback ────────────────────────────────────────────────────── */
64+
65+
/**
66+
* Timer expiry callback.
67+
*
68+
* @param timer_id Timer index.
69+
* @param ctx User context.
70+
*/
71+
typedef void (*mtimer_cb_fn)(uint8_t timer_id, void *ctx);
72+
73+
/* ── Timer descriptor ──────────────────────────────────────────────────── */
74+
75+
typedef struct {
76+
const char *name; /**< Timer name (static string). */
77+
uint32_t interval_ms; /**< Timer interval / delay. */
78+
mtimer_mode_t mode; /**< ONESHOT or PERIODIC. */
79+
mtimer_cb_fn callback; /**< Expiry callback. */
80+
void *ctx; /**< User context for callback. */
81+
82+
/* Runtime state */
83+
mtimer_state_t state;
84+
uint32_t start_ms; /**< When timer was (re)started. */
85+
uint32_t remaining_ms; /**< For pause/resume. */
86+
uint32_t fire_count; /**< Times this timer has fired. */
87+
bool allocated; /**< Slot in use? */
88+
} mtimer_entry_t;
89+
90+
/* ── Timer manager instance ────────────────────────────────────────────── */
91+
92+
typedef struct {
93+
mtimer_entry_t timers[MTIMER_MAX_TIMERS];
94+
mtimer_clock_fn clock;
95+
uint32_t tick_count; /**< Total tick() calls. */
96+
uint32_t fire_count; /**< Total callbacks fired. */
97+
} mtimer_t;
98+
99+
/* ── Init ──────────────────────────────────────────────────────────────── */
100+
101+
mtimer_err_t mtimer_init(mtimer_t *tm, mtimer_clock_fn clock);
102+
103+
/* ── Create / Destroy ──────────────────────────────────────────────────── */
104+
105+
/**
106+
* Create a timer.
107+
*
108+
* @param tm Timer manager.
109+
* @param name Timer name (static/const, for debug).
110+
* @param interval_ms Interval or delay in ms.
111+
* @param mode MTIMER_ONESHOT or MTIMER_PERIODIC.
112+
* @param callback Expiry callback.
113+
* @param ctx User context.
114+
* @return Timer ID (0-based) or negative error.
115+
*/
116+
int mtimer_create(mtimer_t *tm, const char *name, uint32_t interval_ms,
117+
mtimer_mode_t mode, mtimer_cb_fn callback, void *ctx);
118+
119+
/**
120+
* Destroy a timer (free the slot).
121+
*/
122+
mtimer_err_t mtimer_destroy(mtimer_t *tm, uint8_t id);
123+
124+
/* ── Control ───────────────────────────────────────────────────────────── */
125+
126+
/** Start (or restart) a timer. */
127+
mtimer_err_t mtimer_start(mtimer_t *tm, uint8_t id);
128+
129+
/** Stop a timer. */
130+
mtimer_err_t mtimer_stop(mtimer_t *tm, uint8_t id);
131+
132+
/** Pause a running timer (saves remaining time). */
133+
mtimer_err_t mtimer_pause(mtimer_t *tm, uint8_t id);
134+
135+
/** Resume a paused timer. */
136+
mtimer_err_t mtimer_resume(mtimer_t *tm, uint8_t id);
137+
138+
/** Restart with a new interval. */
139+
mtimer_err_t mtimer_set_interval(mtimer_t *tm, uint8_t id, uint32_t interval_ms);
140+
141+
/* ── Tick ──────────────────────────────────────────────────────────────── */
142+
143+
/**
144+
* Process all timers. Call from main loop or periodic interrupt.
145+
*
146+
* Checks each running timer against the clock. Fires callbacks for
147+
* expired timers. Periodic timers auto-restart. Oneshot timers stop.
148+
*
149+
* @return Number of timers that fired this tick.
150+
*/
151+
int mtimer_tick(mtimer_t *tm);
152+
153+
/* ── Query ─────────────────────────────────────────────────────────────── */
154+
155+
uint8_t mtimer_count(const mtimer_t *tm);
156+
const mtimer_entry_t *mtimer_at(const mtimer_t *tm, uint8_t id);
157+
int mtimer_find(const mtimer_t *tm, const char *name);
158+
mtimer_state_t mtimer_state(const mtimer_t *tm, uint8_t id);
159+
uint32_t mtimer_remaining(const mtimer_t *tm, uint8_t id);
160+
uint32_t mtimer_fire_count(const mtimer_t *tm, uint8_t id);
161+
uint32_t mtimer_total_fires(const mtimer_t *tm);
162+
uint32_t mtimer_total_ticks(const mtimer_t *tm);
163+
164+
#ifdef __cplusplus
165+
}
166+
#endif
167+
168+
#endif /* MTIMER_H */

0 commit comments

Comments
 (0)