diff --git a/samples/modules/cmsis_dsp/moving_average/CMakeLists.txt b/samples/modules/cmsis_dsp/moving_average/CMakeLists.txt new file mode 100644 index 000000000000..64052f8e1c4c --- /dev/null +++ b/samples/modules/cmsis_dsp/moving_average/CMakeLists.txt @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(moving_average) + +target_sources(app PRIVATE src/main.c) diff --git a/samples/modules/cmsis_dsp/moving_average/README.rst b/samples/modules/cmsis_dsp/moving_average/README.rst new file mode 100644 index 000000000000..dd99f5885e3a --- /dev/null +++ b/samples/modules/cmsis_dsp/moving_average/README.rst @@ -0,0 +1,63 @@ +.. zephyr:code-sample:: cmsis-dsp-moving-average + :name: CMSIS-DSP moving average + + Use the CMSIS-DSP library to calculate the moving average of a signal. + +Overview +******** + +This sample demonstrates how to use the CMSIS-DSP library to calculate the moving average of a +signal. + +It can be run on any board supported in Zephyr, but note that CMSIS-DSP is specifically optimized +for ARM Cortex-A and Cortex-M processors. + +A **moving average** filter is a common method used for smoothing noisy data. It can be implemented +as a finite impulse response (FIR) filter where the filter coefficients are all equal to 1/N, where +N is the number of "taps" (i.e. the size of the moving average window). + +The sample uses a very simple input signal of 32 samples, and computes the moving average using a +"window" of 10 samples. The resulting output is computed in one single call to the ``arm_fir_q31()`` +CMSIS-DSP function, and displayed on the console. + +.. note:: + In order to allow an easy comparison of the efficiency of the CMSIS-DSP library when used on ARM + processors vs. other architectures, the sample outputs the time and number of cycles it took to + compute the moving average. + +Requirements +************ + +CMSIS-DSP is an optional module and needs to be added explicitly to your Zephyr workspace: + +.. code-block:: shell + + west config manifest.project-filter -- +cmsis-dsp + west update cmsis-dsp + +Building and Running +********************* + +The demo can be built as follows: + +.. zephyr-app-commands:: + :zephyr-app: samples/modules/cmsis-dsp/moving_average + :host-os: unix + :board: qemu_cortex_m0 + :goals: run + :compact: + +The sample will output the number of cycles it took to compute the moving averages, as well as the +computed average for each 10-sample long window of the input signal. + +.. code-block:: console + + *** Booting Zephyr OS build v3.6.0-224-gb55824751d6c *** + Time: 244 us (244 cycles) + Input[00]: 0 0 0 0 0 0 0 0 0 0 | Output[00]: 0.00 + Input[01]: 0 0 0 0 0 0 0 0 0 1 | Output[01]: 0.10 + Input[02]: 0 0 0 0 0 0 0 0 1 2 | Output[02]: 0.30 + Input[03]: 0 0 0 0 0 0 0 1 2 3 | Output[03]: 0.60 + ... + Input[30]: 21 22 23 24 25 26 27 28 29 30 | Output[30]: 25.50 + Input[31]: 22 23 24 25 26 27 28 29 30 31 | Output[31]: 26.50 diff --git a/samples/modules/cmsis_dsp/moving_average/prj.conf b/samples/modules/cmsis_dsp/moving_average/prj.conf new file mode 100644 index 000000000000..73412a76fb0a --- /dev/null +++ b/samples/modules/cmsis_dsp/moving_average/prj.conf @@ -0,0 +1,5 @@ +CONFIG_REQUIRES_FULL_LIBC=y +CONFIG_REQUIRES_FLOAT_PRINTF=y + +CONFIG_CMSIS_DSP=y +CONFIG_CMSIS_DSP_FILTERING=y diff --git a/samples/modules/cmsis_dsp/moving_average/sample.yaml b/samples/modules/cmsis_dsp/moving_average/sample.yaml new file mode 100644 index 000000000000..2bcc8359158f --- /dev/null +++ b/samples/modules/cmsis_dsp/moving_average/sample.yaml @@ -0,0 +1,48 @@ +sample: + description: Use CMSIS DSP to calculate moving average + name: CMSIS DSP Moving Average +tests: + sample.modules.cmsis_dsp.moving_average: + tags: + - samples + integration_platforms: + - qemu_cortex_m0 + - native_sim + modules: + - cmsis-dsp + harness: console + harness_config: + type: multi_line + regex: + - "Input\\[00\\]: 0 0 0 0 0 0 0 0 0 0 | Output\\[00\\]: 0.00" + - "Input\\[01\\]: 0 0 0 0 0 0 0 0 0 1 | Output\\[01\\]: 0.10" + - "Input\\[02\\]: 0 0 0 0 0 0 0 0 1 2 | Output\\[02\\]: 0.30" + - "Input\\[03\\]: 0 0 0 0 0 0 0 1 2 3 | Output\\[03\\]: 0.60" + - "Input\\[04\\]: 0 0 0 0 0 0 1 2 3 4 | Output\\[04\\]: 1.00" + - "Input\\[05\\]: 0 0 0 0 0 1 2 3 4 5 | Output\\[05\\]: 1.50" + - "Input\\[06\\]: 0 0 0 0 1 2 3 4 5 6 | Output\\[06\\]: 2.10" + - "Input\\[07\\]: 0 0 0 1 2 3 4 5 6 7 | Output\\[07\\]: 2.80" + - "Input\\[08\\]: 0 0 1 2 3 4 5 6 7 8 | Output\\[08\\]: 3.60" + - "Input\\[09\\]: 0 1 2 3 4 5 6 7 8 9 | Output\\[09\\]: 4.50" + - "Input\\[10\\]: 1 2 3 4 5 6 7 8 9 10 | Output\\[10\\]: 5.50" + - "Input\\[11\\]: 2 3 4 5 6 7 8 9 10 11 | Output\\[11\\]: 6.50" + - "Input\\[12\\]: 3 4 5 6 7 8 9 10 11 12 | Output\\[12\\]: 7.50" + - "Input\\[13\\]: 4 5 6 7 8 9 10 11 12 13 | Output\\[13\\]: 8.50" + - "Input\\[14\\]: 5 6 7 8 9 10 11 12 13 14 | Output\\[14\\]: 9.50" + - "Input\\[15\\]: 6 7 8 9 10 11 12 13 14 15 | Output\\[15\\]: 10.50" + - "Input\\[16\\]: 7 8 9 10 11 12 13 14 15 16 | Output\\[16\\]: 11.50" + - "Input\\[17\\]: 8 9 10 11 12 13 14 15 16 17 | Output\\[17\\]: 12.50" + - "Input\\[18\\]: 9 10 11 12 13 14 15 16 17 18 | Output\\[18\\]: 13.50" + - "Input\\[19\\]: 10 11 12 13 14 15 16 17 18 19 | Output\\[19\\]: 14.50" + - "Input\\[20\\]: 11 12 13 14 15 16 17 18 19 20 | Output\\[20\\]: 15.50" + - "Input\\[21\\]: 12 13 14 15 16 17 18 19 20 21 | Output\\[21\\]: 16.50" + - "Input\\[22\\]: 13 14 15 16 17 18 19 20 21 22 | Output\\[22\\]: 17.50" + - "Input\\[23\\]: 14 15 16 17 18 19 20 21 22 23 | Output\\[23\\]: 18.50" + - "Input\\[24\\]: 15 16 17 18 19 20 21 22 23 24 | Output\\[24\\]: 19.50" + - "Input\\[25\\]: 16 17 18 19 20 21 22 23 24 25 | Output\\[25\\]: 20.50" + - "Input\\[26\\]: 17 18 19 20 21 22 23 24 25 26 | Output\\[26\\]: 21.50" + - "Input\\[27\\]: 18 19 20 21 22 23 24 25 26 27 | Output\\[27\\]: 22.50" + - "Input\\[28\\]: 19 20 21 22 23 24 25 26 27 28 | Output\\[28\\]: 23.50" + - "Input\\[29\\]: 20 21 22 23 24 25 26 27 28 29 | Output\\[29\\]: 24.50" + - "Input\\[30\\]: 21 22 23 24 25 26 27 28 29 30 | Output\\[30\\]: 25.50" + - "Input\\[31\\]: 22 23 24 25 26 27 28 29 30 31 | Output\\[31\\]: 26.50" diff --git a/samples/modules/cmsis_dsp/moving_average/src/main.c b/samples/modules/cmsis_dsp/moving_average/src/main.c new file mode 100644 index 000000000000..bab71912c185 --- /dev/null +++ b/samples/modules/cmsis_dsp/moving_average/src/main.c @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2024 Benjamin Cabé + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include "arm_math.h" + +#define NUM_TAPS 10 /* Number of taps in the FIR filter (length of the moving average window) */ +#define BLOCK_SIZE 32 /* Number of samples processed per block */ + +/* + * Filter coefficients are all equal for a moving average filter. Here, 1/NUM_TAPS = 0.1f. + */ +q31_t firCoeffs[NUM_TAPS] = {0x0CCCCCCD, 0x0CCCCCCD, 0x0CCCCCCD, 0x0CCCCCCD, 0x0CCCCCCD, + 0x0CCCCCCD, 0x0CCCCCCD, 0x0CCCCCCD, 0x0CCCCCCD, 0x0CCCCCCD}; + +arm_fir_instance_q31 sFIR; +q31_t firState[NUM_TAPS + BLOCK_SIZE - 1]; + +int main(void) +{ + q31_t input[BLOCK_SIZE]; + q31_t output[BLOCK_SIZE]; + uint32_t start, end; + + /* Initialize input data with a ramp from 0 to 31 */ + for (int i = 0; i < BLOCK_SIZE; i++) { + input[i] = i << 24; /* Convert to Q31 format */ + } + + /* Initialize the FIR filter */ + arm_fir_init_q31(&sFIR, NUM_TAPS, firCoeffs, firState, BLOCK_SIZE); + + /* Apply the FIR filter to the input data and measure how many cycles this takes */ + start = k_cycle_get_32(); + arm_fir_q31(&sFIR, input, output, BLOCK_SIZE); + end = k_cycle_get_32(); + + printf("Time: %u us (%u cycles)\n", k_cyc_to_us_floor32(end - start), end - start); + + for (int i = 0; i < BLOCK_SIZE; i++) { + printf("Input[%02d]: ", i); + for (int j = NUM_TAPS - 1; j >= 0; j--) { + if (j <= i) { + printf("%2d ", (int)(input[i - j] >> 24)); + } else { + printf("%2d ", 0); + } + } + printf("| Output[%02d]: %6.2f\n", i, (double)output[i] / (1 << 24)); + } + + return 0; +}