Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

samples: cmsis_dsp: add moving average sample #66726

Merged
merged 1 commit into from
Jun 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions samples/modules/cmsis_dsp/moving_average/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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)
63 changes: 63 additions & 0 deletions samples/modules/cmsis_dsp/moving_average/README.rst
Original file line number Diff line number Diff line change
@@ -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
5 changes: 5 additions & 0 deletions samples/modules/cmsis_dsp/moving_average/prj.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
CONFIG_REQUIRES_FULL_LIBC=y
CONFIG_REQUIRES_FLOAT_PRINTF=y

CONFIG_CMSIS_DSP=y
CONFIG_CMSIS_DSP_FILTERING=y
48 changes: 48 additions & 0 deletions samples/modules/cmsis_dsp/moving_average/sample.yaml
Original file line number Diff line number Diff line change
@@ -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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using mps2_an521_remote here would be a nice opportunity to show MVE in action :).

- 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"
58 changes: 58 additions & 0 deletions samples/modules/cmsis_dsp/moving_average/src/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Copyright (c) 2024 Benjamin Cabé <[email protected]>
*
* SPDX-License-Identifier: Apache-2.0
*/

#include <stdio.h>
#include <zephyr/kernel.h>

#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();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does averaging 32 samples take long enough to show up at the cycle resolution?
The standard API for profiling durations is zephyr/timing/timing.h with CONFIG_TIMING_FUNCTIONS=y from my understanding.


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;
}
Loading