416 lines
13 KiB
C++
416 lines
13 KiB
C++
#ifndef MAX30102_H
|
|
#define MAX30102_H
|
|
|
|
#include "../../Platforms.h"
|
|
//#include "../../io/HardI2C.h"
|
|
#include "../../io/SoftI2C.h"
|
|
|
|
// https://www.roboter-bausatz.de/media/pdf/bf/52/37/RBS12853_MAX30102-DS-917698.pdf
|
|
// https://github.com/sparkfun/SparkFun_MAX3010x_Sensor_Library/blob/master/src/MAX30105.cpp
|
|
// https://github.com/vrano714/max30102-tutorial-raspberrypi/blob/master/max30102.py
|
|
|
|
/** SPI-like touch controller */
|
|
template <typename I2C> class Max30102 {
|
|
|
|
public:
|
|
|
|
class Listener {
|
|
public:
|
|
virtual void onIR(const uint32_t ir) = 0;
|
|
virtual void onRed(const uint32_t red) = 0;
|
|
};
|
|
|
|
private:
|
|
|
|
I2C& i2c;
|
|
|
|
static constexpr const char* NAME = "Max30102";
|
|
static constexpr uint8_t ADDR = 0b1010111;
|
|
|
|
// Status Registers
|
|
static const uint8_t MAX30105_INTSTAT1 = 0x00;
|
|
static const uint8_t MAX30105_INTSTAT2 = 0x01;
|
|
static const uint8_t MAX30105_INTENABLE1 = 0x02;
|
|
static const uint8_t MAX30105_INTENABLE2 = 0x03;
|
|
|
|
// FIFO Registers
|
|
static const uint8_t MAX30105_FIFOWRITEPTR = 0x04;
|
|
static const uint8_t MAX30105_FIFOOVERFLOW = 0x05;
|
|
static const uint8_t MAX30105_FIFOREADPTR = 0x06;
|
|
static const uint8_t MAX30105_FIFODATA = 0x07;
|
|
|
|
// Configuration Registers
|
|
static const uint8_t MAX30105_FIFOCONFIG = 0x08;
|
|
static const uint8_t MAX30105_MODECONFIG = 0x09;
|
|
static const uint8_t MAX30105_PARTICLECONFIG = 0x0A; // Note, sometimes listed as "SPO2" config in datasheet (pg. 11)
|
|
static const uint8_t MAX30105_LED1_PULSEAMP = 0x0C;
|
|
static const uint8_t MAX30105_LED2_PULSEAMP = 0x0D;
|
|
//static const uint8_t MAX30105_LED3_PULSEAMP = 0x0E; // not available for MAX30102
|
|
static const uint8_t MAX30105_LED_PROX_AMP = 0x10;
|
|
static const uint8_t MAX30105_MULTILEDCONFIG1 = 0x11;
|
|
static const uint8_t MAX30105_MULTILEDCONFIG2 = 0x12;
|
|
|
|
// Die Temperature Registers
|
|
static const uint8_t MAX30105_DIETEMPINT = 0x1F;
|
|
static const uint8_t MAX30105_DIETEMPFRAC = 0x20;
|
|
static const uint8_t MAX30105_DIETEMPCONFIG = 0x21;
|
|
|
|
// Proximity Function Registers
|
|
static const uint8_t MAX30105_PROXINTTHRESH = 0x30;
|
|
|
|
// Part ID Registers
|
|
static const uint8_t MAX30105_REVISIONID = 0xFE;
|
|
static const uint8_t MAX30105_PARTID = 0xFF; // Should always be 0x15. Identical to MAX30102.
|
|
|
|
// MAX30105 Commands
|
|
// Interrupt configuration (pg 13, 14)
|
|
static const uint8_t MAX30105_INT_A_FULL_MASK = (uint8_t)~0b10000000;
|
|
static const uint8_t MAX30105_INT_A_FULL_ENABLE = 0x80;
|
|
static const uint8_t MAX30105_INT_A_FULL_DISABLE = 0x00;
|
|
|
|
static const uint8_t MAX30105_INT_DATA_RDY_MASK = (uint8_t)~0b01000000;
|
|
static const uint8_t MAX30105_INT_DATA_RDY_ENABLE = 0x40;
|
|
static const uint8_t MAX30105_INT_DATA_RDY_DISABLE = 0x00;
|
|
|
|
static const uint8_t MAX30105_INT_ALC_OVF_MASK = (uint8_t)~0b00100000;
|
|
static const uint8_t MAX30105_INT_ALC_OVF_ENABLE = 0x20;
|
|
static const uint8_t MAX30105_INT_ALC_OVF_DISABLE = 0x00;
|
|
|
|
static const uint8_t MAX30105_INT_PROX_INT_MASK = (uint8_t)~0b00010000;
|
|
static const uint8_t MAX30105_INT_PROX_INT_ENABLE = 0x10;
|
|
static const uint8_t MAX30105_INT_PROX_INT_DISABLE = 0x00;
|
|
|
|
static const uint8_t MAX30105_INT_DIE_TEMP_RDY_MASK = (uint8_t)~0b00000010;
|
|
static const uint8_t MAX30105_INT_DIE_TEMP_RDY_ENABLE = 0x02;
|
|
static const uint8_t MAX30105_INT_DIE_TEMP_RDY_DISABLE = 0x00;
|
|
|
|
static const uint8_t MAX30105_SAMPLEAVG_MASK = (uint8_t)~0b11100000;
|
|
static const uint8_t MAX30105_SAMPLEAVG_1 = 0x00;
|
|
static const uint8_t MAX30105_SAMPLEAVG_2 = 0x20;
|
|
static const uint8_t MAX30105_SAMPLEAVG_4 = 0x40;
|
|
static const uint8_t MAX30105_SAMPLEAVG_8 = 0x60;
|
|
static const uint8_t MAX30105_SAMPLEAVG_16 = 0x80;
|
|
static const uint8_t MAX30105_SAMPLEAVG_32 = 0xA0;
|
|
|
|
static const uint8_t MAX30105_ROLLOVER_MASK = 0xEF;
|
|
static const uint8_t MAX30105_ROLLOVER_ENABLE = 0x10;
|
|
static const uint8_t MAX30105_ROLLOVER_DISABLE = 0x00;
|
|
|
|
static const uint8_t MAX30105_A_FULL_MASK = 0xF0;
|
|
|
|
// Mode configuration commands (page 19)
|
|
static const uint8_t MAX30105_SHUTDOWN_MASK = 0x7F;
|
|
static const uint8_t MAX30105_SHUTDOWN = 0x80;
|
|
static const uint8_t MAX30105_WAKEUP = 0x00;
|
|
|
|
static const uint8_t MAX30105_RESET_MASK = 0xBF;
|
|
static const uint8_t MAX30105_RESET = 0x40;
|
|
|
|
static const uint8_t MAX30105_MODE_MASK = 0xF8;
|
|
static const uint8_t MAX30105_MODE_REDONLY = 0x02;
|
|
static const uint8_t MAX30105_MODE_REDIRONLY = 0x03;
|
|
static const uint8_t MAX30105_MODE_MULTILED = 0x07;
|
|
|
|
// Particle sensing configuration commands (pgs 19-20)
|
|
static const uint8_t MAX30105_ADCRANGE_MASK = 0x9F;
|
|
static const uint8_t MAX30105_ADCRANGE_2048 = 0x00;
|
|
static const uint8_t MAX30105_ADCRANGE_4096 = 0x20;
|
|
static const uint8_t MAX30105_ADCRANGE_8192 = 0x40;
|
|
static const uint8_t MAX30105_ADCRANGE_16384 = 0x60;
|
|
|
|
static const uint8_t MAX30105_SAMPLERATE_MASK = 0xE3;
|
|
static const uint8_t MAX30105_SAMPLERATE_50 = 0x00;
|
|
static const uint8_t MAX30105_SAMPLERATE_100 = 0x04;
|
|
static const uint8_t MAX30105_SAMPLERATE_200 = 0x08;
|
|
static const uint8_t MAX30105_SAMPLERATE_400 = 0x0C;
|
|
static const uint8_t MAX30105_SAMPLERATE_800 = 0x10;
|
|
static const uint8_t MAX30105_SAMPLERATE_1000 = 0x14;
|
|
static const uint8_t MAX30105_SAMPLERATE_1600 = 0x18;
|
|
static const uint8_t MAX30105_SAMPLERATE_3200 = 0x1C;
|
|
|
|
static const uint8_t MAX30105_PULSEWIDTH_MASK = 0xFC;
|
|
static const uint8_t MAX30105_PULSEWIDTH_69 = 0x00;
|
|
static const uint8_t MAX30105_PULSEWIDTH_118 = 0x01;
|
|
static const uint8_t MAX30105_PULSEWIDTH_215 = 0x02;
|
|
static const uint8_t MAX30105_PULSEWIDTH_411 = 0x03;
|
|
|
|
//Multi-LED Mode configuration (pg 22)
|
|
static const uint8_t MAX30105_SLOT1_MASK = 0xF8;
|
|
static const uint8_t MAX30105_SLOT2_MASK = 0x8F;
|
|
static const uint8_t MAX30105_SLOT3_MASK = 0xF8;
|
|
static const uint8_t MAX30105_SLOT4_MASK = 0x8F;
|
|
|
|
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_GREEN_LED = 0x03;
|
|
static const uint8_t SLOT_NONE_PILOT = 0x04;
|
|
static const uint8_t SLOT_RED_PILOT = 0x05;
|
|
static const uint8_t SLOT_IR_PILOT = 0x06;
|
|
static const uint8_t SLOT_GREEN_PILOT = 0x07;
|
|
|
|
static const uint8_t MAX_30105_EXPECTEDPARTID = 0x15;
|
|
|
|
Listener* listener = nullptr;
|
|
|
|
|
|
public:
|
|
|
|
Max30102(I2C& i2c) : i2c(i2c) {
|
|
;
|
|
}
|
|
|
|
void setListener(Listener* l) {
|
|
this->listener = l;
|
|
}
|
|
|
|
bool isPresent() {
|
|
return i2c.query(ADDR);
|
|
}
|
|
|
|
uint8_t getRevisionID() {
|
|
return i2c.readReg8(ADDR, MAX30105_REVISIONID);
|
|
}
|
|
|
|
uint8_t getPartID() {
|
|
return i2c.readReg8(ADDR, MAX30105_PARTID);
|
|
}
|
|
|
|
|
|
void softReset(void) {
|
|
|
|
bitMask(MAX30105_MODECONFIG, MAX30105_RESET_MASK, MAX30105_RESET);
|
|
|
|
// Poll for bit to clear, reset is then complete
|
|
for (int i = 0; i < 100; ++i) {
|
|
const uint8_t response = i2c.readReg8(ADDR, MAX30105_MODECONFIG);
|
|
if ((response & MAX30105_RESET) == 0) break; //We're done!
|
|
vTaskDelay(1 / portTICK_PERIOD_MS);
|
|
}
|
|
|
|
ESP_LOGI(NAME, "softReset done");
|
|
|
|
}
|
|
|
|
// void MAX30105::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)
|
|
// bitMask(MAX30105_MODECONFIG, MAX30105_SHUTDOWN_MASK, MAX30105_SHUTDOWN);
|
|
// }
|
|
|
|
|
|
|
|
void setModePulse() {
|
|
|
|
//wakeUp();
|
|
softReset();
|
|
//wakeUp();
|
|
|
|
writeReg8(MAX30105_INTENABLE1, 0x00);// | MAX30105_INT_DATA_RDY_ENABLE);//64);//16|64);//0xC0);
|
|
writeReg8(MAX30105_INTENABLE2, 0x00);
|
|
|
|
//writeReg8(MAX30105_PROXINTTHRESH, 0);
|
|
//writeReg8(MAX30105_PifROXINTTHRESH, 64);
|
|
|
|
|
|
//setLEDMode(MAX30105_MODE_REDONLY);
|
|
setLEDMode(MAX30105_MODE_REDIRONLY);
|
|
//setLEDMode(MAX30105_MODE_MULTILED);
|
|
|
|
writeReg8(MAX30105_FIFOCONFIG, MAX30105_SAMPLEAVG_8 | MAX30105_ROLLOVER_ENABLE);
|
|
|
|
//setFIFOAverage(MAX30105_SAMPLEAVG_8);
|
|
setADCRange(MAX30105_ADCRANGE_16384);
|
|
setSampleRate(MAX30105_SAMPLERATE_200);
|
|
setPulseWidth(MAX30105_PULSEWIDTH_215);
|
|
|
|
//Default is 0x1F which gets us 6.4mA
|
|
//powerLevel = 0x02, 0.4mA - Presence detection of ~4 inch
|
|
//powerLevel = 0x1F, 6.4mA - Presence detection of ~8 inch
|
|
//powerLevel = 0x7F, 25.4mA - Presence detection of ~8 inch
|
|
//powerLevel = 0xFF, 50.0mA - Presence detection of ~12 inch
|
|
|
|
const int powerLevel = 0x30;
|
|
setPulseAmplitudeRed(powerLevel);
|
|
setPulseAmplitudeIR(powerLevel);
|
|
setPulseAmplitudeProximity(powerLevel);
|
|
|
|
// THIS ONE ONLY WORKS FOR "MAX30105_MODE_MULTILED" ?!
|
|
// writeReg8(MAX30105_MULTILEDCONFIG1, (SLOT_RED_PILOT<<4) | (SLOT_RED_LED<<0) );
|
|
// writeReg8(MAX30105_MULTILEDCONFIG2, (SLOT_IR_PILOT<<4) | (SLOT_IR_LED<<0) );
|
|
// ------
|
|
// writeReg8(MAX30105_MULTILEDCONFIG1, (SLOT_IR_LED<<4) | (SLOT_RED_LED<<0) );
|
|
// writeReg8(MAX30105_MULTILEDCONFIG2, 0 );
|
|
|
|
clearFIFO();
|
|
}
|
|
|
|
void writeReg8(const uint8_t reg, const uint8_t val) {
|
|
ESP_LOGI(NAME, "write reg: 0x%02x <- val: %d", reg, val);
|
|
i2c.writeReg8(ADDR, reg, val);
|
|
}
|
|
|
|
uint8_t readReg8(const uint8_t reg) {
|
|
return i2c.readReg8(ADDR, reg);
|
|
}
|
|
|
|
// //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 safeCheck(uint8_t maxTimeToCheck) {
|
|
|
|
// for (int i = 0; i < maxTimeToCheck; ++i) {
|
|
// if(check() == true) {
|
|
// return(true);
|
|
// }
|
|
// vTaskDelay(1 / portTICK_PERIOD_MS);
|
|
// }
|
|
// return false;
|
|
|
|
// }
|
|
|
|
|
|
void check(void) {
|
|
|
|
// const uint8_t intr1 = readReg8(MAX30105_INTSTAT1);
|
|
// const uint8_t intr2 = readReg8(MAX30105_INTSTAT2);
|
|
// // new data available?
|
|
// //if (intr1 & 64) { .. }
|
|
|
|
|
|
uint8_t readPointer = getReadPointer();
|
|
const uint8_t writePointer = getWritePointer();
|
|
|
|
//uint8_t readPointer = getReadPointer();
|
|
//const uint8_t writePointer = getWritePointer();
|
|
//ESP_LOGI(NAME, "intr. %d %d", readPointer, writePointer);
|
|
|
|
while (readPointer != writePointer) {
|
|
|
|
//printf(";%d;%d\n", readPointer, writePointer);
|
|
|
|
uint8_t tmp[6];
|
|
i2c.readReg(ADDR, MAX30105_FIFODATA, 6, tmp);
|
|
|
|
const uint32_t val1 = ((tmp[0] << 16) | (tmp[1] << 8) | (tmp[2] << 0)) & 0x3FFFF; //Zero out all but 18 bits
|
|
const uint32_t val2 = ((tmp[3] << 16) | (tmp[4] << 8) | (tmp[5] << 0)) & 0x3FFFF; //Zero out all but 18 bits
|
|
|
|
//printf(";%d;%d;%d;%d\n", readPointer, writePointer, val1, val2);//, val3, val4);
|
|
|
|
// inc
|
|
readPointer = (readPointer + 1) % 32;
|
|
|
|
// callback
|
|
if (listener) {
|
|
listener->onRed(val1);
|
|
listener->onIR(val2);
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int8_t getTemp() {
|
|
|
|
// trigger temp acquire
|
|
writeReg8(MAX30105_DIETEMPCONFIG, 1);
|
|
|
|
int8_t full = readReg8(MAX30105_DIETEMPINT);
|
|
//uint8_f frac = i2c.readReg8(MAX30105_DIETEMPFRAC);
|
|
return full;
|
|
}
|
|
|
|
private:
|
|
|
|
void wakeUp(void) {
|
|
// Pull IC out of low power mode (datasheet pg. 19)
|
|
bitMask(MAX30105_MODECONFIG, MAX30105_SHUTDOWN_MASK, MAX30105_WAKEUP);
|
|
}
|
|
|
|
void setLEDMode(uint8_t mode) {
|
|
// Set which LEDs are used for sampling -- Red only, RED+IR only, or custom.
|
|
// See datasheet, page 18
|
|
bitMask(MAX30105_MODECONFIG, MAX30105_MODE_MASK, mode);
|
|
}
|
|
|
|
|
|
|
|
void setPulseAmplitudeRed(uint8_t amplitude) {
|
|
writeReg8( MAX30105_LED1_PULSEAMP, amplitude);
|
|
}
|
|
|
|
void setPulseAmplitudeIR(uint8_t amplitude) {
|
|
writeReg8(MAX30105_LED2_PULSEAMP, amplitude);
|
|
}
|
|
|
|
// void setPulseAmplitudeGreen(uint8_t amplitude) {
|
|
// writeReg8(MAX30105_LED3_PULSEAMP, amplitude);
|
|
// }
|
|
|
|
void setPulseAmplitudeProximity(uint8_t amplitude) {
|
|
writeReg8(MAX30105_LED_PROX_AMP, amplitude);
|
|
}
|
|
|
|
void setFIFOAverage(uint8_t numberOfSamples) {
|
|
bitMask(MAX30105_FIFOCONFIG, MAX30105_SAMPLEAVG_MASK, numberOfSamples);
|
|
}
|
|
|
|
void enableFIFORollover(void) {
|
|
bitMask(MAX30105_FIFOCONFIG, MAX30105_ROLLOVER_MASK, MAX30105_ROLLOVER_ENABLE);
|
|
}
|
|
|
|
void setADCRange(uint8_t adcRange) {
|
|
// adcRange: one of MAX30105_ADCRANGE_2048, _4096, _8192, _16384
|
|
bitMask(MAX30105_PARTICLECONFIG, MAX30105_ADCRANGE_MASK, adcRange);
|
|
}
|
|
|
|
void setSampleRate(uint8_t sampleRate) {
|
|
// sampleRate: one of MAX30105_SAMPLERATE_50, _100, _200, _400, _800, _1000, _1600, _3200
|
|
bitMask(MAX30105_PARTICLECONFIG, MAX30105_SAMPLERATE_MASK, sampleRate);
|
|
}
|
|
|
|
void setPulseWidth(uint8_t pulseWidth) {
|
|
// pulseWidth: one of MAX30105_PULSEWIDTH_69, _188, _215, _411
|
|
bitMask(MAX30105_PARTICLECONFIG, MAX30105_PULSEWIDTH_MASK, pulseWidth);
|
|
}
|
|
|
|
|
|
void clearFIFO(void) {
|
|
writeReg8(MAX30105_FIFOWRITEPTR, 0);
|
|
writeReg8(MAX30105_FIFOOVERFLOW, 0);
|
|
writeReg8(MAX30105_FIFOREADPTR, 0);
|
|
}
|
|
|
|
//Read the FIFO Write Pointer
|
|
uint8_t getWritePointer(void) {
|
|
return readReg8(MAX30105_FIFOWRITEPTR);
|
|
}
|
|
|
|
//Read the FIFO Read Pointer
|
|
uint8_t getReadPointer(void) {
|
|
return readReg8(MAX30105_FIFOREADPTR);
|
|
}
|
|
|
|
//Given a register, read it, mask it, and then set the thing
|
|
void bitMask(const uint8_t reg, const uint8_t mask, const uint8_t thing) {
|
|
|
|
const uint8_t orig = i2c.readReg8(ADDR, reg);
|
|
const uint8_t masked = orig & mask;
|
|
const uint8_t out = masked | thing;
|
|
|
|
ESP_LOGI(NAME, "reg: %d - orig: %d new: %d", reg, orig, out);
|
|
|
|
writeReg8(reg, out);
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
#endif // MAX30102_H
|