port oxullo library Arduino

Dependents:   MAX30100_oxullo

Files at this revision

API Documentation at this revision

Comitter:
AVELARDEV
Date:
Fri Nov 25 00:52:54 2016 +0000
Commit message:
max30100 example

Changed in this revision

MAX30100.cpp Show annotated file Show diff for this revision Revisions of this file
MAX30100.h Show annotated file Show diff for this revision Revisions of this file
MAX30100_BeatDetector.cpp Show annotated file Show diff for this revision Revisions of this file
MAX30100_BeatDetector.h Show annotated file Show diff for this revision Revisions of this file
MAX30100_Filters.h Show annotated file Show diff for this revision Revisions of this file
MAX30100_PulseOximeter.cpp Show annotated file Show diff for this revision Revisions of this file
MAX30100_PulseOximeter.h Show annotated file Show diff for this revision Revisions of this file
MAX30100_Registers.h Show annotated file Show diff for this revision Revisions of this file
MAX30100_SpO2Calculator.cpp Show annotated file Show diff for this revision Revisions of this file
MAX30100_SpO2Calculator.h Show annotated file Show diff for this revision Revisions of this file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MAX30100.cpp	Fri Nov 25 00:52:54 2016 +0000
@@ -0,0 +1,199 @@
+/*
+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/>.
+*/
+
+#include "MAX30100.h"
+
+I2C Wire(I2C_SDA , I2C_SCL );
+
+MAX30100::MAX30100()
+{
+
+}
+
+bool MAX30100::begin()
+{
+//    Wire.begin();
+//    Wire.setClock(I2C_BUS_SPEED);
+
+    Wire.frequency(I2C_BUS_SPEED);
+
+    if(!setMode(DEFAULT_MODE))
+        return false;
+    if(!setLedsPulseWidth(DEFAULT_PULSE_WIDTH))
+        return false;
+    if(!setSamplingRate(DEFAULT_SAMPLING_RATE))
+        return false;
+    if(!setLedsCurrent(DEFAULT_IR_LED_CURRENT, DEFAULT_RED_LED_CURRENT))
+        return false;
+    if(!setHighresModeEnabled(true))
+        return false;
+    
+    return true;
+}
+
+bool MAX30100::setMode(Mode mode)
+{
+    if(!writeRegister(MAX30100_REG_MODE_CONFIGURATION, mode))
+        return false;
+    else
+        return true;
+}
+
+bool MAX30100::setLedsPulseWidth(LEDPulseWidth ledPulseWidth)
+{
+    uint8_t previous;
+    if(readRegister(MAX30100_REG_SPO2_CONFIGURATION, &previous))
+        if(!writeRegister(MAX30100_REG_SPO2_CONFIGURATION, (previous & 0xfc) | ledPulseWidth))
+            return false;
+        else
+            return true; 
+    else
+        return false;       
+}
+
+bool MAX30100::setSamplingRate(SamplingRate samplingRate)
+{
+    uint8_t previous;
+    if(readRegister(MAX30100_REG_SPO2_CONFIGURATION, &previous))
+        if(!writeRegister(MAX30100_REG_SPO2_CONFIGURATION, (previous & 0xe3) | (samplingRate << 2)))
+            return false;
+        else
+            return true; 
+    else
+        return false;  
+}
+
+bool MAX30100::setLedsCurrent(LEDCurrent irLedCurrent, LEDCurrent redLedCurrent)
+{
+    if(!writeRegister(MAX30100_REG_LED_CONFIGURATION, redLedCurrent << 4 | irLedCurrent))
+        return false;
+    else
+        return true;     
+}
+
+bool MAX30100::setHighresModeEnabled(bool enabled)
+{
+    uint8_t previous;
+    if(readRegister(MAX30100_REG_SPO2_CONFIGURATION, &previous))
+        if (enabled) {
+            if(!writeRegister(MAX30100_REG_SPO2_CONFIGURATION, previous | MAX30100_SPC_SPO2_HI_RES_EN))
+                return false;
+            else
+                return true; 
+        } else {
+            if(!writeRegister(MAX30100_REG_SPO2_CONFIGURATION, previous & ~MAX30100_SPC_SPO2_HI_RES_EN))
+                return false;
+            else
+                return true;         
+        }
+    else
+        return false;
+}
+
+
+bool MAX30100::update()
+{
+    if(!readFifoData())
+        return false;
+    else
+        return true;
+}
+
+
+/*
+uint8_t MAX30100::readRegister(uint8_t address)
+{
+    Wire.beginTransmission(MAX30100_I2C_ADDRESS);
+    Wire.write(address);
+    Wire.endTransmission(false);
+    Wire.requestFrom(MAX30100_I2C_ADDRESS, 1);
+
+    return Wire.read();
+}
+*/
+
+bool MAX30100::readRegister(uint8_t uch_addr, uint8_t *puch_data)
+/**
+* \brief        Read a MAX30102 register
+* \par          Details
+*               This function reads a MAX30102 register
+*
+* \param[in]    uch_addr    - register address
+* \param[out]   puch_data    - pointer that stores the register data
+*
+* \retval       true on success
+*/
+{
+  char ch_i2c_data;
+  ch_i2c_data=uch_addr;
+  if(Wire.write(I2C_WRITE_ADDR, &ch_i2c_data, 1, true)!=0)
+    return false;
+  if(Wire.read(I2C_READ_ADDR, &ch_i2c_data, 1, false)==0)
+  {
+    *puch_data=(uint8_t) ch_i2c_data;
+    return true;
+  }
+  else
+    return false;   
+}
+
+/*
+void MAX30100::writeRegister(uint8_t address, uint8_t data)
+{
+    Wire.beginTransmission(MAX30100_I2C_ADDRESS);
+    Wire.write(address);
+    Wire.write(data);
+    Wire.endTransmission();
+}
+*/
+
+bool MAX30100::writeRegister(uint8_t uch_addr, uint8_t uch_data)
+/**
+* \brief        Write a value to a MAX30102 register
+* \par          Details
+*               This function writes a value to a MAX30102 register
+*
+* \param[in]    uch_addr    - register address
+* \param[in]    uch_data    - register data
+*
+* \retval       true on success
+*/
+{
+  char ach_i2c_data[2];
+  ach_i2c_data[0]=uch_addr;
+  ach_i2c_data[1]=uch_data;
+  
+  if(Wire.write(I2C_WRITE_ADDR, ach_i2c_data, 2, false)==0)
+    return true;
+  else
+    return false;    
+}
+
+bool MAX30100::readFifoData()
+{
+    char ach_i2c_data[4];
+
+    ach_i2c_data[0]=MAX30100_REG_FIFO_DATA;
+    if(Wire.write(I2C_WRITE_ADDR, ach_i2c_data, 1, true)!=0)
+        return false;
+    if(Wire.read(I2C_READ_ADDR, ach_i2c_data, 4, false)!=0)
+        return false;
+
+    // Warning: the values are always left-aligned
+    rawIRValue = (ach_i2c_data[0] << 8) | ach_i2c_data[1];
+    rawRedValue = (ach_i2c_data[2] << 8) | ach_i2c_data[3];
+    
+    return true;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MAX30100.h	Fri Nov 25 00:52:54 2016 +0000
@@ -0,0 +1,58 @@
+/*
+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/>.
+*/
+
+#ifndef MAX30100_H
+#define MAX30100_H
+
+#include <stdint.h>
+#include "mbed.h"
+#include "MAX30100_Registers.h"
+
+#define DEFAULT_MODE                MAX30100_MODE_HRONLY
+#define DEFAULT_SAMPLING_RATE       MAX30100_SAMPRATE_100HZ
+#define DEFAULT_PULSE_WIDTH         MAX30100_SPC_PW_1600US_16BITS
+#define DEFAULT_RED_LED_CURRENT     MAX30100_LED_CURR_50MA
+#define DEFAULT_IR_LED_CURRENT      MAX30100_LED_CURR_50MA
+
+#define I2C_BUS_SPEED               400000UL
+
+class MAX30100 {
+public:
+    MAX30100();
+    bool begin();
+    bool setMode(Mode mode);
+    bool setLedsPulseWidth(LEDPulseWidth ledPulseWidth);
+    bool setSamplingRate(SamplingRate samplingRate);
+    bool setLedsCurrent(LEDCurrent irLedCurrent, LEDCurrent redLedCurrent);
+    bool setHighresModeEnabled(bool enabled);
+    bool update();
+
+    uint16_t rawIRValue;
+    uint16_t rawRedValue;
+
+private:
+    /*
+    uint8_t readRegister(uint8_t address);
+    void writeRegister(uint8_t address, uint8_t data);
+    void burstRead(uint8_t baseAddress, uint8_t *buffer, uint8_t length);
+    void readFifoData();
+    */
+    
+    bool writeRegister(uint8_t uch_addr, uint8_t uch_data);
+    bool readRegister(uint8_t uch_addr, uint8_t *puch_data);
+    bool readFifoData();
+};
+
+#endif
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MAX30100_BeatDetector.cpp	Fri Nov 25 00:52:54 2016 +0000
@@ -0,0 +1,124 @@
+/*
+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/>.
+*/
+
+#include "MAX30100_BeatDetector.h"
+
+BeatDetector::BeatDetector() :
+    state(BEATDETECTOR_STATE_INIT),
+    threshold(BEATDETECTOR_MIN_THRESHOLD),
+    beatPeriod(0),
+    lastMaxValue(0),
+    tsLastBeat(0)
+{
+    t.start();
+}
+
+bool BeatDetector::addSample(float sample)
+{
+    return checkForBeat(sample);
+}
+
+float BeatDetector::getRate()
+{
+    if (beatPeriod != 0) {
+        return 1 / beatPeriod * 1000 * 60;
+    } else {
+        return 0;
+    }
+}
+
+float BeatDetector::getCurrentThreshold()
+{
+    return threshold;
+}
+
+bool BeatDetector::checkForBeat(float sample)
+{
+    bool beatDetected = false;
+
+    switch (state) {
+        case BEATDETECTOR_STATE_INIT:
+            if (t.read_ms() > BEATDETECTOR_INIT_HOLDOFF) {
+                state = BEATDETECTOR_STATE_WAITING;
+            }
+            break;
+
+        case BEATDETECTOR_STATE_WAITING:
+            if (sample > threshold) {
+                threshold = std::min((uint32_t)sample, (uint32_t)BEATDETECTOR_MAX_THRESHOLD);
+                state = BEATDETECTOR_STATE_FOLLOWING_SLOPE;
+            }
+
+            // Tracking lost, resetting
+            if (t.read_ms() > BEATDETECTOR_INVALID_READOUT_DELAY) {
+                beatPeriod = 0;
+                lastMaxValue = 0;
+            }
+
+            decreaseThreshold();
+            break;
+
+        case BEATDETECTOR_STATE_FOLLOWING_SLOPE:
+            if (sample < threshold) {
+                state = BEATDETECTOR_STATE_MAYBE_DETECTED;
+            } else {
+                threshold = std::min((uint32_t)sample, (uint32_t)BEATDETECTOR_MAX_THRESHOLD);
+            }
+            break;
+
+        case BEATDETECTOR_STATE_MAYBE_DETECTED:
+            if (sample + BEATDETECTOR_STEP_RESILIENCY < threshold) {
+                // Found a beat
+                beatDetected = true;
+                lastMaxValue = sample;
+                state = BEATDETECTOR_STATE_MASKING;
+                float delta = t.read_ms();
+                if (delta) {
+                    beatPeriod = BEATDETECTOR_BPFILTER_ALPHA * delta +
+                            (1 - BEATDETECTOR_BPFILTER_ALPHA) * beatPeriod;
+                }
+
+                t.reset();
+            } else {
+                state = BEATDETECTOR_STATE_FOLLOWING_SLOPE;
+            }
+            break;
+
+        case BEATDETECTOR_STATE_MASKING:
+            if (t.read_ms() > BEATDETECTOR_MASKING_HOLDOFF) {
+                state = BEATDETECTOR_STATE_WAITING;
+            }
+            decreaseThreshold();
+            break;
+    }
+
+    return beatDetected;
+}
+
+void BeatDetector::decreaseThreshold()
+{
+    // When a valid beat rate readout is present, target the
+    if (lastMaxValue > 0 && beatPeriod > 0) {
+        threshold -= lastMaxValue * (1 - BEATDETECTOR_THRESHOLD_FALLOFF_TARGET) /
+                (beatPeriod / BEATDETECTOR_SAMPLES_PERIOD);
+    } else {
+        // Asymptotic decay
+        threshold *= BEATDETECTOR_THRESHOLD_DECAY_FACTOR;
+    }
+
+    if (threshold < BEATDETECTOR_MIN_THRESHOLD) {
+        threshold = BEATDETECTOR_MIN_THRESHOLD;
+    }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MAX30100_BeatDetector.h	Fri Nov 25 00:52:54 2016 +0000
@@ -0,0 +1,64 @@
+/*
+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/>.
+*/
+
+#ifndef MAX30100_BEATDETECTOR_H
+#define MAX30100_BEATDETECTOR_H
+
+#include "mbed.h"
+#include <stdint.h>
+#include <algorithm>
+
+#define BEATDETECTOR_INIT_HOLDOFF                2000    // in ms, how long to wait before counting
+#define BEATDETECTOR_MASKING_HOLDOFF             200     // in ms, non-retriggerable window after beat detection
+#define BEATDETECTOR_BPFILTER_ALPHA              0.6     // EMA factor for the beat period value
+#define BEATDETECTOR_MIN_THRESHOLD               20      // minimum threshold (filtered) value
+#define BEATDETECTOR_MAX_THRESHOLD               800     // maximum threshold (filtered) value
+#define BEATDETECTOR_STEP_RESILIENCY             30      // maximum negative jump that triggers the beat edge
+#define BEATDETECTOR_THRESHOLD_FALLOFF_TARGET    0.3     // thr chasing factor of the max value when beat
+#define BEATDETECTOR_THRESHOLD_DECAY_FACTOR      0.99    // thr chasing factor when no beat
+#define BEATDETECTOR_INVALID_READOUT_DELAY       2000    // in ms, no-beat time to cause a reset
+#define BEATDETECTOR_SAMPLES_PERIOD              10      // in ms, 1/Fs
+
+
+typedef enum BeatDetectorState {
+    BEATDETECTOR_STATE_INIT,
+    BEATDETECTOR_STATE_WAITING,
+    BEATDETECTOR_STATE_FOLLOWING_SLOPE,
+    BEATDETECTOR_STATE_MAYBE_DETECTED,
+    BEATDETECTOR_STATE_MASKING
+} BeatDetectorState;
+
+
+class BeatDetector
+{
+public:
+    BeatDetector();
+    bool addSample(float sample);
+    float getRate();
+    float getCurrentThreshold();
+
+private:
+    bool checkForBeat(float value);
+    void decreaseThreshold();
+
+    BeatDetectorState state;
+    float threshold;
+    float beatPeriod;
+    float lastMaxValue;
+    uint32_t tsLastBeat;
+    Timer t;
+};
+
+#endif
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MAX30100_Filters.h	Fri Nov 25 00:52:54 2016 +0000
@@ -0,0 +1,71 @@
+/*
+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/>.
+*/
+
+#ifndef MAX30100_FILTERS_H
+#define MAX30100_FILTERS_H
+
+// http://www.schwietering.com/jayduino/filtuino/
+// Low pass butterworth filter order=1 alpha1=0.1
+// Fs=100Hz, Fc=6Hz
+class  FilterBuLp1
+{
+    public:
+        FilterBuLp1()
+        {
+            v[0]=0.0;
+        }
+    private:
+        float v[2];
+    public:
+        float step(float x) //class II
+        {
+            v[0] = v[1];
+            v[1] = (2.452372752527856026e-1 * x)
+                 + (0.50952544949442879485 * v[0]);
+            return
+                 (v[0] + v[1]);
+        }
+};
+
+// http://sam-koblenski.blogspot.de/2015/11/everyday-dsp-for-programmers-dc-and.html
+class DCRemover
+{
+public:
+    DCRemover() : alpha(0), dcw(0)
+    {
+    }
+    DCRemover(float alpha_) : alpha(alpha_), dcw(0)
+    {
+    }
+
+    float step(float x)
+    {
+        float olddcw = dcw;
+        dcw = (float)x + alpha * dcw;
+
+        return dcw - olddcw;
+    }
+
+    float getDCW()
+    {
+        return dcw;
+    }
+
+private:
+    float alpha;
+    float dcw;
+};
+
+#endif
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MAX30100_PulseOximeter.cpp	Fri Nov 25 00:52:54 2016 +0000
@@ -0,0 +1,154 @@
+/*
+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/>.
+*/
+
+#include <mbed.h>
+#include "MAX30100_PulseOximeter.h"
+
+
+PulseOximeter::PulseOximeter() :
+    state(PULSEOXIMETER_STATE_INIT),
+//    tsFirstBeatDetected(0),
+//    tsLastBeatDetected(0),
+//    tsLastSample(0),
+//    tsLastBiasCheck(0),
+//    tsLastCurrentAdjustment(0),
+    redLedPower((uint8_t)RED_LED_CURRENT_START),
+    onBeatDetected(NULL)
+{
+}
+
+bool PulseOximeter::begin(PulseOximeterDebuggingMode debuggingMode_)
+{
+    debuggingMode = debuggingMode_;
+
+    if(!hrm.begin())
+        return false;
+    if(!hrm.setMode(MAX30100_MODE_SPO2_HR))
+        return false;
+    if(!hrm.setLedsCurrent(IR_LED_CURRENT, RED_LED_CURRENT_START))
+        return false;
+
+    irDCRemover = DCRemover(DC_REMOVER_ALPHA);
+    redDCRemover = DCRemover(DC_REMOVER_ALPHA);
+
+    state = PULSEOXIMETER_STATE_IDLE;
+    return true;
+}
+
+
+void PulseOximeter::update()
+{
+    checkSample();
+    checkCurrentBias();   
+}
+
+float PulseOximeter::getHeartRate()
+{
+    return beatDetector.getRate();
+}
+
+uint8_t PulseOximeter::getSpO2()
+{
+    return spO2calculator.getSpO2();
+}
+
+uint8_t PulseOximeter::getRedLedCurrentBias()
+{
+    return redLedPower;
+}
+
+void PulseOximeter::setOnBeatDetectedCallback(void (*cb)())
+{
+    onBeatDetected = cb;
+    t_Sample.start();
+    t_CurrentBias.start();
+}
+
+void PulseOximeter::checkSample()
+{
+    if (t_Sample.read_ms() > 1.0 / SAMPLING_FREQUENCY * 1000.0) {
+        t_Sample.reset();
+        if(hrm.update()){
+            
+            float irACValue = irDCRemover.step(hrm.rawIRValue);
+            float redACValue = redDCRemover.step(hrm.rawRedValue);
+    
+            // The signal fed to the beat detector is mirrored since the cleanest monotonic spike is below zero
+            float filteredPulseValue = lpf.step(-irACValue);
+            bool beatDetected = beatDetector.addSample(filteredPulseValue);
+    
+            if (beatDetector.getRate() > 0) {
+                state = PULSEOXIMETER_STATE_DETECTING;
+                spO2calculator.update(irACValue, redACValue, beatDetected);
+            } else if (state == PULSEOXIMETER_STATE_DETECTING) {
+                state = PULSEOXIMETER_STATE_IDLE;
+                spO2calculator.reset();
+            }
+            /*
+            switch (debuggingMode) {
+                case PULSEOXIMETER_DEBUGGINGMODE_RAW_VALUES:
+                    Serial.print("R:");
+                    Serial.print(hrm.rawIRValue);
+                    Serial.print(",");
+                    Serial.println(hrm.rawRedValue);
+                    break;
+    
+                case PULSEOXIMETER_DEBUGGINGMODE_AC_VALUES:
+                    Serial.print("R:");
+                    Serial.print(irACValue);
+                    Serial.print(",");
+                    Serial.println(redACValue);
+                    break;
+    
+                case PULSEOXIMETER_DEBUGGINGMODE_PULSEDETECT:
+                    Serial.print("R:");
+                    Serial.print(filteredPulseValue);
+                    Serial.print(",");
+                    Serial.println(beatDetector->getCurrentThreshold());
+                    break;
+    
+                default:
+                    break;
+            }
+            */
+            if (beatDetected && onBeatDetected) {
+                onBeatDetected();
+            }
+        }
+    }
+}
+
+void PulseOximeter::checkCurrentBias()
+{
+    // Follower that adjusts the red led current in order to have comparable DC baselines between
+    // red and IR leds. The numbers are really magic: the less possible to avoid oscillations
+    if (t_CurrentBias.read_ms() > CURRENT_ADJUSTMENT_PERIOD_MS) {
+        bool changed = false;
+        if (irDCRemover.getDCW() - redDCRemover.getDCW() > 70000 && redLedPower < MAX30100_LED_CURR_50MA) {
+            ++redLedPower;
+            changed = true;
+        } else if (redDCRemover.getDCW() - irDCRemover.getDCW() > 70000 && redLedPower > 0) {
+            --redLedPower;
+            changed = true;
+        }
+
+        if (changed) {
+            hrm.setLedsCurrent(IR_LED_CURRENT, (LEDCurrent)redLedPower);
+            //tsLastCurrentAdjustment = millis();
+        }
+
+        t_CurrentBias.reset();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MAX30100_PulseOximeter.h	Fri Nov 25 00:52:54 2016 +0000
@@ -0,0 +1,83 @@
+/*
+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/>.
+*/
+
+#ifndef MAX30100_PULSEOXIMETER_H
+#define MAX30100_PULSEOXIMETER_H
+
+#define SAMPLING_FREQUENCY                  100
+#define CURRENT_ADJUSTMENT_PERIOD_MS        500
+#define IR_LED_CURRENT                      MAX30100_LED_CURR_50MA
+#define RED_LED_CURRENT_START               MAX30100_LED_CURR_27_1MA
+#define DC_REMOVER_ALPHA                    0.95
+
+#include <stdint.h>
+#include <vector>
+
+
+#include "MAX30100.h"
+#include "MAX30100_BeatDetector.h"
+#include "MAX30100_Filters.h"
+#include "MAX30100_SpO2Calculator.h"
+
+typedef enum PulseOximeterState {
+    PULSEOXIMETER_STATE_INIT,
+    PULSEOXIMETER_STATE_IDLE,
+    PULSEOXIMETER_STATE_DETECTING
+} PulseOximeterState;
+
+typedef enum PulseOximeterDebuggingMode {
+    PULSEOXIMETER_DEBUGGINGMODE_NONE,
+    PULSEOXIMETER_DEBUGGINGMODE_RAW_VALUES,
+    PULSEOXIMETER_DEBUGGINGMODE_AC_VALUES,
+    PULSEOXIMETER_DEBUGGINGMODE_PULSEDETECT
+} PulseOximeterDebuggingMode;
+
+
+class PulseOximeter {
+public:
+    PulseOximeter();
+
+    bool begin(PulseOximeterDebuggingMode debuggingMode_=PULSEOXIMETER_DEBUGGINGMODE_NONE);
+    void update();
+    float getHeartRate();
+    uint8_t getSpO2();
+    uint8_t getRedLedCurrentBias();
+    void setOnBeatDetectedCallback(void (*cb)());
+
+private:
+    void checkSample();
+    void checkCurrentBias();
+
+    PulseOximeterState state;
+    PulseOximeterDebuggingMode debuggingMode;
+//    uint32_t tsFirstBeatDetected;
+//    uint32_t tsLastBeatDetected;
+//    uint32_t tsLastSample;
+//    uint32_t tsLastBiasCheck;
+//    uint32_t tsLastCurrentAdjustment;
+    BeatDetector beatDetector;
+    DCRemover irDCRemover;
+    DCRemover redDCRemover;
+    FilterBuLp1 lpf;
+    uint8_t redLedPower;
+    SpO2Calculator spO2calculator;
+    
+    MAX30100 hrm;
+
+    void (*onBeatDetected)();
+    Timer t_Sample;
+    Timer t_CurrentBias;
+};
+#endif
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MAX30100_Registers.h	Fri Nov 25 00:52:54 2016 +0000
@@ -0,0 +1,108 @@
+/*
+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/>.
+*/
+
+#ifndef MAX30100_REGISTERS_H
+#define MAX30100_REGISTERS_H
+
+//#define MAX30100_I2C_ADDRESS                    0x57
+#define I2C_WRITE_ADDR 0xAE
+#define I2C_READ_ADDR 0xAF
+
+// Interrupt status register (RO)
+#define MAX30100_REG_INTERRUPT_STATUS           0x00
+#define MAX30100_IS_PWR_RDY                     (1 << 0)
+#define MAX30100_IS_SPO2_RDY                    (1 << 4)
+#define MAX30100_IS_HR_RDY                      (1 << 5)
+#define MAX30100_IS_TEMP_RDY                    (1 << 6)
+#define MAX30100_IS_A_FULL                      (1 << 7)
+
+// Interrupt enable register
+#define MAX30100_REG_INTERRUPT_ENABLE           0x01
+#define MAX30100_IE_ENB_SPO2_RDY                (1 << 4)
+#define MAX30100_IE_ENB_HR_RDY                  (1 << 5)
+#define MAX30100_IE_ENB_TEMP_RDY                (1 << 6)
+#define MAX30100_IE_ENB_A_FULL                  (1 << 7)
+
+// FIFO control and data registers
+#define MAX30100_REG_FIFO_WRITE_POINTER         0x02
+#define MAX30100_REG_FIFO_OVERFLOW_COUNTER      0x03
+#define MAX30100_REG_FIFO_READ_POINTER          0x04
+#define MAX30100_REG_FIFO_DATA                  0x05  // Burst read does not autoincrement addr
+
+// Mode Configuration register
+#define MAX30100_REG_MODE_CONFIGURATION         0x06
+#define MAX30100_MC_TEMP_EN                     (1 << 3)
+#define MAX30100_MC_RESET                       (1 << 6)
+#define MAX30100_MC_SHDN                        (1 << 7)
+typedef enum Mode {
+    MAX30100_MODE_HRONLY    = 0x02,
+    MAX30100_MODE_SPO2_HR   = 0x03
+} Mode;
+
+// SpO2 Configuration register
+// Check tables 8 and 9, p19 of the MAX30100 datasheet to see the permissible
+// combinations of sampling rates and pulse widths
+#define MAX30100_REG_SPO2_CONFIGURATION         0x07
+#define MAX30100_SPC_SPO2_HI_RES_EN             (1 << 6)
+typedef enum SamplingRate {
+    MAX30100_SAMPRATE_50HZ      = 0x00,
+    MAX30100_SAMPRATE_100HZ     = 0x01,
+    MAX30100_SAMPRATE_167HZ     = 0x02,
+    MAX30100_SAMPRATE_200HZ     = 0x03,
+    MAX30100_SAMPRATE_400HZ     = 0x04,
+    MAX30100_SAMPRATE_600HZ     = 0x05,
+    MAX30100_SAMPRATE_800HZ     = 0x06,
+    MAX30100_SAMPRATE_1000HZ    = 0x07
+} SamplingRate;
+
+typedef enum LEDPulseWidth {
+    MAX30100_SPC_PW_200US_13BITS    = 0x00,
+    MAX30100_SPC_PW_400US_14BITS    = 0x01,
+    MAX30100_SPC_PW_800US_15BITS    = 0x02,
+    MAX30100_SPC_PW_1600US_16BITS   = 0x03
+} LEDPulseWidth;
+
+// LED Configuration register
+#define MAX30100_REG_LED_CONFIGURATION          0x09
+typedef enum LEDCurrent {
+    MAX30100_LED_CURR_0MA      = 0x00,
+    MAX30100_LED_CURR_4_4MA    = 0x01,
+    MAX30100_LED_CURR_7_6MA    = 0x02,
+    MAX30100_LED_CURR_11MA     = 0x03,
+    MAX30100_LED_CURR_14_2MA   = 0x04,
+    MAX30100_LED_CURR_17_4MA   = 0x05,
+    MAX30100_LED_CURR_20_8MA   = 0x06,
+    MAX30100_LED_CURR_24MA     = 0x07,
+    MAX30100_LED_CURR_27_1MA   = 0x08,
+    MAX30100_LED_CURR_30_6MA   = 0x09,
+    MAX30100_LED_CURR_33_8MA   = 0x0a,
+    MAX30100_LED_CURR_37MA     = 0x0b,
+    MAX30100_LED_CURR_40_2MA   = 0x0c,
+    MAX30100_LED_CURR_43_6MA   = 0x0d,
+    MAX30100_LED_CURR_46_8MA   = 0x0e,
+    MAX30100_LED_CURR_50MA     = 0x0f
+} LEDCurrent;
+
+// Temperature integer part register
+#define MAX30100_REG_TEMPERATURE_DATA_INT       0x16
+// Temperature fractional part register
+#define MAX30100_REG_TEMPERATURE_DATA_FRAC      0x17
+
+// Revision ID register (RO)
+#define MAX30100_REG_REVISION_ID                0xfe
+// Part ID register
+#define MAX30100_REG_PART_ID                    0xff
+
+#endif
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MAX30100_SpO2Calculator.cpp	Fri Nov 25 00:52:54 2016 +0000
@@ -0,0 +1,71 @@
+/*
+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/>.
+*/
+
+#include <math.h>
+
+#include "MAX30100_SpO2Calculator.h"
+
+// SaO2 Look-up Table
+// http://www.ti.com/lit/an/slaa274b/slaa274b.pdf
+const uint8_t SpO2Calculator::spO2LUT[43] = {100,100,100,100,99,99,99,99,99,99,98,98,98,98,
+                                             98,97,97,97,97,97,97,96,96,96,96,96,96,95,95,
+                                             95,95,95,95,94,94,94,94,94,93,93,93,93,93};
+
+SpO2Calculator::SpO2Calculator() :
+    irACValueSqSum(0),
+    redACValueSqSum(0),
+    beatsDetectedNum(0),
+    samplesRecorded(0),
+    spO2(0)
+{
+}
+
+void SpO2Calculator::update(float irACValue, float redACValue, bool beatDetected)
+{
+    irACValueSqSum += irACValue * irACValue;
+    redACValueSqSum += redACValue * redACValue;
+    ++samplesRecorded;
+
+    if (beatDetected) {
+        ++beatsDetectedNum;
+        if (beatsDetectedNum == CALCULATE_EVERY_N_BEATS) {
+            float acSqRatio = 100.0 * log(redACValueSqSum/samplesRecorded) / log(irACValueSqSum/samplesRecorded);
+            uint8_t index = 0;
+
+            if (acSqRatio > 66) {
+                index = (uint8_t)acSqRatio - 66;
+            } else if (acSqRatio > 50) {
+                index = (uint8_t)acSqRatio - 50;
+            }
+            reset();
+
+            spO2 = spO2LUT[index];
+        }
+    }
+}
+
+void SpO2Calculator::reset()
+{
+    samplesRecorded = 0;
+    redACValueSqSum = 0;
+    irACValueSqSum = 0;
+    beatsDetectedNum = 0;
+    spO2 = 0;
+}
+
+uint8_t SpO2Calculator::getSpO2()
+{
+    return spO2;
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MAX30100_SpO2Calculator.h	Fri Nov 25 00:52:54 2016 +0000
@@ -0,0 +1,41 @@
+/*
+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/>.
+*/
+
+#ifndef MAX30100_SPO2CALCULATOR_H
+#define MAX30100_SPO2CALCULATOR_H
+
+#include <stdint.h>
+
+#define CALCULATE_EVERY_N_BEATS         3
+
+class SpO2Calculator {
+public:
+    SpO2Calculator();
+
+    void update(float irACValue, float redACValue, bool beatDetected);
+    void reset();
+    uint8_t getSpO2();
+
+private:
+    static const uint8_t spO2LUT[43];
+
+    float irACValueSqSum;
+    float redACValueSqSum;
+    uint8_t beatsDetectedNum;
+    uint32_t samplesRecorded;
+    uint8_t spO2;
+};
+
+#endif
\ No newline at end of file