Taking the Mystery out of using the CSMIS-DSP Library

My objective in writing this article grew out of my frustration in trying to implement an IIR filter on a STMG431 MCU using the CSMIS-DSP Library. What should have been a straight forward task took too much of my time. I thought such an article needed to be written to help others.

Background

From the armDeveloper site, "The CMSIS is a vendor-independent hardware abstraction layer for microcontrollers that are based on Arm® Cortex® processors. The CMSIS defines generic tool interfaces and enables consistent device support. It provides simple software interfaces to the processor and the peripherals, simplifying software re-use, reducing the learning curve for microcontroller developers, and reducing the time to market for new devices."

The library is available as part of the Keil MDK developer tools and is available at the ARM site. There is a unique library for each Arm coretex core and whether you are using Little or Big endian and whether or not you want FPU support. Once installed the library for all target device will be located under a common directory, as example file:///C:/Users/UserName/AppData/Local/Arm/Packs/ARM/CMSIS/5.7.0/CMSIS/Documentation/DSP/html/index.html.

For my installation using Keil MDK I used the Real Time Environment option to install CSMIS-DSP Library v1.80.

Getting Started

The CSMIS documentation is extensive but not easy to navigate or as effective as it could be; at least for me. The main page for the Biquad Cascade Infinite Impulse Response (IIR) Lattice filters is located [here]. This page shows the lattice block diagram for a Direct Form I structure. The particualr filter I am using is the arm_biquad_cascade_df1_q31(). Each Biquad stage implements a second order filter using the difference equation:

$$y[n] = b0 \cdot x[n] + b1 \cdot x[n-1] + b2 \cdot x[n-2] + a1 \cdot y[n-1] + a2 \cdot y[n-2]$$

In the z-domain this becomes the transfer function, H[z]:

$$y[z]/x[z] = H[z] =  \frac{b0 + b1 \cdot z^{-1}+ b2 \cdot z^{-2}}{ 1 - a1 \cdot z^{-1} - a2 \cdot z^{-2}}$$

The code is structured in three main pieces. An Instance Structure is used to specify the coefficients and state variables. The Instance Structure can be defined directly with it's declaration or an Initialization Function can be called to initialize the Instance Function. Each instance of a filter requires it's own Instance Function. These are declared and initialized once.

Finally, there is a Filter function which is called each sample time to process data.

arm_biquad_cascade_df1_q31( &S1,
&x0,
&y0,
BLOCKSIZE);

Specifics

The filter function I want to implement is a 4th order Butterworth LPF with a -3db cuttoff of 280Hz and a sampling frequency 30kHz. My first step was to use the Filter Designer App in Matlab [R2020b] to design a Direct Form 1 filter with the listed specifications.

The Matlab design is two 2nd order sections, each with a 2nd order numerator and denominator. For the specified design  the Matlab coeficients are calculated as:

Section 1 Numerator 1 2 1
          Denominator 1 -1.952764 0.956126
          Gain 0.00084065 
Section 2 Numerator 1 2 1
          Denominator 1 -1.894005 0.897266
          Gain 0.00081536

The coefficients as shown are in assending order of $z^{-n}$, for b0, b1, b2 and a0, a1 and a2. The coefficient a0 (normalized to 1) is not used, so when the gains are spread across the coefficients and the coefficients are scaled by 2^29 the results are:

451321 902641 451321 -881322 431519
437743 875486 437743 -829088 392772

In the code this becomes:

const q31_t coeffTable[10] = 
                  { 451321, 902641, 451321, -881322, 431519, 
                    437743, 875486, 437743, -829088, 392772};

Instance Structure

The Instance structure is declared as follows for the particular filter I am using:

arm_biquad_casd_df1_inst_q31 S1;

This is defined in the library include file arm_math.h.

typedef struct
{
uint32_t numStages;   /**< number of 2nd order stages in the filter. Overall order is 2*numStages. */
q31_t *pState;        /**< Points to the array of state coefficients. The array is of length 4*numStages. */
q31_t *pCoeffs;       /**< Points to the array of coefficients. The array is of length 5*numStages. */
uint8_t postShift;    /**< Additional shift, in bits, applied to each output sample. */
} arm_biquad_casd_df1_inst_q31;

Note, prior to including arm_math.h you need to ensure that __FPU_PRESENT is defined, which in my case means "stm32G431xx.h must be included prior to "arm_math.h". Otherwise you will experience an error saying __FPU_PRESENT needs to be defined.

Once declared, the Instance structure can be initialized using the following, called just once:

void ARM_Filter_Init()
{
  arm_biquad_cascade_df1_init_q31 ( &S1,
                    NUMSTAGES,
                    (q31_t *) &coeffTable[0],
                    &biquadStateBuffer[0],
                    POSTSHIFT ); 
}

The filter function is called every fixed sample time, Ts, as follows:

static q31_t FilterBEMF(q31_t x0)
{
  q31_t y0;
  arm_biquad_cascade_df1_q31( &S1, 
                              &x0, 
                              &y0, 
                              BLOCKSIZE);
  return y0;
}

I hope this brief tutorial will help others get past the hurdle of using the CMSIS -DSP Library in their ARM project.

 

Leave a Comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Scroll to Top