BioSlam PPG

In this workshop we’ll use the Arduino Nano to build an ultra low-cost heart rate monitor. The signal we’ll be measuring is called the photoplethysmogram (PPG), which detects small changes in blood vessel dilation caused by the variation in blood pressure that occurs during each heart beat. We measure these variations by shining light into the skin and measuring how much of it is absorbed and how much is reflected. The TCRT5000 infrared reflective sensor used in this system actually contains two separate electronic components – an infrared light emitting diode (LED), which shines light into the finger, and an infrared phototransistor, which measures how much is reflected back.

Kit Contents

  • 1 x Mini breadboard
  • 1 x Arduino Nano
  • 1 x USB cable
  • 1 x LM358N dual opamp (DIP8)
  • 1 x TCRT5000 infrared reflective sensor
  • 1 x Green 5 mm LED
  • 1 x Red 5 mm LED
  • 1 x 220 μF electrolytic capacitor
  • 1 x 100 Ω
  • 5 x 1 kΩ
  • 1 x 470 kΩ
  • 1 x 8 cm red wire
  • 2 x 4 cm red wire
  • 6 x 4 cm black wire
  • 1 x 4 cm green wire
  • 2 x 6 cm pink wire
  • 1 x 8 cm blue wire
  • 1 x 4 cm blue wire

This is a circuit diagram of what we’re building:

The instructions below will lead you through the construction process step by step. Follow them carefully and you’ll end up with a circuit that looks like this and produces a similar PPG signal.

If you run into any problems, we’ll be on hand to help you troubleshoot!

Step 1: Breadboard and Arduino Nano

The breadboard is the rectangular white plastic board we use to build the circuit. Each short row of five holes is a single electrical connection. Two wires inserted into the same row become connected electrically. Each row is marked with a number and each column is marked with a letter, so that each hole in the main section of the board can be uniquely identified using a letter and number. The instructions below use these letters and numbers to identify the exact position of each component.

Begin by orienting your breadboard so that it appears as shown below, with row 1 on the left and row 30 on the right.

Insert the Arduino Nano as far to the right as possible, as shown below. The pin labelled “D12” on the Arduino should be in breadboard hole D30.

Step 2: LM358N operational amplifier

The LM358N is an operational amplifier (commonly referred to as an opamp), which we will use to amplify the tiny variations in voltage produced by the infrared sensor. This chip actually contains two opamps in a single 9-pin package.

Insert the LM358 opamp (the 8-pin microchip) so that pin 1 (one of the corner pins) is in E4 and pin 5 (the diagonally opposite corner) is in F7. There is a small semi-circular indentation at one end of the upper surface of the chip. This semi-circular indentation must be at row 4. If the chip is the wrong way around, the circuit will not work and the chip may get burned out.

Note: Some microchips use a circular indentation in one corner (instead of a semi-circular indentation at one end) to mark which end is which. If the chip you received in your kit has a circular indentation in one corner of its upper surface, make sure the circular indentation is at row 4 of the breadboard, as shown below.

When the LM358N chip and Arduino are fully inserted, they lie flat against the white plastic surface of the breadboard, as shown below.

Step 3: TCRT5000 infrared reflective sensor

Plug the TCRT5000 reflective infrared sensor into holes E1, E2, F1 and F2, as shown below.

Make sure the TCRT5000 is the right way around. The image below shows the correct orientation. Note the letters “TCRT5000” are written on the side of the sensor closest to the end of the breadboard.

Step 4: Red and green LEDs

Diodes are like one-way valves for electric current, so it’s essential that both LEDs are inserted the right way around. The lower rim on each LED is slightly flattened on one side. This flattening marks the negative leg.

  • Insert the green LED in breadboard holes H7 and H8.
  • Insert the red LED in breadboard holes E12 and E13.

The negative leg of the green LED should be in breadboard hole H8. The flattened side of the LED is visible in the photo below.

The negative leg of the red LED should be in breadboard hole E12 (NOTE: POSITION IS SLIGHTLY INCORRECT IN PHOTO!). The flattened side of the LED is visible in the photo below.

Step 5: Resistors

In the instructions below, each resistor is described as either “standing up” or “lying down”. The legs of each resistor are trimmed according to which way it will be inserted into the breadboard. Examples of both types are shown below.

The example image below shows one resistor “standing up” and one resistor “lying down”. Hopefully, it’s self-explanatory.

Insert the five 1 kΩ resistors (colour code: brown, black, red, gold) as follows:

  • 1 kΩ resistor lying down between I2 and I5.
  • 1 kΩ resistor lying down between C5 and C8.
  • 1 kΩ resistor lying down between D8 and D11.
  • 1 kΩ resistor lying down between J7 and the positive (red) rail.
  • 1 kΩ resistor lying down between A12 and the negative (blue) rail.

Insert the 100 Ω resistor (colour code: brown, black, brown, gold) lying down between A2 and the positive (red) rail.

Insert the 470 kΩ resistor (colour code: yellow, violet, yellow, gold) standing up between A4 and A5.

Step 6: Capacitor

The next component to be added to the circuit is the 220 μF capacitor. This is an electrolytic capacitor, so it’s polarised and must be inserted the right way around. The negative leg is marked with a white stripe down one side of the capacitor.

  • Insert the 220 μF capacitor between A7 and A8.
  • The negative leg should be in A7.
  • The positive leg should be in A8.

Here’s how the standing up 470 kΩ resistor and 220 μF capacitor appear when viewed from the side:

Step 7: Black wires (0 V)

Black wires are used to connect the negative supply voltage (0 V) to several points in the circuit.

  • 4 cm black wire between J1 and the blue (negative) rail.
  • 4 cm black wire between J8 and the blue (negative) rail.
  • 4 cm black wire between J17 and the blue (negative) rail.
  • 4 cm black wire between A1 and the blue (negative) rail.
  • 4 cm black wire between A19 and the blue (negative) rail.
  • 4 cm black wire between D7 and F8.

Note: The positive and negative rails on your breadboard may be the other way around (it varies between different breadboards). Be sure to follow the colour marking of the rails on your own breadboard, even if they’re the opposite way around to the ones shown in the photos here. The rail marked with a red line should always be the positive rail.

Step 8: Red wires (5 V)

The red wires are used to connect the positive supply voltage (5 V) to several points in the circuit.

  • 4 cm red wire between J4 and the red (positive) rail.
  • 4 cm red wire between J19 and the red (positive) rail.
  • 8 cm red wire from the positive (red) rail near J30 to the positive (red) rail on the other side of the breadboard near A30.

Step 9: Pink wires (digital outputs)

In this circuit, pink wires are used to connect digital output pins on the Arduino to two points in the circuit. One wire allows the Arduino to turn the red LED on and off. The other allows the Arduino to quickly discharge the 220 μF capacitor, which makes the signal settle faster when a large change occurs.

  • 6 cm pink wire between C11 and C20.
  • 6 cm pink wire between B13 and B21.

Step 10: Green wire (reference voltage, approx. 2 V)

In this circuit, the reference voltage (approximately 2 V) is indicated by green wire. This constant voltage is used to stabilise the signal in the desired range.

  • 4 cm green wire between J5 and J6.

Step 11: Blue wires (sensor signal)

In this circuit, blue wires are used to carry the PPG signal from the infrared sensor to the amplifier and then on to the Arduino.

  • 4 cm blue wire between G2 and D6.
  • 8 cm blue wire between D4 and I20.

Step 12: Final circuit check

Before plugging the Arduino into the PC with the USB cable, please check that everything is in the right place. Pay special attention to the red and black wires because if one of these is connected to the wrong rail, the Arduino could easily burn out!

Step 13: Programming the Arduino

The Arduino basically contains a complete computer in a single chip, albeit a very simple one. We need to write a program to run on that computer to record the signal from our sensor and send it to the PC via the USB connection. Later on, we’ll also program the Arduino to do some basic digital signal processing on the PPG signal.

To upload programs to the Arduino, we use the Arduino Integrated Development Environment (IDE).

Run the Arduino IDE by double clicking on the desktop icon.

The following Arduino program measures the voltage from pin A7 approximately 100 times per second, printing each value via the serial connection, so that they can be displayed or plotted on the screen of a PC or laptop.

void setup()
{
  // Use pin D3 to control red LED
  pinMode(3, OUTPUT);

  // Serial connection to PC at 115200 bits per second
  Serial.begin(115200);

  // Set signal labels in the Serial Plotter
  Serial.println();
  Serial.println("GND:,max:,ppg:");
}

void loop()
{
  int v;                       // variable to store measured voltage
  
  v = analogRead(7);           // measure voltage on pin A7
  
  Serial.print("0,1023,");     // print minimum and maximum values
  Serial.println(v);           // print current signal level

  // Discharge capacitor if signal is stuck close to minimum
  if (v < 30)
  {
    digitalWrite(3, HIGH);     // turn on red LED
    digitalWrite(2, LOW);      // set capacitor discharge pin to 0V
    pinMode(2, OUTPUT);        // enable capacitor discharge pin
    while(analogRead(7) < 30); // discharge until signal back in range
    pinMode(2, INPUT);         // disable capacitor discharge pin
    digitalWrite(3, LOW);      // turn off red LED
  }
  
  delay(10);                   // 10 ms delay limits sampling rate
}

The correct Arduino type and other settings must be configured under the Tools menu of the Arduino development environment, as follows.

  • Board: “Arduino Nano”
  • Processor: “ATmega328P”
  • Port: [This will be assigned when you plug the Arduino into the USB socket of your laptop – it will probably be something like “COM5” or “COM8”]
  • Programmer: “AVRISP mkII”

To upload the program to the Arduino, press the right-facing arrow button on the toolbar (or press the keyboard shortcut, Ctrl + U). If the upload is successful, a message will appear near the bottom left corner of the Arduino window saying “Done uploading.”

Step 14: Serial Monitor

The Serial Monitor tool is built in to the Arduino IDE. It displays text printed by the Arduino on the PC screen. We’ll use it now to verify that our program is running correctly on the Arduino.

  1. To open the Serial Monitor, open the Tools menu in the Arduino IDE and click on “Serial Monitor”.
  2. In the bottom right corner of the Serial Monitor window, set the serial connection speed to “115200 baud”.
  3. Hopefully, you’ll see something similar to the image below. These numbers are being sent over the serial connection by the program running on the Arduino – three numbers on every line.

Step 15: Serial Plotter

The Arduino IDE also includes a tool called the Serial Plotter, which allows columns of numbers arriving over the serial connection to be plotted as signals on a graph in real time.

  1. Under the Tools menu in the Arduino IDE, click on “Serial Plotter”.
  2. In the bottom left corner of the Serial Plotter window, set the serial connection speed to 115200 baud.
  3. Place the tip of your little finger on the TCRT5000 sensor. Hold it very still, but don’t press down too hard.
  4. With luck, you’ll see something like the image below. The green line is the raw PPG signal. Note: Your signal might take a minute to settle down.

Step 16: Digital Signal Processing (DSP)

The raw PPG signal can be quite noisy. The following example program uses a simple digital filter to generate a cleaner version of the signal.

void setup()
{
  // Use pin D3 to control red LED
  pinMode(3, OUTPUT);

  // Serial connection to PC at 115200 bits per second
  Serial.begin(115200);

  // Set signal labels in the Serial Plotter
  Serial.println();
  Serial.println("GND:,max:,smooth:,raw:");
}

void loop()
{
  int raw;                 // variable to store measured voltage
  static float smooth = 0; // variable to store smoothed signal

  raw = analogRead(7);           // measure raw PPG voltage
  smooth = 0.8*smooth + 0.2*raw; // slightly smooth the raw signal

  Serial.print("0,1023,"); // print minimum and maximum values
  Serial.print(smooth);    // print smoothed signal level
  Serial.print(",");       // print comma between numbers on same line
  Serial.println(raw);     // print raw signal level

  // Discharge capacitor if signal is stuck close to minimum
  if (smooth < 30)
  {
    digitalWrite(3, HIGH);     // turn on red LED
    digitalWrite(2, LOW);      // set capacitor discharge pin to 0V
    pinMode(2, OUTPUT);        // enable capacitor discharge pin
    while(analogRead(7) < 30); // discharge until signal back in range
    pinMode(2, INPUT);         // disable capacitor discharge pin
    digitalWrite(3, LOW);      // turn off red LED
  }
  
  delay(10);                   // 10 ms delay limits sampling rate
}

How does this work? Look at the line of code where each new data point in the smoothed signal is calculated…

Try changing the two numbers to see the effect on how smooth the signal becomes.

Step 17: Detecting heart beats

void setup()
{
  // Use pin D3 to control red LED
  pinMode(3, OUTPUT);

  // Serial connection to PC at 115200 bits per second
  Serial.begin(115200);

  // Set signal labels in the Serial Plotter
  Serial.println();
  Serial.println("GND:,max:,smooth:,raw:,flat:,beat:");
}

void loop()
{
  // Declare variables
  int raw;                 // variable to store measured voltage
  static float smooth = 0; // variable to store smoothed signal
  static float flat = 0;   // variable to store flattened voltage
  int beat;                // indicates when a beat is detected

  // Record raw PPG signal from analog input pin A7
  raw = analogRead(7);
  
  // Generate two filtered versions of PPG signal
  int previous_smooth = smooth;  // remember previous signal value
  smooth = 0.8*smooth + 0.2*raw; // slightly smooth the raw signal
  flat = 0.999*flat + 0.001*raw; // heavily flatten the raw signal

  // Detect onset of beat when smooth signal exceeds flat signal
  if (smooth >= flat && previous_smooth < flat) beat = 900;
  else beat = 800;

  // Print current values of all signals on a single line of text
  Serial.print("0,1023,"); // print minimum and maximum values
  Serial.print(smooth);    // print current signal level
  Serial.print(",");       // comma between numbers on same line
  Serial.print(raw);       // print current signal level
  Serial.print(",");       // comma between numbers on same line
  Serial.print(flat);      // print current signal level
  Serial.print(",");       // comma between numbers on same line
  Serial.println(beat);    // display beat detect signal

  // Discharge capacitor if signal is stuck close to minimum
  if (smooth < 30)
  {
    digitalWrite(3, HIGH);     // turn on red LED
    digitalWrite(2, LOW);      // set capacitor discharge pin to 0V
    pinMode(2, OUTPUT);        // enable capacitor discharge pin
    while(analogRead(7) < 30); // discharge until signal back in range
    pinMode(2, INPUT);         // disable capacitor discharge pin
    digitalWrite(3, LOW);      // turn off red LED
  }
  
  delay(10);                   // 10 ms delay limits sampling rate
}

Step 18: Bonus challenge

If you get everything else done, try something a bit more challenging…

The following program makes the red LED flash on and off:

void setup()
{
  pinMode(3, OUTPUT); // Arduino pin D3 controls the red LED
}

void loop()
{
  digitalWrite(3, HIGH); // turn on LED
  delay(1000);           // wait 1000ms
  digitalWrite(3, LOW);  // turn off LED
  delay(1000);           // wait 1000ms
}

Two challenges:

  1. EASY: Can you figure out how to modify this code to make the LED flash at a different rate?
  2. DIFFICULT: Can you combine elements of this code with one of the PPG examples above to make the LED flash each time the heart beats?
  3. REALLY DIFFICULT: Instead of printing measurements to display the PPG waveform, can you program the Arduino to calculate and print an updated estimate of the heartrate every time a heartbeat occurs? You can use the “Serial Monitor” (in the Tools menu of the Arduino IDE) to display the printed text.