A Personal EKG Device and Heart Monitor
A simple way to produce a frequency output to monitor the heart using Arduino MEGA 2560 and AD8232 that the Kardia mobile app could sense.
After my last article [1] on recording electrocardiograms (ECG/EKGs), I became aware of a company that makes a relatively inexpensive ($79) personal EKG device. KardiaMobile by AliveCor uses your smartphone to display your heart rhythm in real-time. The electrodes are simple metal pads that you touch with your left and right fingers. The signal is then transmitted to your phone, which displays the waveform, and after 30 seconds of recording gives feedback on how it interprets what it “sees.” The mobile app will indicate a Normal Sinus Rhythm, Atrial Fibrillation, Bradycardia, or Tachycardia.
AliveCor also has a subscription service, KardiaCare, with which you can get several exclusive features to better manage your heart health from home. The subscription opens up the detection of more arrhythmias than any other personal EKGs—PVCs (Premature Ventricular Contractions), SVE (Supraventricular Ectopy), and Wide QRS.
David Albert, MD, is responsible for the device. His first efforts date back to the 1980s. You can read more about Dr. Albert’s journey toward his dream here [2]. Being the sucker I am for new gizmos, I purchased the basic KardioMobile device just to open it up. This led me to something I didn’t expect.
BREAK IT OPEN
Holding this device in my hand, I was intrigued by its small size, slightly larger than a stick of gum (Figure 1). The front has two metal contacts (electrodes), and the back is a cover for the CR2016 3V lithium coin cell. I found no switches or buttons. Hmm.
So, I downloaded the app (Kardia by AliveCor). When it was loaded, I created a free account, and the app presented a button, “Record your EKG.” I pressed it, and a new screen popped up (Figure 2). Once I was granted access, it told me to touch the electrodes with my left and right hands. Immediately, the signal strength went up, and the heart icon began to beat. After a few seconds of gathering data, the device began recording for 30 seconds. The typical PQRST waveform was displayed in realtime. I noticed small movements would add noise to the signal, so I tried to stay as still as possible. Apparently, it was happy with what it saw, because it told me that I was normal.
The device must have extremely low-power touch detection, which then enables its other circuitry, since the specs say its coin cell should last up to 200 hours or a year of use. Pardon me, Dr. Albert, but it’s time for an amateur to operate. The two pieces of the plastic case seem to be glued together; there are no screws. While forcing it open, I damaged the plastic case, but it’s only cosmetic damage. The internal PCB is thin material, 0.062”. The only external connections are two spring clips that make contact with the two electrodes molded into the plastic case. You can see from my photo in Figure 3 that the circuit board contains a piezo device.
— ADVERTISMENT—
—Advertise Here—
I didn’t remember the device beeping to begin, end, or at any other point in the measurement process. That’s weird. Why would they include a piezo device, if it didn’t beep? As I thought back to using the app, I remembered I didn’t go through any connection routine. I just assumed this was Bluetooth or some other wireless connection. Well, it seems it is wireless, but not in the way I had expected!
SCOPE OUT
I probed the piezo device with my oscilloscope. Nothing. I guess I expected that. Since this device is extremely low power, it doesn’t operate until it senses some resistance on the electrodes. Sure enough, once I was touching the electrodes, the piezo began vibrating. But I heard nothing.
Cranking on the horizontal time scale showed the output of the piezo device was around 19kHz, which is above my ability to hear it. My oscilloscope also showed that the frequency was not constant, but varied by about 10% (Figure 4). I had no idea the microphone and amplifier in my cell phone could reproduce frequencies in this range. So, it looks like the phone demodulates the ultrasonic transmission into an analog level, and displays that overtime to produce the visual interpretation of the KardioMobile electrode.
I figured I should be able to produce a frequency output on a micro that this app could sense. I drew up a circuit that includes a potentiometer input and a piezo output, and wired it on an Arduino protoboard (Figure 5). I will use the pots to allow some variable control on a few parameters—the first being frequency of the piezo output.
MEGA CODE
I’ll be using an Arduino MEGA 2560 as the microcontroller. I’ll want to output some frequency to the piezo device (19kHz). There are various methods of doing this with the Arduino. The tone output can handle the high frequency, but once I looked at that on the oscilloscope, I saw timing glitches even when I was not changing the frequency. I wanted a clean output. Going to an interrupt-driven output did not help, because there were still artifacts instead of constant clean output. The only clean library I found was a hardware PWM library. Although it was clean, since it used up a bunch of bits for the duty cycle, the resolution on the frequency, itself limited, had a limited number of discrete frequencies in the range for which I was looking.
I grabbed the datasheet for the ATmega2560 micro and turned to the section covering the Counter/Timer to see what modes were available. The timer library I had just tried to use was using this module in Mode 8 (PWM, Phase, and Frequency Correct). Mode 4 (CTC) was what I needed. Mode 8 timed both high and low periods of a complete cycle, whereas Mode 4 just used one counter and toggled the output each half cycle.
Only two registers are needed to set up the timer (I am using timer 4), TCCR4A and TCCR4B. The output of the timer 4 module can affect three output pins called A, B, and C. For this module, these refer to pins 3:4:5, respectively, on PORTH. If we look at the Arduino board schematic (Figure 5), we see that these PORT pins relate to the Arduino pins D6:7:8, respectively. My piezo must be connected to one of these (I’ll be using D8). The upper three pairs of bits in the TCCR4A control how timer 4’s output will affect these pins. By default this module is disconnected from these pins since the 2 data bits for each pin is ’00.’ By setting the bits for pin 8 to ’01,’ we allow the time to toggle the output bit each time the counter reaches the compare value (more on this in a sec). The least 2 bits in the TCCR4A register determine the mode of the timer.
The mode value (4 bits) is split between the two timer control registers. Our Mode 4, “0100” is split between TCCR4A (“01” as just discussed) and TCCR4B (“00”). We won’t need to use the upper bits of this register, because they are used only for an input mode. However, the lower 3 bits indicate the clocking rate of the counter. This register also defaults to no clock (or timer stopped), so we will need to change these bits. We can choose an external source for the clock, or use the internal system clock that runs at 8MHz. The system clock can be prescaled by 1:8:64:256:1,024. We want the fastest clock possible—that’s a prescaler of 1 or an 8MHz clock. Each count will therefore equal 250ns.
Once these two registers are set, timer4 will begin counting up from “0000” until it reaches the value in the Output Compare Register (OCR4). This is a 16-bit register that can be updated by a single, 16-bit transfer or by separating the value into two 8-bit transfers using OCR4H:L. Let’s look at how this is accomplished in our Arduino sketch.
— ADVERTISMENT—
—Advertise Here—
void setup()
{
TCCR4B = _BV(WGM42) | _BV(CS40); // Mode 4 CTC, CLK/1
TCCR4A = _BV(COM4C0); // Toggle PORT H.5
OCR4A = 8064000/19000; // Clock speed / Frequency of interest = counts
}
Note that we don’t have to initialize any of these variables, because they are already defined!
A quick compile and download and we see… nothing. Hmm. Ahh. The digital pins all default to inputs! We could use pinMode(8,OUTPUT), but I thought I’d stick with the actual register on the microcontroller responsible for this, Data Direction Register for PORTH (DDRH). Hopefully, this will remove some of the magic from dealing with a microcontroller directly. This can be done with a single additional line:
DDRH = DDRH | 1<<DDH5; // PORT H.5 as output
Note that setting the bit (“1”) makes this an output. This is the opposite logic that I’m used to when dealing with PIC micros, where a “1” is an input. Now we can compile and run the application and see a clean 19kHz output on pin 8.
LET’S PLAY
With the piezo device connected to pin 8, we have a nice, solid, ultrasonic output. There are two factors to investigate: what center frequency to use, and how much to modulate this frequency. I’ll use two of the pots to allow some adjustment of each of these parameters. Our application will now get a bit more complicated, but at least we don’t have to deal with any limitations on how the code added will affect the timing of the output frequency, since it is being handled totally in hardware, and none of our sketch code will affect it.
We have three pots, so let’s measure all three at once. Note that the conversion values (0 to 1,023) are all changes to signed values (-512 to 511), which will allow their values to affect up and down from a given value. Pot1 will adjust (±500) the center frequency, base = 19,000. Pot2 is going to be the deviation, and I’ll double that (±1,000). The deviation will act as the change in modulation. The value that I need to write to the OCR register is the number of ticks (myOCRValue) that corresponds to the frequency of interest (#Ticks/frequency), where the frequency is base plus the adjustment (pot1) plus the deviation (pot2) (Listing 1).
To support this routine we’ll need a few constants and variables defined (Listing 2). With a bit more code we can continually read the pots and update the OCR (Listing 3).
Listing 1
Reading the three analog potentiometer positions is easy. Here each value is offset/scaled to make the results easier to be used.
void readPots()
{
pot1 = analogRead(pot1_Pin) - 512; // pot1 value = -512 to 511
pot2 = analogRead(pot2_Pin) - 512; // pot2 value = -512 to 511
pot3 = analogRead(pot3_Pin)/10; // pot3 value = 0 to 102
}
Listing 2
All the I/O and variables/registers are defined for use in the application.
const unsigned long OCRTPS = 8000000;
// Timer 4 clock = 1/125ns
const byte piezo_Pin = 8; // digital piezo output pin
const byte pot1_Pin = A5; // Pot1 analog input pin
const byte pot2_Pin = A4; // Pot2 analog input pin
const byte pot3_Pin = A3; // Pot3 analog input pin
const int delayBetweenSamples = 10000;
// Set up sample rate for data
//
float deviation; // Modulation Depth
//
int base = 18000; // Center Frequency (before pot1)
int myBase = base; // final freqency
//
unsigned long myOCRValue = OCRTPS/myBase;
// CTC count to produce myBase
unsigned long time; // used for sample timing
//
int pot1; // pot1 value
int pot2; // pot2 value
int pot3; // pot3 value
//
Listing 3
The main loop reads the analog potentiometers, calculates the hardware timer value, and updates the timer. Then, the application waits until it is time for the next sample. The calculated routine begins with the ‘base’ frequency (18KHz) and adds pot1 adjustment to this (17.5-18.5KHz). Pot2 acts as modulation or deviation from that adjusted frequency, the modulation is based on manually adjusting pot2. Then the timer reloads value is calculated based on the total adjusted frequency.
void loop()
{
readPots(); // read analog pot inputs
calculate(); // calculate CTC value
OCR4A = myOCRValue; // update hardware Timer 4
while(time > micros()); // wait for micros() to reach time
time = time + delayBetweenSamples; // setup time for next delay
}
void calculate()
{
myBase = base; // start with center frequency
myBase = myBase + pot1; // pot1 value = -512 to 511
deviation = pot2 * 2; // 2 times pot2
myBase = myBase + deviation; // added as a deviation to myBase
Serial.println(deviation); // for use with Arduino Plotter
myOCRValue = OCRTPS/myBase; // actual calculation for the CTC reload value
}
Now we have enough control to try this with the KardiaMobile app. The first thing the app seeks is something to listen to. I’m not sure exactly what this is, but I suspect it is looking for the center or base frequency, and trying to demodulate the data to find a heartbeat. Then it will go on to phase 2, which is actually recording 30 seconds of data. With my oscilloscope looking at the ultrasonic output, I can move the base frequency around a bit with pot1, and then modulate it with pot2, and see the mobile app respond to the piezo output that is way beyond my hearing.
Manually twiddling pot2 moves (modulates) the frequency around, but that isn’t a very good data source. I can use the sin() function to create a repeating sine wave. Since this function gives me a value from 1 to -1 with each 360 degrees, I’ll add two routines. The first is a counter (i) that counts from 0-99, 100 samples for each rotation of 360 degrees, and gains control to multiply times the sin() function’s result. The counter goes at the beginning of the loop() function,
— ADVERTISMENT—
—Advertise Here—
i = i+1; // universal count
if(i>=numberOfSamples)
// keeps it 0-99
{
i=0;
}
and the calculate() function as shown in (Listing 4).
The serial plotter in Figure 6 shows the deviation (modulation), and the amount of modulation can be adjusted using pot3. I adjust the offset and gain pots to closely resemble the waveform I first saw (back in Figure 4), and the mobile app shows me a nice sine wave at a rate of 60 beats per minute (BPM), but can’t resolve any heart data. So, let’s give it some.

The serial plotter can be used to assure you of the data that’s being presented to the mobile app. It automatically adjusts its vertical gain to maximize the use of the display. If you crank on the gain, it will readjust itself.
Last month I talked about the parts of the typical PQRST EKG output [1]. I divided one cycle of this into 100 parts (i=0-99, my counter), and wrote down the value of the PQRST waveform at each of the 100 points. I added an array to the sketch that holds these 100 data points (Listing 5).
Now the data from analogArray[i] times some gain, becomes the new deviation (Listing 6).
Listing 4
Here the calculated routine is modified to use port 3 as a gain control to sine output of a complete 360-degree rotation. This provides a sine wave as a modulation of the base frequency.
void calculate()
{
myBase = base; // start with center frequency
fraction = i / numberOfSamples; // what part of a rotation is this
deviation = sin(PI * 2 * fraction) * pot3; // sine of that part times gain
myBase = myBase + deviation; // added as a deviation to myBase
Serial.println(deviation); // for use with Arduino Plotter
myOCRValue = OCRTPS/myBase; // actual calculation for the CTC reload value
}
Listing 5
This is an array of 100 sample values that are taken from a ‘normal’ PQRST heartbeat. It is used to simulate live input from an EKG device.
int analogArray[100] = {
55, 60, 63, 64, 62, // 0-4
60, 55, 55, 55, 55, // 5-9
55, 55, 55, 55, 55, // 10-14
55, 55, 55, 52, 48, // 15-19
60, 80,112, 80, 50, // 20-24
32, 42, 52, 55, 55, // 25-29
55, 55, 55, 55, 55, // 30-34
55, 55, 55, 55, 55, // 35-39
55, 55, 55, 55, 55, // 40-44
57, 59, 64, 66, 68, // 45-49
70, 72, 72, 71, 69, // 50-54
67, 63, 59, 55, 55, // 55-59
55, 57, 59, 59, 57, // 60-64
55, 55, 55, 55, 55, // 65-69
55, 55, 55, 55, 55, // 70-74
55, 55, 55, 55, 55, // 75-79
55, 55, 55, 55, 55, // 80-84
55, 55, 55, 55, 55, // 85-89
55, 55, 55, 55, 55, // 90-94
55, 55, 55, 55, 55, // 95-99
}; // array of a PQRST waveform
Listing 6
Modified once more, this new calculation routine uses the array of values to produce a simulated ‘normal’ waveform as modulation.
void calculate()
{
myBase = base; // start with center frequency
deviation = analogArray[i] * pot3; // preprogrammed sample times gain
myBase = myBase + deviation; // added as a deviation to myBase
Serial.println(deviation); // for use with Arduino Plotter
myOCRValue = OCRTPS/myBase; // actual calculation for the CTC reload value
}
The deviation is clearly shown using the serial plotter, and with a bit of twiddling of pot1 and pot3, I can get the scope waveform to be in the right range. The mobile app responds nicely and states that this was a Normal Sinus Rhythm at 60BPM. If you look back at the constant delayBetweenSamples you’ll notice it is 10,000. That’s the delay (in microseconds) between samples and is 10ms or 1/100 of a second (remember, 100 samples). The 60BPM reported rate is 1 beat per second—exactly the time of one cycle through the array data. So, by changing this value, we can send out the data at a different rate. If you change this, then you will change the sample rate to:
BPM = number of mircoseconds in a minute / (delayBetweenSamples * numberOf Samples)
BPM = 60000 / (10000 * 100) = 60
BPM = 60000 / (15000 * 100) = 40
BPM = 60000 / (5000 * 100) = 120
When this constant is changed and the sketch is rerun, the mobile application will correctly identify the data as Bradycardia 40BPM and Tachycardia 120BPM. Wow, pretty cool! But how will this work with data from a live person instead of programmed data? To get to this level, you will have to refer to last month’s article [1], where I presented the SparkFun Single Lead Heart Rate Monitor, which uses an AD8232. Because I am using the same microcontroller for this project, a MEGA 2560, along with an Arduino proto-type board, the SparkFun module can easily be added to this project, and we can add its output, an analog signal, to a spare analog input (A0). Now we just need to substitute a single line of code in the calculate routine:
deviation = analogRead(AD8232_Pin) * pot3; // sample times gain
to allow the AD8232 data (and some gain control from pot3) to drive the modulation.
Figure 7 shows the mobile app’s screen on successfully reading the data pumped out from the AD8232. While this isn’t proof of a clean bill of health, it shows that inexpensive circuitry can, in fact, be used to monitor basic heart function. Consistent and repeatable electrical impulses can be collected successfully. Let’s not forget what it takes to analyze the recorded data.

The AD8232’s output seems to be successfully modulating the carrier frequency of the ultrasonic output, since the KardiaMobile app gives me a clean bill of health. The record can be output as a PDF, as shown here.
SMARTPHONE
Our smartphones are powerful computers. There is now more computing power in your hand than was used by NASA to send Americans to the moon. What began as a simple communication device, now has other useful functions. We have handy access to a flashlight, camera, GPS, calendar, game console, and movie theater. We are only scratching the surface as to the power of this tool, which is now an integral part of our daily lives.
As for this project, I can’t express strongly enough the importance of the application software written by the KardiaMobile folks. While not exactly AI, it uses strong pattern-matching algorithms to determine where the data falls. This doesn’t replace having your heart monitored under clinical conditions and having a cardiologist interpret the graphed output, but you can check some basic heart parameters, even when you don’t have access to a healthcare professional.
My hat is off (if I wore one) to Dr. Albert for his years of effort in pursuing his vision, and to AliveCor, Inc. for understanding the potential of delivering a device like KardiaMobile. There have been advances in this technology lately, and additional products have been introduced. Although using the app requires you to create an account, at this time, there is no obligation to join KardiaCare for $10 a month, unless you wish to take advantage of some advanced determinations and services. I suggest you check out their website for more information [3].
This is a fascinating time to be alive. The advances in science and technology are staggering and promote a longer and more productive life for those of us lucky enough to enjoy it. I appeal to all that we approach the way we treat each other with such passion. So much to accomplish, so little time.
REFERENCES
[1] “Small Signal Sampling – In a Heartbeat” Circuit Cellar {383}, June, 2022
https://cc-webshop.com/collections/circuit-cellar-2022/products/circuit-cellar-381-june-2022-pdf
[2] -Ahead of His Time: David Albert, MD, Reimagined ECG Technology.
https://www.mddionline.com/news/ahead-his-time
[3] AliveCor, Inc., KardiaMobile
https://www.alivecor.com/
PUBLISHED IN CIRCUIT CELLAR MAGAZINE • JULY 2022 #384 – Get a PDF of the issue
Sponsor this ArticleJeff Bachiochi (pronounced BAH-key-AH-key) has been writing for Circuit Cellar since 1988. His background includes product design and manufacturing. You can reach him at: jeff.bachiochi@imaginethatnow.com or at: www.imaginethatnow.com.