Hardware components | ||||||
![]() |
| × | 1 | |||
![]() |
| × | 1 | |||
Software apps and online services | ||||||
![]() |
| |||||
I made a pulse oximeter using Seeed Wio Terminal.
Wio TerminalWio Terminal is LCD, Bluetooth, WiFi, infrared and illuminance sensor, etc.It is a multifunctional microcontroller in which everything else is packaged.
MAX30105 is used for the sensor. MAX30105 has red, green and infrared LEDs and a high-sensitivity photon detector, and can measure dust and blood oxygen saturation (SpO2).
Connection with Wio TerminalConnect the MAX30105 to the 4-pin Grove connector on the Wio Terminal.
According to the schematic, the left side of the two Grove connectors connects to the hardware I2C.
Oxygen saturation (SpO2), pulse rate and ECG are displayed. A buzzer sounds as the heart beats.
The MAX30105 library is below.https://github.com/sparkfun/SparkFun_MAX3010x_Sensor_Library
I coded with reference to the following sample code.
The LCD display of Wio Terminal is based on the following.https://wiki.seeedstudio.com/Wio-Terminal-LCD-Linecharts/
I referred to the following about the buzzer.https://wiki.seeedstudio.com/Wio-Terminal-Buzzer/
#include <Wire.h>
#include "MAX30105.h"
#include"seeed_line_chart.h" //include the library
#include "spo2_algorithm.h"
MAX30105 particleSensor;
TFT_eSPI tft;
long baseValue = 0;
long HB = 0, oldHB = 0;
int diffHB = 0;
int state = 0;
int th = -500;
#define max_size 50 //maximum size of data
doubles data; //Initilising a doubles type to store data
TFT_eSprite spr = TFT_eSprite(&tft); // Sprite
long lastBeat = 0; //Time at which the last beat occurred
const byte RATE_SIZE = 4; //Increase this for more averaging. 4 is good.
byte rates[RATE_SIZE]; //Array of heart rates
byte rateSpot = 0;
float beatsPerMinute;
int beatAvg;
long delta;
#if defined(__AVR_ATmega328P__) || defined(__AVR_ATmega168__)
//Arduino Uno doesn't have enough SRAM to store 100 samples of IR led data and red led data in 32-bit format
//To solve this problem, 16-bit MSB of the sampled data will be truncated. Samples become 16-bit data.
uint16_t irBuffer[100]; //infrared LED sensor data
uint16_t redBuffer[100]; //red LED sensor data
#else
uint32_t irBuffer[100]; //infrared LED sensor data
uint32_t redBuffer[100]; //red LED sensor data
#endif
int32_t bufferLength; //data length
int32_t spo2; //SPO2 value
int8_t validSPO2; //indicator to show if the SPO2 calculation is valid
int32_t heartRate; //heart rate value
int8_t validHeartRate; //indicator to show if the heart rate calculation is valid
void setup() {
pinMode(WIO_BUZZER, OUTPUT);
tft.begin();
tft.setRotation(3);
spr.createSprite(TFT_HEIGHT,TFT_WIDTH);
Serial.begin(115200);
Serial.println("Initializing...");
// Initialize sensor
if (!particleSensor.begin(Wire, I2C_SPEED_FAST)) //Use default I2C port, 400kHz speed
{
Serial.println("MAX30105 was not found. Please check wiring/power. ");
while (1);
}
particleSensor.setup(); //Configure sensor with default settings
particleSensor.setPulseAmplitudeRed(20); //Turn Red LED to low to indicate sensor is running
particleSensor.setPulseAmplitudeGreen(0); //Turn off Green LED
}
void loop() {
bufferLength = 100; //buffer length of 100 stores 4 seconds of samples running at 25sps
//read the first 100 samples, and determine the signal range
for (byte i = 0 ; i < bufferLength ; i++)
{
while (particleSensor.available() == false) //do we have new data?
particleSensor.check(); //Check the sensor for new data
redBuffer[i] = particleSensor.getRed();
irBuffer[i] = particleSensor.getIR();
particleSensor.nextSample(); //We're finished with this sample so move to next sample
Serial.print(F("red="));
Serial.print(redBuffer[i], DEC);
Serial.print(F(", ir="));
Serial.println(irBuffer[i], DEC);
}
//calculate heart rate and SpO2 after first 100 samples (first 4 seconds of samples)
maxim_heart_rate_and_oxygen_saturation(irBuffer, bufferLength, redBuffer, &spo2, &validSPO2, &heartRate, &validHeartRate);
//Continuously taking samples from MAX30102. Heart rate and SpO2 are calculated every 1 second
while (1)
{
//dumping the first 25 sets of samples in the memory and shift the last 75 sets of samples to the top
for (byte i = 25; i < 100; i++)
{
redBuffer[i - 25] = redBuffer[i];
irBuffer[i - 25] = irBuffer[i];
display();
}
//take 25 sets of samples before calculating the heart rate.
for (byte i = 75; i < 100; i++)
{
while (particleSensor.available() == false) //do we have new data?
particleSensor.check(); //Check the sensor for new data
redBuffer[i] = particleSensor.getRed();
irBuffer[i] = particleSensor.getIR();
particleSensor.nextSample(); //We're finished with this sample so move to next sample
//send samples and calculation result to terminal program through UART
Serial.print(F("red="));
Serial.print(redBuffer[i], DEC);
Serial.print(F(", ir="));
Serial.print(irBuffer[i], DEC);
Serial.print(F(", HR="));
Serial.print(heartRate, DEC);
Serial.print(F(", HRvalid="));
Serial.print(validHeartRate, DEC);
Serial.print(F(", SPO2="));
Serial.print(spo2, DEC);
Serial.print(F(", SPO2Valid="));
Serial.println(validSPO2, DEC);
display();
}
//After gathering 25 new samples recalculate HR and SP02
maxim_heart_rate_and_oxygen_saturation(irBuffer, bufferLength, redBuffer, &spo2, &validSPO2, &heartRate, &validHeartRate);
}
}
void display() {
spr.fillSprite(TFT_WHITE);
if (data.size() == max_size) {
data.pop();//this is used to remove the first read variable
}
HB = particleSensor.getIR();
diffHB = HB - oldHB;
data.push(diffHB); //read variables and store in data
if(state == 0 && diffHB < th){
delta = millis() - lastBeat;
lastBeat = millis();
beatsPerMinute = 60 / (delta / 1000.0);
rates[rateSpot++] = (byte)beatsPerMinute; //Store this reading in the array
rateSpot %= RATE_SIZE; //Wrap variable
//Take average of readings
beatAvg = 0;
for (byte x = 0 ; x < RATE_SIZE ; x++)
beatAvg += rates[x];
beatAvg /= RATE_SIZE;
Serial.println(beatAvg);
state = 1;
analogWrite(WIO_BUZZER, 128);
Serial.println("Beat!!!!!!!");
}else if(state == 1 && diffHB > th){
state = 0;
analogWrite(WIO_BUZZER, 0);
}
//Settings for SpO2
String stSpO2 = " SpO2:";
if(validSPO2){
stSpO2 += spo2;
}else{
stSpO2 += "-";
}
char charSpO2[20];
stSpO2.toCharArray(charSpO2, 20);
auto header = text(0, 0)
.value(charSpO2)
.align(left)
.valign(vcenter)
.width(tft.width())
.thickness(3);
header.height(header.font_height() * 2);
header.draw(); //Header height is the twice the height of the font
//Settings for HeartRate
String stHB = " HeartRate:";
stHB += beatAvg;
char charHB[20];
stHB.toCharArray(charHB, 20);
auto header2 = text(0, header.height())
.value(charHB)
.align(left)
.valign(vcenter)
.width(tft.width())
.thickness(3);
header2.height(header.font_height() * 2);
header2.draw(); //Header height is the twice the height of the font
//Settings for the line graph
auto content = line_chart(0, header.height() + header2.height()); //(x,y) where the line graph begins
content
.height(tft.height() - (header.height() + header2.height()) * 1.0) //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_PURPLE) //Setting the color for the line
.draw();
spr.pushSprite(0, 0);
oldHB = HB;
}




Comments