Analog Devices / Mbed OS EVAL-CN0540-ARDZ

Dependencies:   platform_drivers LTC26X6 AD77681

Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers cn0540_adi_fft.c Source File

cn0540_adi_fft.c

00001 /******************************************************************************
00002  *Copyright (c)2020 Analog Devices, Inc.  
00003  *
00004  * Licensed under the 2020-04-27-CN0540EC License(the "License");
00005  * you may not use this file except in compliance with the License.
00006  *
00007  ****************************************************************************/
00008  
00009 #include "cn0540_adi_fft.h"
00010 #include "cn0540_windowing.h"
00011 #include "stdio.h"
00012 #include "stdlib.h"
00013 #include <math.h>
00014 #include <stdbool.h>
00015 
00016 static arm_cfft_radix4_instance_f32 S;
00017 
00018 /**
00019  * Initialize the FFT structure
00020  * @param **fft_entry_init - the FFT data structure
00021  * @param **fft_meas - the FFT measurements structure
00022  */
00023 int32_t FFT_init_params(struct fft_entry **fft_entry_init, struct fft_measurements **fft_meas)
00024 {
00025     struct fft_entry *fft_data_init;
00026     struct fft_measurements *fft_meas_init;
00027 
00028     fft_data_init = (struct fft_entry *)malloc(sizeof(*fft_data_init));
00029     if (!fft_data_init) {
00030         return -1;
00031     }   
00032     fft_meas_init = (struct fft_measurements *)malloc(sizeof(*fft_meas_init));
00033     if (!fft_meas_init) {
00034         return -1;
00035     }
00036     fft_data_init->window = BLACKMAN_HARRIS_7TERM;
00037     fft_data_init->vref = (float)(4096);
00038     fft_data_init->sample_rate = 32000;
00039     fft_data_init->mclk = 16384;
00040     fft_data_init->fft_length = 4096;
00041     fft_data_init->bin_width = 0.0;
00042     fft_data_init->fft_done = false;
00043     
00044     *fft_entry_init = fft_data_init;
00045     
00046     fft_meas_init->fundamental = 0.0;
00047     fft_meas_init->pk_spurious_noise = 0.0;
00048     fft_meas_init->pk_spurious_freq = 0;    
00049     fft_meas_init->THD = 0.0;
00050     fft_meas_init->SNR = 0.0;
00051     fft_meas_init->DR = 0.0;
00052     fft_meas_init->SINAD = 0.0;
00053     fft_meas_init->SFDR_dbc = 0.0;
00054     fft_meas_init->SFDR_dbfs = 0.0;
00055     fft_meas_init->ENOB = 0.0;
00056     fft_meas_init->RMS_noise = 0.0;
00057     fft_meas_init->average_bin_noise = 0.0;
00058     fft_meas_init->max_amplitude = 0.0;
00059     fft_meas_init->min_amplitude = 0.0; 
00060     fft_meas_init->pk_pk_amplitude = 0.0;
00061     fft_meas_init->DC = 0.0;
00062     fft_meas_init->transition_noise = 0.0;
00063     fft_meas_init->max_amplitude_LSB = 0;
00064     fft_meas_init->min_amplitude_LSB = 0; 
00065     fft_meas_init->pk_pk_amplitude_LSB = 0;
00066     fft_meas_init->DC_LSB = 0;
00067     fft_meas_init->transition_noise_LSB = 0.0;
00068     
00069     for (uint8_t i = 0; i < 7; i++) {
00070         fft_meas_init->harmonics_mag_dbfs[i] = 0.0;
00071         fft_meas_init->harmonics_freq[i] = 0;
00072         fft_meas_init->harmonics_power[i] = 0.0;
00073     }
00074     
00075     *fft_meas = fft_meas_init;
00076     
00077     return 0;
00078 }
00079 
00080 /**
00081  * Initialize the FFT module
00082  * The funciton has to be called, everytime when user wants to change the number of samples
00083  * @param sample_count - desired FFT samples
00084  * @param *fft_data - fft_data structure
00085  */
00086 void FFT_init(uint16_t sample_count, struct fft_entry *fft_data)
00087 {
00088     arm_cfft_radix4_init_f32(&S, sample_count, 0, 1);
00089     fft_data->fft_length = sample_count / 2;
00090 }
00091 
00092 /**
00093  * Update reference voltage and Master clock
00094  * The funciton has to be called, everytime when user wants to change Vref or Mclk  
00095  * @param referemce - The reference voltage in mV
00096  * @param master_clock - The master clock frequnecy in kHz
00097  * @param sampling_rate - The samplingrate frequnecy in kHz
00098  * @param *fft_data - fft_data structure
00099  */
00100 void update_FFT_enviroment(uint16_t reference, uint16_t master_clock, uint16_t sampling_rate, struct fft_entry *fft_data)
00101 {
00102     fft_data->vref = ((float)(reference)) / ((float)(1000.0));          // Convert reference voltage to V
00103     fft_data->mclk = master_clock;                                              // MCLK in kHz
00104     fft_data->sample_rate = sampling_rate;                                      // Sampling rate
00105 }
00106 
00107 
00108 
00109 
00110 /**
00111  * Perform the FFT
00112  * @param *data - pointer to sampled data
00113  * @param *fft_data     -   fft_data structure
00114  * @param *fft_meas     -   fft_meas structure
00115  * @param sample_rate   -   sample rate based on MCLK, MCLK_DIV and Digital filter settings
00116  */
00117 void perform_FFT(uint32_t *data, struct fft_entry *fft_data, struct fft_measurements *fft_meas, uint32_t sample_rate)
00118 {
00119     uint32_t i;
00120     int32_t shifted_data = 0;   
00121     double coeffs_sum = 0.0;
00122     
00123     fft_data->fft_done = false;
00124     fft_data->sample_rate = sample_rate;                                                        // get sample rate
00125     fft_data->bin_width = (float)(fft_data->sample_rate) / ((float)(fft_data->fft_length)*2);   // get bin width
00126     
00127     
00128     //Converting RAW adc data to codes
00129     for(i = 0 ; i < fft_data->fft_length * 2 ; i++) {
00130         if (data[i] & 0x800000)
00131             shifted_data = (int32_t)((0xFF << 24) | data[i]);   
00132         else
00133             shifted_data = (int32_t)((0x00 << 24) | data[i]);   
00134         
00135         fft_data->codes[i] = shifted_data;                                                      // Codes in range 0 +/- ZERO SCALE
00136         fft_data->zero_scale_codes[i] = shifted_data + ADC_ZERO_SCALE;                          // Codes shifted up by ZERO SCALE - range ZERO SCALE +/- ZERO SCALE
00137     }   
00138     // Find max, min, pk-pk amplitude, DC offset, Tranition noise
00139     FFT_waveform_stat(fft_data, fft_meas);  
00140     
00141     // Converting codes without DC offset to "volts" without respect to Vref voltage
00142     for (i = 0; i < fft_data->fft_length * 4; i++) {
00143         // Filling array for FFT, conversion to voltage withour respect to Vref voltage     
00144         fft_data->fft_input[i] = ((((float)(fft_data->codes[i / 2])) / ADC_ZERO_SCALE));
00145                 
00146         // Voltage array with respect to full scale voltage, Vpp
00147         fft_data->voltage[i / 2] = (float)((2*fft_data->vref / ADC_ZERO_SCALE) * fft_data->codes[i / 2]);
00148                 
00149         // Imaginary part
00150         fft_data->fft_input[++i] = 0;
00151     }   
00152     // Apply windowing
00153     FFT_windowing(fft_data, &coeffs_sum);   
00154     //perform FFT, passing the input array, complex FFT values will be stored to the iput array
00155     arm_cfft_radix4_f32(&S, fft_data->fft_input);   
00156     //transform from complex FFT to magnitude
00157     arm_cmplx_mag_f32(fft_data->fft_input, fft_data->fft_magnitude, fft_data->fft_length);  
00158     // Convert FFT magnitude to dB for plot
00159     FFT_maginutde_do_dB(fft_data, coeffs_sum);  
00160     //Calculate THD
00161     FFT_calculate_THD(fft_data, fft_meas);  
00162     // Calculate noise and its parameters from FFT points
00163     FFT_calculate_noise(fft_data, fft_meas);    
00164     // FFT finish flag
00165     fft_data->fft_done = true;
00166 }
00167 
00168 /**
00169  * Windowing function
00170  * 7-term Blackman-Harris and Rectangular widow for now, prepared for more windowing functions if needed
00171  * You can use precalculated coeficients for 4096 sample length
00172  * @param *sample           -   pointer to sample
00173  * @param sample_length     -   2 * FFT_length, because working with samples, not with FFT yet
00174  * @param *sum              -   pointer to sum of all the coeffs
00175  * 
00176  */
00177 void static FFT_windowing(struct fft_entry *fft_data, double *sum)
00178 {
00179     uint8_t j;
00180     uint16_t i;
00181     double term = 0.0;
00182     const double sample_count = (double)((fft_data->fft_length * 2) - 1);
00183     
00184     for (i = 0; i < fft_data->fft_length * 4; i++)
00185     {
00186         switch (fft_data->window) {                                                                 // Switch prepard for other windowing functions
00187         case BLACKMAN_HARRIS_7TERM:     
00188             if (fft_data->fft_length == 2048)                                                   // For 4096 samples, precalculated coefficients are used
00189                 term = Seven_Term_Blackman_Harris_4096[i / 2];          
00190             else {                                                                          // 7-term BH windowing formula 
00191                 for (j = 0; j < 7; j++)
00192                     term += seven_term_BH_coefs[j] * cos((double)((2.0 * PI * j * (i / 2))) / sample_count);
00193             }
00194             break;
00195         case RECTANGULAR:                                                                           // No window, all terms = 1
00196             term = 1;
00197             break;          
00198         default:
00199             break;
00200         }
00201         *sum += term;                                                                               // Getting sum of all terms, which will be used for amplitude correction
00202         fft_data->fft_input[i] *= (float)(term);                                                // Multiplying each (real) sample by windowing term
00203         term = 0.0;
00204         i++;                                                                                        // +1, to consider only real (not imaginary) samples
00205     }
00206 }
00207 
00208 
00209 /**
00210  * Transfer magnitude to dB
00211  * @param *fft_data -   fft_data structure
00212  * @param sum       -   sum of all windowing coeffs
00213  */
00214 void static FFT_maginutde_do_dB(struct fft_entry *fft_data, double sum)
00215 {
00216     uint16_t i;
00217     float correction = 0;
00218     
00219     // Getting sum of coeffs
00220     // If rectangular window is choosen = no windowing, sum of coeffs is number of samples
00221     const float coeff_sum = ((fft_data->fft_length == 2048) && 
00222                             (fft_data->window == BLACKMAN_HARRIS_7TERM)) ? ((float)(Seven_Term_Blackman_Harris_4096_sum)) :
00223                             ((fft_data->window == RECTANGULAR) ? ((float)(fft_data->fft_length * 2.0)) : (float)(sum));
00224     
00225     for (i = 0; i < fft_data->fft_length; i++) {
00226         // Apply a correction factor
00227         // Divide magnigude by a sum of the windowing function coefficients
00228         // Multiple by 2 because of power spread over spectrum below and above the Nyquist frequency
00229         correction = (fft_data->fft_magnitude[i] * 2.0) / coeff_sum;
00230         
00231         // FFT magnitude with windowing correction
00232         fft_data->fft_magnitude_corrected[i] = correction;
00233         
00234         //Convert to dB without respect to Vref
00235         fft_data->fft_dB[i] = 20.0 * (log10f(correction));
00236     }
00237 }
00238 
00239 /**
00240  * THD Calculation with support of harmonics folding to 1-st nyquist zone
00241  * @param *fft_data - fft_data structure
00242  * @param *fft_meas - fft_meas structure
00243  */
00244 void static FFT_calculate_THD(struct fft_entry *fft_data, struct fft_measurements *fft_meas)
00245 {
00246     const uint16_t first_nyquist_zone = fft_data->fft_length;
00247     uint16_t i, j, k = 0, fund_freq = 0, harmonic_position;
00248     int8_t m, nyquist_zone;
00249     float mag_helper = -200.0, freq_helper, sum = 0.0, fund_mag = -200.0; 
00250     float fund_pow_bins[21], harm_pow_bins[5][7];
00251     
00252     // Looking for the fundamental frequency and amplitude
00253     for(i = DC_BINS ; i < fft_data->fft_length ; i++) {                                             // Not counting DC bins
00254         if (fft_data->fft_dB[i] > fund_mag) {
00255             fund_mag = fft_data->fft_dB[i];
00256             fund_freq = i;
00257         }
00258     }
00259     
00260     fft_meas->harmonics_freq[0] = fund_freq;                                                    // Fundamental frequency bin
00261     fft_meas->harmonics_mag_dbfs[0] = fund_mag;                                                 // Fundamental magnitude in dBFS
00262     fft_meas->fundamental = dbfs_to_volts(fft_data->vref, fund_mag);                            // Fundamental magnitude in V
00263 
00264     for(i = 1 ; i < 6 ; i++) {      
00265         if (fft_meas->harmonics_freq[0] * (i + 1) < first_nyquist_zone)                         // Checking if 2nd harmonic is outside of the first nyquist zone
00266             harmonic_position = fft_meas->harmonics_freq[0] * (i + 1);
00267         else {
00268             nyquist_zone = 1 + (fft_meas->harmonics_freq[0] * (i + 1) / first_nyquist_zone);    // Determine the nyquist zone
00269             if(nyquist_zone % 2)                                                                // Odd nyquist zones: 3, 5, 7...
00270                 harmonic_position = first_nyquist_zone - (first_nyquist_zone * nyquist_zone - fft_meas->harmonics_freq[0] * (i + 1));
00271             else                                                                                // Even nyquist zones: 2, 4, 6...
00272                 harmonic_position = first_nyquist_zone * nyquist_zone - fft_meas->harmonics_freq[0] * (i + 1);
00273         }
00274         // Extend searching range by 3 bins around expected position of the harmonic
00275         for(m = -HARM_BINS ; m <= HARM_BINS ; m++) {
00276             if (fft_data->fft_dB[harmonic_position + m] > mag_helper) {
00277                 mag_helper = fft_data->fft_dB[harmonic_position + m];
00278                 freq_helper = (harmonic_position + m);
00279             }   
00280         }
00281         
00282         fft_meas->harmonics_freq[i] = freq_helper;
00283         fft_meas->harmonics_mag_dbfs[i]  = mag_helper;
00284         mag_helper = -200.0;
00285     }   
00286     // Power leakage of the fundamental
00287     for(i = fft_meas->harmonics_freq[0] - FUND_BINS ; i <= fft_meas->harmonics_freq[0] + FUND_BINS ; i++) {
00288         sum += powf(((fft_data->fft_magnitude_corrected[i] / (2.0*SQRT_2))), 2.0);
00289         fund_pow_bins[k] = fft_data->fft_magnitude_corrected[i];
00290         k++;
00291     }       
00292     // Finishing the RSS of power-leaked fundamental
00293     sum = sqrt(sum);
00294     fft_meas->harmonics_power[0] = sum * 2.0 * SQRT_2;
00295     sum = 0.0;
00296     k = 0;  
00297     // Power leakage of the harmonics
00298     for(j = 1 ; j <= 5 ; j++) {
00299         for (i = fft_meas->harmonics_freq[j] - HARM_BINS; i <= fft_meas->harmonics_freq[j] + HARM_BINS; i++) {
00300             sum += powf(((fft_data->fft_magnitude_corrected[i] / (2.0*SQRT_2))), 2.0);
00301             harm_pow_bins[j - 1][k] = fft_data->fft_magnitude_corrected[i];
00302             k++;
00303         }       
00304         // Finishing the RSS of power-leaked harmonics
00305         k = 0;
00306         sum = sqrt(sum);
00307         fft_meas->harmonics_power[j] = sum * 2.0 * SQRT_2;
00308         sum = 0.0;
00309     }   
00310     // The THD formula
00311     fft_meas->THD = sqrtf(powf(fft_meas->harmonics_power[1], 2.0) + powf(fft_meas->harmonics_power[2], 2.0) + powf(fft_meas->harmonics_power[3], 2.0) + powf(fft_meas->harmonics_power[4], 2.0) + powf(fft_meas->harmonics_power[5], 2.0)) / fft_meas->harmonics_power[0];
00312     // Back from volts to dB
00313     fft_meas->THD = 20.0 * log10f(fft_meas->THD);
00314 }
00315 
00316 /**
00317  * Calculate amplitudes: min, max, pk-pk amplitude and DC part
00318  * @param *fft_data - fft_data structure
00319  * @param *fft_meas - fft_meas structure
00320  */
00321 void static FFT_waveform_stat(struct fft_entry *fft_data, struct fft_measurements *fft_meas)
00322 {
00323     uint16_t i;
00324     int16_t max_position, min_position;
00325     int32_t max = -ADC_ZERO_SCALE, min = ADC_ZERO_SCALE, offset_correction;
00326     int64_t sum = 0;
00327     double deviation = 0.0, mean;
00328     
00329     // summ of all coeffs, to find the Mean value
00330     for(i = 0; i < fft_data->fft_length * 2; i++)
00331         sum += fft_data->codes[i];
00332     
00333     // Calculating mean value = DC offset
00334     mean = (sum / (fft_data->fft_length * 2));
00335     
00336     // DC part in LSBs
00337     fft_meas->DC_LSB = (int32_t)(mean) + ADC_ZERO_SCALE;
00338     offset_correction = (int32_t)(mean);
00339     
00340     // Min, Max amplitudes + Deviation
00341     for (i = 0; i < fft_data->fft_length * 2; i++) {                        // Working with codes = fft_length * 2
00342         // Calculating the Deviation for Transition noise
00343         deviation += pow(fft_data->codes[i] - mean, 2.0);
00344         
00345         // Looking for MAX value
00346         if (fft_data->codes[i] > max) {
00347             max = fft_data->codes[i];
00348             max_position = i;
00349         }
00350         // Looking for MIN value
00351         if (fft_data->codes[i] < min) {
00352             min = fft_data->codes[i];
00353             min_position = i;
00354         }   
00355     }   
00356     // Amplitudes in Volts
00357     fft_meas->max_amplitude = (2.0 * fft_data->vref * fft_data->codes[max_position]) / ADC_FULL_SCALE; 
00358     fft_meas->min_amplitude = (2.0 * fft_data->vref * fft_data->codes[min_position]) / ADC_FULL_SCALE; 
00359     fft_meas->pk_pk_amplitude = fft_meas->max_amplitude - fft_meas->min_amplitude;
00360     fft_meas->DC = (2.0 * fft_data->vref * ((float)(((int32_t)(fft_meas->DC_LSB) - ADC_ZERO_SCALE)))) / ADC_FULL_SCALE;
00361     
00362     // Amplitudes in LSBs
00363     fft_meas->max_amplitude_LSB = fft_data->codes[max_position] + ADC_ZERO_SCALE;
00364     fft_meas->min_amplitude_LSB = fft_data->codes[min_position] + ADC_ZERO_SCALE;
00365     fft_meas->pk_pk_amplitude_LSB = fft_meas->max_amplitude_LSB - fft_meas->min_amplitude_LSB;
00366     
00367     // Transition noise
00368     deviation = (sqrt(deviation / (fft_data->fft_length * 2.0)));
00369     fft_meas->transition_noise_LSB = (uint32_t)(deviation);
00370     fft_meas->transition_noise = (2.0 * fft_data->vref * fft_meas->transition_noise_LSB) / ADC_FULL_SCALE;
00371     
00372     // RMS noise
00373     fft_meas->RMS_noise = fft_meas->transition_noise;
00374     
00375     // Applying mean value to each sample = removing DC offset
00376     for(i = 0 ; i < fft_data->fft_length * 2 ; i++)
00377         fft_data->codes[i] -= offset_correction;
00378     
00379 }
00380 
00381 /**
00382  * Calculate the RMS noise from the FFT plot
00383  * @param *fft_data - fft_data structure
00384  * @param *fft_meas - fft_meas structure
00385  */
00386 void static FFT_calculate_noise(struct fft_entry *fft_data, struct fft_measurements *fft_meas)
00387 {
00388     const float LW_DR_correction_const = 4.48;                                                                      // Magic constant from the LabView FFT core correcting only dynamic range
00389     uint16_t i, j;
00390     float biggest_spur = -300;
00391     double RSS = 0.0, mean = 0.0;
00392     
00393     // Initalizing pk_spurious variables
00394     fft_meas->pk_spurious_noise = -200.0;
00395     fft_meas->pk_spurious_freq = 0;
00396     
00397     for (i = 0; i < DC_BINS; i++)                                                                                       // Ignoring DC bins
00398         fft_data->noise_bins[i] = 0.0;
00399     for (i = DC_BINS; i < fft_data->fft_length; i++) {
00400         // Ignoring spread near the fundamental
00401         if ((i <= fft_meas->harmonics_freq[0] + FUND_BINS) && (i >= fft_meas->harmonics_freq[0] - FUND_BINS))           
00402             fft_data->noise_bins[i] = 0.0;
00403             
00404         else if((i <= fft_meas->harmonics_freq[1] + HARM_BINS) && (i >= fft_meas->harmonics_freq[1] - HARM_BINS))       // Ignoring spread near harmonics
00405             fft_data->noise_bins[i] = 0.0;
00406         
00407         else if((i <= fft_meas->harmonics_freq[2] + HARM_BINS) && (i >= fft_meas->harmonics_freq[2] - HARM_BINS))
00408             fft_data->noise_bins[i] = 0.0;
00409 
00410         else if((i <= fft_meas->harmonics_freq[3] + HARM_BINS) && (i >= fft_meas->harmonics_freq[3] - HARM_BINS))
00411             fft_data->noise_bins[i] = 0.0;
00412         
00413         else if((i <= fft_meas->harmonics_freq[4] + HARM_BINS) && (i >= fft_meas->harmonics_freq[4] - HARM_BINS))
00414             fft_data->noise_bins[i] = 0.0;
00415 
00416         else if((i <= fft_meas->harmonics_freq[5] + HARM_BINS) && (i >= fft_meas->harmonics_freq[5] - HARM_BINS))
00417             fft_data->noise_bins[i] = 0.0;
00418 
00419         else {      
00420             // Root Sum Square = RSS for noise calculations
00421             fft_data->noise_bins[i] = fft_data->fft_magnitude_corrected[i];
00422             RSS += pow(((double)(fft_data->fft_magnitude_corrected[i] / (2.0*SQRT_2))), 2.0);
00423             
00424             // Average bin noise
00425             mean += fft_data->fft_magnitude_corrected[i];
00426             
00427             // Peak spurious amplitude
00428             if(fft_data->fft_magnitude_corrected[i] > fft_meas->pk_spurious_noise) {
00429                 fft_meas->pk_spurious_noise = fft_data->fft_magnitude_corrected[i];
00430                 fft_meas->pk_spurious_freq = i;
00431             }
00432             
00433         }   
00434     }   
00435     mean /= (double)(fft_data->fft_length);
00436     
00437     // RSS of FFT spectrum without DC, Fund. and Harmonics
00438     RSS = sqrt(RSS);
00439     RSS = RSS * 2.0 * SQRT_2;
00440     
00441     // Peak spurious amplitude = Highest amplitude excluding DC, the Fundamental and the Harmonics
00442     fft_meas->pk_spurious_noise = 20.0 * log10f(1.0 / fft_meas->pk_spurious_noise);
00443     
00444     // Looking for the biggest spur among harmonics
00445     for(i = 1 ; i < 6 ; i++) {
00446         if (fft_meas->harmonics_mag_dbfs[i] > biggest_spur)
00447             biggest_spur = fft_meas->harmonics_mag_dbfs[i]; 
00448     }   
00449     // Looking for the biggest spur among harmonics and pk_spurious_noise
00450     if(biggest_spur > fft_meas->pk_spurious_noise)
00451         biggest_spur = fft_meas->pk_spurious_noise;
00452     
00453     // Spurious Free Dynamic Range SFDR related to the carrer = biggest spur - the Fundamental, [dBc] - Decibels related to the carrier
00454     fft_meas->SFDR_dbc = biggest_spur - fft_meas->harmonics_mag_dbfs[0];
00455 
00456     // Spurious Free Dynamic Range SFDR related to the full-scale = biggest spur - full-scale [dBFS], where full-scale is 0 dBFS
00457     fft_meas->SFDR_dbfs = biggest_spur;
00458     
00459     // Average bin noise = Mean value of FFT spectrum excluding DC, the Fundamental and the Harmonics
00460     fft_meas->average_bin_noise = (float)(20.0 * log10(mean));
00461     
00462     // DR = 1 / RSS of FFT spectrum without DC, Fund. and Harmonics + Magic constant from the Labview FFT core
00463     fft_meas->DR = (20.0 * log10f(1.0 / (float)(RSS))) + LW_DR_correction_const;
00464     
00465     // SNR = Power of the fundamental / RSS of FFT spectrum without DC, Fund. and Harmonics
00466     fft_meas->SNR = 20.0 * log10f((fft_meas->harmonics_power[0]) / (RSS));
00467     
00468     // SINAD
00469     fft_meas->SINAD = -10.0 * log10f(powf(10.0, (fabs(fft_meas->SNR))*(-1.0) / 10.0) + powf(10.0, fabs(fft_meas->THD)*(-1.0) / 10.0));
00470     
00471     // ENOB - Effective number of bits
00472     fft_meas->ENOB = (fft_meas->SINAD - 1.67 + fabs(fft_meas->harmonics_mag_dbfs[0])) / 6.02; 
00473 }
00474 
00475 /**
00476  * Convert dBFS to volts in Pk-Pk
00477  * @param vref - reference voltage in volts
00478  * @param *fft_meas - fft_meas structure
00479  */
00480 float static dbfs_to_volts(float vref, float value)
00481 {
00482     return ( 2 * vref * powf(10.0, value / 20.0) );
00483 }