A reusable moving-average filter library for Zephyr v3.7 LTS, packaged with a demo application, Ztest unit tests, and Twister-driven CI.
This repository is a small but complete Zephyr project built the way a production Zephyr project is structured. The feature itself (an integer moving-average filter) is intentionally simple; the value is in the scaffolding around it:
- A reusable, dependency-free filter library shared by both the app and the tests.
- A sample application that builds for the host simulator and for real STM32 hardware.
- A host-runnable Ztest unit suite driven by Twister.
- GitHub Actions CI that runs the tests and cross-builds the firmware on every push.
It uses the T2 (star) west topology: this repo is the manifest repo and imports
Zephyr as a dependency. The filter logic is kept free of Zephyr headers so it can be
unit-tested on native_sim in milliseconds, with the RTOS and hardware glue confined
to the sample app. The app is devicetree-aware and board-portable, driving the LED
through gpio_dt_spec and DT_ALIAS(led0) so it works on any board that defines an
led0 alias.
The public API lives in include/moving_average.h and is
implemented in lib/moving_average.c. It uses only <stddef.h>
and <stdint.h>, so it has no Zephyr or RTOS dependency.
The filter operates on int32_t samples and keeps a running sum for O(1) updates. The
window is a fixed-capacity ring buffer of up to MA_MAX_WINDOW (16) samples.
#define MA_MAX_WINDOW 16U
struct moving_average {
int32_t samples[MA_MAX_WINDOW];
size_t window; /* configured window length (<= MA_MAX_WINDOW) */
size_t count; /* how many samples are currently held */
size_t head; /* index of the oldest sample */
int64_t sum; /* running sum of held samples */
};
/* Initialise with a window length. Clamped to [1, MA_MAX_WINDOW]. */
void ma_init(struct moving_average *ma, size_t window);
/* Add a sample; returns the current average (rounded to nearest int32). */
int32_t ma_add(struct moving_average *ma, int32_t sample);
/* Number of samples currently held. */
size_t ma_count(const struct moving_average *ma);Behavioural notes, all covered by the unit tests:
ma_initclamps the requested window into[1, MA_MAX_WINDOW]: a window of 0 becomes 1, and any value above 16 becomes 16.- While the window is still filling, the average is taken over the samples held so far, so the first sample is returned as its own average.
- Once the window is full it slides: the oldest sample is evicted and
ma_countsaturates at the window length. ma_addreturns the sum divided by the current count, rounded to the nearest integer, with correct rounding for negative averages (for example, -10 and -5 average to -8).
Typical usage:
struct moving_average ma;
ma_init(&ma, 8); /* 8-sample window */
int32_t filtered = ma_add(&ma, raw); /* feed a reading, get the smoothed value */- Zephyr v3.7 LTS (pinned to
v3.7.0inwest.yml). - The
westmeta-tool. - The Zephyr SDK (developed against SDK 0.16.8). CI installs the
arm-zephyr-eabitoolchain via the officialaction-zephyr-setup.
Create a west workspace from this repository's manifest:
west init -m https://github.com/ryantra/zephyr-moving-average --mr main my-workspace
cd my-workspace
west updateBuild and run the demo app on the host simulator (no hardware required):
west build -b native_sim zephyr-moving-average/app
./build/zephyr/zephyr.exeBuild and flash the demo app on a Nucleo-F411RE:
west build -b nucleo_f411re zephyr-moving-average/app
west flashThe app prints raw versus filtered readings over printk and toggles the board LED
each cycle.
The Ztest suite in tests/moving_average/ runs on native_sim
and needs no hardware. From the workspace root, run it with Twister:
west twister -T zephyr-moving-average/tests -p native_sim --inline-logs -vThe suite (lib.moving_average) uses ZTEST_SUITE with five ZTEST cases covering the
first-sample average, averaging while the window fills, the sliding window and count
saturation, negative-value rounding, and window clamping.
.github/workflows/build.yml runs on every push and pull
request. It uses the official zephyrproject-rtos/action-zephyr-setup action to install
west, the Zephyr SDK, and the workspace from this repo's west.yml, then:
- Runs the unit tests on
native_simviawest twister -T tests -p native_sim. - Cross-builds the sample firmware with
west build -b nucleo_f411re app.
west.yml T2 west manifest; imports Zephyr v3.7.0 and the
cmsis and hal_stm32 modules
include/moving_average.h public filter API (dependency-free)
lib/moving_average.c filter implementation (shared by app and tests)
app/ sample application
src/main.c prints raw vs. filtered readings, toggles led0
prj.conf, CMakeLists.txt app configuration and build
sample.yaml Twister metadata (build for native_sim + nucleo_f411re)
tests/moving_average/ Ztest unit suite
src/main.c ZTEST cases
prj.conf, CMakeLists.txt test configuration and build
testcase.yaml Twister metadata (run on native_sim)
.github/workflows/build.yml CI: Twister tests + nucleo_f411re cross-build
Apache-2.0. See LICENSE.