Sorry for the lengthy write-up, want to give as much info as possible so you have the info needed to advise. Not sure, if this should go under "projects" because it involves programming and hardware and decision making on further development. But would like to ask with the most acute problem I have now: 60hz (mains) interference with FFT reading on laptop USB Serial port.
I get good FFT values, but the first few FFT bins readings (0Hz-100Hz) are killing the rest of the FFT string due to 60 Hz interference leaking in to the input signal. If I exclude the first few bins between 0 HZ to around 100 Hz and then going down the FFT string, I can identify/match the correct osope-measured frequency as the next highest magnitude FFT reading value.
The project will use batteries, but for now while developing I am using my laptop's USB port for power and USB serial input, an an oscope. The measured circuit and the laptop and the oscope inputs are all on a common ground and I have the wires as short as possible at this stage.
Based on my "research", the best solution is to use hardware (i.e. RC) to condition the ADC input signal for the desired (high and low) bandpass. The next best option is to pad the "offending" bins with mean FFT readings, and the last resort is padding them with zeros.
So first, a hardware high-pass filter question: what R and C values should I use on the Seed XIAO 52840:
- Nordic nRF52840, ARM® Cortex®-M4 32-bit processor with FPU, 64 MHz
- ADC sampling with input impedance between 6M Ohm-10M Ohm, max. sampling rate 250 kHhz capability, 12 bits max, set at 2.4V Internal Reference
The input signal is:
- active op am amplified filtered AC sine wave (DC removed) between 0-2.4V, resting at 0-60mV, FFT reading triggered above 1V and then the measured input signal length period is between 40us and 1ms at a relatively narrow frequency band that is located anywhere between 500hz to 25Khz.
- do not know how many mA's on the input line, but could measure if necessary
In my case, I would like to measure FFT above 500Hz and below 25kHz (in the most desirable case 500Hz-100Khz).
For the high-pass RC filter, want to read only above 500 Hz, so I calculated: 1K for R and 300nF for C. I am wondering if the 1K resistance would weaken the input signal too much?
As far as a possible programming solution: I am using a slightly modified ArduinoFFT library on this XIAO nrf52840 in mbed mode. I set an FFT-measurement start trigger threshold at 1V input and the ADC is set at 12-bit and 2.4V Internal REF.
Sampling rate: 50kHz Sample Size: 4096
With lower sample sizes like 64. 128. 256 the actual signal freq is detected by the FFT, but with higher ones like 1024 and 2048 and 4096 progressively I get the 60 Hz interference creeping in. I tried to change the 1.0 variable in the "sampling_period_us" values set at (1000000*(1.0/samplingFrequency)). But with much higher values of the 1.0 (longer sampling period) the FFT stops reading. I suspect that something here also leads to m second concern, that the FFT values read (when it works) are about 2.2 times consistently the actual freq measure with the oscope. For example:
actual input freq: 2160 Hz measured FFT: 4812 Hz
actual input freq: 2890 Hz measured FFT: 6360 Hz
(50kHz sampling rate, 2048 sample size, 12-bit)
Here is an example of the input signal burst captured by the oscope
//https://github.com/kosme/arduinoFFT/blob/master/Examples/FFT_speedup/FFT_speedup.ino
#include "arduinoFFT.h"
#include <Arduino.h>
/*
These values can be changed in order to evaluate the functions
*/
#define CHANNEL A0 //default A0
const uint16_t samples = 4096; //4096???
const float samplingFrequency = 50000; //Hz, must be less than 10000 due to ADC (AVR only???) Nrf52 ADC sampling can go as high as 35 khz or more
unsigned int sampling_period_us;
unsigned long microseconds;
/*
These are the input and output vectors
Input vectors receive computed results from FFT
*/
float vReal[samples];
float vImag[samples];
/* Create FFT object with weighing factor storage */
ArduinoFFT<float> FFT = ArduinoFFT<float>(vReal, vImag, samples, samplingFrequency, true);
#define SCL_INDEX 0x00
#define SCL_TIME 0x01
#define SCL_FREQUENCY 0x02
#define SCL_PLOT 0x03
void setup() {
// Set the resolution to 12-bit (0..4095)
analogReadResolution(12); // Can be 8, 10, 12 or 14
// Set the analog reference to 2.4V (default = 3.6V)
analogReference(AR_INTERNAL2V4); //signal threshold on oscope is 1.2V
/*
AR_VDD: the default 3.3 V reference
AR_INTERNAL: built-in 0.6 V reference
AR_INTERNAL1V2: 1.2 V reference (internal 0.6 V reference with 2x gain)
AR_INTERNAL2V4: 2.4 V reference (internal 0.6 V reference with 4x gain)
*/
sampling_period_us = round(1000000 * (1.0 / samplingFrequency)); //1 million -> 1 sec
Serial.begin(115200);
}
void loop() {
float y = 1; // analog in reading value multiplier
if ((analogRead(CHANNEL) * y) > 1000) { // starts a sampling period if exceed inout signal threshold
/*SAMPLING*/
microseconds = micros(); //start timer
for (int i = 5; i < samples; i++) { //sampling into real and img arrays
vReal[i] = (analogRead(CHANNEL) * y); //
vImag[i] = 0;
while (micros() - microseconds < sampling_period_us) { // sampling for ex: 1M/(1.0*256) = 28.5us period of time ,
//empty loop
}
microseconds += sampling_period_us;
}
/* Print the results of the sampling according to time */
// Serial.println("Data:");
//PrintVector(vReal, samples, SCL_TIME);
FFT.windowing(FFTWindow::Hamming, FFTDirection::Forward); /* Weigh data */
// Serial.println("Weighed data:");
// PrintVector(vReal, samples, SCL_TIME);
FFT.compute(FFTDirection::Forward); /* Compute FFT */
// Serial.println("Computed Real values:");
// PrintVector(vReal, samples, SCL_INDEX);
// Serial.println("Computed Imaginary values:");
//PrintVector(vImag, samples, SCL_INDEX);
FFT.complexToMagnitude(); /* Compute magnitudes */
Serial.println("Computed magnitudes:");
PrintVector(vReal, (samples >> 1), SCL_FREQUENCY);
float x = FFT.majorPeak();
Serial.print(" Dominant Hz ");
Serial.println(x, 2); //Print out what frequency is the most dominant. //high cutoff 1khz
//while(1); /* Run Once */
//delay(500); /* Repeat after delay */
} // if abalogRead() loop
}
void PrintVector(float *vData, uint16_t bufferSize, uint8_t scaleType) {
for (uint16_t i = 0; i < bufferSize; i++) {
float abscissa;
// Print abscissa value
switch (scaleType) {
case SCL_INDEX:
abscissa = (i * 1.0);
break;
case SCL_TIME:
abscissa = ((i * 1.0) / samplingFrequency);
break;
case SCL_FREQUENCY:
abscissa = ((i * 1.0 * samplingFrequency) / samples);
break;
}
Serial.print(abscissa, 6); //num of digits to print /default 6
if (scaleType == SCL_FREQUENCY)
Serial.print(" Hz");
Serial.print(" ");
Serial.println(vData[i], 4);
}
Serial.println();
}