I am programming an STM32 cortex M4 microcontroller. I made a simple circuit to offset a sinusoid signal so that it varies (at its maximum output) from o to 3v3, basically I built a circuit to give the sinusoidal signal a DC offset because the mirocontroller's ADC does not support negative voltages.
I am sampling at a frequency of approximately 10kHz and I programmed the DMA to do 128 acquisitions before generating an interruption so that I can treat my data.
What I want to do
Perform the 128 point FFT on my signal and then some other calculations (RMS value, power, etc). Basically I have two signals, one for voltage and one for current and I want to do all the electrical calculations using the FFT. I implemented this in python and it works.
What I did
RMS value of an FFT of a signal is
import numpy as np
RMS = np.sqrt(np.sum(signal.real * signal.real + signal.imag * signal.imag))/N
where N is the number of data points you are using and signal is any signal ... in order to ignore the DC offset you have to do the following
RMS = np.sqrt(np.sum(signal[1:].real * signal[1:].real + signal[1:].imag * signal[1:].imag))/N
Here is my C code using the CMSIS DSP library
q15_t adcData[256] = {0}; /*dma reads 128 points into this buffer*/
q31_t auxRms = 0;
q31_t auxReal = 0;
q31_t auxImag = 0;
q31_t sqrt = 0;
float result = 0.0F;
int i;
arm_cfft_q15(&arm_cfft_sR_q15_len128, adcData, 0, 1);
for(i = 1; i < N; i++) {
auxReal = adcData[2*i];
auxImag = adcData[2*i+1];
auxReal = auxReal * auxReal;
auxImag = auxImag * auxImag;
auxRms += auxImag + auxReal;
}
arm_sqrt_q31(interim_rms, &sqrt);
arm_q31_to_float(&sqrt, &result, 1);
The same thing as in python, square the real value, square the imaginary value sum them and accumulate, but since this is q15 math, I did the q15 square root and the transformed it to float, which gives me an output between -1 and 1 but this does not work
It doesn't matter if I insert a signal with its maximum output or if I leave just the DC signal, the result is basically the same, which is wrong ... very wrong.
In order to test if this was working I created my own sine wave in the microcontroller and the I did the same to it. Here's my code:
float var = 0.0F;
float fbuff[256] = {0};
for(i = 0; i < 128; i++) {
var = 0.03125 * arm_sin_f32(2 * PI * i / 128);
fbuff[i] = var;
arm_float_to_q15(&var, &buff[i], 1);
buff[i] += 2048;
}
With this I get a sine wave that imitates the ADC gicing its maximum value for the signal, since it goes from 0 to 4095 (it's a 12 bit ADC) and the DC offset is 2048. The only difference is that I took the same created signal and did the same calculations and also implemented the same function in python and compared the results.
IN python the RMS value was 0.02209708691207961 In the float calculations I got 0.176776692, which is correct ... because I need to get this value, divide it by 128 multiply it by 8 and then multiply it by 2. This is because the given value of the cfft needs to be dampened (probably not the correct term) by N samples (this case 128, but if using a 64 sample FFt then 64 and so on) which gives me the exact same value as the python function. In the q15 function I got 0.489307404 which is about 22 times greater than the expected value so ... obviously wrong. Even when I compare the output of the float FFT with the q15 FFT of the "perfect" sinusoidal signal it seems wrong ... to notice this I checked the float FFT result and the q15 FFT result as well as the python FFt result.
What else have I tried?
Since the above failed I tried the following. Instead of doing my own RMS calculation I used the arm_rms_q15 function which should receive a 128 samples q15 buffer and output a q15 RMS value, which only gives me a big fat 0 (certainly due to saturation instructions) and I also tried converting my entire buffer into a float buffer using the arm_q15_to_float(&src, dst, size) and the doing the FFT BUT the resulting buffer is obviously wrong.
I have recently tested this again .. I created the float sinusoidal wave and then converted it into q31 and did the FFT and the result was good, it was dampened by a factor of 128 in comparison to the float output but the output was the correct value. I even tried creating the sine wave in floating point, convert it to q15 and then convert it to q31 from the q15 value and the result of the q31 FFT was the same. So I can only assume there is a bug in the q15 implementation of the FFT, which I might have introduced myself when I changed PCs, since a few months ago I was able to use the q15 FFT and the results were OK (yeah I know ... I forgot to mention something as important as that, but I honestly just remembered it). So I'll get to looking for this problem again on monday and see if there is a bug in the "new" CMSIS library I'm using (I might have changed versions when I changed PCs) or if I might have changed a configuration accidentally ... but please if anyone might know what is wrong please let me know before I go bug hunting ... it will certainly be faster this way.
An example of input data and FFT result for the "perfect sinusoidal", the first one is the sample array and the second one is the FFT array using the q15 algorithm
The values of my created signal are
[ 02048 02098 02148 02198 02248 02297 02345 02393
02440 02486 02531 02574 02617 02658 02698 02736
02772 02807 02840 02870 02899 02926 02951 02974
02994 03012 03028 03041 03052 03061 03067 03071
03072 03071 03067 03061 03052 03041 03028 03012
02994 02974 02951 02926 02899 02870 02840 02807
02772 02736 02698 02658 02617 02574 02531 02486
02440 02393 02345 02297 02248 02198 02148 02098
02048 01998 01948 01898 01848 01799 01751 01703
01656 01610 01565 01522 01479 01438 01398 01360
01324 01289 01256 01226 01197 01170 01145 01122
01102 01084 01068 01055 01044 01035 01029 01025
01024 01025 01029 01035 01044 01055 01068 01084
01102 01122 01145 01170 01197 01226 01256 01289
01324 01360 01398 01438 01479 01522 01565 01610
01656 01703 01751 01799 01848 01898 01948 01998
]
and the resulting FFT is
[ 01020 01020 00872 -00424 00252 -00248 00108 -00334
-00002 00000 00116 -00148 -00004 -00004 00092 -00094
00000 00000 00078 -00066 -00002 00000 00066 -00050
00000 00000 00058 -00040 -00002 -00002 00052 -00030
00000 00000 00048 -00026 -00002 00000 00044 -00020
-00002 00000 00040 -00016 00000 00000 00038 -00012
00000 00000 00036 -00010 -00002 -00002 00034 -00008
00000 00000 00032 -00006 -00002 00000 00030 -00004
00000 00000 00030 00000 00000 -00002 00028 00002
-00002 00000 00028 00002 00000 00000 00026 00004
00000 00000 00026 00006 00000 00000 00024 00006
00000 00000 00022 00008 00000 00002 00022 00008
00000 00000 00022 00008 -00002 -00002 00020 00010
-00002 00000 00018 00010 00000 00000 00018 00012
00000 00000 00018 00012 00000 00000 00016 00014
00000 00000 00016 00014 00000 00000 00016 00014
00000 00000 00016 00016 00000 00000 00014 00018
-00002 00000 00014 00018 00000 00000 00012 00018
00000 00000 00012 00020 00000 00000 00010 00020
00000 00000 00008 00022 00000 -00002 00008 00020
00000 00000 00008 00022 00000 00000 00006 00022
-00002 00000 00004 00024 00000 00000 00004 00024
00000 00000 00004 00026 -00002 00000 00002 00026
00000 00000 00000 00028 -00002 00000 00000 00030
00000 00000 -00002 00032 00000 -00002 -00004 00032
-00002 00000 -00006 00036 00000 00000 -00010 00036
00000 00000 -00012 00040 00000 -00002 -00014 00042
00000 00000 -00020 00046 00000 00000 -00024 00048
00000 00000 -00030 00052 -00002 00000 -00038 00060
-00002 00000 -00050 00066 00000 00000 -00066 00078
00000 00000 -00094 00092 -00002 00000 -00150 00114
00000 00000 -00342 00102 -00256 00268 -00414 00884]
EDIT:
I forgot to mention that the ADC is inputting valid data, I get a nice sinusoidal wave ranging from 500 to 3500 approximately. But this is not that important since it is possible to see that the FFT q15 algorithm is not working even in the "perfect wave"