Using the FMAC in the STM32G431

The STM32G431 has a Filter Math ACellerator (FMAC) hardware unit inside of it. This unit can take be used to implement an FIR or IIR filter without burdening the CPU. The FMAC unit has input and output circular buffers as well as a coefficient buffer. It is possible to connect the input buffer to an ADC using DMA and similarly it is possible to connect an output buffer directly to a DAC over DMA. In the case of this project I used an ADC interrupt handler to manage data input and output to the FMAC.

There are lots of tools to help you design a digital filter. I chose to use python and jupyter notebook in this case. The jupyter notebook code is as follows (it is also on the github site linked below)

import numpy as np
import scipy as sp
import scipy.signal as sg
import matplotlib.pyplot as plt
Fs=48000
Fpass=1000
Order=16
Wp=Fpass/(Fs/2)
b=sg.firwin(Order+1,Wp,window = "hamming",pass_zero = True)
w,h=sg.freqz(b)
mag=20*np.log10(abs(h))
plt.figure()
plt.semilogx(w*(Fs/(2*np.pi)), mag)
plt.show()
bmax=np.max(np.abs(b))
# Working out the scale factor can be a bit tricky.  There is a 
# 24 bit accumulator in the FMAC.  The ADC has a 12bit range.
# This leaves 12 bits for coefficients if overflows are to be prevented.
# Furthermore, the multiply and accumulate nature of the FIR will push 
# results beyond 24 bits if we are not careful.  This is more pronounced with
# lower cut-off frequencies where there is a large central lobe to the filter 
# coefficients which may lead to overflows, particularly at low input 
# frequencies.  For now I'm just doing this by trial and error
ScaleFactor=4095/(bmax)
f = open('coffs.h', 'w')
f.write("#include <stdint.h>\n")
f.write("#define SCALE_FACTOR ")
f.write(str(int(np.round(ScaleFactor))))
f.write("\n")
f.write("#define FILTER_LENGTH ")
f.write(str(Order))
f.write("\n")
f.write("const int16_t b[]={")
for coeff in b:
    f.write(str(int(np.round(coeff*ScaleFactor))))
    f.write(",\n")
f.write("};\n")

f.close();
plt.figure();
plt.plot(b);

This code outputs a header file that includes the filter coefficients for a low pass FIR filter with a cutoff frequency of 1000Hz. The output at 2kHz is shown below

And it 4kHz this becomes:

It would appear that the filter is indeed working however there are a number of caveats. The FMAC uses fixed point arithmetic so coefficients and input signals must be shifted and scaled appropriately. The FMAC has a limited numeric range (24 bits of fractional data internally, 15 bits input and output) and overflows will happen. This is a particular problem at low frequencies with filters whose coefficients are mostly/all positive. I had to do some manual tweaking of the coefficients to get the output performance I wanted. When testing for such overflows it is useful to input a DC signal of maximum voltage to ensure that no overflows occur.

As usual, code is available over on github