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