Simple DIY ECG + Pulse Oximeter (version 2)

Of the hundreds of projects I’ve shared over the years, none has attracted more attention than my DIY ECG machine on the cheap posted almost 4 years ago. This weekend I re-visited the project and made something I’m excited to share!  The original project was immensely popular, my first featured article on Hack-A-Day, and today “ECG” still represents the second most searched term by people who land on my site. My gmail account also has had 194 incoming emails from people asking details about the project. A lot of it was by frustrated students trying to recreate the project running into trouble because it was somewhat poorly documented. Clearly, it’s a project that a wide range of people are interested in, and I’m happy to revisit it bringing new knowledge and insight to the project. I will do my best to document it thoroughly so anyone can recreate it!

The goal of this project is to collect heartbeat information on a computer with minimal cost and minimal complexity.  I accomplished this with fewer than a dozen components (all of which can be purchased at RadioShack). It serves both as a light-based heartbeat monitor (similar to a pulse oximeter, though it’s not designed to quantitatively measure blood oxygen saturation), and an electrocardiogram (ECG) to visualize electrical activity generated by heart while it contracts. Let’s jump right to the good part – this is what comes out of the machine:

That’s my actual heartbeat. Cool, right? Before I go into how the circuit works, let’s touch on how we measure heartbeat with ECG vs. light (like a pulse oximeter).  To form a heartbeat, the pacemaker region of the heart (called the SA node, which is near the upper right of the heart) begins to fire and the atria (the two top chambers of the heart) contract. The SA node generates a little electrical shock which stimulated a synchronized contraction. This is exactly what defibrillators do when a heart has stopped beating. When a heart attack is occurring and a patient is undergoing ventricular fibrillation, it means that heart muscle cells are contracting randomly and not in unison, so the heart quivers instead of pumping as an organ. Defibrillators synchronize the heart beat with a sudden rush of current over the heart to reset all of the cells to begin firing at the same time (thanks Ron for requesting a more technical description).  If a current is run over the muscle, the cells (cardiomyocytes) all contract at the same time, and blood moves. The AV node (closer to the center of the heart) in combination with a slow conducting pathway (called the bundle of His) control contraction of the ventricles (the really large chambers at the bottom of the heart), which produce the really large spikes we see on an ECG.  To measure ECG, optimally we’d place electrodes on the surface of the heart. Since that would be painful, we do the best we can by measuring voltage changes (often in the mV range) on the surface of the skin. If we amplify it enough, we can visualize it. Depending on where the pads are placed, we can see different regions of the heart contract by their unique electrophysiological signature. ECG requires sticky pads on your chest and is extremely sensitive to small fluctuations in voltage. Alternatively, a pulse oximeter measures blood oxygenation and can monitor heartbeat by clipping onto a finger tip. It does this by shining light through your finger and measuring how much light is absorbed. This goes up and down as blood is pumped through your finger. If you look at the relationship between absorbency in the red vs. infrared wavelengths, you can infer the oxygenation state of the blood. I’m not doing that today because I’m mostly interested in detecting heart beats.

For operation as a pulse oximeter-type optical heartbeat detector (a photoplethysmograph which produces a photoplethysmogram), I use a bright red LED to shine light through my finger and be detected by a phototransistor (bottom left of the diagram). I talk about how this works in more detail in a previous post. Basically the phototransistor acts like a variable resistor which conducts different amounts of current depending on how much light it sees. This changes the voltage above it in a way that changes with heartbeats. If this small signal is used as the input, this device acts like a pulse oximeter.

For operation as an electrocardiograph (ECG), I attach the (in) directly to a lead on my chest. One of them is grounded (it doesn’t matter which for this circuit – if they’re switched the ECG just looks upside down), and the other is recording. In my original article, I used pennies with wires soldered to them taped to my chest as leads. Today, I’m using fancier sticky pads which are a little more conductive. In either case, one lead goes in the center of your chest, and the other goes to your left side under your arm pit. I like these sticky pads because they stick to my skin better than pennies taped on with electrical tape. I got 100 Nikomed Nikotabs EKG Electrodes 0315 on eBay for $5.51 with free shipping (score!). Just gator clip to them and you’re good to go!

In both cases, I need to build a device to amplify small signals. This is accomplished with the following circuit. The core of the circuit is an LM324 quad operational amplifier.  These chips are everywhere, and extremely cheap. It looks like Thai Shine sells 10 for $2.86 (with free shipping). That’s about a quarter each. Nice!  A lot of ECG projects use instrumentation amplifiers like the AD620 (which I have used with fantastic results), but these are expensive (about $5.00 each). The main difference is that instrumentation amplifiers amplify the difference between two points (which reduces noise and probably makes for a better ECG machine), but for today an operational amplifier will do a good enough job amplifying a small signal with respect to ground. I get around the noise issue by some simple filtering techniques. Let’s take a look at the circuit.

This project utilizes one of the op-amps as a virtual ground. One complaint of using op-amps in simple projects is that they often need + and – voltages. Yeah, this could be done with two 9V batteries to generate +9V and -9V, but I think it’s easier to use a single power source (+ and GND). A way to get around that is to use one of the op-amps as a current source and feed it half of the power supply voltage (VCC), and use the output as a virtual ground (allowing VCC to be your + and 0V GND to be your -). For a good description of how to do this intelligently, read the single supply op amps web page. The caveat is that your signals should remain around VCC/2, which can be done if it is decoupled by feeding it through a series capacitor. The project works at 12V or 5V, but was designed for (and has much better output) at 12V. The remaining 3 op-amps of the LM324 serve three unique functions:

STAGE 1: High gain amplifier. The input signals from either the ECG or pulse oximeter are fed into a chain of 3 opamp stages. The first is a preamplifier. The output is decoupled through a series capacitor to place it near VCC/2, and amplified greatly thanks to the 1.8Mohm negative feedback resistor. Changing this value changes initial gain.

STAGE 2: active low-pass filter. The 10kOhm variable resistor lets you adjust the frequency cutoff. The opamp serves as a unity gain current source / voltage follower that has high input impedance when measuring the output f the low-pass filter and reproduces its voltage with a low impedance output. There’s some more information about active filtering on this page. It’s best to look at the output of this stage and adjust the potentiometer until the 60Hz noise (caused by the AC wiring in the walls) is most reduced while the lower-frequency component of your heartbeat is retained. With the oximeter, virtually no noise gets through. Because the ECG signal is much smaller, this filter has to be less aggressive, and this noise is filtered-out by software (more on this later).

STAGE 3: final amplifier with low-pass filter. It has a gain of ~20 (determined by the ratio of the 1.8kOhm to 100Ohm resistors) and lowpass filtering components are provided by the 22uF capacitor across the negative feedback resistor. If you try to run this circuit at 5V and want more gain (more voltage swing), consider increasing the value of the 1.8kOhm resistor (wit the capacitor removed). Once you have a good gain, add different capacitor values until your signal is left but the noise reduced. For 12V, these values work fine. Let’s see it in action!

Now for the second half – getting it into the computer. The cheapest and easiest way to do this is to simply feed the output into a sound card! A sound card is an analog-to-digital converter (ADC) that everybody has and can sample up to 48 thousand samples a second! (overkill for this application) The first thing you should do is add an output potentiometer to allow you to drop the voltage down if it’s too big for the sound card (in the case of the oximeter) but but also allow full-volume in the case of sensitive measurements (like ECG). Then open-up sound editing software (I like GoldWave for Windows or Audacity for Linux, both of which are free) and record the input. You can do filtering (low-pass filter at 40Hz with a sharp cutoff) to further eliminate any noise that may have sneaked through. Re-sample at 1,000 Hz (1kHz) and save the output as a text file and you’re ready to graph it! Check it out.

Here are the results of some actual data recorded and processed with the method shown in the video. let’s look at the pulse oximeter first.

That looks pretty good, certainly enough for heartbeat detection. There’s obvious room for improvement, but as a proof of concept it’s clearly working. Let’s switch gears and look at the ECG. It’s much more challenging because it’s signal is a couple orders of magnitude smaller than the pulse oximeter, so a lot more noise gets through. Filtering it out offers dramatic improvements!

Here’s the code I used to generate the graphs from the text files that GoldWave saves. It requires Python, Matplotlib (pylab), and Numpy. In my case, I’m using 32-bit 2.6 versions of everything.

# DIY Sound Card ECG/Pulse Oximeter
# by Scott Harden (2013) http://www.SWHarden.com

import pylab
import numpy

f=open("light.txt")
raw=f.readlines()[1:]
f.close()

data = numpy.array(raw,dtype=float)
data = data-min(data) #make all points positive
data = data/max(data)*100.0 #normalize
times = numpy.array(range(len(data)))/1000.0
pylab.figure(figsize=(15,5))
pylab.plot(times,data)
pylab.xlabel("Time Elapsed (seconds)")
pylab.ylabel("Amplitude (% max)")
pylab.title("Pulse Oximeter - filtered")
pylab.subplots_adjust(left=.05,right=.98)
pylab.show()

Future directions involve several projects I hope to work on soon. First, it would be cool to miniaturize everything with surface mount technology (SMT) to bring these things down to the size of a postage stamp. Second, improved finger, toe, or ear clips (or even taped-on sensors) over long duration would provide a pretty interesting way to analyze heart rate variability or modulation in response to stress, sleep apnea, etc. Instead of feeding the signal into a computer, one could send it to a micro-controller for processing. I’ve made some darn-good progress making multi-channel cross-platform USB option for getting physiology data into a computer, but have some work still to do. Alternatively, this data could be graphed on a graphical LCD for an all-in-one little device that doesn’t require a computer. Yep, lots of possible projects can use this as a starting point.

Notes about safety: If you’re worried about electrical shock, or unsure of your ability to make a safe device, don’t attempt to build an ECG machine. For an ECG to work, you have to make good electrical contact with your skin near your heart, and some people feel this is potentially dangerous. Actually, some people like to argue about how dangerous it actually is, as seen on Hack-A-Day comments and my previous post comments. Some people have suggested the danger is negligible and pointed-out that it’s similar to inserting ear-bud headphones into your ears. Others have suggested that it’s dangerous and pointed-out that milliamps can kill a person. Others contest that pulses of current are far more dangerous than a continuous applied current. Realists speculate that virtually no current would be delivered by this circuit if it is wired properly. Rational, cautionary people worried about it reduce risk of accidental current by applying bidirectional diodes at the level of the chest leads, which short any current (above 0.7V) similar to that shown here. Electrically-savvy folks would design an optically decoupled solution. Intelligent folks who abstain from arguing on the internet would probably consult the datasheets regarding ECG input protection. In all cases, don’t attach electrical devices to your body unless you are confident in their safety. As a catch-all, I present the ECG circuit for educational purposes only, and state that it may not be safe and should not be replicated  There, will that cover me in court in case someone tapes wires to their chest and plugs them in the wall socket?

LET ME KNOW WHAT YOU THINK! If you make this, I’m especially interested to see how it came out. Take pictures of your projects and send them my way! If you make improvements, or take this project further, I’d be happy to link to it on this page. I hope this page describes the project well enough that anyone can recreate it, regardless of electronics experience. Finally, I hope that people are inspired by the cool things that can be done with surprisingly simple electronics. Get out there, be creative, and go build something cool!



Single Wavelength Pulse Oximeter

I want to create a microcontroller application which will utilize information obtained from a home-brew pulse oximeter. Everybody and their cousin seems to have their own slant how to make DIY pulse detectors, but I might as well share my experience. Traditionally, pulse oximeters calculate blood oxygen saturation by comparing absorbance of blood to different wavelengths of light. In the graph below (from Dildy et al., 1996 that deoxygenated blood (dark line) absorbs light differently than oxygenated blood (thin line), especially at 660nm (red) and 920nm (infrared). Therefore, the ratio of the difference of absorption at 660nm vs 920nm is an indication of blood oxygenation. Fancy (or at least well-designed) pulse oximeters continuously look at the ratio of these two wavelengths. Analog devices has a nice pulse oximeter design using an ADuC7024 microconverter. A more hackerish version was made and described on this non-english forum. A fail-at-the-end page of a simpler project is also shown here, but not well documented IMO.

That’s not how mine works. I only use a single illumination source (~660nm) and watch it change with respect to time. Variability is due to a recombination effect of blood volume changes and blood oxygen saturation changes as blood pulses through my finger. Although it’s not quite as good, it’s a bit simpler, and it definitely works. Embedded-lab has a similar project but the output is only a pulsing LED (not what I want) and a voltage output that only varies by a few mV (not what I want).

Here’s what the device looks like assembled in a breadboard:

I made a sensor by drilling appropriately-sized holes in a clothespin for the emitter (LED) and sensor (phototransistor). I had to bend the metal spring to make it more comfortable to wear. Light pressure is better than firm pressure, not only because it doesn’t hurt as much, but because a firm pinch restricts blood flow considerably.

An obvious next step is microcontroller + LCD (or computer) digitization, but for now all you can do is check it out on my old-school analog oscilloscope. Vertical squares represent 1V (nice!). You can see the pulse provides a solid 2V spike.

Here’s some video of it in action:

Out of principal, I’m holding-back the circuit diagram until I work through it a little more. I don’t want to mislead people by having them re-create ill-conceived ideas on how to create analog amplifiers. I’ll post more as I develop it.



Multichannel USB Analog Sensor with ATMega48

Sometimes it’s tempting to re-invent the wheel to make a device function exactly the way you want. I am re-visiting the field of homemade electrophysiology equipment, and although I’ve already published a home made electocardiograph (ECG), I wish to revisit that project and make it much more elegant, while also planning for a pulse oximeter, an electroencephalograph (EEG), and an electrogastrogram (EGG). This project is divided into 3 major components: the low-noise microvoltage amplifier, a digital analog to digital converter with PC connectivity, and software to display and analyze the traces. My first challenge is to create that middle step, a device to read voltage (from 0-5V) and send this data to a computer.

This project demonstrates a simple solution for the frustrating problem of sending data from a microcontroller to a PC with a USB connection. My solution utilizes a USB FTDI serial-to-usb cable, allowing me to simply put header pins on my device which I can plug into providing the microcontroller-computer link. This avoids the need for soldering surface-mount FTDI chips (which gets expensive if you put one in every project). FTDI cables are inexpensive (about $11 shipped on eBay) and I’ve gotten a lot of mileage out of mine and know I will continue to use it for future projects. If you are interested in MCU/PC communication, consider one of these cables as a rapid development prototyping tool. I’m certainly enjoying mine!

It is important to me that my design is minimalistic, inexpensive, and functions natively on Linux and Windows without installing special driver-related software, and can be visualized in real-time using native Python libraries, such that the same code can be executed identically on all operating systems with minimal computer-side configuration. I’d say I succeeded in this effort, and while the project could use some small touches to polish it up, it’s already solid and proven in its usefulness and functionality.

This is my final device. It’s reading voltage on a single pin, sending this data to a computer through a USB connection, and custom software (written entirely in Python, designed to be a cross-platform solution) displays the signal in real time. Although it’s capable of recording and displaying 5 channels at the same time, it’s demonstrated displaying only one. Let’s check-out a video of it in action:

This 5-channel realtime USB analog sensor, coupled with custom cross-platform open-source software, will serve as the foundation for a slew of electrophysiological experiments, but can also be easily expanded to serve as an inexpensive multichannel digital oscilloscope. While more advanced solutions exist, this has the advantage of being minimally complex (consisting of a single microchip), inexpensive, and easy to build.

 To the right is my working environment during the development of this project. You can see electronics, my computer, microchips, and coffee, but an intriguingly odd array of immunological posters in the background. I spent a couple weeks camping-out in a molecular biology laboratory here at UF and got a lot of work done, part of which involved diving into electronics again. At the time this photo was taken, I hadn’t worked much at my home workstation. It’s a cool picture, so I’m holding onto it.

Below is a simplified description of the circuit schematic that is employed in this project. Note that there are 6 ADC (analog to digital converter) inputs on the ATMega48 IC, but for whatever reason I ended-up only hard-coding 5 into the software. Eventually I’ll go back and re-declare this project a 6-channel sensor, but since I don’t have six things to measure at the moment I’m fine keeping it the way it is. RST, SCK, MISO, and MOSI are used to program the microcontroller and do not need to be connected to anything for operation. The max232 was initially used as a level converter to allow the micro-controller to communicate with a PC via the serial port. However, shortly after this project was devised an upgrade was used to allow it to connect via USB. Continue reading for details…

Below you can see the circuit breadboarded. The potentiometer (small blue box) simulated an analog input signal.

The lower board is my AVR programmer, and is connected to RST, SCK, MISO, MOSI, and GND to allow me to write code on my laptop and program the board. It’s a Fun4DIY.com AVR programmer which can be yours for $11 shipped! I’m not affiliated with their company, but I love that little board. It’s a clone of the AVR ISP MK-II.

As you can see, the USB AVR programmer I’m using is supported in Linux. I did all of my development in Ubuntu Linux, writing AVR-GCC (C) code in my favorite Linux code editor Geany, then loaded the code onto the chip with AVRDude.

I found a simple way to add USB functionality in a standard, reproducible way that works without requiring the soldering of a SMT FTDI chip, and avoids custom libraries like V-USB which don’t easily have drivers that are supported by major operating systems (Windows) without special software. I understand that the simplest long-term and commercially-logical solution would be to use that SMT chip, but I didn’t feel like dealing with it. Instead, I added header pins which allow me to snap-on a pre-made FTDI USB cable. They’re a bit expensive ($12 on ebay) but all I need is 1 and I can use it in all my projects since it’s a sinch to connect and disconnect. Beside, it supplies power to the target board! It’s supported in Linux and in Windows with established drivers that are shipped with the operating system. It’s a bit of a shortcut, but I like this solution. It also eliminates the need for the max232 chip, since it can sense the voltages outputted by the microcontroller directly.

The system works by individually reading the 10-bit ADC pins on the microcontroller (providing values from 0-1024 to represent voltage from 0-5V or 0-1.1V depending on how the code is written), converting these values to text, and sending them as a string via the serial protocol. The FTDI cable reads these values and transmits them to the PC through a USB connection, which looks like “COM5” on my Windows computer. Values can be seen in any serial terminal program (i.e., hyperterminal), or accessed through Python with the PySerial module.

As you can see, I’m getting quite good at home-brewn PCBs. While it would be fantastic to design a board and have it made professionally, this is expensive and takes some time. In my case, I only have a few hours here or there to work on projects. If I have time to design a board, I want it made immediately! I can make this start to finish in about an hour. I use a classic toner transfer method with ferric chloride, and a dremel drill press to create the holes. I haven’t attacked single-layer SMT designs yet, but I can see its convenience, and look forward to giving it a shot before too long.

Here’s the final board ready for digitally reporting analog voltages. You can see 3 small headers on the far left and 2 at the top of the chip. These are for RST, SCK, MISO, MOSI, and GND for programming the chip. Once it’s programmed, it doesn’t need to be programmed again. Although I wrote the code for an ATMega48, it works fine on a pin-compatible ATMega8 which is pictured here. The connector at the top is that FTDI USB cable, and it supplies power and USB serial connectivity to the board.

If you look closely, you can see that modified code has been loaded on this board with a Linux laptop. This thing is an exciting little board, because it has so many possibilities. It could read voltages of a single channel in extremely high speed and send that data continuously, or it could read from many channels and send it at any rate, or even cooler would be to add some bidirectional serial communication capabilities to allow the computer to tell the microcontroller which channels to read and how often to report the values back. There is a lot of potential for this little design, and I’m glad I have it working.

Unfortunately I lost the schematics to this device because I formatted the computer that had the Eagle files on it. It should be simple and intuitive enough to be able to design again. The code for the microcontroller and code for the real-time visualization software will be posted below shortly. Below are some videos of this board in use in one form or another:

Here is the code that is loaded onto the microcontroller:

#define F_CPU 8000000UL
#include <avr/io.h>
#include <util/delay.h>

void readADC(char adcn){
		//ADMUX = 0b0100000+adcn; // AVCC ref on ADCn
		ADMUX = 0b1100000+adcn; // AVCC ref on ADCn
		ADCSRA |= (1<<ADSC); // reset value
        while (ADCSRA & (1<<ADSC)) {}; // wait for measurement
}

int main (void){
    DDRD=255;
	init_usart();
    ADCSRA = 0b10000111; //ADC Enable, Manual Trigger, Prescaler
    ADCSRB = 0;

    int adcs[8]={0,0,0,0,0,0,0,0};

    char i=0;
	for(;;){
		for (i=0;i<8;i++){readADC(i);adcs[i]=ADC>>6;}
		for (i=0;i<5;i++){sendNum(adcs[i]);send(44);}
		readADC(0);
		send(10);// LINE BREAK
		send(13); //return
		_delay_ms(3);_delay_ms(5);
	}
}

void sendNum(unsigned int num){
	char theIntAsString[7];
	int i;
	sprintf(theIntAsString, "%u", num);
	for (i=0; i < strlen(theIntAsString); i++){
		send(theIntAsString[i]);
	}
}


void send (unsigned char c){
	while((UCSR0A & (1<<UDRE0)) == 0) {}
	UDR0 = c;
}

void init_usart () {
	// ATMEGA48 SETTINGS
	int BAUD_PRESCALE = 12;
	UBRR0L = BAUD_PRESCALE; // Load lower 8-bits
	UBRR0H = (BAUD_PRESCALE >> 8); // Load upper 8-bits
	UCSR0A = 0;
	UCSR0B = (1<<RXEN0)|(1<<TXEN0); //rx and tx
	UCSR0C = (1<<UCSZ01) | (1<<UCSZ00); //We want 8 data bits
}

Here is the code that runs on the computer, allowing reading and real-time graphing of the serial data. It’s written in Python and has been tested in both Linux and Windows. It requires *NO* non-standard python libraries, making it very easy to distribute. Graphs are drawn (somewhat inefficiently) using lines in TK. Subsequent development went into improving the visualization, and drastic improvements have been made since this code was written, and updated code will be shared shortly. This is functional, so it’s worth sharing.

import Tkinter, random, time
import socket, sys, serial

class App:

	def white(self):
		self.lines=[]
		self.lastpos=0

		self.c.create_rectangle(0, 0, 800, 512, fill="black")
		for y in range(0,512,50):
			self.c.create_line(0, y, 800, y, fill="#333333",dash=(4, 4))
			self.c.create_text(5, y-10, fill="#999999", text=str(y*2), anchor="w")
		for x in range(100,800,100):
			self.c.create_line(x, 0, x, 512, fill="#333333",dash=(4, 4))
			self.c.create_text(x+3, 500-10, fill="#999999", text=str(x/100)+"s", anchor="w")

		self.lineRedraw=self.c.create_line(0, 800, 0, 0, fill="red")

		self.lines1text=self.c.create_text(800-3, 10, fill="#00FF00", text=str("TEST"), anchor="e")
		for x in range(800):
			self.lines.append(self.c.create_line(x, 0, x, 0, fill="#00FF00"))

	def addPoint(self,val):
		self.data[self.xpos]=val
		self.line1avg+=val
		if self.xpos%10==0:
			self.c.itemconfig(self.lines1text,text=str(self.line1avg/10.0))
			self.line1avg=0
		if self.xpos>0:self.c.coords(self.lines[self.xpos],(self.xpos-1,self.lastpos,self.xpos,val))
		if self.xpos<800:self.c.coords(self.lineRedraw,(self.xpos+1,0,self.xpos+1,800))
		self.lastpos=val
		self.xpos+=1
		if self.xpos==800:
			self.xpos=0
			self.totalPoints+=800
			print "FPS:",self.totalPoints/(time.time()-self.timeStart)
		t.update()

	def __init__(self, t):
		self.xpos=0
		self.line1avg=0
		self.data=[0]*800
		self.c = Tkinter.Canvas(t, width=800, height=512)
		self.c.pack()
		self.totalPoints=0
		self.white()
		self.timeStart=time.time()

t = Tkinter.Tk()
a = App(t)

#ser = serial.Serial('COM1', 19200, timeout=1)
ser = serial.Serial('/dev/ttyUSB0', 38400, timeout=1)
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)

while True:
	while True: #try to get a reading
		#print "LISTENING"
		raw=str(ser.readline())
		#print raw
		raw=raw.replace("n","").replace("r","")
		raw=raw.split(",")
		#print raw
		try:
			point=(int(raw[0])-200)*2
			break
		except:
			print "FAIL"
			pass
	point=point/2
	a.addPoint(point)

If you re-create this device of a portion of it, let me know! I’d love to share it on my website. Good luck!



DIY ECG Machine On The Cheap

[notice]

I have simplified and improved my ECG machine design! Check out the new post:

http://www.swharden.com/blog/2013-04-14-simple-diy-ecg-pulse-oximeter-version-2/

[/notice]

Note from the Author: This page documents how I made an incredibly simple ECG machine with a minimum of parts to view the electrical activity of my own heart. Feel free to repeat my experiment, but do so at your own risk. There are similar projects floating around on the internet, but I aim to provide a more complete, well-documented, and cheaper solution, with emphasis on ECG processing and analysis, rather than just visualization. If you have any questions or suggestions please contact me. Also, if you attempt this project yourself I’d love to post your results! Good luck!
–Scott

Background

You’ve probably seen somebody in a hospital setting hooked up to a big mess of wires used to analyze their heartbeat. ecgmanThe goal of such a machine (called an electrocardiograph, or ECG) is to amplify, measure, and record the natural electrical potential created by the heart. Note that cardiac electrical signals are different than heart sounds, which are listened to with a stethoscope. The intrinsic cardiac pacemaker system is responsible for generating these electrical signals which serve to command and coordinate contraction of the four chambers at the heart at the appropriate intervals [atria (upper chambers) first, then the ventricles (lower chambers) a fraction of a second later], and their analysis reveals a wealth of information about cardiac regulation, as well insights into pathological conditions. Each heartbeat produces a similar pattern in the ECG signal, called a PQRST wave. ecg_principle_slow [picture] The smooth curve in the ECG (P) is caused by the stimulation of the atria via the Sinoatrial (SA) node in the right atrium. There is a brief pause, as the electrical impulse is slowed by the Atrioventricular (AV) node and Purkinje fibers in the bundle of His. The prominent spike in the ECG (the QRS complex) is caused by this step, where the electrical impulse travels through the inter-ventricular septum and up through the outer walls of the ventricles. The sharp peak is the R component, and exact heart rate can be calculated as the inverse of the R-to-R interval (RRi). Fancy, huh?

Project Goal

The goal of this project is to generate an extremely cheap, functional ECG machine made from common parts, most of which can be found around your house. This do-it-yourself (DIY) ECG project is different than many others on the internet in that it greatly simplifies the circuitry by eliminating noise reduction components, accomplishing this via software-based data post-processing. Additionally, this writeup is intended for those without any computer, electrical, or biomedical experience, and should be far less convoluted than the suspiciously-cryptic write-ups currently available online. In short, I want to give everybody the power to visualize and analyze their own heartbeat!

The ECG of my own heart:

ecg31

Video Overview

I know a lot of Internet readers aren’t big fans of reading. Therefore, I provided an outline of the process in video form. Check out the videos, and if you like what you see read more!

Video 1/3: Introducing my ECG machine

Video 2/3: Recording my ECG

Video 3/3: Analyzing my ECG

Electrical Theory

Measurement: The electrical signals which command cardiac musculature can be detected on the surface of the skin. In theory one could grab the two leads of a standard volt meter, one with each hand, and see the voltage change as their heart beats, but the fluctuations are rapid and by the time these signals reach the skin they are extremely weak (a few millionths of a volt) and difficult to detect with simple devices. Therefore, amplification is needed.

Amplification: A simple way to amplify the electrical difference between two points is to use a operational amplifier, otherwise known as an op-amp. The gain (multiplication factor) of an op-amp is controlled by varying the resistors attached to it, and an op-amp with a gain of 1000 will take a 1 millivolt signal and amplify it to 1 volt. There are many different types of microchip op-amps, and they’re often packaged with multiple op-amps in one chip (such as the quad-op-amp lm324, or the dual-op-amp lm358n). Any op-amp designed for low voltage will do for our purposes, and we only need one.

Noise: Unfortunately, the heart is not the only source of voltage on the skin. Radiation from a variety of things (computers, cell phones, lights, and especially the wiring in your walls) is absorbed by your skin and is measured with your ECG, in many cases masking your ECG in a sea of electrical noise. The traditional method of eliminating this noise is to use complicated analog circuitry, but since this noise has a characteristic, repeating, high-frequency wave pattern, it can be separated from the ECG (which is much slower in comparison) using digital signal processing computer software!

Digitization: Once amplified, the ECG signal along with a bunch of noise is in analog form. You could display the output with an oscilloscope, but to load it into your PC you need an analog-to-digital converter. Don’t worry! If you’ve got a sound card with a microphone input, you’ve already got one! It’s just that easy. We’ll simply wire the output of our ECG circuit to the input of our sound card, record the output of the op-amp using standard sound recording software, remove the noise from the ECG digitally, and output gorgeous ECG traces ready for visualization and analysis!

Parts/Cost

I’ll be upfront and say that I spent $0.00 making my ECG machine, because I was able to salvage all the parts I needed from a pile of old circuit boards. If you need specific components, check your local RadioShack. If that’s a no-go, hit-up Digikey (it’s probably cheaper too). Also, resistor values are flexible. Use mine as a good starter set, and vary them to suit your needs. If you buy everything from Digikey, the total cost of this project would be about $1. For now, here’s a list of all the parts you need:

  • 1x low voltage op-amp LM358N $0.40
  • 1x 100kOhm resistor (brn,blk,yel) virtually free
  • 1x 1kOhm resistor (brn,blk,red) virtually free
  • 1x 0.1uF capacitor (104Z) virtually free
  • Microphone cable to get from the op-amp to your PC
  • Electrodes 3 pennies should do. ($0.03)

Making the Device

Keep in mind that I’m not an electrical engineer (I have a masters in molecular biology but I’m currently a dental student if you must know) and I’m only reporting what worked well for me. I don’t claim this is perfect, and I’m certainly open for (and welcome) suggestions for improvement. With that in mind, here’s what I did!

img_2694

This is pretty much it. First off is a power source. If you want to be safe, use three AAA batteries in series. If you’re a daredevil and enjoy showing off your ghettorigging skills, do what I did and grab 5v from a free USB plug! Mua ha ha ha. The power goes into the circuit and so do the leads/electrodes connected to the body. You can get pretty good results with only two leads, but if you want to experiment try hooking up an extra ground lead and slap it on your foot. More on the electrodes later. The signal from the leads is amplified by the circuit and put out the headphone cable, ready to enter your PC’s sound card through the microphone jack!

img_2686

Note how I left room in the center of the circuit board. That was intentional! I wanted to expand this project by adding a microcontroller to do some on-board, real-time analysis. Specifically, an ATMega8! I never got around to it though. Its purpose would be to analyze the output of the op-amp and graph the ECG on a LCD screen, or at least measure the time between beats and display HR on a screen. (More ideas are at the bottom of this document.) Anyway, too much work for now, maybe I’ll do it one day in the future.

ECG circuit diagram:

simple_ecg_circuit

This is the circuit diagram. This is a classical high-gain analog differential amplifier. It just outputs the multiplied difference of the inputs. The 0.1uF capacitor helps stabilize the signal and reduce high frequency noise (such as the audio produced by a nearby AM radio station). Use Google if you’re interested in learning exactly how it works.

ECG schematic:

simple_ecg_circuit2

This is how I used my LM358N to create the circuit above. Note that there is a small difference in my board from the photos and this diagram. This diagram is correct, but the circuit in some of the pictures is not. Briefly, when I built it I accidentally connected the (-) lead directly to ground, rather than to the appropriate pin on the microchip. This required me to place a 220kOhm between the leads to stabilize the signal. I imagine if you wire it CORRECTLY (as shown in these circuit diagrams) it will work fine, but if you find it too finicky (jumping quickly from too loud to too quiet), try tossing in a high-impedance resistor between the leads like I did. Overall, this circuit is extremely flexible and I encourage you to build it on a breadboard and try different things. Use this diagram as a starting point and experiment yourself!

The Electrodes:

img_2704

You can make electrodes out of anything conductive. The most recent graphs were created from wires with gator clips on them clamping onto pennies (pictured). Yeah, I know I could solder directly to the pennies (they’re copper) but gator clips are fast, easy, and can be clipped to different materials (such as aluminum foil) for testing. A dot of moisturizing lotion applied to the pennies can be used to improve conduction between the pennies and the skin, but I didn’t find this to be very helpful. If pressed firmly on the body, conduction seems to be fine. Oh! I just remembered. USE ELECTRICAL TAPE TO ATTACH LEADS TO YOUR BODY! I tried a million different things, from rubber bands to packaging tape. The bottom line is that electrical tape is stretchy enough to be flexible, sticky enough not to fall off (even when moistened by the natural oils/sweat on your skin), and doesn’t hurt that bad to peel off.

Some of the best electrodes I used were made from aluminum cans! Rinse-out a soda can, cut it into “pads”, and use the sharp edge of a razor blade or pair of scissors to scrape off the wax coating on all contact surfaces. Although a little unconformable and prone to cut skin due to their sharp edges, these little guys work great!

Hooking it Up

This part is the most difficult part of the project! This circuit is extremely finicky. The best way to get it right is to open your sound editor (In Windows I use GoldWave because it’s simple, powerful, and free, but similar tools exist for Linux and other Unix-based OSes) and view the low-frequency bars in live mode while you set up. When neither electrode is touched, it should be relatively quiet. When only the + electrode is touched, it should go crazy with noise. When you touch both (one with each hand) the noise should start to go away, possibly varying by how much you squeeze (how good of a connection you have). The whole setup process is a game between too much and too little conduction. You’ll find that somewhere in the middle, you’ll see (and maybe hear) a low-frequency burst of noise once a second corresponding to your heartbeat. [note: Did you know that’s how the second was invented? I believe it was ] Once you get that good heartbeat, tape up your electrodes and start recording. If you can’t get it no matter what you do, start by putting the ground electrode in your mouth (yeah, I said it) and pressing the + electrode firmly and steadily on your chest. If that works (it almost always does), you know what to look for, so keep trying on your skin. For short recordings (maybe just a few beats) the mouth/chest method works beautifully, and requires far less noise reduction (if any), but is simply impractical for long-term recordings. I inside vs. outside potential is less susceptible to noise-causing electrical radiation. Perhaps other orifices would function similarly? I’ll leave it at that. I’ve also found that adding a third electrode (another ground) somewhere else on my body helps a little, but not significantly. Don’t give up at this step if you don’t get it right away! If you hear noise when + is touched, your circuit is working. Keep trying and you’ll get it eventually.

Recording the ECG

This is the easy part. Keep an eye on your “bars” display in the audio program to make sure something you’re doing (typing, clicking, etc) isn’t messing up the recording. If you want, try surfing the net or playing computer games to see how your heart varies. Make sure that as you tap the keyboard and click the mouse, you’re not getting noise back into your system. If this is a problem, try powering your device by batteries (a good idea for safety’s sake anyway) rather than another power source (such as USB power). Record as long as you want! Save the file as a standard, mono, wave file.

Digitally Eliminating Noise

Now it’s time to clean-up the trace. Using GoldWave, first apply a lowpass filter at 30 Hz. This kills most of your electrical noise (> 30hz), while leaving the ECG intact (< 15Hz). However, it dramatically decreases the volume (potential) of the audio file. Increase the volume as necessary to maximize the window with the ECG signal. You should see clear heartbeats at this point. You may want to apply an auto-gain filter to normalize the heartbeats potentials. Save the file as a raw sound file (.snd) at 1000 Hz (1 kHz) resolution.

Presentation and Analysis

Now you’re ready to analyze! Plop your .snd file in the same folder as my [ecg.py script], edit the end of the script to reflect your .snd filename, and run the script by double-clicking it. (Keep in mind that my script was written for python 2.5.4 and requires numpy 1.3.0rc2 for python 2.5, and matplotlib 0.99 for python 2.5 – make sure you get the versions right!) Here’s what you’ll see!

diy_ecg_sample_trace

This is a small region of the ECG trace. The “R” peak is most obvious, but the details of the other peaks are not as visible. If you want more definition in the trace (such as the blue one at the top of the page), consider applying a small collection of customized band-stop filters to the audio file rather than a single, sweeping lowpass filter. Refer to earlier posts in the DIY ECG category for details. Specifically, code on Circuits vs. Software for noise reduction entry can help. For our purposes, calculating heart rate from R-to-R intervals (RRIs) can be done accurately with traces such as this.

diy_ecg_heart_rate_over_time

Your heart rate fluctuates a lot over time! By plotting the inverse of your RRIs, you can see your heart rate as a function of time. Investigate what makes it go up, go down, and how much. You’d be surprised by what you find. I found that checking my email raises my heart rate more than first-person-shooter video games. I get incredibly anxious when I check my mail these days, because I fear bad news from my new university (who knows why, I just get nervous about it). I wonder if accurate RRIs could be used to assess nervousness for the purposes of lie detection?

diy_ecg_rr_beat_interval

This is the RRI plot where the value of each RRI (in milliseconds) is represented for each beat. It’s basically the inverse of heart rate. Miscalculated heartbeats would show up as extremely high or extremely low dots on this graph. However, excluding points above or below certain bounds means that if your heart did double-beat, or skip a beat, you wouldn’t see it. Note that I just realized my axis label is wrong (it should be sec, not ms). Oh well =o

diy_ecg_poincare_plot

A Poincare Plot is a commonly-used method to visually assess heart rate variability as a function of RRIs. In this plot, each RRI is plotted against the RRI of the next subsequent beat. In a heart which beats at the same speed continuously, only a single dot would be visible in the center. In a heart which beats mostly-continuously, and only changes its rate very slowly, a linear line of dots would be visible in a 1:1 ratio. However, in real life the heart varies RRIs greatly from beat to beat, producing a small cloud of dots. The size of the cloud corresponds to the speed at which the autonomic nervous system can modulate heart rate in the time frame of a single beat.

diy_ecg_rr_deviation_histogram

The frequency of occurrence of various RRIs can be expressed by a histogram. The center peak corresponds to the standard heart rate. Peaks to the right and left of the center peak correspond to increased and decreased RRIs, respectively. A gross oversimplification of the interpretation of such data would be to state that the upper peak represents the cardio-inhibitory parasympathetic autonomic nervous system component, and the lower peak represents the cardio-stimulatory sympathetic autonomic nervous system component.

diy_ecg_power_spectrum_raw

Taking the Fast Fourier Transformation of the data produces a unique trace whose significance is extremely difficult to interpret. Near 0Hz (infinite time) the trace heads toward ∞ (infinite power). To simplify the graph and eliminate the near-infinite, low-frequency peak we will normalize the trace by multiplying each data point by its frequency, and dividing the vertical axis units by Hz to compensate. This will produce the following graph…

diy_ecg_power_spectrum_weighted
This is the power spectrum density (PSD) plot of the ECG data we recorded. Its physiological interpretation is extraordinarily difficult to understand and confirm, and is the subject of great debate in the field of autonomic neurological cardiac regulation. An oversimplified explanation of the significance of this graph is that the parasympathetic (cardio-inhibitory) branch of the autonomic nervous system works faster than the sympathetic (cardio-stimulatory) branch. Therefore, the lower peak corresponds to the sympathetic component (combined with persistent parasympathetic input, it’s complicated), while the higher-frequency peak corresponds to the parasympathetic component, and the sympathetic/parasympathetic relationship can be assessed by the ratio of the integrated areas of these peaks after a complicated curve fitting processes which completely separates overlapping peaks. To learn more about power spectral analysis of heart rate over time in the frequency domain, I recommend skimming this introduction to heart rate variability website and the article on Heart Rate Variability following Myocardial Infarction (heart attack). Also, National Institute of Health (NIH) funded studies on HRV should be available from pubmed.org. If you want your head to explode, read Frequency-Domain Characteristics and Filtering of Blood Flow Following the Onset of Exercise: Implications for Kinetics Analysis for a lot of good frequency-domain-analysis-related discussion and rationalization.

Encouraging Words:

Please, if you try this don’t die. The last thing I want is to have some kid calling me up and yelling at me that he nearly electrocuted himself when he tried to plug my device directly into a wall socket and now has to spend the rest of his life with two Abraham Lincolns tattooed onto his chest resembling a second set of nipples. Please, if you try this use common sense, and of course you’re responsible for your own actions. I provide this information as a description of what I did and what worked for me. If you make something similar that works, I’ve love to see it! Send in your pictures of your circuit, charts of your traces, improved code, or whatever you want and I’ll feature it on the site. GOOD LUCK!

Fancier Circuit:

If you want to try this, go for it! Briefly, this circuit uses 6 op-amps to help eliminate effects of noise. It’s also safer, because of the diodes interconnecting the electrodes. It’s the same circuit as on [this page].

Last minute thoughts:

  • More homemade ECG information can be found on my earlier posts in the DIY ECG category, however this page is the primary location of my most recent thoughts and ideas.
  • You can use moisturizing lotion between the electrodes and your skin to increase conduction. However, keep in mind that better conduction is not always what you want. You’ll have to experiment for yourself.
  • Variation in location of electrodes will vary the shape of the ECG. I usually place electrodes on each side of my chest near my arms. If your ECG appears upside-down, reverse the leads!
  • Adding extra leads can improve grounding. Try grounding one of your feet with a third lead to improve your signal. Also, if you’re powering your device via USB power consider trying battery power – it should be less noisy.
  • While recording, be aware of what you do! I found that if I’m not well-grounded, my ECG is fine as long as I don’t touch my keyboard. If I start typing, every keypress shows up as a giant spike, bigger than my heartbeat!
  • If you get reliable results, I wonder if you could make the device portable? Try using a portable tape recorder, voice recorder, or maybe even minidisc recorder to record the output of the ECG machine for an entire day. I haven’t tried it, but why wouldn’t it work? If you want to get fancy, have a microcontroller handle the signal processing and determine RRIs (should be easy) and save this data to a SD card or fancy flash logger.
  • The microcontroller could output heart rate via the serial port.
  • If you have a microcontroller on board, why not display heart rate on a character LCD?
  • While you have a LCD on there, display the ECG graphically!
  • Perhaps a wireless implementation would be useful.
  • Like, I said, there are other, more complicated analog circuits which reduce noise of the outputted signal. I actually built Jason Nguyen’s fancy circuit which used 6 op-amps but the result wasn’t much better than the simple, 1 op-amp circuit I describe here once digital filtering was applied.
  • Arrhythmic heartbeats (where your heart screws-up and misfires, skips a beat, double-beats, or beats awkwardly) are physiological (normal) and surprisingly common. Although shocking to hear about, sparse, single arrhythmic heartbeats are normal and are a completely different ball game than chronic, potentially deadly heart arrhythmias in which every beat is messed-up. If you’re in tune with your body, you might actually feel these occurrences happening. About three times a week I feel my heart screw up a beat (often when it’s quiet), and it feels like a sinking feeling in my chest. I was told by a doctor that it’s totally normal and happens many times every day without me noticing, and that most people never notice these single arrhythmic beats. I thought it was my heart skipping a beat, but I wasn’t sure. That was my motivation behind building this device – I wanted to see what my arrhythmic beats looked like. It turns out that it’s more of a double-beat than a skipped beat, as observed when I captured a single arrhythmic heartbeat with my ECG machine, as described in this entry.
  • You can improve the safety of this device by attaching diodes between leads, similar to the more complicated circuit. Theory is that if a huge surge of energy does for whatever reason get into the ECG circuit, it’ll short itself out at the circuit level (conducting through the diodes) rather than at your body (across your chest / through your heart).
  • Alternatively, use an AC opto-isolator between the PC sound card and the ECG circuit to eliminate the possibility of significant current coming back from the PC.
  • On the Hackaday post, Flemming Frandsen noted that an improperly grounded PC could be dangerous because the stored charge would be manifest in the ground of the microphone jack. If you were to ground yourself to true ground (using a bench power supply or sticking your finger in the ground socket of an AC wall plug) this energy could travel through you! So be careful to only ground yourself with respect to the circuit using only battery power to minimize this risk.
  • Do not attempt anything on this page. Ever. Don’t even read it. You read it already! You’re sill reading it aren’t you? Yeah. You don’t follow directions well do you?

SAMPLE FILTERED RECORDING:

I think this is the same one I used in the 3rd video from my single op-amp circuit. [scottecg.snd] It’s about an hour long, and in raw sound format (1000 Hz). It’s already been filtered (low-pass filtered at 30Hz). You can use it with my code below!

CODE

print "importing libraries..."
import numpy, pylab
print "DONE"

class ECG:

    def trim(self, data,degree=100):
        print 'trimming'
        i,data2=0,[]
        while i<len(data):
            data2.append(sum(data[i:i+degree])/degree)
            i+=degree
        return data2

    def smooth(self,list,degree=15):
        mults=[1]
        s=[]
        for i in range(degree): mults.append(mults[-1]+1)
        for i in range(degree): mults.append(mults[-1]-1)
        for i in range(len(list)-len(mults)):
            small=list[i:i+len(mults)]
            for j in range(len(small)):
                small[j]=small[j]*mults[j]
            val=sum(small)/sum(mults)
            s.append(val)
        return s

    def smoothWindow(self,list,degree=10):
        list2=[]
        for i in range(len(list)):
            list2.append(sum(list[i:i+degree])/float(degree))
        return list2

    def invertYs(self):
        print 'inverting'
        self.ys=self.ys*-1

    def takeDeriv(self,dist=5):
        print 'taking derivative'
        self.dys=[]
        for i in range(dist,len(self.ys)):
            self.dys.append(self.ys[i]-self.ys[i-dist])
        self.dxs=self.xs[0:len(self.dys)]

    def genXs(self, length, hz):
        print 'generating Xs'
        step = 1.0/(hz)
        xs=[]
        for i in range(length): xs.append(step*i)
        return xs

    def loadFile(self, fname, startAt=None, length=None, hz=1000):
        print 'loading',fname
        self.ys = numpy.memmap(fname, dtype='h', mode='r')*-1
        print 'read %d points.'%len(self.ys)
        self.xs = self.genXs(len(self.ys),hz)
        if startAt and length:
            self.ys=self.ys[startAt:startAt+length]
            self.xs=self.xs[startAt:startAt+length]

    def findBeats(self):
        print 'finding beats'
        self.bx,self.by=[],[]
        for i in range(100,len(self.ys)-100):
          if self.ys[i]<15000: continue # SET THIS VISUALLY
          if self.ys[i]<self.ys[i+1] or self.ys[i]<self.ys[i-1]: continue
          if self.ys[i]-self.ys[i-100]>5000 and self.ys[i]-self.ys[i+100]>5000:
              self.bx.append(self.xs[i])
              self.by.append(self.ys[i])
        print "found %d beats"%(len(self.bx))

    def genRRIs(self,fromText=False):
        print 'generating RRIs'
        self.rris=[]
        if fromText: mult=1
        else: 1000.0
        for i in range(1,len(self.bx)):
            rri=(self.bx[i]-self.bx[i-1])*mult
            #if fromText==False and len(self.rris)>1:
                #if abs(rri-self.rris[-1])>rri/2.0: continue
            #print i, "%.03ft%.03ft%.2f"%(bx[i],rri,60.0/rri)
            self.rris.append(rri)

    def removeOutliers(self):
        beatT=[]
        beatRRI=[]
        beatBPM=[]
        for i in range(1,len(self.rris)):
            #CHANGE THIS AS NEEDED
            if self.rris[i]<0.5 or self.rris[i]>1.1: continue
            if abs(self.rris[i]-self.rris[i-1])>self.rris[i-1]/5: continue
            beatT.append(self.bx[i])
            beatRRI.append(self.rris[i])
        self.bx=beatT
        self.rris=beatRRI

    def graphTrace(self):
        pylab.plot(self.xs,self.ys)
        #pylab.plot(self.xs[100000:100000+4000],self.ys[100000:100000+4000])
        pylab.title("Electrocardiograph")
        pylab.xlabel("Time (seconds)")
        pylab.ylabel("Potential (au)")

    def graphDeriv(self):
        pylab.plot(self.dxs,self.dys)
        pylab.xlabel("Time (seconds)")
        pylab.ylabel("d/dt Potential (au)")

    def graphBeats(self):
        pylab.plot(self.bx,self.by,'.')

    def graphRRIs(self):
        pylab.plot(self.bx,self.rris,'.')
        pylab.title("Beat Intervals")
        pylab.xlabel("Beat Number")
        pylab.ylabel("RRI (ms)")

    def graphHRs(self):
        #HR TREND
        hrs=(60.0/numpy.array(self.rris)).tolist()
        bxs=(numpy.array(self.bx[0:len(hrs)])/60.0).tolist()
        pylab.plot(bxs,hrs,'g',alpha=.2)
        hrs=self.smooth(hrs,10)
        bxs=bxs[10:len(hrs)+10]
        pylab.plot(bxs,hrs,'b')
        pylab.title("Heart Rate")
        pylab.xlabel("Time (minutes)")
        pylab.ylabel("HR (bpm)")

    def graphPoincare(self):
        #POINCARE PLOT
        pylab.plot(self.rris[1:],self.rris[:-1],"b.",alpha=.5)
        pylab.title("Poincare Plot")
        pylab.ylabel("RRI[i] (sec)")
        pylab.xlabel("RRI[i+1] (sec)")

    def graphFFT(self):
        #PSD ANALYSIS
        fft=numpy.fft.fft(numpy.array(self.rris)*1000.0)
        fftx=numpy.fft.fftfreq(len(self.rris),d=1)
        fftx,fft=fftx[1:len(fftx)/2],abs(fft[1:len(fft)/2])
        fft=self.smoothWindow(fft,15)
        pylab.plot(fftx[2:],fft[2:])
        pylab.title("Raw Power Sprectrum")
        pylab.ylabel("Power (ms^2)")
        pylab.xlabel("Frequency (Hz)")

    def graphFFT2(self):
        #PSD ANALYSIS
        fft=numpy.fft.fft(numpy.array(self.rris)*1000.0)
        fftx=numpy.fft.fftfreq(len(self.rris),d=1)
        fftx,fft=fftx[1:len(fftx)/2],abs(fft[1:len(fft)/2])
        fft=self.smoothWindow(fft,15)
        for i in range(len(fft)):
            fft[i]=fft[i]*fftx[i]
        pylab.plot(fftx[2:],fft[2:])
        pylab.title("Power Sprectrum Density")
        pylab.ylabel("Power (ms^2)/Hz")
        pylab.xlabel("Frequency (Hz)")

    def graphHisto(self):
        pylab.hist(self.rris,bins=20,ec='none')
        pylab.title("RRI Deviation Histogram")
        pylab.ylabel("Frequency (count)")
        pylab.xlabel("RRI (ms)")
        #pdf, bins, patches = pylab.hist(self.rris,bins=100,alpha=0)
        #pylab.plot(bins[1:],pdf,'g.')
        #y=self.smooth(list(pdf[1:]),10)
        #x=bins[10:len(y)+10]
        #pylab.plot(x,y)

    def saveBeats(self,fname):
        print "writing to",fname
        numpy.save(fname,[numpy.array(self.bx)])
        print "COMPLETE"

    def loadBeats(self,fname):
        print "loading data from",fname
        self.bx=numpy.load(fname)[0]
        print "loadded",len(self.bx),"beats"
        self.genRRIs(True)

def snd2txt(fname):
    ## SND TO TXT ##
    a=ECG()
    a.loadFile(fname)#,100000,4000)
    a.invertYs()
    pylab.figure(figsize=(7,4),dpi=100);pylab.grid(alpha=.2)
    a.graphTrace()
    a.findBeats()
    a.graphBeats()
    a.saveBeats(fname)
    pylab.show()

def txt2graphs(fname):
    ## GRAPH TXT ##
    a=ECG()
    a.loadBeats(fname+'.npy')
    a.removeOutliers()
    pylab.figure(figsize=(7,4),dpi=100);pylab.grid(alpha=.2)
    a.graphHRs();pylab.subplots_adjust(left=.1,bottom=.12,right=.96)
    pylab.savefig("DIY_ECG_Heart_Rate_Over_Time.png");
    pylab.figure(figsize=(7,4),dpi=100);pylab.grid(alpha=.2)
    a.graphFFT();pylab.subplots_adjust(left=.13,bottom=.12,right=.96)
    pylab.savefig("DIY_ECG_Power_Spectrum_Raw.png");
    pylab.figure(figsize=(7,4),dpi=100);pylab.grid(alpha=.2)
    a.graphFFT2();pylab.subplots_adjust(left=.13,bottom=.12,right=.96)
    pylab.savefig("DIY_ECG_Power_Spectrum_Weighted.png");
    pylab.figure(figsize=(7,4),dpi=100);pylab.grid(alpha=.2)
    a.graphPoincare();pylab.subplots_adjust(left=.1,bottom=.12,right=.96)
    pylab.savefig("DIY_ECG_Poincare_Plot.png");
    pylab.figure(figsize=(7,4),dpi=100);pylab.grid(alpha=.2)
    a.graphRRIs();pylab.subplots_adjust(left=.1,bottom=.12,right=.96)
    pylab.savefig("DIY_ECG_RR_Beat_Interval.png");
    pylab.figure(figsize=(7,4),dpi=100);pylab.grid(alpha=.2)
    a.graphHisto();pylab.subplots_adjust(left=.1,bottom=.12,right=.96)
    pylab.savefig("DIY_ECG_RR_Deviation_Histogram.png");
    pylab.show();

fname='publish_05_10min.snd' #CHANGE THIS AS NEEDED
#raw_input("npress ENTER to analyze %s..."%(fname))
snd2txt(fname)
#raw_input("npress ENTER to graph %s.npy..."%(fname))
txt2graphs(fname)

NEW:

Salil notified me that he used a similar concept to create an ECG machine using some fancier circuitry to eliminate noise with hardware rather than rely on software. Way to go Salil! Here’s a video of his project: http://youtu.be/uV8UyEQxVII



Defibrillating My DIY ECG Project

I’ve done a lot of random things the last few months, but few things were as random, cool, or googled-for as my Do-It-Yourself Electrocardiography project . My goal was to produce an effective ECG machine which interfaced the computer sound card for as little cost as possible. I started out small with an extremely simple circuit which technically worked, but required a lot of custom-written software to do a ton of math to decipher the ECG signal from the noise (such as inverse fast flourier transformations after band-stopping several bands of predictable, high-frequency noise). I later started building more complicated circuits in an attempt to minimize the noise, which worked well but were much more difficult to construct. For some reason, my nice ECG circuit died (burned? broke? don’t know why) right after I started to actually generate useful data about my occasional double-beats (which apparently are common, normal, and even expected during basal physiological states).
UPDATE: [2am, nextday] Here’s some video of the prototype briefly demonstrating the concept of how to use a minimum of parts to generate a great ECG trace using digital signal processing on the PC side.

simple_ecg_circuit_output

I’ve decided to revitalize this project quickly and effectively, going back to its roots and focusing on cost-minimal solutions, and using software (rather than complicated analog circuitry) to eliminate the noise. This will be a beautiful marriage of biomedical analog circuitry with software-based processing and linear data analysis, all on the cheap. If there were ever a project that represented my early 20s life, this would be it. Briefly, I built a circuit with only 3 components (!) which produces extraordinary results (above). That’s the signal after minimal processing.

Check it out yourself! I’ll provide data file for this trace (snd2.zip) along with the Python code to graph it (below) which requires numpy and matplotlib in addition to the Python scripting language. I’ll post the circuity along with some more intricate code when my project progresses a little further.

import numpy, pylab

def trim(data,degree=100):
    i,data2=0,[]
    while i<len(data):
        data2.append(sum(data[i:i+degree])/degree)
        i+=degree
    return data2

def genXs(length,trim=100,hz=44100):
    step = 1.0/(hz/trim)
    xs=[]
    for i in range(length):
        xs.append(step*i)
    return xs

data = numpy.memmap("ecg2.snd", dtype='h', mode='r')
data = trim(data)
pylab.grid(alpha=.2)
pylab.plot(genXs(len(data)),data)
pylab.title("Simplified ECG Circuit Output")
pylab.xlabel("Time (seconds)")
pylab.ylabel("Potential (Au)")
pylab.show()