From 7390199c5cf883a8ebbc1bfaaeb163c811c948f5 Mon Sep 17 00:00:00 2001
From: schneider <schneider@blinkenlichts.net>
Date: Sun, 23 Jun 2019 01:43:57 +0200
Subject: [PATCH] feat(ecg): Experimental support for second ecg

---
 hw-tests/ecgtest/main.c               | 196 ++++++++
 lib/card10/meson.build                |   1 +
 lib/meson.build                       |   1 +
 lib/vendor/Maxim/MAX86150/max86150.c  | 667 ++++++++++++++++++++++++++
 lib/vendor/Maxim/MAX86150/max86150.h  | 103 ++++
 lib/vendor/Maxim/MAX86150/meson.build |  14 +
 6 files changed, 982 insertions(+)
 create mode 100644 lib/vendor/Maxim/MAX86150/max86150.c
 create mode 100644 lib/vendor/Maxim/MAX86150/max86150.h
 create mode 100644 lib/vendor/Maxim/MAX86150/meson.build

diff --git a/hw-tests/ecgtest/main.c b/hw-tests/ecgtest/main.c
index a2f627d30..282a0685d 100644
--- a/hw-tests/ecgtest/main.c
+++ b/hw-tests/ecgtest/main.c
@@ -101,6 +101,7 @@ static void ecg_config(bool enable_internal_pull)
     //CNFG_ECG_r.bits.gain = 3;       // ECG gain = 160V/V
     CNFG_ECG_r.bits.gain = 0;
     CNFG_ECG_r.bits.rate = 2;       // Sample rate = 128 sps
+    //CNFG_ECG_r.bits.rate = 1;       // Sample rate = 256 sps
     ecg_write_reg(CNFG_ECG , CNFG_ECG_r.all);
 
 
@@ -203,6 +204,162 @@ void update(void)
     LCD_Update();
 }
 
+#if 0
+/*
+
+FIR filter designed with
+http://t-filter.appspot.com
+
+sampling frequency: 200 Hz
+
+* 0 Hz - 40 Hz
+  gain = 1
+  desired ripple = 5 dB
+  actual ripple = 4.1393894966071585 dB
+
+* 50 Hz - 100 Hz
+  gain = 0
+  desired attenuation = -40 dB
+  actual attenuation = -40.07355419274887 dB
+
+*/
+
+#define FILTER_TAP_NUM 21
+
+static double filter_taps[FILTER_TAP_NUM] = {
+  -0.02010411882885732,
+  -0.05842798004352509,
+  -0.061178403647821976,
+  -0.010939393385338943,
+  0.05125096443534972,
+  0.033220867678947885,
+  -0.05655276971833928,
+  -0.08565500737264514,
+  0.0633795996605449,
+  0.310854403656636,
+  0.4344309124179415,
+  0.310854403656636,
+  0.0633795996605449,
+  -0.08565500737264514,
+  -0.05655276971833928,
+  0.033220867678947885,
+  0.05125096443534972,
+  -0.010939393385338943,
+  -0.061178403647821976,
+  -0.05842798004352509,
+  -0.02010411882885732
+};
+#endif
+#if 1
+/*
+
+FIR filter designed with
+http://t-filter.appspot.com
+
+sampling frequency: 200 Hz
+
+* 0 Hz - 40 Hz
+  gain = 1
+  desired ripple = 0.5 dB
+  actual ripple = 0.3562617633495587 dB
+
+* 45 Hz - 100 Hz
+  gain = 0
+  desired attenuation = -40 dB
+  actual attenuation = -40.37207584888854 dB
+
+*/
+
+#define FILTER_TAP_NUM 71
+
+static double filter_taps[FILTER_TAP_NUM] = {
+  0.0057445089465822975,
+  0.007451288673767406,
+  -0.0011523652638272097,
+  -0.0030609271459832005,
+  -0.002596310763437956,
+  0.004452951934981218,
+  0.003740429140762826,
+  -0.002351310203707235,
+  -0.00638990322759006,
+  0.000013108391204023357,
+  0.007226767366250225,
+  0.003994033360879168,
+  -0.00665189327484351,
+  -0.008058131222070393,
+  0.0035814528837470965,
+  0.011450459869389184,
+  0.0016796020911726648,
+  -0.012657785603199918,
+  -0.008611714082779583,
+  0.010587105712461298,
+  0.01581354994840992,
+  -0.0044174953741535905,
+  -0.021419548904285074,
+  -0.005927897725436821,
+  0.023159558089340265,
+  0.01973520678092361,
+  -0.018604692334579287,
+  -0.035495574517452874,
+  0.004958680854279203,
+  0.05116349477789529,
+  0.02244341207607204,
+  -0.06449371846227892,
+  -0.0790927656214483,
+  0.07342794634140555,
+  0.3089023630319309,
+  0.42341742792869774,
+  0.3089023630319309,
+  0.07342794634140555,
+  -0.0790927656214483,
+  -0.06449371846227892,
+  0.02244341207607204,
+  0.05116349477789529,
+  0.004958680854279203,
+  -0.035495574517452874,
+  -0.018604692334579287,
+  0.01973520678092361,
+  0.023159558089340265,
+  -0.005927897725436821,
+  -0.021419548904285074,
+  -0.0044174953741535905,
+  0.01581354994840992,
+  0.010587105712461298,
+  -0.008611714082779583,
+  -0.012657785603199918,
+  0.0016796020911726648,
+  0.011450459869389184,
+  0.0035814528837470965,
+  -0.008058131222070393,
+  -0.00665189327484351,
+  0.003994033360879168,
+  0.007226767366250225,
+  0.000013108391204023357,
+  -0.00638990322759006,
+  -0.002351310203707235,
+  0.003740429140762826,
+  0.004452951934981218,
+  -0.002596310763437956,
+  -0.0030609271459832005,
+  -0.0011523652638272097,
+  0.007451288673767406,
+  0.0057445089465822975
+};
+#endif
+int32_t fir(int32_t a)
+{
+    static int32_t x[FILTER_TAP_NUM];
+    memmove(x + 1, x, sizeof(*x) * (FILTER_TAP_NUM-1));
+    x[0] = a;
+
+    int32_t y = 0;
+    int i;
+    for(i = 0; i < FILTER_TAP_NUM; i++) {
+        y += filter_taps[i] * x[i];
+    }
+    return y;
+}
+
 static uint8_t sample_count = 0;
 
 static void add_sample(int16_t sample)
@@ -244,6 +401,8 @@ int main(void)
     GPIO_OutClr(&analog_switch); // Wrist
 
     internal_pull = true;
+
+    #if 1
     ecg_config(internal_pull);
 
     for(int i=0; i<0x20; i++) {
@@ -252,6 +411,11 @@ int main(void)
     }
 
     ecg_write_reg(SYNCH, 0);
+    #else
+    max86150_begin();
+    max86150_setup(0x1F, 4, 3, 400, 411, 4096);
+    #endif
+
 
     uint32_t ecgFIFO, readECGSamples, idx, ETAG[32], status;
     int16_t ecgSample[32];
@@ -262,7 +426,9 @@ int main(void)
     const int ETAG_BITS_MASK = 0x7;
 
 
+
     while(1) {
+        #if 1
         // Read back ECG samples from the FIFO
         if( ecgFIFOIntFlag ) {
             ecgFIFOIntFlag = false;
@@ -317,5 +483,35 @@ int main(void)
                 }
             }
         }
+        #else
+        static float y1, y2, y3;
+
+        static const float alpha = 0.001;
+
+        if(max86150_check()>0) {
+            while(max86150_available()) {
+                uint32_t a = max86150_getFIFORed();
+                uint32_t b = max86150_getFIFOIR();
+                //if( a & (0x1 << 17) ) {
+                //    a |= 0xFFFC0000;
+                //}
+
+                //int32_t b = (int32_t)a;
+                //int32_t b = (int16_t)max86150_getFIFOEcg();
+                int16_t c = (int16_t)max86150_getFIFOECG();
+                c = fir(c);
+
+                y1 = (1. - alpha) * y1 + alpha * a;
+                y2 = (1. - alpha) * y2 + alpha * b;
+                y3 = (1. - alpha) * y3 + alpha * c;
+
+                //printf("%d,%d,%d\n", a - y1, b - y2, c - y3);
+                //printf("%d,%d,%d\n", a, b, c);
+
+                add_sample(c - y3);
+                max86150_nextSample();
+            }
+        }
+        #endif
     }
 }
diff --git a/lib/card10/meson.build b/lib/card10/meson.build
index 9797d7bf7..4def91a10 100644
--- a/lib/card10/meson.build
+++ b/lib/card10/meson.build
@@ -17,6 +17,7 @@ deps = [
     board_card10,
     libgfx,
     max77650,
+    max86150,
     periphdriver,
 ]
 
diff --git a/lib/meson.build b/lib/meson.build
index ed96479d3..53c1a6fef 100644
--- a/lib/meson.build
+++ b/lib/meson.build
@@ -4,6 +4,7 @@ subdir('./vendor/Bosch/BHy1/')
 subdir('./vendor/Bosch/BME680/')
 subdir('./vendor/Bosch/BMA400/')
 subdir('./vendor/Maxim/MAX77650/')
+subdir('./vendor/Maxim/MAX86150/')
 subdir('./gfx/')
 
 subdir('./micropython/')
diff --git a/lib/vendor/Maxim/MAX86150/max86150.c b/lib/vendor/Maxim/MAX86150/max86150.c
new file mode 100644
index 000000000..ab14bb108
--- /dev/null
+++ b/lib/vendor/Maxim/MAX86150/max86150.c
@@ -0,0 +1,667 @@
+/***************************************************
+  Arduino library written for the Maxim MAX86150 ECG and PPG integrated sensor
+
+	Written by Ashwin Whitchurch, ProtoCentral Electronics (www.protocentral.com)
+
+  Based on code written by Peter Jansen and Nathan Seidle (SparkFun) for the MAX30105 sensor
+  BSD license, all text above must be included in any redistribution.
+ *****************************************************/
+
+#include "max86150.h"
+
+#include "tmr_utils.h"
+#include "i2c.h"
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+
+typedef uint8_t byte;
+
+static const uint8_t MAX86150_INTSTAT1 =		0x00;
+static const uint8_t MAX86150_INTSTAT2 =		0x01;
+static const uint8_t MAX86150_INTENABLE1 =		0x02;
+static const uint8_t MAX86150_INTENABLE2 =		0x03;
+
+static const uint8_t MAX86150_FIFOWRITEPTR = 	0x04;
+static const uint8_t MAX86150_FIFOOVERFLOW = 	0x05;
+static const uint8_t MAX86150_FIFOREADPTR = 	0x06;
+static const uint8_t MAX86150_FIFODATA 		=		0x07;
+
+static const uint8_t MAX86150_FIFOCONFIG = 		0x08;
+static const uint8_t MAX86150_FIFOCONTROL1= 	0x09;
+static const uint8_t MAX86150_FIFOCONTROL2 = 	0x0A;
+
+static const uint8_t MAX86150_SYSCONTROL = 	0x0D;
+static const uint8_t MAX86150_PPGCONFIG1 = 		0x0E;
+static const uint8_t MAX86150_PPGCONFIG2 = 		0x0F;
+static const uint8_t MAX86150_LED_PROX_AMP = 	0x10;
+
+static const uint8_t MAX86150_LED1_PULSEAMP = 	0x11;
+static const uint8_t MAX86150_LED2_PULSEAMP = 	0x12;
+static const uint8_t MAX86150_LED_RANGE 		= 	0x14;
+static const uint8_t MAX86150_LED_PILOT_PA 	= 	0x15;
+
+static const uint8_t MAX86150_ECG_CONFIG1 	= 	0x3C;
+static const uint8_t MAX86150_ECG_CONFIG3 	= 	0x3E;
+static const uint8_t MAX86150_PROXINTTHRESH = 	0x10;
+
+static const uint8_t MAX86150_PARTID = 			0xFF;
+
+// MAX86150 Commands
+static const uint8_t MAX86150_INT_A_FULL_MASK =		(byte)~0b10000000;
+static const uint8_t MAX86150_INT_A_FULL_ENABLE = 	0x80;
+static const uint8_t MAX86150_INT_A_FULL_DISABLE = 	0x00;
+
+static const uint8_t MAX86150_INT_DATA_RDY_MASK = (byte)~0b01000000;
+static const uint8_t MAX86150_INT_DATA_RDY_ENABLE =	0x40;
+static const uint8_t MAX86150_INT_DATA_RDY_DISABLE = 0x00;
+
+static const uint8_t MAX86150_INT_ALC_OVF_MASK = (byte)~0b00100000;
+static const uint8_t MAX86150_INT_ALC_OVF_ENABLE = 	0x20;
+static const uint8_t MAX86150_INT_ALC_OVF_DISABLE = 0x00;
+
+static const uint8_t MAX86150_INT_PROX_INT_MASK = (byte)~0b00010000;
+static const uint8_t MAX86150_INT_PROX_INT_ENABLE = 0x10;
+static const uint8_t MAX86150_INT_PROX_INT_DISABLE = 0x00;
+
+static const uint8_t MAX86150_SAMPLEAVG_MASK =	(byte)~0b11100000;
+static const uint8_t MAX86150_SAMPLEAVG_1 = 	0x00;
+static const uint8_t MAX86150_SAMPLEAVG_2 = 	0x20;
+static const uint8_t MAX86150_SAMPLEAVG_4 = 	0x40;
+static const uint8_t MAX86150_SAMPLEAVG_8 = 	0x60;
+static const uint8_t MAX86150_SAMPLEAVG_16 = 	0x80;
+static const uint8_t MAX86150_SAMPLEAVG_32 = 	0xA0;
+
+static const uint8_t MAX86150_ROLLOVER_MASK = 	0xEF;
+static const uint8_t MAX86150_ROLLOVER_ENABLE = 0x10;
+static const uint8_t MAX86150_ROLLOVER_DISABLE = 0x00;
+
+static const uint8_t MAX86150_A_FULL_MASK = 	0xF0;
+
+static const uint8_t MAX86150_SHUTDOWN_MASK = 	0x7F;
+static const uint8_t MAX86150_SHUTDOWN = 		0x80;
+static const uint8_t MAX86150_WAKEUP = 			0x00;
+
+static const uint8_t MAX86150_RESET_MASK = 		0xFE;
+static const uint8_t MAX86150_RESET = 			0x01;
+
+static const uint8_t MAX86150_MODE_MASK = 		0xF8;
+static const uint8_t MAX86150_MODE_REDONLY = 	0x02;
+static const uint8_t MAX86150_MODE_REDIRONLY = 	0x03;
+static const uint8_t MAX86150_MODE_MULTILED = 	0x07;
+
+static const uint8_t MAX86150_ADCRANGE_MASK = 	0x9F;
+static const uint8_t MAX86150_ADCRANGE_2048 = 	0x00;
+static const uint8_t MAX86150_ADCRANGE_4096 = 	0x20;
+static const uint8_t MAX86150_ADCRANGE_8192 = 	0x40;
+static const uint8_t MAX86150_ADCRANGE_16384 = 	0x60;
+
+static const uint8_t MAX86150_SAMPLERATE_MASK = 0xE3;
+static const uint8_t MAX86150_SAMPLERATE_50 = 	0x00;
+static const uint8_t MAX86150_SAMPLERATE_100 = 	0x04;
+static const uint8_t MAX86150_SAMPLERATE_200 = 	0x08;
+static const uint8_t MAX86150_SAMPLERATE_400 = 	0x0C;
+static const uint8_t MAX86150_SAMPLERATE_800 = 	0x10;
+static const uint8_t MAX86150_SAMPLERATE_1000 = 0x14;
+static const uint8_t MAX86150_SAMPLERATE_1600 = 0x18;
+static const uint8_t MAX86150_SAMPLERATE_3200 = 0x1C;
+
+static const uint8_t MAX86150_PULSEWIDTH_MASK = 0xFC;
+static const uint8_t MAX86150_PULSEWIDTH_69 = 	0x00;
+static const uint8_t MAX86150_PULSEWIDTH_118 = 	0x01;
+static const uint8_t MAX86150_PULSEWIDTH_215 = 	0x02;
+static const uint8_t MAX86150_PULSEWIDTH_411 = 	0x03;
+
+static const uint8_t MAX86150_SLOT1_MASK = 		0xF0;
+static const uint8_t MAX86150_SLOT2_MASK = 		0x0F;
+static const uint8_t MAX86150_SLOT3_MASK = 		0xF0;
+static const uint8_t MAX86150_SLOT4_MASK = 		0x0F;
+
+static const uint8_t SLOT_NONE 				= 			0x00;
+static const uint8_t SLOT_RED_LED 		= 			0x01;
+static const uint8_t SLOT_IR_LED 			= 			0x02;
+static const uint8_t SLOT_RED_PILOT 	=				0x09;
+static const uint8_t SLOT_IR_PILOT 		= 			0x0A;
+static const uint8_t SLOT_ECG					= 			0x0D;
+
+static const uint8_t MAX_30105_EXPECTEDPARTID = 0x1E;
+
+static uint8_t _i2caddr;
+
+//activeLEDs is the number of channels turned on, and can be 1 to 3. 2 is common for Red+IR.
+static byte activeDevices; //Gets set during max86150_setup. Allows max86150_check() to calculate how many bytes to read from FIFO
+
+static void max86150_bitMask(uint8_t reg, uint8_t mask, uint8_t thing);
+
+#define STORAGE_SIZE 128 //Each long is 4 bytes so limit this to fit on your micro
+typedef struct Record
+{
+    uint32_t red[STORAGE_SIZE];
+    uint32_t IR[STORAGE_SIZE];
+    int32_t ecg[STORAGE_SIZE];
+    byte head;
+    byte tail;
+} sense_struct; //This is our circular buffer of readings from the sensor
+
+static sense_struct sense;
+
+static void delay(int ms)
+{
+    TMR_Delay(MXC_TMR0, MSEC(ms), 0);
+}
+
+bool max86150_begin(void)
+{
+  _i2caddr = MAX86150_ADDRESS;
+
+  // Step 1: Initial Communication and Verification
+  // Check that a MAX86150 is connected
+  if (max86150_readPartID() != MAX_30105_EXPECTEDPARTID) {
+    // Error -- Part ID read from MAX86150 does not match expected part ID.
+    // This may mean there is a physical connectivity problem (broken wire, unpowered, etc).
+    return false;
+  }
+  return true;
+}
+
+//
+// Configuration
+//
+
+//Begin Interrupt configuration
+uint8_t max86150_getINT1(void)
+{
+  return (max86150_readRegister8(_i2caddr, MAX86150_INTSTAT1));
+}
+uint8_t max86150_getINT2(void) {
+  return (max86150_readRegister8(_i2caddr, MAX86150_INTSTAT2));
+}
+
+void max86150_enableAFULL(void) {
+  max86150_bitMask(MAX86150_INTENABLE1, MAX86150_INT_A_FULL_MASK, MAX86150_INT_A_FULL_ENABLE);
+}
+void max86150_disableAFULL(void) {
+  max86150_bitMask(MAX86150_INTENABLE1, MAX86150_INT_A_FULL_MASK, MAX86150_INT_A_FULL_DISABLE);
+}
+
+void max86150_enableDATARDY(void) {
+  max86150_bitMask(MAX86150_INTENABLE1, MAX86150_INT_DATA_RDY_MASK, MAX86150_INT_DATA_RDY_ENABLE);
+}
+void max86150_disableDATARDY(void) {
+  max86150_bitMask(MAX86150_INTENABLE1, MAX86150_INT_DATA_RDY_MASK, MAX86150_INT_DATA_RDY_DISABLE);
+}
+
+void max86150_enableALCOVF(void) {
+  max86150_bitMask(MAX86150_INTENABLE1, MAX86150_INT_ALC_OVF_MASK, MAX86150_INT_ALC_OVF_ENABLE);
+}
+void max86150_disableALCOVF(void) {
+  max86150_bitMask(MAX86150_INTENABLE1, MAX86150_INT_ALC_OVF_MASK, MAX86150_INT_ALC_OVF_DISABLE);
+}
+
+void max86150_enablePROXINT(void) {
+  max86150_bitMask(MAX86150_INTENABLE1, MAX86150_INT_PROX_INT_MASK, MAX86150_INT_PROX_INT_ENABLE);
+}
+void max86150_disablePROXINT(void) {
+  max86150_bitMask(MAX86150_INTENABLE1, MAX86150_INT_PROX_INT_MASK, MAX86150_INT_PROX_INT_DISABLE);
+}
+//End Interrupt configuration
+
+void max86150_softReset(void) {
+  max86150_bitMask(MAX86150_SYSCONTROL, MAX86150_RESET_MASK, MAX86150_RESET);
+
+  // Poll for bit to clear, reset is then complete
+  // Timeout after 100ms
+  //TODO
+  //unsigned long startTime = millis();
+  //while (millis() - startTime < 100)
+  {
+    //uint8_t response = max86150_readRegister8(_i2caddr, MAX86150_SYSCONTROL);
+    //if ((response & MAX86150_RESET) == 0) break; //We're done!
+    delay(1); //Let's not over burden the I2C bus
+  }
+}
+
+void max86150_shutDown(void) {
+  // Put IC into low power mode (datasheet pg. 19)
+  // During shutdown the IC will continue to respond to I2C commands but will
+  // not update with or take new readings (such as temperature)
+  max86150_bitMask(MAX86150_SYSCONTROL, MAX86150_SHUTDOWN_MASK, MAX86150_SHUTDOWN);
+}
+
+void max86150_wakeUp(void) {
+  // Pull IC out of low power mode (datasheet pg. 19)
+  max86150_bitMask(MAX86150_SYSCONTROL, MAX86150_SHUTDOWN_MASK, MAX86150_WAKEUP);
+}
+
+void max86150_setLEDMode(uint8_t mode) {
+  // Set which LEDs are used for sampling -- Red only, RED+IR only, or custom.
+  // See datasheet, page 19
+  //max86150_bitMask(MAX86150_PPGCONFIG1, MAX86150_MODE_MASK, mode);
+}
+
+void max86150_setADCRange(uint8_t adcRange) {
+  // adcRange: one of MAX86150_ADCRANGE_2048, _4096, _8192, _16384
+  //max86150_bitMask(MAX86150_PARTICLECONFIG, MAX86150_ADCRANGE_MASK, adcRange);
+}
+
+void max86150_setSampleRate(uint8_t sampleRate) {
+  // sampleRate: one of MAX86150_SAMPLERATE_50, _100, _200, _400, _800, _1000, _1600, _3200
+  //max86150_bitMask(MAX86150_PARTICLECONFIG, MAX86150_SAMPLERATE_MASK, sampleRate);
+}
+
+void max86150_setPulseWidth(uint8_t pulseWidth) {
+  // pulseWidth: one of MAX86150_PULSEWIDTH_69, _188, _215, _411
+  //max86150_bitMask(MAX86150_PPGCONFIG1, MAX86150_PULSEWIDTH_MASK, pulseWidth);
+}
+
+// NOTE: Amplitude values: 0x00 = 0mA, 0x7F = 25.4mA, 0xFF = 50mA (typical)
+// See datasheet, page 21
+void max86150_setPulseAmplitudeRed(uint8_t amplitude)
+{
+  max86150_writeRegister8(_i2caddr, MAX86150_LED2_PULSEAMP, amplitude);
+}
+
+void max86150_setPulseAmplitudeIR(uint8_t amplitude)
+{
+  max86150_writeRegister8(_i2caddr, MAX86150_LED1_PULSEAMP, amplitude);
+}
+
+void max86150_setPulseAmplitudeProximity(uint8_t amplitude) {
+  max86150_writeRegister8(_i2caddr, MAX86150_LED_PROX_AMP, amplitude);
+}
+
+void max86150_setProximityThreshold(uint8_t threshMSB)
+{
+  // The threshMSB signifies only the 8 most significant-bits of the ADC count.
+  max86150_writeRegister8(_i2caddr, MAX86150_PROXINTTHRESH, threshMSB);
+}
+
+//Given a slot number assign a thing to it
+//Devices are SLOT_RED_LED or SLOT_RED_PILOT (proximity)
+//Assigning a SLOT_RED_LED will pulse LED
+//Assigning a SLOT_RED_PILOT will ??
+void max86150_enableSlot(uint8_t slotNumber, uint8_t device)
+{
+	  //uint8_t originalContents;
+
+	  switch (slotNumber) {
+	    case (1):
+	      max86150_bitMask(MAX86150_FIFOCONTROL1, MAX86150_SLOT1_MASK, device);
+	      break;
+	    case (2):
+	      max86150_bitMask(MAX86150_FIFOCONTROL1, MAX86150_SLOT2_MASK, device << 4);
+	      break;
+	    case (3):
+	      max86150_bitMask(MAX86150_FIFOCONTROL2, MAX86150_SLOT3_MASK, device);
+	      break;
+	    case (4):
+	      max86150_bitMask(MAX86150_FIFOCONTROL2, MAX86150_SLOT4_MASK, device << 4);
+	      break;
+	    default:
+	      //Shouldn't be here!
+	      break;
+	  }
+}
+
+//Clears all slot assignments
+void max86150_disableSlots(void)
+{
+  max86150_writeRegister8(_i2caddr, MAX86150_FIFOCONTROL1, 0);
+  max86150_writeRegister8(_i2caddr, MAX86150_FIFOCONTROL2, 0);
+}
+
+//
+// FIFO Configuration
+//
+
+void max86150_setFIFOAverage(uint8_t numberOfSamples)
+{
+  max86150_bitMask(MAX86150_FIFOCONFIG, MAX86150_SAMPLEAVG_MASK, numberOfSamples);
+}
+
+//Resets all points to start in a known state
+void max86150_clearFIFO(void) {
+  max86150_writeRegister8(_i2caddr, MAX86150_FIFOWRITEPTR, 0);
+  max86150_writeRegister8(_i2caddr, MAX86150_FIFOOVERFLOW, 0);
+  max86150_writeRegister8(_i2caddr, MAX86150_FIFOREADPTR, 0);
+}
+
+//Enable roll over if FIFO over flows
+void max86150_enableFIFORollover(void) {
+  max86150_bitMask(MAX86150_FIFOCONFIG, MAX86150_ROLLOVER_MASK, MAX86150_ROLLOVER_ENABLE);
+}
+
+//Disable roll over if FIFO over flows
+void max86150_disableFIFORollover(void) {
+  max86150_bitMask(MAX86150_FIFOCONFIG, MAX86150_ROLLOVER_MASK, MAX86150_ROLLOVER_DISABLE);
+}
+
+//Power on default is 32 samples
+//Note it is reverse: 0x00 is 32 samples, 0x0F is 17 samples
+void max86150_setFIFOAlmostFull(uint8_t numberOfSamples) {
+  max86150_bitMask(MAX86150_FIFOCONFIG, MAX86150_A_FULL_MASK, numberOfSamples);
+}
+
+//Read the FIFO Write Pointer
+uint8_t max86150_getWritePointer(void) {
+  return (max86150_readRegister8(_i2caddr, MAX86150_FIFOWRITEPTR));
+}
+
+//Read the FIFO Read Pointer
+uint8_t max86150_getReadPointer(void) {
+  return (max86150_readRegister8(_i2caddr, MAX86150_FIFOREADPTR));
+}
+
+// Set the PROX_INT_THRESHold
+void max86150_setPROXINTTHRESH(uint8_t val) {
+  max86150_writeRegister8(_i2caddr, MAX86150_PROXINTTHRESH, val);
+}
+
+//
+// Device ID and Revision
+//
+uint8_t max86150_readPartID() {
+  return max86150_readRegister8(_i2caddr, MAX86150_PARTID);
+}
+
+//Setup the sensor
+//The MAX86150 has many settings. By default we select:
+// Sample Average = 4
+// Mode = MultiLED
+// ADC Range = 16384 (62.5pA per LSB)
+// Sample rate = 50
+//Use the default max86150_setup if you are just getting started with the MAX86150 sensor
+void max86150_setup(byte powerLevel, byte sampleAverage, byte ledMode, int sampleRate, int pulseWidth, int adcRange)
+{
+		activeDevices=3;
+		max86150_writeRegister8(_i2caddr,MAX86150_SYSCONTROL,0x01);
+		delay(100);
+		max86150_writeRegister8(_i2caddr,MAX86150_FIFOCONFIG,0x7F);
+
+		//FIFO Configuration
+		//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+		//The chip will average multiple samples of same type together if you wish
+		if (sampleAverage == 1) max86150_setFIFOAverage(MAX86150_SAMPLEAVG_1); //No averaging per FIFO record
+		else if (sampleAverage == 2) max86150_setFIFOAverage(MAX86150_SAMPLEAVG_2);
+		else if (sampleAverage == 4) max86150_setFIFOAverage(MAX86150_SAMPLEAVG_4);
+		else if (sampleAverage == 8) max86150_setFIFOAverage(MAX86150_SAMPLEAVG_8);
+		else if (sampleAverage == 16) max86150_setFIFOAverage(MAX86150_SAMPLEAVG_16);
+		else if (sampleAverage == 32) max86150_setFIFOAverage(MAX86150_SAMPLEAVG_32);
+		else max86150_setFIFOAverage(MAX86150_SAMPLEAVG_4);
+
+		uint16_t FIFOCode = 0x00;
+
+	  FIFOCode = FIFOCode<<4 | 0x0009;// : FIFOCode;  //insert ECG front of ETI in FIFO
+	  FIFOCode = FIFOCode<<8 | 0x0021;//) : FIFOCode; //insert Red(2) and IR (1) in front of ECG in FIFO
+
+
+		//FIFO Control 1 = FD2|FD1, FIFO Control 2 = FD4|FD3
+
+		max86150_writeRegister8(_i2caddr,MAX86150_FIFOCONTROL1,(0b00100001));
+		//max86150_writeRegister8(_i2caddr,MAX86150_FIFOCONTROL1,(0b00001001));
+		//max86150_writeRegister8(_i2caddr,MAX86150_FIFOCONTROL1,(0b00000010));
+		max86150_writeRegister8(_i2caddr,MAX86150_FIFOCONTROL2,(0b00001001));
+		//max86150_writeRegister8(_i2caddr,MAX86150_FIFOCONTROL2,(0b00000000));
+		//max86150_writeRegister8(_i2caddr,MAX86150_FIFOCONTROL1, (char)(FIFOCode & 0x00FF) );
+		//max86150_writeRegister8(_i2caddr,MAX86150_FIFOCONTROL2, (char)(FIFOCode >>8) );
+
+		max86150_writeRegister8(_i2caddr,MAX86150_PPGCONFIG1,0b11010001);
+		//max86150_writeRegister8(_i2caddr,MAX86150_PPGCONFIG1,0b11100111);
+
+		max86150_writeRegister8(_i2caddr,MAX86150_PPGCONFIG2, 0x06);
+		max86150_writeRegister8(_i2caddr,MAX86150_LED_RANGE, 0x00 ); // PPG_ADC_RGE: 32768nA
+
+		max86150_writeRegister8(_i2caddr,MAX86150_SYSCONTROL,0x04);//start FIFO
+
+		max86150_writeRegister8(_i2caddr,MAX86150_ECG_CONFIG1,0b00000011);
+		//max86150_writeRegister8(_i2caddr,MAX86150_ECG_CONFIG1,0b00000001);
+		max86150_writeRegister8(_i2caddr,MAX86150_ECG_CONFIG3,0b00001101);
+
+		max86150_setPulseAmplitudeRed(0xFF);
+		max86150_setPulseAmplitudeIR(0xFF);
+
+  //Multi-LED Mode Configuration, Enable the reading of the three LEDs
+  //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+  //max86150_enableSlot(1, SLOT_RED_LED);
+  //if (ledMode > 1)
+	//max86150_enableSlot(2, SLOT_IR_LED);
+  //if (ledMode > 2)
+	//max86150_enableSlot(3, SLOT_ECG);
+  //max86150_enableSlot(1, SLOT_RED_PILOT);
+  //max86150_enableSlot(2, SLOT_IR_PILOT);
+  //max86150_enableSlot(3, SLOT_GREEN_PILOT);
+  //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+  	max86150_clearFIFO(); //Reset the FIFO before we begin checking the sensor
+}
+
+//Tell caller how many samples are max86150_available
+uint8_t max86150_available(void)
+{
+  int8_t numberOfSamples = sense.head - sense.tail;
+  if (numberOfSamples < 0) numberOfSamples += STORAGE_SIZE;
+
+  return (numberOfSamples);
+}
+
+//Report the most recent red value
+uint32_t max86150_getRed(void)
+{
+  //Check the sensor for new data for 250ms
+  if(max86150_safeCheck(250))
+    return (sense.red[sense.head]);
+  else
+    return(0); //Sensor failed to find new data
+}
+
+//Report the most recent IR value
+uint32_t max86150_getIR(void)
+{
+  //Check the sensor for new data for 250ms
+  if(max86150_safeCheck(250))
+    return (sense.IR[sense.head]);
+  else
+    return(0); //Sensor failed to find new data
+}
+
+//Report the most recent Green value
+int32_t max86150_getECG(void)
+{
+  //Check the sensor for new data for 250ms
+  if(max86150_safeCheck(250))
+    return (sense.ecg[sense.head]);
+  else
+    return(0); //Sensor failed to find new data
+}
+
+//Report the next Red value in the FIFO
+uint32_t max86150_getFIFORed(void)
+{
+  return (sense.red[sense.tail]);
+}
+
+//Report the next IR value in the FIFO
+uint32_t max86150_getFIFOIR(void)
+{
+  return (sense.IR[sense.tail]);
+}
+
+//Report the next Green value in the FIFO
+int32_t max86150_getFIFOECG(void)
+{
+  return (sense.ecg[sense.tail]);
+}
+
+//Advance the tail
+void max86150_nextSample(void)
+{
+  if(max86150_available()) //Only advance the tail if new data is max86150_available
+  {
+    sense.tail++;
+    sense.tail %= STORAGE_SIZE; //Wrap condition
+  }
+}
+
+//Polls the sensor for new data
+//Call regularly
+//If new data is max86150_available, it updates the head and tail in the main struct
+//Returns number of new samples obtained
+uint16_t max86150_check(void)
+{
+  //Read register FIDO_DATA in (3-byte * number of active LED) chunks
+  //Until FIFO_RD_PTR = FIFO_WR_PTR
+
+  byte readPointer = max86150_getReadPointer();
+  byte writePointer = max86150_getWritePointer();
+
+  int numberOfSamples = 0;
+
+  //Do we have new data?
+  if (readPointer != writePointer)
+  {
+    //Calculate the number of readings we need to get from sensor
+    numberOfSamples = writePointer - readPointer;
+    if (numberOfSamples < 0) numberOfSamples += 32; //Wrap condition
+
+    //We now have the number of readings, now calc bytes to read
+    //For this example we are just doing Red and IR (3 bytes each)
+    int bytesLeftToRead = numberOfSamples * activeDevices * 3;
+
+    //Get ready to read a burst of data from the FIFO register
+    uint8_t command[] = {MAX86150_FIFODATA};
+    I2C_MasterWrite(MXC_I2C1_BUS0, _i2caddr << 1, command, 1, 0);
+
+    //We may need to read as many as 288 bytes so we read in blocks no larger than I2C_BUFFER_LENGTH
+    //I2C_BUFFER_LENGTH changes based on the platform. 64 bytes for SAMD21, 32 bytes for Uno.
+    //Wire.requestFrom() is limited to BUFFER_LENGTH which is 32 on the Uno
+    while (bytesLeftToRead > 0)
+    {
+      int toGet = bytesLeftToRead;
+      if (toGet > I2C_BUFFER_LENGTH)
+      {
+        //If toGet is 32 this is bad because we read 6 bytes (Red+IR * 3 = 6) at a time
+        //32 % 6 = 2 left over. We don't want to request 32 bytes, we want to request 30.
+        //32 % 9 (Red+IR+GREEN) = 5 left over. We want to request 27.
+
+        toGet = I2C_BUFFER_LENGTH - (I2C_BUFFER_LENGTH % (activeDevices * 3)); //Trim toGet to be a multiple of the samples we need to read
+      }
+
+      bytesLeftToRead -= toGet;
+
+      //Request toGet number of bytes from sensor
+      //_i2cPort->requestFrom(_i2caddr, toGet);
+      uint8_t data[toGet];
+      uint8_t *p = data;
+      I2C_MasterRead(MXC_I2C1_BUS0, _i2caddr << 1, data, toGet, 0);
+
+      while (toGet > 0)
+      {
+        sense.head++; //Advance the head of the storage struct
+        sense.head %= STORAGE_SIZE; //Wrap condition
+
+        byte temp[sizeof(uint32_t)]; //Array of 4 bytes that we will convert into long
+        uint32_t tempLong;
+
+        //Burst read three bytes - RED
+        temp[3] = 0;
+        temp[2] = *p++;
+        temp[1] = *p++;
+        temp[0] = *p++;
+
+        //Convert array to long
+        memcpy(&tempLong, temp, sizeof(tempLong));
+
+				tempLong &= 0x7FFFF; //Zero out all but 18 bits
+
+        sense.red[sense.head] = tempLong; //Store this reading into the sense array
+
+        if (activeDevices > 1)
+        {
+          //Burst read three more bytes - IR
+          temp[3] = 0;
+          temp[2] = *p++;
+          temp[1] = *p++;
+          temp[0] = *p++;
+
+          //Convert array to long
+          memcpy(&tempLong, temp, sizeof(tempLong));
+					//Serial.println(tempLong);
+				  tempLong &= 0x7FFFF; //Zero out all but 18 bits
+
+				  sense.IR[sense.head] = tempLong;
+        }
+
+        if (activeDevices > 2)
+        {
+          //Burst read three more bytes - ECG
+					int32_t tempLongSigned;
+
+          temp[3] = 0;
+          temp[2] = *p++;
+          temp[1] = *p++;
+          temp[0] = *p++;
+
+					//Serial.println(tempLong);
+          //Convert array to long
+          memcpy(&tempLongSigned, temp, sizeof(tempLongSigned));
+
+		  		//tempLong &= 0x3FFFF; //Zero out all but 18 bits
+
+          sense.ecg[sense.head] = tempLongSigned;
+        }
+
+        toGet -= activeDevices * 3;
+      }
+    } //End while (bytesLeftToRead > 0)
+  } //End readPtr != writePtr
+  return (numberOfSamples); //Let the world know how much new data we found
+}
+
+//Check for new data but give up after a certain amount of time
+//Returns true if new data was found
+//Returns false if new data was not found
+bool max86150_safeCheck(uint8_t maxTimeToCheck)
+{
+  // TODO
+  //uint32_t markTime = millis();
+
+  while(1)
+  {
+	//if(millis() - markTime > maxTimeToCheck) return(false);
+
+	if(max86150_check() == true) //We found new data!
+	  return(true);
+
+	delay(1);
+  }
+}
+
+//Given a register, read it, mask it, and then set the thing
+void max86150_bitMask(uint8_t reg, uint8_t mask, uint8_t thing)
+{
+  // Grab current register context
+  uint8_t originalContents = max86150_readRegister8(_i2caddr, reg);
+
+  // Zero-out the portions of the register we're interested in
+  originalContents = originalContents & mask;
+
+  // Change contents
+  max86150_writeRegister8(_i2caddr, reg, originalContents | thing);
+}
+
+uint8_t max86150_readRegister8(uint8_t address, uint8_t reg) {
+
+  uint8_t tempData = 0;
+  uint8_t command[] = {reg};
+  I2C_MasterWrite(MXC_I2C1_BUS0, address << 1, command, 1, 0);
+
+  I2C_MasterRead(MXC_I2C1_BUS0, address << 1, &tempData, 1, 0);
+
+  return tempData;
+}
+
+void max86150_writeRegister8(uint8_t address, uint8_t reg, uint8_t value) {
+  uint8_t command[] = {reg, value};
+  I2C_MasterWrite(MXC_I2C1_BUS0, address << 1, command, 2, 0);
+}
diff --git a/lib/vendor/Maxim/MAX86150/max86150.h b/lib/vendor/Maxim/MAX86150/max86150.h
new file mode 100644
index 000000000..c9454ea51
--- /dev/null
+++ b/lib/vendor/Maxim/MAX86150/max86150.h
@@ -0,0 +1,103 @@
+/***************************************************
+  Arduino library written for the Maxim MAX86150 ECG and PPG integrated sensor
+
+	Written by Ashwin Whitchurch, ProtoCentral Electronics (www.protocentral.com)
+
+	https://github.com/protocentral/protocentral_max86150
+
+  Based on code written by Peter Jansen and Nathan Seidle (SparkFun) for the MAX30105 sensor
+  BSD license, all text above must be included in any redistribution.
+ *****************************************************/
+
+#pragma once
+
+#define MAX86150_ADDRESS          0x5E //7-bit I2C Address
+
+#define I2C_BUFFER_LENGTH 64
+
+#include <stdint.h>
+#include <stdbool.h>
+
+typedef uint8_t byte;
+
+bool max86150_begin(void);
+
+uint32_t max86150_getRed(void); //Returns immediate red value
+uint32_t max86150_getIR(void); //Returns immediate IR value
+int32_t max86150_getECG(void); //Returns immediate ECG value
+bool max86150_safeCheck(uint8_t maxTimeToCheck); //Given a max amount of time, check for new data
+
+// Configuration
+void max86150_softReset();
+void max86150_shutDown();
+void max86150_wakeUp();
+
+void max86150_setLEDMode(uint8_t mode);
+
+void max86150_setADCRange(uint8_t adcRange);
+void max86150_setSampleRate(uint8_t sampleRate);
+void max86150_setPulseWidth(uint8_t pulseWidth);
+
+void max86150_setPulseAmplitudeRed(uint8_t value);
+void max86150_setPulseAmplitudeIR(uint8_t value);
+void max86150_setPulseAmplitudeProximity(uint8_t value);
+
+void max86150_setProximityThreshold(uint8_t threshMSB);
+
+//Multi-led configuration mode (page 22)
+void max86150_enableSlot(uint8_t slotNumber, uint8_t device); //Given slot number, assign a device to slot
+void max86150_disableSlots(void);
+
+// Data Collection
+
+//Interrupts (page 13, 14)
+uint8_t max86150_getINT1(void); //Returns the main interrupt group
+uint8_t max86150_getINT2(void); //Returns the temp ready interrupt
+void max86150_enableAFULL(void); //Enable/disable individual interrupts
+void max86150_disableAFULL(void);
+void max86150_enableDATARDY(void);
+void max86150_disableDATARDY(void);
+void max86150_enableALCOVF(void);
+void max86150_disableALCOVF(void);
+void max86150_enablePROXINT(void);
+void max86150_disablePROXINT(void);
+void max86150_enableDIETEMPRDY(void);
+void max86150_disableDIETEMPRDY(void);
+
+//FIFO Configuration (page 18)
+void max86150_setFIFOAverage(uint8_t samples);
+void max86150_enableFIFORollover();
+void max86150_disableFIFORollover();
+void max86150_setFIFOAlmostFull(uint8_t samples);
+
+//FIFO Reading
+uint16_t max86150_check(void); //Checks for new data and fills FIFO
+uint8_t max86150_available(void); //Tells caller how many new samples are available (head - tail)
+void max86150_nextSample(void); //Advances the tail of the sense array
+uint32_t max86150_getFIFORed(void); //Returns the FIFO sample pointed to by tail
+uint32_t max86150_getFIFOIR(void); //Returns the FIFO sample pointed to by tail
+int32_t max86150_getFIFOECG(void); //Returns the FIFO sample pointed to by tail
+
+uint8_t max86150_getWritePointer(void);
+uint8_t max86150_getReadPointer(void);
+void max86150_clearFIFO(void); //Sets the read/write pointers to zero
+
+//Proximity Mode Interrupt Threshold
+void max86150_setPROXINTTHRESH(uint8_t val);
+
+// Die Temperature
+float max86150_readTemperature();
+float max86150_readTemperatureF();
+
+// Detecting ID/Revision
+uint8_t max86150_getRevisionID();
+uint8_t max86150_readPartID();
+uint8_t max86150_readRegLED();
+
+// Setup the IC with user selectable settings
+//void max86150_setup(byte powerLevel = 0x1F, byte sampleAverage = 4, byte ledMode = 3, int sampleRate = 400, int pulseWidth = 411, int adcRange = 4096);
+void max86150_setup(byte powerLevel, byte sampleAverage, byte ledMode, int sampleRate, int pulseWidth, int adcRange);
+
+// Low-level I2C communication
+uint8_t max86150_readRegister8(uint8_t address, uint8_t reg);
+void max86150_writeRegister8(uint8_t address, uint8_t reg, uint8_t value);
diff --git a/lib/vendor/Maxim/MAX86150/meson.build b/lib/vendor/Maxim/MAX86150/meson.build
new file mode 100644
index 000000000..8254e3009
--- /dev/null
+++ b/lib/vendor/Maxim/MAX86150/meson.build
@@ -0,0 +1,14 @@
+includes = include_directories(
+  './',
+)
+
+lib = static_library(
+  'max86150',
+  'max86150.c',
+  dependencies: periphdriver,
+)
+
+max86150 = declare_dependency(
+  include_directories: includes,
+  link_with: lib,
+)
-- 
GitLab