SUMMARY: A small group of high school students taking an AP class for college credit launched a high-altitude weather balloon with a small payload. In addition to a video transmitter and GPS transmitter, they decided to include a simple transmitter built from scratch. This is the story of the project, with emphasis on the simple transmitter’s design, construction, implementation, and reception (which surprised me, being detected ~200 miles away and lasting the entire duration of the flight!) [sample.ogg]
6/16/2010 – TRACKING
I’m completely amazed at how well the transmitter/receiver worked! For only a few milliwatts, I was able to track that thing all the way from takeoff to landing in Gainesville, FL a few hundred miles away. Here is the data assembled in a special, annotated way!
ANALYSIS: the text on the image describes most if it, but one of the most interesting features is the “multipathing” during the final moments of the descent, where the single carrier signal splits into two. I believe this is due to two Doppler shifts: (1) as the distance between the falling transmitter and the receiver is decreasing, producing a slight in increase in frequency, and (2) a signal reflected off of a layer of the atmosphere above the craft (the ionosphere?) before it gets to the receiver, the distance of which is increasing as the craft falls, producing a decrease in frequency. I’ll bet I can mathematically work backwards and determine how high the craft was, how fast it was falling, and/or how high the layer of the reflecting material is – but that’s more work than this dental student is prepared to do before his morning coffee!
HERE IS SOME AUDIO of some of the strongest signals I received. Pretty good for a few milliwatts a hundred miles away! [beeps.ogg]
6/16/2010 – THE FLIGHT
The launch:
This is the design team:
Walking the balloon to its launch destination at NASA with an awesome rocket (Saturn 1B – identified by Lee, KU4OS) in the background.
The team again, getting ready for launch. I’ve been informed that the reason their hands are up is to prevent the balloon from tilting over too much. I’d imagine that a brush with a grass blade could be bad news for the project!
Last minute checks – you can see the transmitter and battery holders for it taped to the Styrofoam.
The transmitter in its final position. Note the coil of yellow wire. That serves as a rudimentary “ground” for the antenna’s signal to push off of. I wasn’t very clear on my instructions on how to make it. I meant that it should be a huge coil wrapped around the entire payload (as large as it can be), which would have probably produced a better signal, but since I was able to capture the signal during the whole flight it turned out to be a non-issue.
The antenna can be seen dropping down as a yellow wire beneath the payload. (arrow)
Awesome photo.
Launch! Look how fast that balloon is rising!
It’s out of our hands now. When I got the text message that it launched, I held my breath. I was skeptical that the transmitter would even work!
One of the students listening to my transmitter with QRSS VD software (score!)
Video capture from an on-board camera was also attempted (900MHz), but from what I hear it didn’t function well for very long.
6/15/2010 – IMPROVED BUILD
Here you can see me (center arrow) showing the students how to receive the Morse code signal sent from the small transmitter (left arrow) using a laptop running QRSS VD (my software) analyzing audio from and an Icom706 mkII radio receiver attached to a dipole (right arrow).
I amped-up the output of the oscillator using an octal buffer chip (74HC240) with some decent results. I’m pleased! It’s not perfect (it’s noisy as heck) but it should be functional for a 2 hour flight.
Closeup of the transmitter showing the oscillator at 29.4912 MHz, the Atmel ATTiny44a AVR microcontroller (left chip), octal buffer 74HC240 (right chip), and some status lights which blink as the code is executed.
This is my desk where I work from home. Note the styrofoam box in the background – that’s where my low-power transmitter lives (the one that’s spotted around the world). All I needed to build this device was a soldering iron.
Although I had a radio, it is not capable of receiving 29MHz so I was unable to test the transmitter from home. I had to take it to the university to assess its transmitting capabilities.
At UF I used an oscilloscope to measure the waveform of the transmitter.
I connected the leads to the output of the transmitter, shorted by a 39ohm resistor. By measuring the peak-to-peak voltage of the signal going into a resistor, we can measure its power.
Here’s the test setup. The transmitter is on the blue pad on the right, and the waveform can be seen on the oscilloscope on the upper left.
Here’s a closer view.
With the amplifier off, the output power is just that of the oscillator. Although the wave should look like a sine wave, it’s noisy, and simply does not. While this is unacceptable if our goal is a clean radio signal with maximum efficiency, this is good enough to be heard at our target frequency. The PPV (peak-to-peak voltage) as seen on the screen is about 100mV. Since I’m using a x10 probe, this value should be multiplied by 10 = 1V. 1V PPV into 39 ohms is about 3 milliwatts! ((1/(2*2^.5))^2/39*1000=3.2). For the math, see this post
With the amplifier, the output is much more powerful. At 600mV peak-to-peak with a 10x probe (actually 6V peak-to-peak, expected because that’s the voltage of the 4xAAA battery supply we’re using) into 39 ohms we get 115 millivolts! (6/(2*2^.5))^2/39*1000=115.38.
Notes about power: First of all, the actual power output isn’t 115mW. The reason is that the math equations I used work only for pure sine waves. Since our transmitter has multiple waves in it, less than that power is going to produce our primary signal. It’s possible that only 50mW are going to our 29MHz signal, so the power output assessment is somewhat qualitative. Something significant however is the difference between the measured power with and without the amplifier. The 6x increase in peak-to-peak voltage results in a 36x (6^2) increase in power, which is very beneficial. I’m glad I added this amplifier! A 36 times increase in power will certainly help.
The final schematic is here:
6/14/2010 – THE BUILD
Last week I spoke with a student in the UF aerospace engineering department who told me he was working with a group of high school students to add a payload to a high-altitude balloon being launched at (and tracked by) NASA. We tossed around a few ideas about what to put on it, and we decided it was worth a try to add a transmitter. I’ll slowly add to this post as the project unfolds, but with only 2 days to prepare (wow!) I picked a simplistic design which should be extremely easy to understand by everyone. Here’s the schematic:
The code is as simple as it gets. It sends some Morse code (”go gators”), then a long tone (about 15 seconds) which I hope can be measured QRSS style. I commented virtually every line so it should be easy to understand how the program works.
#include <avr /io.h>
#include <util /delay.h>
char call[]={2,2,1,0,2,2,2,0,0,2,2,1,0,1,2,0,2,0,2,2,2,0,1,2,1,0,1,1,1,0,0};
// 0 for space, 1 for dit, 2 for dah
void sleep(){
_delay_ms(100); // sleep for a while
PORTA^=(1<<PA1); // "flip" the state of the TICK light
}
void ON(){
PORTB=255; // turn on transmitter
PORTA|=(1<<PA3); // turn on the ON light
PORTA&=~(1<<PA2); // turn off the ON light
}
void OFF(){
PORTB=0; // turn off transmitter
PORTA|=(1<<PA2); // turn on the OFF light
PORTA&=~(1<<PA3); // turn off the OFF light
}
void ID(){
for (char i=0;i<sizeof(call);i++){
if (call[i]==0){OFF();} // space
if (call[i]==1){ON();} // dot
if (call[i]==2){ON();sleep();sleep();} // dash
sleep();OFF();sleep();sleep(); // between letters
}
}
void tone(){
ON(); // turn on the transmitter
for (char i=0;i<200;i++){ // do this a lot of times
sleep();
}
OFF();sleep();sleep();sleep(); // a little pause
}
int main(void) // PROGRAM STARTS HERE
{
DDRB = 255; // set all of port B to output
DDRA = 255; // set all of port A to output
PORTA = 1; // turn on POWER light
while (1){ // loop forever
ID(); // send morse code ID
tone(); // send a long beep
}
}
I’m now wondering if I should further amplify this signal’s output power. Perhaps a 74HC240 can handle 9V? … or maybe it would be better to use 4 AAA batteries in series to give me about 6V. [ponders] this is the schematic I’m thinking of building.
UPDATE
This story was featured on Hack-A-Day! Way to go everyone!
I found a way to quadruple the output power of my QRSS transmitter without changing its input parameters. Thanks to a bunch of people (most of whom are on the Knights QRSS mailing list) I decided to go with a push-pull configuration using 2 pairs of 4 gates (8 total) of a 74HC240. I’ll post circuit diagrams when I perfect it, but for now check out these waveforms!
First of all, this is the waveform before and after amplification with the 74HC240. I artificially weakened the input signal (top) with a resistor and fed it to the 74HC240. For the rest of the images, the input is 5v p-p and the output is similar, so amplification won’t be observed. The wave I’m starting with is the output of a microcontroller which is non-sinusoidal, but this can be fixed later with lowpass filtering.
Here you can see the test circuit I’m using. It should be self-explanatory.
Here’s the output of the microcontroller compared to the in-phase output of the 74HC240
Here are the two outputs of the 74HC240. 4 of the gates are used to create output in-phase with the input, and the other four are used to create out-of-phase wave. Here are the two side by side. The top is 0 to 5v, the bottom is 0 to -5v, so we have a push-pull thing going on… woo hoo!
The waves, when overlapped, look similar (which I guess is a good thing) with a slight (and I mean VERY slight) offset of the out-of-phase signal. I wonder if this is caused by the delay in the time it takes to trigger the 74HC240 to make the out-of-phase signal? The signal I’m working with is 1MHz.
Okay, that’s it for now. I’m just documenting my progress. 73
Haray! I’m making awesome progress with my QRSS transmitter design. Because my current transmitter (previous few posts) was randomly freezing-up (likely due to the oscillator stopping its oscillating due to being overloaded) so I moved the oscillator from in-chip to an external oscillator. It’s been made small enough to fit in an altoids tin, and I already tested it with the solar panel and it works! Awesome! Here are some photos. Again, when I perfect the design I’ll post final schematics.
Sticking out are wires for power and an antenna on each side. The goal is to hang the device between two trees by its own antenna.
That’s my new chip development board. I made it with what I needed on it. It’s so convenient! It uses 5v of power from the USB port too!
Alltogether I’ve tested the device and confirmed it transmits radio when the solar panel is illuminated. I’m thinking of making it more effective by adding more panels… but that’s it for now!
I re-wrote the code from the previous entry to do several things. Once of which was to make a gator rather than a fish. It’s more appropriate since I’m planning on housing the transmitter at the University of Florida. To do it, I drew a gator in paint and wrote a python script to convert the image into a series of points. I’ll post it later. One thing to note was that size was a SERIOUS issue. I only have two thousand bytes of code, and every point of that gator was a byte, so it was a memory hog. I helped it dramatically by using repeating segments wherever possible, and some creative math to help out the best I could (i.e., the spines on the back) Here’s what it looks like, and the code below it…
#include <avr/io.h>
#include <util/delay.h>
// front top LED - PA0
// inside top LED - PA1
// inside bot LED - PA2
// front bot LED - PA3
unsigned long int t_unit; // units of time
const int tDit = 100; //units for a dit
const int tDah = 255; //units for a dah
char fsk; // degree of frequency shift to use for CW
char fsk2; // degree of frequency shift to use for HELL
char light = 0; // which lights are on/off
void delay(){
_delay_loop_2(t_unit);
}
void blink(){
return;
if (light==0){
PORTA|=(1<<PA0); //on
PORTA|=(1<<PA1); //on
PORTA&=~(1<<PA2); //off
PORTA&=~(1<<PA3); //off
light=1;
} else {
PORTA|=(1<<PA2); //on
PORTA|=(1<<PA3); //on
PORTA&=~(1<<PA0); //off
PORTA&=~(1<<PA1); //off
light=0;
}
}
void tick(unsigned long ticks){
while (ticks>0){
delay();
delay();
ticks--;
}
}
void pwm_init() {
//Output on PA6, OC1A pin (ATTiny44a)
OCR1A = 0x00; //enter the pulse width. We will use 0x00 for now, which is 0 power.
TCCR1A = 0x81; //8-bit, non inverted PWM
TCCR1B = 1; //start PWM
}
void set(int freq, int dly){
OCR1A = freq;
tick(dly);
}
void fish(){
char mult = 3;
char f2[]={2, 3, 4, 5, 6, 7, 4, 3, 7, 4, 7, 7, 6, 5, 4, 3, 2, 2, 2, 3, 3, 3, 2, 2, 2, 3, 3, 3, 2, 2, 2, 3, 4, 5, 6, 7, 8, 4, 9, 5, 9, 6, 9, 6, 9, 6, 9, 8, 8, 7, 7, 6, 5, 4, 3, 3, 3, 4, 5, 5};
for (int i=0;i<sizeof(f2);i++) {
OCR1A = f2[i]*mult;
blink();
tick(20);
OCR1A = 1*mult;
blink();
tick(20);
}
char f3[]={1,2,3,4,3,2};
char offset=0;
while (offset<9){
for (char j=0;j<3;j++){
for (char i=0;i<sizeof(f3);i++){
char val = (f3[i]+5-offset)*mult;
if (val<mult || val > 10*mult){val=mult;}
OCR1A = val;
blink();
tick(20);
OCR1A = 1*mult;
blink();
tick(20);
}
}
offset++;
}
}
void id(){
char f[]={0,0,1,2,0,1,2,2,2,0,1,1,1,1,2,0,1,1,1,2,0,2,1,1,0,0};
char i=0;
while (i<sizeof(f)) {
blink();
if (f[i]==0){OCR1A = 0;tick(tDah);}
if (f[i]==1){OCR1A = fsk;tick(tDit);}
if (f[i]==2){OCR1A = fsk;tick(tDah);}
blink();
OCR1A=0;
tick(tDit);
i++;
}
}
void slope(){
char i=0;
while (i<25){
OCR1A = 255-i;
i++;
}
while (i>0){
i--;
OCR1A = 255-i;
}
}
int main(void)
{
DDRA = 255;
blink();
pwm_init();
t_unit=1000;fsk=10;id(); // set to fast and ID once
//fsk=50;//t_unit = 65536; // set to slow for QRSS
t_unit=60000;
while(1){;
fish();
id();
}
return 1;
}
Finally! After a few years tumbling around in my head, a few months of reading-up on the subject, a few weeks of coding, a few days of bread-boarding, a few hours of building, a few minutes of soldering, and a few seconds of testing I’ve finally done it – I’ve created my first QRSS transmitter! I’ll describe it in more detail once I finalize the design, but for now an awesome working model. It’s ~100% digital, consisting of 2 ICs (an ATTiny44a for the PWM-controlled frequency modulation, and an octal buffer for the preamplifier) followed by a simple pi low-pass filter. I don’t want to waste time typing – let’s show some pics!
My desk is a little messy. I’m hard at work! Actually, I’m thinking of building another desk. I love the glass because I don’t have to worry (as much) about fires. Scary, I know…
This is the transmitter. The box is mostly empty space, but it consists of the circuit, an antenna connection, a variable capacitor for center frequency tuning, and a potentiometer for setting the degree of frequency shift modulation.
AMAZING! Yeah, that’s a fishy. Specifically a goldfish (the cracker). It’s made with a single tone, shifting rapidly (0.5 sec) between tones. So cool. Anyway, I’m outta here for now – getting back to the code! I think I’ll try to make a gator…
As it is, here’s the code. It sends my ID quickly, some fish, then my ID in QRSS speed using PWM. You can figure out the pinout… I’ll document it with circuit diagrams soon!
#include <avr/io.h>
#include <util/delay.h>
const int tDit = 270/3;
const int tDah = 270;
char fsk;
unsigned long int t_unit;
void delay(){
_delay_loop_2(t_unit);
}
void blink(){
PORTA^=(1<<0);
PORTA^=(1<<1);
PORTA^=(1<<2);
PORTA^=(1<<3);
}
void tick(unsigned long ticks){
while (ticks>0){
delay();
delay();
ticks--;
}
}
void pwm_init() {
//Output on PA6, OC1A pin (ATTiny44a)
OCR1A = 0x00; //enter the pulse width. We will use 0x00 for now, which is 0 power.
TCCR1A = 0x81; //8-bit, non inverted PWM
TCCR1B = 1; //start PWM
}
void set(int freq, int dly){
OCR1A = freq;
tick(dly);
}
void fish(){
char f[]={0,0,0,4,5,3,6,2,7,1,5,6,8,1,8,1,8,1,8,1,8,2,7,3,6,2,7,1,8,1,8,4,5,2,3,6,7,0,0,0};
char i=0;
while (i<sizeof(f)) {
i++;
OCR1A = 255-f[i]*15;
blink();
tick(20);
}
}
void id(){
char f[]={0,0,1,2,0,1,2,2,2,0,1,1,1,1,2,0,1,1,1,2,0,2,1,1,0,0};
char i=0;
while (i<sizeof(f)) {
blink();
if (f[i]==0){OCR1A = 255;tick(tDah);}
if (f[i]==1){OCR1A = 255-fsk;tick(tDit);}
if (f[i]==2){OCR1A = 255-fsk;tick(tDah);}
blink();
OCR1A = 255;tick(tDit);
i++;
}
}
void slope(){
char i=0;
while (i<25){
OCR1A = 255-i;
i++;
}
while (i>0){
i--;
OCR1A = 255-i;
}
}
int main(void)
{
DDRA = 255;
PORTA^=(1<<0);
PORTA^=(1<<1);
pwm_init();
t_unit=2300;fsk=50;id(); // set to fast and ID once
fsk=50;t_unit = 65536; // set to slow for QRSS
while(1){
id();
for (char i=0;i<3;i++){
fish();
}
}
return 1;
}
UPDATE I shouldn’t claim this idea as novel. After googling around, I found another guy who’s doing the same thing, and now that I read his page I remember reading his page before, which is likely where I got the idea in the first place. Anyhow, I want to give him credit ASAP and hope that my project can turn out successfully like his! His project page is at http://clayton.isnotcrazy.com/mept_v1.
Random idea: I only have a moment to write but I have to get this on paper. The core of any transmitter is usually an oscillator circuit, and in simple transmitters (i.e., QRSS devices), it’s often a crystal. Frequency adjustment is accomplished by adjusting capacitance to ground on one of the crystal legs. Simple oscillators such as the Colpitts design (based on an NPN transistor) are often used. (pictured) However in my quest to design a minimal-case long-distance transmitter, I’m trying to think outside the box. Although it’s relatively simple, that’s still several parts just to make an oscillating sine wave from a crystal. The result still has to be preamplified before sending the signal to an antenna. I’m starting to wonder about the oscillator circuitry inside a microcontroller which has the ability to be clocked by an external crystal. For example, take the pinout diagram from an Atmel ATTiny2313 AVR: :
See how pins 4 and 5 allow for a crystal, and pin 6 has a “CKOUT” feature?” I’m still not sure exactly what the waveform of its output looks like. The datasheet is almost intentionally cryptic. About the only thing I’ve been able to discover from the Internet is that it’s sufficient to clock another microcontroller. However, if it’s an amplified sine wave output, how cool is it that it might be able to produce RF at the same frequency at which it’s clocked?
Taking it a step further, I wonder if I could write code for the microcontroller to allow it to adjust its own clock speed / frequency output by adjusting capacitance on one of the legs of the crystal. A reverse-biased LED with variable voltage pressed against it from an output pin of the microcontroller might accomplish this. How cool would this be – a single chip transmitter and frequency-shifting keyer all in one? Just drop in a crystal of your choice and BAM, ready to go. Believe it or not I’ve tested this mildly and it’s producing enough RF to be able to be picked up easily by a receiver in the same room, but I’m still unsure of the power output or the waveform. If the waveform is an amplified sine wave I’m going to pass out. More likely it’s a weak sine wave needing a preamplifier still, or perhaps even amplified square waves in need of lowpass filtering…
My wife snapped a photo of me working! It’s a funny pic – I’m in my own little world somtimes…
Here’s the power supply I’m using:
Here’s my transmitter breadboarded-out:
Alltogether:
And close to the oscilliscope…
Anyway, it’s just my thoughts tonight. I had to get it out. 73!
Okay, here’s a preview of my project plans for Christmas break! My goal is to make a fun and comfortably functional HF CW transmitter / direct-conversion receiver capable of working any HF band by merging the incredibly simple and cheap (~$10) Pixie II CW transceiver with a SI 570 digital programmable oscillator controlled by an ATTiny 2313 microcontroller! (it’s often used in SDRs controlled by USB as seen here).
AUDIO FILES!!! [80m.mp3] and [40m.mp3] are already ready already!
Preliminary work demonstrates a functional receiver powered by a crystal. I don’t think the currently-configured digital oscillator is putting out enough power to run the circuit, but it’ll take more time to get that up and running. For now, here are some photos of what I’ve got working and real sound clips of the thing are below. I’m happy with the case I built it in (thanks Ron!), and happy with my level drilling of the holes for power, output, and the CW key! Briefly, the device is powered by a 9V battery. It’s hooked up to an 80m dipole antenna. Output is fed into a computer sound card for amplification / PSK31 analysis. The internals reveal that it’s a simple circuit powered by a single crystal. More crystals are tucked in the case, stuck in foam, for easy transport. Decoding of PSK31 is happening here, using output from my circuit with the 40m crystal in it.
AUDIO FILES approx. 1 minute in length from my circuit:
>> [80m.mp3]
>> [40m.mp3]
A few weeks into dental school I feel I’m fairing decently. I have reached a point where I know everything will be okay, but am still disappointed at the [immense] amount of time it requires. There are so many things I wish I could do, but all of my projects need to be placed on a 4-year hiatus. I can bask in the satisfaction of the few projects I completed this summer, and I only hope that it’s enough to last me for four years. Dentistry, while important, is nothing more than emulation/repetition of what everybody else does. I simply have to satisfy my creative and ingenuitive desires in my hobbies, whatever they may be. For now, this website will cease to grow. Perhaps when I become more in control of my studies I will contribute to it, but in all likelihood I won’t be able to do anything worth writing about until 4 years from now [sigh]. With that being said, adieu, and goodnight.
I realized I never posted video of my finished prime number generator, so here it is. Full details are described on the project page. In brief, the 2-digit display on the left is the last two digits (in base-10, decimal) of a number currently being tested for primeness. This number is also displayed on the bottom red bar above the yellow lights (in base-2, binary). Once proven to be prime (by attempting to divide it by every number between 2 and its square root, every 1000th attempted number shown in yellow lights in binary), it’s loaded onto the top row of red lights (binary) and on the character LCD. N represents the Nth prime, with V representing its value. Half way through the video, the display says that the 16,595,044′th prime (N) equals 306,692,621 (V). Don’t believe it? Check my work.
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.The 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. [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:
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:
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!
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!
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:
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:
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:
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!
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.
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?
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\
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.
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.
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…
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.
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, "%.03f\t%.03f\t%.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)
My microcontroller-powered prime number generator/calculator is virtually complete! Although I’m planning on improving the software (better menus, the addition of sound, and implementation of a more efficient algorithm) and hardware (a better enclosure would be nice, battery/DC wall power, and a few LEDs on the bottom row are incorrectly wired), this device is currently functional therefore theoretically complete (I met my goal). This entry will serve as the primary reference page for the project, so I will provide a brief description of what it is and what it does. First, here’s a picture of the device in its current state (click to enlarge):
BRIEF DESCRIPTION: This device generates large prime numbers (v) while keeping track of how many prime numbers have been identified (N). The 5′th prime number is 11. Therefore, at one time this device displayed N=5 and V=11. N/V values are displayed on the 20×2 LCD. In the photo, the 16,521,486th prime is 305,257,039 (see for yourself!). The LCD had some history. In December, 2003 (6 years ago) I worked with this SAME display, and I even located the blog entry on November 25′th, 2003 where I mentioned I was thinking of buying the LCD (it was $19 at the time). Funny stuff. Okay, fast forward to today. Primes (Ns and Vs) are displayed on the LCD, but what’s with all those other LED lights? I’ll tell you:
In short, each row of LEDs displays a number. Each row of 30 LEDs allows me to represent numbers up to 2^31-1 (2,147,483,647, about 2.15 billion) in the binary numeral system. Since there’s no known algorithm to generate prime numbers (especially the Nth prime), the only way to generate large Nth primes is to start small (2) and work up (to 2 billion) testing every number along the way for primeness. The number being tested is displayed on the middle row (Ntest). The last two digits of Ntest are shown on the top left. To test a number (Ntest) for primeness, it is divided by every number from 2 to the square root of Ntest. If any divisor divides evenly (with a remainder of zero) it’s assumed not to be prime, and Ntest is incremented. If it can’t be evenly divided by any number, it’s assumed to be prime and loaded into the top row. In the photo (with the last prime found over 305 million) the device is generating new primes every ~10 seconds. Not bad! Let’s discuss technical details.
I’d like to emphasize that this device is not so much technologically innovative as it is creative. I made it because no one’s ever made one. It’s not realistic, practical, or particularly useful. It’s just unique. The brain behind it is an ATMEL ATMega8 AVR microcontroller (What is a microcontroller?), the big 28-pin microchip near the center of the board. (Note: I usually work with ATTiny2313 chips, but for this project I went with the ATMega8 in case I wanted to do analog-to-digital conversions. The fact that the ATMega8 is the heart of the Arduino is coincidental, as I’m not a fan of Arduino for purposes I won’t go into here).
I’d like to thank my grandmother’s brother and his wife (my great uncle and aunt I guess) for getting me interested in microcontrollers almost 10 years ago when they gave me BASIC Stamp kit (similar to this one) for Christmas. I didn’t fully understand it or grasp its significance at the time, but every few years I broke it out and started working with it, until a few months ago when my working knowledge of circuitry let me plunge way into it. I quickly outgrew it and ventured into directly programming cheaper microcontrollers which were nearly disposable (at $2 a pop, compared to $70 for a BASIC stamp), but that stamp kit was instrumental in my transition from computer programming to microchip programming.
The microcontroller is currently running at 1 MHz, but can be clocked to run faster. The PC I’m writing this entry on is about 2,100 MHz (2.1 GHz) to put it in perspective. This microchip is on par with computers of the 70s that filled up entire rooms. I program it with the C language (a language designed in the 70s with those room-sized computers in mind, perfectly suited for these microchips) and load software onto it through the labeled wires two pictures up. The microcontroller uses my software to bit-bang data through a slew of daisy-chained shift registers (74hc595s, most of the 16-pin microchips), allowing me to control over 100 pin states (on/off) using only 3 pins of the microcontroller. There are also 2 4511-type CMOS chips which convert data from 4 pins (a binary number) into the appropriate signals to illuminate a 7-segment display. Add in a couple switches, buttons, and a speaker, and you’re ready to go!
I’ll post more pictures, videos, and the code behind this device when it’s a little more polished. For now it’s technically complete and functional, and I’m very pleased. I worked on it a little bit every day after work. From its conception on May 27th to completion July 5th (under a month and a half) I learned a heck of a lot, challenged my fine motor skills to complete an impressive and confusing soldering job, and had a lot of fun in the process.
My most glorious summer yet is reaching its end. With about a month and a half before I begin dental school, I pause to reflect on what I’ve done, and what I still plan to do. Unlike previous summers where my time was devoted to academic/thesis requirements, this summer hosted a 9am-5pm job with time to do whatever I want to after. I’ve made great progress in the realm of microcontroller programming, and am nearing the completion of my prime number calculator. I’m very happy with its progress. I think it’s time for some photos.
Here I can be seen working on my prime number calculator. The primary display is nearing completion, and now it’s time to start wiring the buttons, switches, speaker, etc. Note the vintage scope in the background. In the photo it’s showing 60Hz (I couldn’t think of anything more profound to display?) which I’ll say is a representation of the fact that your body is continuously bombarded by electromagnetic radiation whenever you set foot in a house.
This is the current state of the back panel of the prime number calculator. It’s becoming quite complicated.
As you can see, most of the LEDs are working but I’m still missing a few 74hc595 shift registers. It’s not that they’re missing, so much as I broke them. (D’oh!) I have to wait for a dozen more to come in the mail so I can continue this project. Shift registers are also responsible for powering the binary-to-7-segment chips on the upper left, whose sockets are currently empty. Since this project is on pause, I began work hacking a VFD I heard about at Skycraft. It’s a 20×2 character display (forgot to photograph the front) and if I can make it light up, it will be gorgeous.
Here’s a high resolution photo of the back panel of the VFD. I believe it used to belong to an old cash register, and it has some digital interfacing circuitry between the driver chips (the big OKI ones) and the 9-pin input connector. I think my best bet for being able to control this guy as much as I want is to attack those driver chips, with help from the Oki C1162A datasheet. It looks fairly straightforward. As long as I don’t screw up my surface-mount soldering, and assuming that I come up with 65 volts to power the thing (!) I think it’s a doable project.
Update: I found a funny photo from field day. After the tents, antennas, and radios were mostly set up, everyone was exhausted. I was ready to make some contacts! I fired-up my ‘ol netbook and tried communicating over 40m using psk (a digital mode), a mode I’ve never used, with software I’ve never used, on a band I’ve never used. It wasn’t working either. I spent the first several hours in frustration because what I was trying to do wasn’t working, and I couldn’t figure out why. This photo was taken at the height of my frustration =o)
Here’s a rough approximation of the current schematic of the prime number calculator I’m working on. Last night I finished wiring all 12 shift registers for the primary display, so now it’s time to start working on software. Notice that we have a lot of pins free still. This will be advantageous if I decide to go crazy adding extraneous functionality, such as fancy displays (LCD?, 7-segment LEDs?, VFD?, all 3?!) or creative input systems (how about a numerical keypad?). After feeling the stink of paying almost $15 for 100′ of yellow, 24 gauge, solid-core wire from DigiKey I was relieved (and a little embarrassed) to find I could score 1,000′ of yellow, 24 gauge, threaded wire for $10 at Skycraft! Anyway, here’s the current schematic:
I’ll update my progress on this project as I go. I added a lot more light bars to the shift registers on my prime number generator project. I’m up to 5 daisy-chained shift registers completed (powering 40 LEDs) with 7 more to go! I’m using 22 gauge solid-core (fancy and expensive, from digikey, 100′ 14$!) wire for the back of this project. Being that I plan to keep it for many years, I want it to look crazy awesome. Remember, I’m only about 1/3 done so far…
I powered the device up and it produced proper output. Yay! I was so discouraged yesterday when I wired-up an entire row (the top one), powered it on, and 1/2 the LEDs didn’t work. At first I thought it was software, but then I realized that I burned the LEDs out in the soldering process by getting them too hot. I had to de-solder EVERYTHING, rip out the destroyed LED bars, and start over. I’ll have to pick up some more light bars at Skycraft soon. This is what it looks like currently:
I’m making this project a priority because I only have a few weeks before I move to Gainesville, FL for dental school (the cutoff date for all electronics/radio/programming projects). I’ll be busy the next few days with other obligations (work, apartment hunting, field day, etc.) but I hope to resume this project soon.
UPDATE (June 26, 2009 @ 7:30pm): I finished wiring all the light bars I have. I need to purchase 3 more 10-led bars at Skycraft to replace the ones I melted with my soldering iron. D’oh! Anyway, here’s the beaut:
Bitwise programming techniques (manipulating binary numbers) is simple in theory, but it’s often hard to remember how to do specific tasks if you don’t do them often. Recently in my microcontroller programming endeavors (where you’re pressed to conserve every bit of memory) I’ve needed to perform a lot of bitwise operations. If I’m storing true/false (1-bit) information in variables, it’s a waste to assign a whole variable to the task (even a char, the smallest variable in C is a waste because it uses 8 bits of memory!). When cramming multiple values into individual variables, it’s nice to know how to manipulate each bit of a variable.
Questions like “how do I retrieve the value of a certain bit in a variable”, “how do I set the value of a certain bit in a variable”, and “how do I flip a certain bit in a variable” can eventually be answered by twiddling around with bitwise operators in C, but often the solutions you randomly discover this way are not elegant or efficient. This afternoon I ran across the following chart on an Arduino help site and although I’m not a fan of Arduino, I can certainly appreciate the chart. I hope you find it as useful as I did.
y = (x>>n)&1; // stores nth bit of x in y. y becomes 0 or 1.
x&=~(1<<n); // forces nth bit of x to be 0. all other bits left alone.
x&=(1<<(n+1))-1; // leaves lowest n bits of x; all higher bits set to 0.
x|=(1<<n); // forces nth bit of x to be 1. all other bits left alone.
x^=(1<<n); // toggles nth bit of x. all other bits left alone.
x=~x; // toggles ALL the bits in x.
I’m currently challenging myself by creating a microcontroller-based project a few orders of magnitude more complex than anything I’ve ever done before. Although this is probably on par with projects you might see being created by senior electrical engineering seniors, keep in mind that I have no formal training in engineering, and that my MS is in molecular biology. I just started learning about circuitry / microcontrollers a few months ago, and challenge myself to learn more by continually attacking greater and greater challenges. Here’s what I began working on last night:
This if the first entry describing the creation of my non-prototype microcontroller-powered prime number generator. I made a proof of concept device a few weeks ago which calculates prime numbers (up to 2^25, about 33.5 million) and displays the results in binary form using 25 LEDs assembled in a 5×5 matrix. I added an extra column of 5 LEDs for a final matrix size of 6×5, illuminated by multiplexing through 11 IO pins of an ATMEL ATTiny2313. My new project will do the same thing, except it can calculate prime numbers up to 2^30 (over 1 billion!). Instead of only displaying 1 number, it will display 3 numbers (last prime, test number, and the divisor) using 90 LEDs. The picture above is of the main circuit board before I began soldering. The empty sockets will house a combination of 8-bit shift registers, binary-to-digital converters, and 7-segment display drivers all powered by an ATMEL ATMega8 microcontroller crystal-clocked at 10.042 MHz (arbitrary, but stable). Here’s what the underside looked like before I began soldering:
I anticipate that this project will develop into a soldering nightmare. The board is nearly too small as it is, and I don’t have good wire for soldering. (I’m actually using the small wires from an old phone cord right now.) I included a potentiometer, 2 buttons, and 3 switches to aid with various settings (brightness, menus, etc). 3 Rows of LEDs (60 pins each) requires 12 shift registers (16 pins each) plus the 28-pin microcontroller makes about 400 solder points (YIKES!), so I anticipate the underside of this project will quickly grow to become a daunting mess of wires. Last night I finished the connections necessary to program the microcontroller, and for the microcontroller to control a single 8-bit shift register, allowing the first 8 LEDs of the first number to be controlled. Here’s what the soldering looked like. Remember, the dense clump of connections only controls 8 LEDs, so multiply this by more than 10 and that’s what I’ll have to do JUST to power the display.
After programming with a straight-through DAPA style parallel-port programmer, I was able to shift data out to the single HC595 I had wired. I spent hours banging my head against the wall because nothing I did in the software would make the LEDs illuminate. I thought I was sending signals to the shift register wrong, or that I soldered something wrong. I finally concluded that somehow (probably when I was troubleshooting by applying 5v of power directly to the pins of the shift registers) I managed to burn out all 8 LEDs of the first light bar. I had to de-solder ALL of the connections you see in that picture, replace the bar with a new one (thank goodness I had an extra), and re-solder everything. I have a feeling that by the end of this project, I’ll be an expert at soldering. Here’s the program running controlling the first 8 bits only:
Supposedly the hard part is done for the display. The software was written in such a way that it will automatically begin lighting-up more LEDs as I wire them. The small node for the first shift register and 8 LEDs will be identical to the other 11, and as I solder them one by one I’ll get closer to my end goal. It will probably be many hours of soldering. In retrospect, I wish I purchased a bigger perfboard. Actually, in retrospect I wish I made a PCB!!
Update: After a few more hours (of soldering, troubleshooting, desoldering, and rewiring, and resoldering) I have my second 8-bit segment working. Note all the yellow (newly-added) wires. Multiply this by 10, and that’s what I have left to wire for the display alone!
For the last several weeks I’ve been hacking together a small prototype of a microcontroller (ATTiny2313)-powered prime number generator. I can finally say that it (the prototype) is complete, functional, and elegant [get the song I'm referencing while it's up!]. The name says what it does, so I won’t waste time describing it. If you’re interested in how it does what it does, check out the other posts with the same category tag for a full (and I mean full as in “more than you ever wanted to know”) description. A schematic is soon to come. Code is below. For now, here’s a video of the completed project:
And the source code I used. I chose the ATTiny2313 because it was cheap (~$2) and has 18 IO pins to work with. I had to fight memory usage every step of the way. I’m limited to 2kb, and this program compiles within 1% of that! For future projects (more LEDs, more menus) I’ll use the 20-pin and/or 40-pin ATMega series. I’m thinking about having one be in charge of the display (multiplexing) leaving the other to think about generating prime numbers.
#define F_CPU 10240000
#include <avr/interrupt.h>
#include <util/delay.h>
#include <math.h>
// ~~~ PORT D ~~~
#define r1 0b00000100
#define r2 0b00000010
#define r3 0b00001000
#define r4 0b00100000
#define r5 0b00010000
#define f1 0b01000000
#define id 0b00000001
#define f0 0b10111111
// ~~~ PORT B ~~~
#define c1 0b00010000
#define c2 0b00000001
#define c3 0b00000010
#define c4 0b00000100
#define c5 0b00001000
char delay=1;
int redo=0;
char rows[] = {r1,r2,r3,r4,r5};
char cols[] = {c1,c2,c3,c4,c5};
char vals[] = {0,0,0,0,0};
char funcs=f1+id;
unsigned long showNum=5;//33554431;
void displayNum();
void incrimentNum();
void menu_pause();
void menuCheck();
char button();
char isPrime(unsigned long);
unsigned int twoTo(char);
int main(void) {
DDRD = r1+r2+r3+r4+r5+f1+id;
DDRB = c1+c2+c3+c4+c5;
DDRB &= ~_BV(DDB7);
PORTB = 0;
PORTD = 0;
unsigned int i=0;
int j;
//convertNum();
//showNum=5;
while (1) {
showNum+=1;
if (isPrime(showNum)){
convertNum();
}
for (j=0;j<redo;j++) {
displayNum();
menu();
}
if (i%10==0){funcs^=r1;
funcs^=id;
if (i%100==0){funcs^=r2;
if (i%1000==0){funcs^=r3;i=0;
}
}
}
i++;
}
return 0;
}
char isPrime(unsigned long test){
if (test%2==0) return 0;
unsigned long div = 3;
while(div*div<test+1){
if (test%div==0) return 0;
div+=2;
displayNum();
}
return 1;
}
void menu(){
//return;
char j,but;
but = button();
if (but==0) return;
else if (but==1){
while (1) {
PORTD=id;
if (PINB & _BV(PB7)) {
funcs=f1;
for (j=0;j<200;j++){
if (j%25==0) funcs^=r1;
displayNum();
}
}
else {
if (redo==0) redo=200;
else redo=0;
_delay_ms(300);
return;
}
}
}
return;
}
char button(){
PORTD=id;
if (PINB & _BV(PB7)) return 0; // not pressed
_delay_ms(1000);
if (PINB & _BV(PB7)) return 1; // pressed
return 2; // held down
}
void convertNum(){
char col,row,rowcode;
unsigned long msk=1;
for (col=0;col<5;col++){
rowcode=0;
for (row=0;row<5;row++){
if (showNum&msk) rowcode+=rows[row];
msk = msk < < 1;
}
vals[col]=rowcode;
}
return;
}
void displayNum(){
char i,j;
PORTB = 0b0;
PORTD = funcs;
_delay_ms(delay);
for (i=0;i<5;i++){
PORTD = vals[i];
PORTB = cols[i];
_delay_ms(delay);
}
return;
}
I decided to crank up the power on my prime number identifier and attempt to document its efficiency in both C (GCC) and Python. I used the [very inefficient] code from the previous entry, modified it to test every integer up to 1×10^8, and let them both run on my laboratory computer overnight (an AMD Athlon 64-bit X2 Dual Core 2 GHz PC with 2GB of ram). The Python program took 3.24 processor hours, and the C program took 1.85 processor hours to complete. Each step along the way each program recorded how long it took to test the last 10,000 integers, creating a logfile with 1,000 data points, which can be easily graphed (see previous entry). On this nice, fast computer the data look very clean and curve-like. I curve-fitted the data using my new favorite website, ZunZun.com. Here’s what I came up with…
Coeffecients:
### C (GCC) ###
a = 1.4193535434065586E-03
b = -1.2708466972699874E-07
c = -4.9489218065978358E-16
d = 1.3032518272036044E-11
e = 1.3231610935638881E-06
### PYTHON ###
a = 2.6579973323520396E-03
b = -2.8299592901357803E-07
c = -1.2080374779761445E-15
d = 3.0281624700595501E-11
e = 2.4778595094623538E-06
### Generating Data Points: ###
Ys=[]
for x in range(len(values)):
Ys.append(a*(x**.5)+b*(x)+c*(x**2)+d*(x**1.5)+e)
Note that this fitted curve is the representation of how long (in seconds, vertical axis) it takes to systematically test 10,000 integers for primeness (starting at an integer on the horizontal axis), This does NOT represent how long it takes to reach a certain X. To properly calculate this, one would need to integrate the equation. This would allow for the easy prediction of how long it would take to reach a certain integer (the solution would be the integral of the provided equation from 0 to the integer). I’ll let someone else attack that. It’s time for me to get back to work!
While working on my current microcontroller project I’ve become a little obsessed with the prospect of rapidly generating lists of prime numbers. I’ve been testing various strategies for speeding up the process using logic modifications to a base concept. I’ve been doing my conceptual work in Python because it’s so fast and easy to rapidly develop applications with. Now that I have a good understanding of my strategy (code scheme) of choice, I’m starting to wonder how much better the same code would run in C. It’s no secret that Python’s strength is its versatility and ease of development of scritps, not its speed. C on the other hand can be very cumbersome and awkward to develop with, but its compiled code is very efficient so it’s better suited for mathematical applications which require billions of repetitive tasks.
To visualize the speed difference between C and Python, I wrote the same prime-number-testing code in both languages and timed their output. Basically I wrote identical code in each language (variable names and functions are the same, almost a line-by-line translation) and timed how long it took to find all prime numbers up to 10,000,000. (It reported the length of runtime at every 100,000 integers, totalling 100 time points).
The results:
The black line is code written in C and the blue line is Python. According to this output, Pythin is about 4 times slower than C at identifying primes utilizing the identical method. I’m not a coding expert, and there may be better ways to code/compile things in each language, so I’ll asy this is an indication of speed rather than a detailed, scientific study comparing the two languages. For fairness, I’ll provide my code.
Python code to generate prime numbers up to 10^7:
import time
def isPrime(testPrime):
for div in xrange(2,int(testPrime**.5)+1):
if testPrime%div==0: return False
return True
def testSpeed(startAt, stopAt):
primesFound=0
startTime = time.clock()
for testPrime in xrange(startAt,stopAt):
if isPrime(testPrime):
primesFound+=1
s="%d,%d,%d,%fn"%(primesFound,startAt,stopAt,time.clock()-startTime)
print s
f=open("log_py.txt",'a')
f.write(s)
f.close()
return
for i in range(100):
testSpeed(100000*i,100000*(i+1))
raw_input()
C code to generate prime numbers up to 10^7:
#include <stdio.h>
#include <math.h>
#include <time.h>
char isPrime(int testPrime){
int div;
for (div=2;div<(int)sqrt(testPrime)+1;div++){
if (testPrime%div==0) return 0;
}
return 1;
}
void testSpeed(unsigned int startAt, unsigned int stopAt){
int primesFound=0;
char buf[256],len;
unsigned int testPrime;
clock_t startTime = clock();
for(testPrime=startAt;testPrime<stopAt;testPrime++){
if (isPrime(testPrime)) primesFound++;
}
len=sprintf(buf,"n%i,%u,%u,%f",primesFound,startAt,stopAt,
(float)(clock()-startTime)/CLOCKS_PER_SEC);
printf("%s",buf);
FILE *fptr = fopen("log_c.txt","a");
fputs(buf,fptr);
fclose(fptr);
return;
}
int main(){
char i;
for (i=0;i<100;i++){
testSpeed(100000*i,100000*(i+1));
}
return 0;
}
Python code to graph the difference utilizing MatPlotLib:
import pylab
def graphIt(fname,col):
f=open(fname,'r')
data=f.readlines()
f.close()
val,time=[],[]
for line in data:
if line.count(',')<3: continue
line=line.strip('n').split(',')
val.append(int(line[1]))
time.append(float(line[3]))
pylab.plot(val,time,color=col)
return [val,time]
pylab.figure()
pylab.subplot(311)
v,tc=graphIt('log_c.txt','k');
v,tp=graphIt('log_py.txt','b');
pylab.title("C vs Python: test 10,000 numbers for primeness")
pylab.ylabel("time (seconds)")
pylab.xlabel("starting value")
pylab.grid(alpha=.3)
pylab.subplot(312)
pylab.grid(alpha=.3)
graphIt('log_c.txt','k');
graphIt('log_py.txt','b');
pylab.ylabel("time (seconds)")
pylab.xlabel("starting value")
pylab.semilogy()
pylab.subplot(313)
ratios=[]
for x in range(len(v)):
ratios.append(float(tp[x])/tc[x])
pylab.plot(v,ratios,'k')
pylab.ylabel("speed ratio")
pylab.xlabel("starting value")
pylab.axis([None,None,None,5])
pylab.grid(alpha=.3)
pylab.show()
I don’t think I’ll keep the slang title McPPNG for my Microcontroller Powered Prime Number Generator, however I have some good news to report. I have a FULLY FUNCTIONAL prototype of this project and it looks better than I ever could have imagined, especially for the prototype. There are a few more features I want to add, and I want to work on the code some more, but I hope to be done tomorrow. I have a ton more photos taken and some videos in the works, but I’m saving them until this prototype is complete before I share it. The coolest part is that I’ve included an internal button which drives a pause/resume and speed-controller menu based upon the length of button presses! There’s a lot of awesome stuff I want to write, but once again, I’ll save it for the completed project page. Here are some pics to entice your curiosity… Before you lose your cool about the number on the screen not being prime, calm down. I mislabeled all of the LEDs. The first LED should be 2^0 (1), and the second should be 2^1 (2), etc. Also, 2^22 and 2^23 are mislabeled – oops! But the thing really does generate, differentiate, and display [only[ prime numbers. Once again, videos (including demonstration of the menus and the programming options) and source code will be posted soon.