Moving Average Filter Documentation
Overview
This header-only library provides a macro-based generator for static moving average filters.
Filter Theory
The filter maintains a circular buffer of the last N samples (where N is defined by MOVING_AVERAGE_WINDOW_SIZE).
To maintain O(1) time complexity, the implementation tracks a running sum. When a new sample arrives, the oldest sample is subtracted from the sum, the new one is added, and the average is returned.
Macro Name Generation
The DEFINE_MOVING_AVG macro uses C preprocessor token pasting (the ## operator) to dynamically create unique struct names and function identifiers. This allows you to define multiple independent filters (e.g., one for Voltage, one for Current) without naming collisions.
Internal Logic
The header uses the ## operator to concatenate the user-provided name with specific suffixes. For example, the update function is defined internally as:
static inline TYPE NAME##_update(NAME *ma, TYPE sample) { ... }
When the compiler processes DEFINE_MOVING_AVG(CellVoltage, uint16_t, uint32_t, 20), it generates a unique function signature:
static inline uint16_t CellVoltage_update(CellVoltage *ma, uint16_t sample)
Generation Format
When you call the macro as DEFINE_MOVING_AVG(NAME, TYPE, SUM_TYPE, MOVING_AVERAGE_WINDOW_SIZE), the preprocessor maps your NAME argument to the following identifiers:
| Generated Component | Format | Example (if NAME is Battery) |
|---|---|---|
| Struct Type | NAME | Battery |
| Initialization | NAME##_init | Battery_init |
| Update/Process | NAME##_update | Battery_update |
Usage Guide
1. Define the Filter
Use the DEFINE_MOVING_AVG macro to generate a custom struct and its associated functions.
// DEFINE_MOVING_AVG(NAME, TYPE, SUM_TYPE)
DEFINE_MOVING_AVG(VoltageFilter, uint16_t, uint32_t, 20);
- NAME: The prefix for the generated struct and functions.
- TYPE: The data type of the input samples (e.g., int16_t).
- SUM_TYPE: A larger type used for the internal accumulator to prevent overflow. For uint16_t samples and a window of 8, a uint32_t is recommended.
- MOVING_AVERAGE_WINDOW_SIZE: The size of that moving average window
2. Initialization
Initialize the filter state (clears the buffer and resets the sum) before use.
VoltageFilter myFilter;
VoltageFilter_init(&myFilter);
3. Updating and Filtering
Pass the handle and the new sample to the _update function. It returns the newly calculated average.
uint16_t raw_adc = HAL_ADC_GetValue(&hadc1);
uint16_t filtered_adc = VoltageFilter_update(&myFilter, raw_adc);
Common Pitfalls
Overflow Prevention
Always ensure SUM_TYPE is large enough to hold TYPE_MAX * MOVING_AVERAGE_WINDOW_SIZE. For example, if your window is 256 and your type is uint16_t, the sum could exceed 16 million, which fits in a uint32_t but not a uint16_t.