I'll try to avoid the pitfalls and failings of other people posting questions about ECG/EKG projects! Thus, this amount of data may be overkill.
This is based on this project: DIY ECG with 1 op-amp
I'll go in to more detail below, but, what I'm trying to figure out is how to:
- Get a clean plot of the heartbeat in at least the serial plotter as demonstrated in the code running in python on my PC
- Detect the highest point of the heartbeat in order to count beats per minute (Reach goal!)
First, the circuit:
As you can see, it's very similar to the one used in the SWHarden example:
But instead of a headphone jack, I'm plugging it into A0 on an Arduino Uno, and connecting ground to.... well, ground!
It's still powered off of a 9 volt because I'd rather not be electrocuted. (please excuse the messy Fritzing, I had to download it and mess with it for the first time, for this question)
"How do you know the circuit you're using is working?"
Because when I run SWHarden's code on my computer, it generates this:
(P.S. Notice the 49hz low pass filter there)
My first attempt to just take a look at what was coming out of the circuit on my arduino was this code:
#include <FilterOnePole.h>
float filterFrequency =49.0 ;
// create a one pole (RC) lowpass filter
FilterOnePole lowpassFilter( LOWPASS, filterFrequency );
void setup() {
Serial.begin(115200);
}
// the loop routine runs over and over again forever:
void loop() {
// read the input on analog pin 0:
//int sensorValue = analogRead(A0);
int sensorValue = lowpassFilter.input( analogRead(A0) );
// print out the value you read:
// Serial.println(sensorValue);
// about 256Hz sample rate
//delayMicroseconds(3900);
//delayMicroseconds(125);
Serial.println(sensorValue);
}
That low pass filter is me trying to deal with house current interference.
On the serial plotter:
notice that dipped bit there? It was consistent with my heartbeat, so something is getting through.
Now, bear with me here. This is the important part of SWHarden's Python code, I think:
def FFT(data,rate):
"""given some data points and a rate, return [freq,power]"""
data=data*np.hamming(len(data))
fft=np.fft.fft(data)
fft=10*np.log10(np.abs(fft))
freq=np.fft.fftfreq(len(fft),1/rate)
return freq[:int(len(freq)/2)],fft[:int(len(fft)/2)]
def softEdges(self,data,fracEdge=.05):
"""multiple edges by a ramp of a certain percentage."""
rampSize=int(len(data)*fracEdge)
mult = np.ones(len(data))
window=np.hanning(rampSize*2)
mult[:rampSize]=window[:rampSize]
mult[-rampSize:]=window[-rampSize:]
return data*mult
def getFiltered(self,freqHighCutoff=50):
if freqHighCutoff<=0:
return self.data
fft=np.fft.fft(self.softEdges(self.data)) #todo: filter needed?
trim=len(fft)/self.rate*freqHighCutoff
fft[int(trim):-int(trim)]=0
return np.real(np.fft.ifft(fft))
It appears they're defining a function to use FFT to find the dominant signal? Given a sampling rate?
And then they're.... softening the edges? That's starting to get well outside my knowledge.
I tried to get something rolling with example FFT Code for arduino but it's more or less a disaster.
#include "arduinoFFT.h"
#define SAMPLES 128 //Must be a power of 2
#define SAMPLING_FREQUENCY 44100 //Hz, must be less than 10000 due to ADC
//#define SAMPLING_FREQUENCY 256 //Hz, must be less than 10000 due to ADC
#include <FilterOnePole.h>
arduinoFFT FFT = arduinoFFT();
float filterFrequency = 49.0 ;
// create a one pole (RC) lowpass filter
FilterOnePole lowpassFilter( LOWPASS, filterFrequency );
unsigned int sampling_period_us;
unsigned long microseconds;
double vReal[SAMPLES];
double vImag[SAMPLES];
void setup() {
Serial.begin(115200);
pinMode(LED_BUILTIN, OUTPUT);
sampling_period_us = round(1000000 * (1.0 / SAMPLING_FREQUENCY));
digitalWrite(LED_BUILTIN, LOW);
}
void loop() {
/*SAMPLING*/
for (int i = 0; i < SAMPLES; i++)
{
microseconds = micros(); //Overflows after around 70 minutes!
//vReal[i] = analogRead(0);
vReal[i] = lowpassFilter.input(analogRead(A0));
vImag[i] = 0;
while (micros() < (microseconds + sampling_period_us)) {
}
}
/*FFT*/
FFT.Windowing(vReal, SAMPLES, FFT_WIN_TYP_HAMMING, FFT_FORWARD);
FFT.Compute(vReal, vImag, SAMPLES, FFT_FORWARD);
FFT.ComplexToMagnitude(vReal, vImag, SAMPLES);
double peak = FFT.MajorPeak(vReal, SAMPLES, SAMPLING_FREQUENCY);
/*PRINT RESULTS*/
Serial.println(peak); //Print out what frequency is the most dominant.
for (int i = 0; i < (SAMPLES / 2); i++)
{
/*View all these three lines in serial terminal to see which frequencies has which amplitudes*/
// Serial.println((i * 1.0 * SAMPLING_FREQUENCY) / SAMPLES, 1);
// Serial.print(" ");
// Serial.println(vReal[i], 1); //View only this line in serial plotter to visualize the bins
}
delayMicroseconds(125);
}
That code in the plotter does this:
Those totally empty spots are where I unplugged stuff to make sure it was "sensing" something at all.
Okay, with all of that madness behind us, what I'm trying to figure out the meaning of the FFT code SWharden has there.
Am I sampling at the wrong rate? Do I need to set my arduino's processor to a particular speed so I can sample appropriately? I have a feeling this is some kind of mismatch between frequency and sampling but I could be totally wrong. I suppose I'm looking for a direction to go with my research. Why do I need FFT in order to get that clean EKG signal?
Any help is appreciated, thanks! I'll do my best to do my own research but I've read through a lot of failed threads, and a lot of other code (mostly based on the LED based heartbeat modules) and none of it seemed to lead me in the correct direction.
Thanks for sticking with me through all of that, and again, thanks for any help!