Question
I'm trying to do some DSP that I have never done before and a nudge into the right direction would come in handy. The context is replication of this project.
The system architecture is the following:
An excitation signal is generated by a DAC. I use a 10 sample sine table to generate a 200 kHz sine wave which is subsequently filtered and the resulting SINAD is about 60 dB. The signal is then connected to the eight port passive system (see project description) via two analogue multiplexers, one carrying the excitation signal and the other the reference GND. All eight ports are connected to a 12 bit (at least 10 ENOB) ADC. The DC component is centred within the ADCs FSR by the input filter. The DAC and the ADCs are integrated into a microcomputer (Cortex-M4F).
The information I'm interested in is in the incoming signal's amplitude.
My current solution is the following:
- sample the signal with 1.25 Msps and collect 256 samples (about 40 periods of the signal with about 6 samples per period)
- estimate the signals mean and MS (mean square, like RMS but without applying the sqrt) using the 256 samples and the common equations
- calculate the MS of the AC component with $$ U_{RMS} = \sqrt{U_{DC}^2 + U_{AC,RMS}^2} \Leftrightarrow ()^2\\ U_{MS} = U_{DC}^2 + U_{AC,MS} \Leftrightarrow\\ U_{AC,MS} = U_{MS} - U_{DC}^2 $$
I'm happy with the MS of the AC component because it incorporates the information, just like the amplitude of the signal because of their direct relation.
I've read a few articles, especially this one Precision measurement of sine wave amplitude with ADC which sounded like it was exactly my problem. I couldn't wrap my head around the accepted solution though and thus asking this question.
I'm not very experienced in DSP and don't know efficient algorithms to make the most of my simple setup. What would you do to measure the signals amplitude, especially in the context of maximising the result's ENOB under the condition of 256 samples?
I captured some ADC data. Have a look at it here to get an impression of the signal's properties.
The steps in the signal amplitude hail from switching the system excitation and measurement ports to a different state. I included the switching action because it gives an impression of the signal conditions in all relevant cases, amplitude-wise. The sections with no visible signal aren't relevant, that's the case when the specific ADC channel is connected to the reference ground.
Implementation
I started with the implementation of Olli's and Cedron's idea today. Let me show you the code I wrote.
First I generated the sine and cosine tables for the DFT. First try, I didn't choose a period of 45 samples, as Olli suggested, but the theoretical length of
$$ \frac{samples}{period} = \frac{f_{sample}}{f_{signal}} = \frac{1.25~MHz}{200~kHz} = \frac{25}{4}\\ N_{samples} = \frac{25}{4} \cdot N_{periods} \mid N_{samples}, N_{periods} \in \mathbb{N}^+\\ \Rightarrow N_{periods} = 4 \cdot k \mid k \in \mathbb{N}^+ $$
which means that my tables need to be 25 entries long. I generated them with Matlab.
s1_15 = quantizer('fixed', 'round', 'saturate', [16, 15]);
x = linspace(0, 4 * 2 * pi, 25 + 1);
x(end) = [];
sin_table = num2int(s1_15, sin(x));
cos_table = num2int(s1_15, cos(x));
My previous sample buffer was 256 deep, the new sample buffer holds 250, the next smallest length that meets the requirement above.
// ch1 and ch2 alternating
u16 adc_data[500];
// sine and cosine table for single frequency DFT calculation
// s1.15 number format
const s16 dft_sin_table[25] = {0, 27667, 29649, 4107, -25248, -31164, -8149, 22431, 32188, 12063, -19261, -32703, -15786, 15786, 32703, 19261, -12063, -32188, -22431, 8149, 31164, 25248, -4107, -29649, -27667};
const s16 dft_cos_table[25] = {32767, 17558, -13952, -32510, -20887, 10126, 31739, 23887, -6140, -30467, -26510, 2058, 28715, 28715, 2058, -26510, -30467, -6140, 23887, 31739, 10126, -20887, -32510, -13952, 17558};
void evaluation(void) {
// Accumulators for dot product
// s21.15 number format
s64 dot_prod_sin[2] = {0};
s64 dot_prod_cos[2] = {0};
// Index for sine and cosine lookup
uint table_index = 0;
// for each sample in sample buffer
for (uint it_sample = 0; it_sample <= 500 - 1; it_sample += 2) {
// First channel
// sample in u12.0 number format
// multiplication result in s13.15 number format
dot_prod_sin[0] += adc_data[it_sample] * dft_sin_table[table_index];
dot_prod_cos[0] += adc_data[it_sample] * dft_cos_table[table_index];
// Second channel
// sample in u12.0 number format
// multiplication result in s13.15 number format
dot_prod_sin[1] += adc_data[it_sample + 1] * dft_sin_table[table_index];
dot_prod_cos[1] += adc_data[it_sample + 1] * dft_cos_table[table_index];
// Increment and wrap table index
table_index += 1;
if (table_index > 24) {
table_index = 0;
}
}
}
The first results looked pretty good and where lots more expressive than my previous solution. The computational complexity was totally acceptable and worked for me.