Heart Rate Monitor
This project uses a sensitive light-detecting resistor to detect small changes in the light beaming through your finger as your blood pumps through tiny arteries in the finger. This device is not the same as the heart rate monitors typically used in hospitals because those use skin probes that detect changes in electrical activity as the heart beats. This heart rate monitor actually "sees" your pulse through the body, so it can be used on a finger, toe, or even your earlobe.
This project can be built on a small breadboard with a few inexpensive components in a day or two. The completed heart rate monitor is very stable and as accurate as any you would find built into high-quality exercise equipment. There is much room for improvement and modification owing to the simplicity of the microcontroller code, so you can easily adapt this device to just about any hardware or data-logging device.
Adapted from B. Graham and K. McGowan, Mind Performance Projects for the Evil Genius, McGraw-Hill Education, 2010
Things You Will Need
|IC1||1||LM324 quad op amp||Electronics supplier|
|IC2||1||Atmega88 microprocessor||Computer store, Electronics supplier|
|XT1||1||4 MHz crystal||Electronics supplier|
|Q1, Q2, Q3||3||2N3904 or similar NPN transistor||Electronics supplier|
|LDR||1||Light-dependent resistor||Dollar store, electronics supplier|
|VR1||1||100K variable resistor||Electronics supplier|
|LED1||1||High-brightness red LED||Electronics supplier|
|LED2, LED3||2||Low-current LED||Electronics supplier|
|R1, R4, R8, R11, R12, R17, R18, R19, R20, R21, R22, R23||12||1K resistor||Electronics supplier|
|R3||1||2.2K resistor||Electronics supplier|
|R2, R7, R10, R14, R15, R16||6||10K resistor||Electronics supplier|
|R9||1||20K resistor||Electronics supplier|
|R6||1||47K resistor||Electronics supplier|
|R5, R13||2||220K resistor||Electronics supplier|
|C1||1||1 μF electrolytic capacitor||Electronics supplier|
|C2||1||0.1 μF ceramic capacitor||Electronics supplier|
|C3, C4||2||100 μF electrolytic capacitor||Electronics supplier|
|C5||1||1000 μF electrolytic capacitor||Electronics supplier|
|Battery||1||9-V battery||Drug store, Electronics supplier|
|Breadboard||1||Solderless breadboard||Electronics supplier|
|Wire||Category 5 (Cat5) wire, cut and stripped for use in the breadboard||Electronics supplier|
|Finger clip||1||Hair clip, cut as needed to fit a finger||Dollar store, Beauty supply store|
|Performance (perf) board||1||Perf board at least 4 x 4 inches||Electronics supplier|
|Plastic box with cover||1||Small plastic electrical box||Hardware store|
- Soldering equipment
- Dull blade for stripping wire
BE CAREFUL! Soldering irons and solder can get very hot and cause serious burns. Always use caution when using any tool that can burn or cut you. Adults should supervise children's use of tools.
1. Assemble the circuitry on a breadboard
The heart rate monitor is a combination of analog and digital circuitry (see Fig. 1). The LM324 quad op amp (IC1) forms a sensitive amplifier and a low-pass filter that will "lock" onto tiny variations in voltage that fall within the typical heart rate frequencies. The varying voltage comes from the light-dependent resistor (LDR), which changes a very small amount each time blood pumps through the tiny arteries in between the visible red LED and the LDR surface.
After the signal is conditioned by the low-pass filter, it is fed out of the op amp into the analog input of an Atmega88 microcontroller, where the program counts the beats and keeps a running average of beats per minute. The resulting heart rate is displayed as a three-digit number on the triple seven-segment LED displays. Because the microcontroller was added to this device after it was designed, you could decide to leave out the digital part of this project and take the output directly from the op amp and feed it either to an LED that will blink with each heartbeat or to some other data logger or device of your own making.
The output from the op amp is very close to being TTL (transistor, transistor logic) compliant, so it could be adapted easily to drive most digital equipment requiring a 5-V TTL signal input. It is recommended that you build the analog part of this circuit first, because it is the most complex part of the circuit. LED2 will blink each time a heartbeat is detected, so this makes debugging the system much easier. When you get to the digital part of the circuit, another debugging LED (LED3) will blink each time the microcontroller receives a pulse signal from the analog section of this project.
The light-dependent resistor (LDR) is a commonly used semiconductor that can be found n practically every device that needs to respond to some change in ambient light levels. Street lights, security lights, light meters, and even dollar-store night lights such as the one shown in Fig. 2 will contain an LDR. You also can purchase an LDR from most electronics suppliers, but the dollar-store night light is more convenient, costs about the same as a bare LDR, and gives you a few other bits for your junk box, such as a triac and a few resistors. An LDR is easy to identify because it will be visible to the light it must sense and will look like a tiny button with a snakelike pattern on its surface. This can be seen under the tiny plastic eye in the figure.
The snaking track along the surface of the LDR shown in Fig. 3 is connected to the two pins that exit the device. As light strikes the surface of the LDR, the resistance drops greatly, acting like an analog light switch of sorts. Notice how amazingly simply the circuit ripped from the night light really is—just an LDR feeding a triac through a resistor to turn on the 120-V ac light bulb. To remove the LDR, either use a soldering iron to release it from the circuit, or just bend the leads back and forth until the device breaks free.
The breadboarded heart rate monitor is shown in Fig. 4. As you can see, most of the real estate has been taken up by the triple-digit LED display and all its supporting hardware. The analog pulse-detection circuitry at the top of the breadboard consists of the LM324 op amp and a few resistors and capacitors. The large 1000-μF capacitor (C5) shown under the 9-V battery is ;necessary only if you are using a dc poser adapter or find that the pulse-detection circuit falsely triggers when the LEDs change. The analog circuit is so sensitive that the change in voltage from a single LED coming on actually can trigger the circuit into an oscillation if your power supply is not rock solid.
The huge capacitor acts as a power-supply buffer to help filter out any tiny fluctuations caused by the LED display circuitry. The circuit will work fine without the large capacitor as long as it runs from a battery pack. Using a dc adapter will require the large filter capacitor to be included.
The component to the right of the 9-V battery is an LM7805 regulator to bring the supply voltage down to 5 V, as needed by the microcontroller. If you are building only the analog part of this circuit, it actually can be run from a supply voltage as high as 12 V directly. When first powered up, the LED display will read “060” and not change until a pulse is detected. Every 10 seconds, the average is recalculated, and then the readout is ramped up or down by a value of one on each following pulse. This slow ramp helps to filter out glitches in the average owing to movements of the body that might be detected as pulses. There is a lot of room to improve the microcontroller code because it was made to be short and simple to keep it printable.
2. Test the analog circuit
To test the analog circuit, place a high-brightness red LED over the LDR, as shown in Fig. 5, so that there is just enough room to place your finger in between the light and the LDR. You will have to keep your hand extremely still and at the same time take your own pulse using your other hand on your neck. When the analog circuit is functioning properly, the pulse-indicator LED (LED2) will flash almost at the same time that you feel the pulse in your neck.
If you just can’t hold your hand still enough over the breadboard, move ahead and build the finger clip, which will eliminate false readings from accidental movement. You also will have to adjust the variable resistor (VR1) to some setting near its center of rotation to get the strongest reading on the pulse-indicator LED. The variable resistor biases the LDR in either the positive or negative direction, so you do not have to be overly concerned about the type of visible red LED you choose. The LED does have to be fairly bright and needs to be red because that is the color that passes through our skin the easiest. White light also will work, but green and blue LEDs will not work very well at all in this system.
You may find that the system works perfectly on the breadboard as long as you can keep your hand still enough not to send false readings to the analog circuit. If you rest the side of your hand on the desk, and place your thumb between the red LED and the LDR, then you will rarely get a false reading.
Fig. 6 shows the result of 60 seconds of testing, getting a heart rate of 72 beats per minute, which matched the pulse on the subject's neck. At this point, the analog circuit is fairly quirky when it comes to hand movement or even ambient light changes in the room, so some type of finger clasp will be needed to make the system much more stable.
You may want to use a hair clip, like the one shown in Fig. 7, that fits around your finger or thumb perfectly, and is not so tight as to became uncomfortable. There are hundreds of devices you could invent to attach the visible red LED and LDR to your body, and remember that this device also will work through your earlobe. The method used to position the sensor should be both comfortable and secure enough not to move around easily.
To make the finger clip as compact as possible, trim the two ends of the hair clip, as shown in Fig. 8. The clip now will work on any finger or on a large toe, although it may work best on the thumb because of its large artery. Shooting the beam through the area of your fingernail may also be the most effective place to get a strong and solid reading on the pulse-indicator LED.
Place the LDR and the visible red LED at each end of the hair clip, as shown in Fig. 9, so that the beam is directed onto the surface of the LDR once the clip is placed around a finger or thumb. Your LDR may or may not have a plastic bubble over the top of the detector surface. If moisture from your skin affects an LDR without a lens, then a small piece of tape or clear plastic placed over the surface of the LDR solves the problem easily. Don’t worry if the LED beam does not always line up perfectly onto the surface of the LDR, because your finger will diffuse the light anyway before it reaches the sensor. Because the analog circuitry is so sensitive, it takes very little light to actually cause the pulse detector to operate.
To ensure that the finger clip is working as well as or better than the original breadboard sensor, place it into the circuit for a reading. The variable resistor (VR1) may need a bit of a tweaking, because the finger clip may be better aligned and pass more light to the LDR. After 10 seconds or so, the heart rate display should climb from the default power-on value of 60 to an elevated number of beats per minute (see Fig. 10). It may surprise you how little it takes to make a pulse shoot up from the normal (basal) rate.
3. Transfer the circuit to a perf board
Now the task of moving the circuit from the breadboard to a more suitable home will begin. You will need a performance (perf) board that is at least 4 by 4 in to contain all the components in this project. However, take your time with the wiring, and start with the analog part of the circuit, just as was done on the breadboard. There are a lot of wires!
To keep the circuit board as small as possible, you may want to replace the LED displays with smaller units. If you have some talent with a soldering iron and PCB-making equipment, you probably could shrink the heart rate monitor down to the size of a matchbox, but you might prefer the “old school” perf board method of prototyping, which is very easy to modify and correct errors.
Fig. 11 shows the completed perf board ready to find a new home inside a plastic electrical box found at a local hardware store. In this example, the variable resistor (VR1) was replaced with a board-mounted potentiometer because it only required adjustment once for the new clip-mounted optical sensor. If you plan to experiment with different sensor styles, then mounting the variable resistor externally might be a better option because it could require tweaking depending on the angle and amount of light hitting the LDR.
The completed heart rate monitor shown in Fig. 12 performs as well as any store-bought unit but allows easy modification to adapt to just about any device you can imagine. There is plenty of room inside the cabinet for a small rf module, which would allow the heart pulses to be sent wirelessly to a computer for more advanced data logging. Because there are free IO pins on the microcontroller, it would be very easy to control some other device through a transistor or relay for further enhancements to the hardware.
4. Compile the source code
The source code for the heart rate monitor is also very easy to adapt and expand, because it was written to be as minimal as possible using the Basic language. Each block of code is explained below so that you can easily convert it to any microcontroller family or language.
If you are an experienced programmer, then this trivial code probably is something you could write in 15 minutes from scratch, but if you have never written a program in your life, not to worry because Basic is called that for a reason and is easy to understand. The lines in the program listing that start with an apostrophe (') are comments, as explained below. The compiler used was the Bascom AVR, which is a very good compiler for the Atmega family, and includes a very comprehensive instruction set.
The code following this comment is required to tell Bascom that you are going to target the Atmega88 device and that you clock will be an external crystal resonator running at 4 MHz. Telling the compiler your clock speed becomes important when using commands that deal with timing-sensitive routines such as serial transmission or analog-to-digital readings. Defining the device also helps the compiler to generate user errors that have to do with IO pins. In this way, you can't accidentally try to toggle an IO pin that does not exist on the actual device.
This block of code sets up the IO pins that will connect to the LED display outputs. You can change these around any way you like to make your wiring as simple as possible, but it is best to keep the LED digits to a single port for simplicity. Port D is being used here.
Basic uses variables, which are letters or words used to hold values. Single letters such as A, B, and C are often used for simple programs such as this one, but when you are working on a large, complex program, use of more descriptive variable names is recommended. “TIMER2” or “REDLED1,” for example, would be descriptive variable names that make a lot more sense in a huge block of code.
Variables are also defined as the number of bits they are to contain, so in this code, A and B are 8-bit bytes that can contain a value between 0 and 255. Variable F is an integer that can range in value from –32768 to +32767. Variable D is a “word” that can contain a value between 0 and 65535. Although you could just define all variables using larger data types, this is a waste of memory space and will slow down your code.
This block of code sets up an array of 10 values for the variable “LED.” Although this may seem confusing at first, the values correspond to which of the seven segments will be lighted to display a particular number. To make this a little more confusing, the value in parentheses is actually one higher than the represented numerical value, and to light a segment, you want a low bit, not a high bit, so the value of “Led(9) = 0” says that to display the decimal number 8, you want all bits to be off. This means that all segments light up, and an 8 will be displayed.
It can be a bit of a chore computing these values by adding port bits, but once you have done it once for your segment, the hard work is completed. Your values most likely will be completely different from the ones above as you “map” out the pins and segments on your LED displays.
This sets up the variable K with a default rate that is close to the typical resting adult heart rate. By setting the average to 60 beats per minute rather than starting it at zero, the calculated value does not have to "ramp" up so far to reach its final value. This will make more sense once you see how the heart rate monitor operates.
The "Start Adc" command tells the compiler to include the code necessary to set up and initiate the onboard analog-to-digital converter on the Atmega88. This will allow you to read in an analog voltage and convert it to a value in order to detect changes in voltage from the LDR sensor.
Everything from here on is going to happen continually until the word "Loop" is reached, which causes program execution to start again where it first encountered the word "Do." This is called an endless loop because it never stops unless forced to by another command or an error.
This command reads the analog-to-digital converter on pin 0 of the Atmega88 into variable D, which is where the output from the LDR is connected. Because the ADC returns a 10-bit reading, values can range from 0 to 1024, which is why variable D needed to be a "Word," not a byte.
These three lines compare the current ADC reading D with the last known reading E by subtracting them into F. The "Abs(f)" command changes the value in F to an absolute nonnegative value so that you only get the difference as a whole number, not a negative number, if E were greater than D. Thus, this only gives you the difference since the last ADC reading, not the actual value.
This chunk of code acts like a crude low-pass filter that does not allow pulses to be detected that are much faster than what would be considered the upper limit for a human heart rate. Each tile a new pulse is detected ("F > 4"), a counter is set to 40 and has to count down to zero before another pulse can be registered.
To aid in debugging the circuit and setting up the optical sensor, a pulse-indicator LED will flash on both the analog part of the circuit and the digital part of the circuit. This piece of code just looks to see if the heart beat filter has been reset and then turns an LED on and off for half the counter cycle.
To display the heart rate as beats per minute, a running counter, H, is incremented until the next pulse is detected. The value in H then is divided into a value (9840) that was calculated to give a fairly accurate result of beats per minute. This large number has to do with how many instruction cycles there are between the main loop of this program, and took a bit of effort to get using an oscilloscope. Changing the auscultator frequency would completely throw off the calculated heart rate.
To filter out the regain a little more, changes do not happen immediately, but are "ramped" up or down on each pulse by a value of 1. By waiting until each pulse is detected (G=1), the final value K is compared with the calculated value J, and incremented or decremented accordingly by 1. Without this ramping sequence, a single glitch of false detection would result in a wild and instantaneous change in the LED readout.
Because the LED display contains three individual seven-segment displays, and the actual value is contained in the "Word" variable K, it must be broken down into three digits and sent to the display routine, which expects a value between 0 and 9 to be stored in each variable A, B, and C. By using division and the "Mod" command, the values are taken from the variable K and broken into 1s, 10s, and 100s for the display routine.
The "Loop" command causes program execution to jump back to the main routine where the "Do" command was encountered. This is the end of the infinite loop, and all other commands beyond here must be called by either the "Goto" or "Gosub" command.
This is the routine that displays a digit on each of the three LED displays. A little trick called persistence of vision is used here to switch between displays so fast that your eyes think that they are all on at the same time. As you can see, only one bit of the three "Portb" pins is on at a single time, and then the variables A, B, and C are sent to the display for only 2 ms each. Because 2 ms is so fast, it appears that each display is on all the time, and the seven-segment lines can be shared, saving valuable IO overhead.
Because Bascom array variables start at 1 not 0, the line “D = A + 1” adds 1 to the array pointer so that the value in “Led(d)” is the same as the decimal value you want. This conversion just makes it easier to understand the code, especially when trying to compute the segment bits from scratch the first time. Once all three displays have been lighted for 2 ms each, this routine just “returns” to where it was originally called.
You can find many ways to improve the program code and modify this project to suit your needs. The ability to write Basic code and work with microcontrollers makes rapid prototyping a snap, so there really is no idea you can’t set in motion with a little imagination and work.