Plot PPG curv using the oximeter sensor MAX30100 with Wio Terminal screen

Hello,

I have the oximeter sensor MAX30100 and I would like to display for instance the infrared curv on the Wio Terminal. I already have managed to print the SpO2 and heart value, but I also would like the curv. For that, I have been trying to mix the 2 example codes basic from Seeed Arduino Linechart library and RawData from MAX30100lib library.

  • basic example from Seeed Arduino Linechart
#include"seeed_line_chart.h" //include the library

TFT_eSPI tft;

#define max_size 30 //maximum size of data
doubles data; //Initilising a doubles type to store data
int brightness;
TFT_eSprite spr = TFT_eSprite(&tft);  // Sprite 

void setup() {
    pinMode(A0, INPUT);
    tft.begin();
    spr.createSprite(TFT_HEIGHT,TFT_WIDTH);
    spr.setRotation(3);
    tft.setRotation(3);
}

void loop() {
    spr.fillSprite(TFT_WHITE);
    brightness = analogRead(A0);
    
    if (data.size() == max_size) {
        data.pop();//this is used to remove the first read variable
    }
    data.push(brightness); //read variables and store in data

    //Settings for the line graph title
    auto header =  text(0, 0)
                .value("Light Sensor Readings")
                .align(center)
                .valign(vcenter)
                .width(tft.width())
                .thickness(2);

    header.height(header.font_height() * 2);
    header.draw(); //Header height is the twice the height of the font

  //Settings for the line graph
    auto content = line_chart(20, header.height()); //(x,y) where the line graph begins
         content
                .height(tft.height() - header.height() * 1.5) //actual height of the line chart
                .width(tft.width() - content.x() * 2) //actual width of the line chart
                .based_on(0.0) //Starting point of y-axis, must be a float
                .show_circle(false) //drawing a cirle at each point, default is on.
                .value(data) //passing through the data to line graph
                .color(TFT_RED) //Setting the color for the line
                .draw();
    spr.pushSprite(0, 0);
    delay(100);
}
  • RawData from MAX30100lib library
/*
Arduino-MAX30100 oximetry / heart rate integrated sensor library
Copyright (C) 2016  OXullo Intersecans <x@brainrapers.org>

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

// The example shows how to retrieve raw values from the sensor
// experimenting with the most relevant configuration parameters.
// Use the "Serial Plotter" app from arduino IDE 1.6.7+ to plot the output

#include <Wire.h>
#include "MAX30100.h"

// Sampling is tightly related to the dynamic range of the ADC.
// refer to the datasheet for further info
#define SAMPLING_RATE                       MAX30100_SAMPRATE_100HZ

// The LEDs currents must be set to a level that avoids clipping and maximises the
// dynamic range
#define IR_LED_CURRENT                      MAX30100_LED_CURR_50MA
#define RED_LED_CURRENT                     MAX30100_LED_CURR_27_1MA

// The pulse width of the LEDs driving determines the resolution of
// the ADC (which is a Sigma-Delta).
// set HIGHRES_MODE to true only when setting PULSE_WIDTH to MAX30100_SPC_PW_1600US_16BITS
#define PULSE_WIDTH                         MAX30100_SPC_PW_1600US_16BITS
#define HIGHRES_MODE                        true


// Instantiate a MAX30100 sensor class
MAX30100 sensor;

void setup()
{
    Serial.begin(115200);

    Serial.print("Initializing MAX30100..");

    // Initialize the sensor
    // Failures are generally due to an improper I2C wiring, missing power supply
    // or wrong target chip
    if (!sensor.begin()) {
        Serial.println("FAILED");
        for(;;);
    } else {
        Serial.println("SUCCESS");
    }

    // Set up the wanted parameters
    sensor.setMode(MAX30100_MODE_SPO2_HR);
    sensor.setLedsCurrent(IR_LED_CURRENT, RED_LED_CURRENT);
    sensor.setLedsPulseWidth(PULSE_WIDTH);
    sensor.setSamplingRate(SAMPLING_RATE);
    sensor.setHighresModeEnabled(HIGHRES_MODE);
}

void loop()
{
    uint16_t ir, red;

    sensor.update();

    while (sensor.getRawValues(&ir, &red)) {
        Serial.print(ir);
        Serial.print('\t');
        Serial.println(red);
    }
}

Here is one my attempt :

/*
Arduino-MAX30100 oximetry / heart rate integrated sensor library
Copyright (C) 2016  OXullo Intersecans <x@brainrapers.org>

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

// The example shows how to retrieve raw values from the sensor
// experimenting with the most relevant configuration parameters.
// Use the "Serial Plotter" app from arduino IDE 1.6.7+ to plot the output

#include <Wire.h>
#include "MAX30100.h"

#include "seeed_line_chart.h" //include the library
TFT_eSPI tft;

//for the plot
#define max_size 30 //maximum size of data
doubles data; //Initilising a doubles type to store data
int brightness;
TFT_eSprite spr = TFT_eSprite(&tft);  // Sprite

// Sampling is tightly related to the dynamic range of the ADC.
// refer to the datasheet for further info
#define SAMPLING_RATE                       MAX30100_SAMPRATE_100HZ

// The LEDs currents must be set to a level that avoids clipping and maximises the
// dynamic range
#define IR_LED_CURRENT                      MAX30100_LED_CURR_50MA
#define RED_LED_CURRENT                     MAX30100_LED_CURR_27_1MA

// The pulse width of the LEDs driving determines the resolution of
// the ADC (which is a Sigma-Delta).
// set HIGHRES_MODE to true only when setting PULSE_WIDTH to MAX30100_SPC_PW_1600US_16BITS
#define PULSE_WIDTH                         MAX30100_SPC_PW_1600US_16BITS
#define HIGHRES_MODE                        true


// Instantiate a MAX30100 sensor class
MAX30100 sensor;

uint16_t vIR, vR; //valeur IR, valeur IR

void setup()
{
    Serial.begin(115200);

    Serial.print("Initializing MAX30100..");

    // Initialize the sensor
    // Failures are generally due to an improper I2C wiring, missing power supply
    // or wrong target chip
    if (!sensor.begin()) {
        Serial.println("FAILED");
        for(;;);
    } else {
        Serial.println("SUCCESS");
    }

    // Set up the wanted parameters
    sensor.setMode(MAX30100_MODE_SPO2_HR);
    sensor.setLedsCurrent(IR_LED_CURRENT, RED_LED_CURRENT);
    sensor.setLedsPulseWidth(PULSE_WIDTH);
    sensor.setSamplingRate(SAMPLING_RATE);
    sensor.setHighresModeEnabled(HIGHRES_MODE);

    tft.begin();
    spr.createSprite(TFT_HEIGHT,TFT_WIDTH);
    spr.setRotation(3);
    tft.setRotation(3);
}

void loop()
{
     spr.fillSprite(TFT_WHITE);
     
     sensor.update();

    if (sensor.getRawValues(&vIR, &vR))
    {
        Serial.print(vIR);
        Serial.print('\t');
        Serial.println(vR);
        brightness = vIR;
    if (data.size() == max_size) {
        data.pop();//this is used to remove the first read variable
    }}
    
    
    data.push(brightness); //read variables and store in data

    //Settings for the line graph title
    auto header =  text(0, 0)
                .value("PPG wave")
                .align(center)
                .valign(vcenter)
                .width(tft.width())
                .thickness(2);

    header.height(header.font_height() * 2);
    header.draw(); //Header height is the twice the height of the font

  //Settings for the line graph
    auto content = line_chart(20, header.height()); //(x,y) where the line graph begins
         content
                .height(tft.height() - header.height() * 1.5) //actual height of the line chart
                .width(tft.width() - content.x() * 2) //actual width of the line chart
                .based_on(6600) //Starting point of y-axis, must be a float
                .show_circle(false) //drawing a cirle at each point, default is on.
                .value(data) //passing through the data to line graph
                .color(TFT_RED) //Setting the color for the line
                .draw();
    spr.pushSprite(0, 0);
    
    //delay(100);

 
}

However I haven't managed to make it work. Each example work separately.

I have noticed some points:

  • The printing instructions from the void setup of RawData example like Serial.print("Initializing MAX30100.."); do not seem to get executed even if the rest of the program seems to work.
  • the while loop inside the void loop in RawData is executing 5 times in each void loop (). I think we can replace the while by a if.

Besides I am not sure how to understand exactly the line TFT_eSprite spr = TFT_eSprite(&tft); // Sprite in the basic example even if I kind of manage to get it.

Does anyone have an idea of how to make the global program working?

Thanks for reading,

To understand where the problem could come from, I have tried the following code, that prints both the values of the MAX30100 sensor and the analog read from the linechart basic example.

#include <Wire.h>
#include "MAX30100.h"
#include"seeed_line_chart.h" //include the library

TFT_eSPI tft;

#define max_size 30 //maximum size of data
doubles data; //Initilising a doubles type to store data
int brightness;
TFT_eSprite spr = TFT_eSprite(&tft);  // Sprite 

// Sampling is tightly related to the dynamic range of the ADC.
// refer to the datasheet for further info
#define SAMPLING_RATE                       MAX30100_SAMPRATE_100HZ

// The LEDs currents must be set to a level that avoids clipping and maximises the
// dynamic range
#define IR_LED_CURRENT                      MAX30100_LED_CURR_50MA
#define RED_LED_CURRENT                     MAX30100_LED_CURR_27_1MA

// The pulse width of the LEDs driving determines the resolution of
// the ADC (which is a Sigma-Delta).
// set HIGHRES_MODE to true only when setting PULSE_WIDTH to MAX30100_SPC_PW_1600US_16BITS
#define PULSE_WIDTH                         MAX30100_SPC_PW_1600US_16BITS
#define HIGHRES_MODE                        true

// Instantiate a MAX30100 sensor class
MAX30100 sensor;
uint16_t vIR, vR;

void setup() {
     Serial.begin(115200);

    Serial.print("Initializing MAX30100..");

    // Initialize the sensor
    // Failures are generally due to an improper I2C wiring, missing power supply
    // or wrong target chip
    if (!sensor.begin()) {
        Serial.println("FAILED");
        for(;;);
    } else {
        Serial.println("SUCCESS");
    }
        // Set up the wanted parameters
    sensor.setMode(MAX30100_MODE_SPO2_HR);
    sensor.setLedsCurrent(IR_LED_CURRENT, RED_LED_CURRENT);
    sensor.setLedsPulseWidth(PULSE_WIDTH);
    sensor.setSamplingRate(SAMPLING_RATE);
    sensor.setHighresModeEnabled(HIGHRES_MODE);
        
    pinMode(A0, INPUT);
 //   tft.begin();
//    spr.createSprite(TFT_HEIGHT,TFT_WIDTH);
//    spr.setRotation(3);
//    tft.setRotation(3);
    
}

void loop() {
    sensor.update();

    if (sensor.getRawValues(&vIR, &vR)) {
            }
        Serial.print("IR \t");
        Serial.print(vIR);
        Serial.print("\t R \t");
        Serial.println(vR);
        
//    spr.fillSprite(TFT_WHITE);
    brightness = analogRead(A0);
//    
//    if (data.size() == max_size) {
//        data.pop();//this is used to remove the first read variable
//    }
//    data.push(brightness); //read variables and store in data
//
    Serial.print("brightness \t");
    Serial.println(brightness);
//
//    //Settings for the line graph title
//    auto header =  text(0, 0)
//                .value("Light Sensor Readings")
//                .align(center)
//                .valign(vcenter)
//                .width(tft.width())
//                .thickness(2);
//
//    header.height(header.font_height() * 2);
//    header.draw(); //Header height is the twice the height of the font
//
//  //Settings for the line graph
//    auto content = line_chart(20, header.height()); //(x,y) where the line graph begins
//         content
//                .height(tft.height() - header.height() * 1.5) //actual height of the line chart
//                .width(tft.width() - content.x() * 2) //actual width of the line chart
//                .based_on(0.0) //Starting point of y-axis, must be a float
//                .show_circle(false) //drawing a cirle at each point, default is on.
//                .value(data) //passing through the data to line graph
//                .color(TFT_RED) //Setting the color for the line
//                .draw();
//    spr.pushSprite(0, 0);
    delay(100);
}

One first thing that I don't understand is why the vIR and vR are not printed if the following lines are left inside the if (sensor.getRawValues(&vIR, &vR)) loop.

        Serial.print("IR \t");
        Serial.print(vIR);
        Serial.print("\t R \t");
        Serial.println(vR);

My second point is that if I uncomment the line tft.begin(); in the void setup, the vIR and vR values become equal to zero. Is there an incompatibility between the getRawValue function of MAX30100lib and the TFT_eSPI library?

I would be grateful for any help.