feat: 全量同步 254 个常用的 Arduino 扩展库文件
This commit is contained in:
@@ -0,0 +1,755 @@
|
||||
/***************************************************
|
||||
This is a library written for the Maxim MAX30105 Optical Smoke Detector
|
||||
It should also work with the MAX30102. However, the MAX30102 does not have a Green LED.
|
||||
|
||||
These sensors use I2C to communicate, as well as a single (optional)
|
||||
interrupt line that is not currently supported in this driver.
|
||||
|
||||
Written by Peter Jansen and Nathan Seidle (SparkFun)
|
||||
BSD license, all text above must be included in any redistribution.
|
||||
*****************************************************/
|
||||
|
||||
#include "MAX30105.h"
|
||||
|
||||
// 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;
|
||||
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 = (byte)~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 = (byte)~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 = (byte)~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 = (byte)~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 = (byte)~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 = (byte)~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;
|
||||
|
||||
MAX30105::MAX30105() {
|
||||
// Constructor
|
||||
}
|
||||
|
||||
boolean MAX30105::begin(TwoWire &wirePort, uint32_t i2cSpeed, uint8_t i2caddr) {
|
||||
|
||||
_i2cPort = &wirePort; //Grab which port the user wants us to use
|
||||
|
||||
_i2cPort->begin();
|
||||
_i2cPort->setClock(i2cSpeed);
|
||||
|
||||
_i2caddr = i2caddr;
|
||||
|
||||
// Step 1: Initial Communication and Verification
|
||||
// Check that a MAX30105 is connected
|
||||
if (readPartID() != MAX_30105_EXPECTEDPARTID) {
|
||||
// Error -- Part ID read from MAX30105 does not match expected part ID.
|
||||
// This may mean there is a physical connectivity problem (broken wire, unpowered, etc).
|
||||
return false;
|
||||
}
|
||||
|
||||
// Populate revision ID
|
||||
readRevisionID();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//
|
||||
// Configuration
|
||||
//
|
||||
|
||||
//Begin Interrupt configuration
|
||||
uint8_t MAX30105::getINT1(void) {
|
||||
return (readRegister8(_i2caddr, MAX30105_INTSTAT1));
|
||||
}
|
||||
uint8_t MAX30105::getINT2(void) {
|
||||
return (readRegister8(_i2caddr, MAX30105_INTSTAT2));
|
||||
}
|
||||
|
||||
void MAX30105::enableAFULL(void) {
|
||||
bitMask(MAX30105_INTENABLE1, MAX30105_INT_A_FULL_MASK, MAX30105_INT_A_FULL_ENABLE);
|
||||
}
|
||||
void MAX30105::disableAFULL(void) {
|
||||
bitMask(MAX30105_INTENABLE1, MAX30105_INT_A_FULL_MASK, MAX30105_INT_A_FULL_DISABLE);
|
||||
}
|
||||
|
||||
void MAX30105::enableDATARDY(void) {
|
||||
bitMask(MAX30105_INTENABLE1, MAX30105_INT_DATA_RDY_MASK, MAX30105_INT_DATA_RDY_ENABLE);
|
||||
}
|
||||
void MAX30105::disableDATARDY(void) {
|
||||
bitMask(MAX30105_INTENABLE1, MAX30105_INT_DATA_RDY_MASK, MAX30105_INT_DATA_RDY_DISABLE);
|
||||
}
|
||||
|
||||
void MAX30105::enableALCOVF(void) {
|
||||
bitMask(MAX30105_INTENABLE1, MAX30105_INT_ALC_OVF_MASK, MAX30105_INT_ALC_OVF_ENABLE);
|
||||
}
|
||||
void MAX30105::disableALCOVF(void) {
|
||||
bitMask(MAX30105_INTENABLE1, MAX30105_INT_ALC_OVF_MASK, MAX30105_INT_ALC_OVF_DISABLE);
|
||||
}
|
||||
|
||||
void MAX30105::enablePROXINT(void) {
|
||||
bitMask(MAX30105_INTENABLE1, MAX30105_INT_PROX_INT_MASK, MAX30105_INT_PROX_INT_ENABLE);
|
||||
}
|
||||
void MAX30105::disablePROXINT(void) {
|
||||
bitMask(MAX30105_INTENABLE1, MAX30105_INT_PROX_INT_MASK, MAX30105_INT_PROX_INT_DISABLE);
|
||||
}
|
||||
|
||||
void MAX30105::enableDIETEMPRDY(void) {
|
||||
bitMask(MAX30105_INTENABLE2, MAX30105_INT_DIE_TEMP_RDY_MASK, MAX30105_INT_DIE_TEMP_RDY_ENABLE);
|
||||
}
|
||||
void MAX30105::disableDIETEMPRDY(void) {
|
||||
bitMask(MAX30105_INTENABLE2, MAX30105_INT_DIE_TEMP_RDY_MASK, MAX30105_INT_DIE_TEMP_RDY_DISABLE);
|
||||
}
|
||||
|
||||
//End Interrupt configuration
|
||||
|
||||
void MAX30105::softReset(void) {
|
||||
bitMask(MAX30105_MODECONFIG, MAX30105_RESET_MASK, MAX30105_RESET);
|
||||
|
||||
// Poll for bit to clear, reset is then complete
|
||||
// Timeout after 100ms
|
||||
unsigned long startTime = millis();
|
||||
while (millis() - startTime < 100)
|
||||
{
|
||||
uint8_t response = readRegister8(_i2caddr, MAX30105_MODECONFIG);
|
||||
if ((response & MAX30105_RESET) == 0) break; //We're done!
|
||||
delay(1); //Let's not over burden the I2C bus
|
||||
}
|
||||
}
|
||||
|
||||
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 MAX30105::wakeUp(void) {
|
||||
// Pull IC out of low power mode (datasheet pg. 19)
|
||||
bitMask(MAX30105_MODECONFIG, MAX30105_SHUTDOWN_MASK, MAX30105_WAKEUP);
|
||||
}
|
||||
|
||||
void MAX30105::setLEDMode(uint8_t mode) {
|
||||
// Set which LEDs are used for sampling -- Red only, RED+IR only, or custom.
|
||||
// See datasheet, page 19
|
||||
bitMask(MAX30105_MODECONFIG, MAX30105_MODE_MASK, mode);
|
||||
}
|
||||
|
||||
void MAX30105::setADCRange(uint8_t adcRange) {
|
||||
// adcRange: one of MAX30105_ADCRANGE_2048, _4096, _8192, _16384
|
||||
bitMask(MAX30105_PARTICLECONFIG, MAX30105_ADCRANGE_MASK, adcRange);
|
||||
}
|
||||
|
||||
void MAX30105::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 MAX30105::setPulseWidth(uint8_t pulseWidth) {
|
||||
// pulseWidth: one of MAX30105_PULSEWIDTH_69, _188, _215, _411
|
||||
bitMask(MAX30105_PARTICLECONFIG, MAX30105_PULSEWIDTH_MASK, pulseWidth);
|
||||
}
|
||||
|
||||
// NOTE: Amplitude values: 0x00 = 0mA, 0x7F = 25.4mA, 0xFF = 50mA (typical)
|
||||
// See datasheet, page 21
|
||||
void MAX30105::setPulseAmplitudeRed(uint8_t amplitude) {
|
||||
writeRegister8(_i2caddr, MAX30105_LED1_PULSEAMP, amplitude);
|
||||
}
|
||||
|
||||
void MAX30105::setPulseAmplitudeIR(uint8_t amplitude) {
|
||||
writeRegister8(_i2caddr, MAX30105_LED2_PULSEAMP, amplitude);
|
||||
}
|
||||
|
||||
void MAX30105::setPulseAmplitudeGreen(uint8_t amplitude) {
|
||||
writeRegister8(_i2caddr, MAX30105_LED3_PULSEAMP, amplitude);
|
||||
}
|
||||
|
||||
void MAX30105::setPulseAmplitudeProximity(uint8_t amplitude) {
|
||||
writeRegister8(_i2caddr, MAX30105_LED_PROX_AMP, amplitude);
|
||||
}
|
||||
|
||||
void MAX30105::setProximityThreshold(uint8_t threshMSB) {
|
||||
// Set the IR ADC count that will trigger the beginning of particle-sensing mode.
|
||||
// The threshMSB signifies only the 8 most significant-bits of the ADC count.
|
||||
// See datasheet, page 24.
|
||||
writeRegister8(_i2caddr, MAX30105_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 MAX30105::enableSlot(uint8_t slotNumber, uint8_t device) {
|
||||
|
||||
uint8_t originalContents;
|
||||
|
||||
switch (slotNumber) {
|
||||
case (1):
|
||||
bitMask(MAX30105_MULTILEDCONFIG1, MAX30105_SLOT1_MASK, device);
|
||||
break;
|
||||
case (2):
|
||||
bitMask(MAX30105_MULTILEDCONFIG1, MAX30105_SLOT2_MASK, device << 4);
|
||||
break;
|
||||
case (3):
|
||||
bitMask(MAX30105_MULTILEDCONFIG2, MAX30105_SLOT3_MASK, device);
|
||||
break;
|
||||
case (4):
|
||||
bitMask(MAX30105_MULTILEDCONFIG2, MAX30105_SLOT4_MASK, device << 4);
|
||||
break;
|
||||
default:
|
||||
//Shouldn't be here!
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//Clears all slot assignments
|
||||
void MAX30105::disableSlots(void) {
|
||||
writeRegister8(_i2caddr, MAX30105_MULTILEDCONFIG1, 0);
|
||||
writeRegister8(_i2caddr, MAX30105_MULTILEDCONFIG2, 0);
|
||||
}
|
||||
|
||||
//
|
||||
// FIFO Configuration
|
||||
//
|
||||
|
||||
//Set sample average (Table 3, Page 18)
|
||||
void MAX30105::setFIFOAverage(uint8_t numberOfSamples) {
|
||||
bitMask(MAX30105_FIFOCONFIG, MAX30105_SAMPLEAVG_MASK, numberOfSamples);
|
||||
}
|
||||
|
||||
//Resets all points to start in a known state
|
||||
//Page 15 recommends clearing FIFO before beginning a read
|
||||
void MAX30105::clearFIFO(void) {
|
||||
writeRegister8(_i2caddr, MAX30105_FIFOWRITEPTR, 0);
|
||||
writeRegister8(_i2caddr, MAX30105_FIFOOVERFLOW, 0);
|
||||
writeRegister8(_i2caddr, MAX30105_FIFOREADPTR, 0);
|
||||
}
|
||||
|
||||
//Enable roll over if FIFO over flows
|
||||
void MAX30105::enableFIFORollover(void) {
|
||||
bitMask(MAX30105_FIFOCONFIG, MAX30105_ROLLOVER_MASK, MAX30105_ROLLOVER_ENABLE);
|
||||
}
|
||||
|
||||
//Disable roll over if FIFO over flows
|
||||
void MAX30105::disableFIFORollover(void) {
|
||||
bitMask(MAX30105_FIFOCONFIG, MAX30105_ROLLOVER_MASK, MAX30105_ROLLOVER_DISABLE);
|
||||
}
|
||||
|
||||
//Set number of samples to trigger the almost full interrupt (Page 18)
|
||||
//Power on default is 32 samples
|
||||
//Note it is reverse: 0x00 is 32 samples, 0x0F is 17 samples
|
||||
void MAX30105::setFIFOAlmostFull(uint8_t numberOfSamples) {
|
||||
bitMask(MAX30105_FIFOCONFIG, MAX30105_A_FULL_MASK, numberOfSamples);
|
||||
}
|
||||
|
||||
//Read the FIFO Write Pointer
|
||||
uint8_t MAX30105::getWritePointer(void) {
|
||||
return (readRegister8(_i2caddr, MAX30105_FIFOWRITEPTR));
|
||||
}
|
||||
|
||||
//Read the FIFO Read Pointer
|
||||
uint8_t MAX30105::getReadPointer(void) {
|
||||
return (readRegister8(_i2caddr, MAX30105_FIFOREADPTR));
|
||||
}
|
||||
|
||||
|
||||
// Die Temperature
|
||||
// Returns temp in C
|
||||
float MAX30105::readTemperature() {
|
||||
|
||||
//DIE_TEMP_RDY interrupt must be enabled
|
||||
//See issue 19: https://github.com/sparkfun/SparkFun_MAX3010x_Sensor_Library/issues/19
|
||||
|
||||
// Step 1: Config die temperature register to take 1 temperature sample
|
||||
writeRegister8(_i2caddr, MAX30105_DIETEMPCONFIG, 0x01);
|
||||
|
||||
// Poll for bit to clear, reading is then complete
|
||||
// Timeout after 100ms
|
||||
unsigned long startTime = millis();
|
||||
while (millis() - startTime < 100)
|
||||
{
|
||||
//uint8_t response = readRegister8(_i2caddr, MAX30105_DIETEMPCONFIG); //Original way
|
||||
//if ((response & 0x01) == 0) break; //We're done!
|
||||
|
||||
//Check to see if DIE_TEMP_RDY interrupt is set
|
||||
uint8_t response = readRegister8(_i2caddr, MAX30105_INTSTAT2);
|
||||
if ((response & MAX30105_INT_DIE_TEMP_RDY_ENABLE) > 0) break; //We're done!
|
||||
delay(1); //Let's not over burden the I2C bus
|
||||
}
|
||||
//TODO How do we want to fail? With what type of error?
|
||||
//? if(millis() - startTime >= 100) return(-999.0);
|
||||
|
||||
// Step 2: Read die temperature register (integer)
|
||||
int8_t tempInt = readRegister8(_i2caddr, MAX30105_DIETEMPINT);
|
||||
uint8_t tempFrac = readRegister8(_i2caddr, MAX30105_DIETEMPFRAC); //Causes the clearing of the DIE_TEMP_RDY interrupt
|
||||
|
||||
// Step 3: Calculate temperature (datasheet pg. 23)
|
||||
return (float)tempInt + ((float)tempFrac * 0.0625);
|
||||
}
|
||||
|
||||
// Returns die temp in F
|
||||
float MAX30105::readTemperatureF() {
|
||||
float temp = readTemperature();
|
||||
|
||||
if (temp != -999.0) temp = temp * 1.8 + 32.0;
|
||||
|
||||
return (temp);
|
||||
}
|
||||
|
||||
// Set the PROX_INT_THRESHold
|
||||
void MAX30105::setPROXINTTHRESH(uint8_t val) {
|
||||
writeRegister8(_i2caddr, MAX30105_PROXINTTHRESH, val);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Device ID and Revision
|
||||
//
|
||||
uint8_t MAX30105::readPartID() {
|
||||
return readRegister8(_i2caddr, MAX30105_PARTID);
|
||||
}
|
||||
|
||||
void MAX30105::readRevisionID() {
|
||||
revisionID = readRegister8(_i2caddr, MAX30105_REVISIONID);
|
||||
}
|
||||
|
||||
uint8_t MAX30105::getRevisionID() {
|
||||
return revisionID;
|
||||
}
|
||||
|
||||
|
||||
//Setup the sensor
|
||||
//The MAX30105 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 setup if you are just getting started with the MAX30105 sensor
|
||||
void MAX30105::setup(byte powerLevel, byte sampleAverage, byte ledMode, int sampleRate, int pulseWidth, int adcRange) {
|
||||
softReset(); //Reset all configuration, threshold, and data registers to POR values
|
||||
|
||||
//FIFO Configuration
|
||||
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
|
||||
//The chip will average multiple samples of same type together if you wish
|
||||
if (sampleAverage == 1) setFIFOAverage(MAX30105_SAMPLEAVG_1); //No averaging per FIFO record
|
||||
else if (sampleAverage == 2) setFIFOAverage(MAX30105_SAMPLEAVG_2);
|
||||
else if (sampleAverage == 4) setFIFOAverage(MAX30105_SAMPLEAVG_4);
|
||||
else if (sampleAverage == 8) setFIFOAverage(MAX30105_SAMPLEAVG_8);
|
||||
else if (sampleAverage == 16) setFIFOAverage(MAX30105_SAMPLEAVG_16);
|
||||
else if (sampleAverage == 32) setFIFOAverage(MAX30105_SAMPLEAVG_32);
|
||||
else setFIFOAverage(MAX30105_SAMPLEAVG_4);
|
||||
|
||||
//setFIFOAlmostFull(2); //Set to 30 samples to trigger an 'Almost Full' interrupt
|
||||
enableFIFORollover(); //Allow FIFO to wrap/roll over
|
||||
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
|
||||
|
||||
//Mode Configuration
|
||||
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
|
||||
if (ledMode == 3) setLEDMode(MAX30105_MODE_MULTILED); //Watch all three LED channels
|
||||
else if (ledMode == 2) setLEDMode(MAX30105_MODE_REDIRONLY); //Red and IR
|
||||
else setLEDMode(MAX30105_MODE_REDONLY); //Red only
|
||||
activeLEDs = ledMode; //Used to control how many bytes to read from FIFO buffer
|
||||
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
|
||||
|
||||
//Particle Sensing Configuration
|
||||
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
|
||||
if(adcRange < 4096) setADCRange(MAX30105_ADCRANGE_2048); //7.81pA per LSB
|
||||
else if(adcRange < 8192) setADCRange(MAX30105_ADCRANGE_4096); //15.63pA per LSB
|
||||
else if(adcRange < 16384) setADCRange(MAX30105_ADCRANGE_8192); //31.25pA per LSB
|
||||
else if(adcRange == 16384) setADCRange(MAX30105_ADCRANGE_16384); //62.5pA per LSB
|
||||
else setADCRange(MAX30105_ADCRANGE_2048);
|
||||
|
||||
if (sampleRate < 100) setSampleRate(MAX30105_SAMPLERATE_50); //Take 50 samples per second
|
||||
else if (sampleRate < 200) setSampleRate(MAX30105_SAMPLERATE_100);
|
||||
else if (sampleRate < 400) setSampleRate(MAX30105_SAMPLERATE_200);
|
||||
else if (sampleRate < 800) setSampleRate(MAX30105_SAMPLERATE_400);
|
||||
else if (sampleRate < 1000) setSampleRate(MAX30105_SAMPLERATE_800);
|
||||
else if (sampleRate < 1600) setSampleRate(MAX30105_SAMPLERATE_1000);
|
||||
else if (sampleRate < 3200) setSampleRate(MAX30105_SAMPLERATE_1600);
|
||||
else if (sampleRate == 3200) setSampleRate(MAX30105_SAMPLERATE_3200);
|
||||
else setSampleRate(MAX30105_SAMPLERATE_50);
|
||||
|
||||
//The longer the pulse width the longer range of detection you'll have
|
||||
//At 69us and 0.4mA it's about 2 inches
|
||||
//At 411us and 0.4mA it's about 6 inches
|
||||
if (pulseWidth < 118) setPulseWidth(MAX30105_PULSEWIDTH_69); //Page 26, Gets us 15 bit resolution
|
||||
else if (pulseWidth < 215) setPulseWidth(MAX30105_PULSEWIDTH_118); //16 bit resolution
|
||||
else if (pulseWidth < 411) setPulseWidth(MAX30105_PULSEWIDTH_215); //17 bit resolution
|
||||
else if (pulseWidth == 411) setPulseWidth(MAX30105_PULSEWIDTH_411); //18 bit resolution
|
||||
else setPulseWidth(MAX30105_PULSEWIDTH_69);
|
||||
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
|
||||
|
||||
//LED Pulse Amplitude Configuration
|
||||
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
|
||||
//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
|
||||
|
||||
setPulseAmplitudeRed(powerLevel);
|
||||
setPulseAmplitudeIR(powerLevel);
|
||||
setPulseAmplitudeGreen(powerLevel);
|
||||
setPulseAmplitudeProximity(powerLevel);
|
||||
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
|
||||
|
||||
//Multi-LED Mode Configuration, Enable the reading of the three LEDs
|
||||
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
|
||||
enableSlot(1, SLOT_RED_LED);
|
||||
if (ledMode > 1) enableSlot(2, SLOT_IR_LED);
|
||||
if (ledMode > 2) enableSlot(3, SLOT_GREEN_LED);
|
||||
//enableSlot(1, SLOT_RED_PILOT);
|
||||
//enableSlot(2, SLOT_IR_PILOT);
|
||||
//enableSlot(3, SLOT_GREEN_PILOT);
|
||||
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
|
||||
|
||||
clearFIFO(); //Reset the FIFO before we begin checking the sensor
|
||||
}
|
||||
|
||||
//
|
||||
// Data Collection
|
||||
//
|
||||
|
||||
//Tell caller how many samples are available
|
||||
uint8_t MAX30105::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 MAX30105::getRed(void)
|
||||
{
|
||||
//Check the sensor for new data for 250ms
|
||||
if(safeCheck(250))
|
||||
return (sense.red[sense.head]);
|
||||
else
|
||||
return(0); //Sensor failed to find new data
|
||||
}
|
||||
|
||||
//Report the most recent IR value
|
||||
uint32_t MAX30105::getIR(void)
|
||||
{
|
||||
//Check the sensor for new data for 250ms
|
||||
if(safeCheck(250))
|
||||
return (sense.IR[sense.head]);
|
||||
else
|
||||
return(0); //Sensor failed to find new data
|
||||
}
|
||||
|
||||
//Report the most recent Green value
|
||||
uint32_t MAX30105::getGreen(void)
|
||||
{
|
||||
//Check the sensor for new data for 250ms
|
||||
if(safeCheck(250))
|
||||
return (sense.green[sense.head]);
|
||||
else
|
||||
return(0); //Sensor failed to find new data
|
||||
}
|
||||
|
||||
//Report the next Red value in the FIFO
|
||||
uint32_t MAX30105::getFIFORed(void)
|
||||
{
|
||||
return (sense.red[sense.tail]);
|
||||
}
|
||||
|
||||
//Report the next IR value in the FIFO
|
||||
uint32_t MAX30105::getFIFOIR(void)
|
||||
{
|
||||
return (sense.IR[sense.tail]);
|
||||
}
|
||||
|
||||
//Report the next Green value in the FIFO
|
||||
uint32_t MAX30105::getFIFOGreen(void)
|
||||
{
|
||||
return (sense.green[sense.tail]);
|
||||
}
|
||||
|
||||
//Advance the tail
|
||||
void MAX30105::nextSample(void)
|
||||
{
|
||||
if(available()) //Only advance the tail if new data is available
|
||||
{
|
||||
sense.tail++;
|
||||
sense.tail %= STORAGE_SIZE; //Wrap condition
|
||||
}
|
||||
}
|
||||
|
||||
//Polls the sensor for new data
|
||||
//Call regularly
|
||||
//If new data is available, it updates the head and tail in the main struct
|
||||
//Returns number of new samples obtained
|
||||
uint16_t MAX30105::check(void)
|
||||
{
|
||||
//Read register FIDO_DATA in (3-byte * number of active LED) chunks
|
||||
//Until FIFO_RD_PTR = FIFO_WR_PTR
|
||||
|
||||
byte readPointer = getReadPointer();
|
||||
byte writePointer = 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 * activeLEDs * 3;
|
||||
|
||||
//Get ready to read a burst of data from the FIFO register
|
||||
_i2cPort->beginTransmission(MAX30105_ADDRESS);
|
||||
_i2cPort->write(MAX30105_FIFODATA);
|
||||
_i2cPort->endTransmission();
|
||||
|
||||
//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 % (activeLEDs * 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(MAX30105_ADDRESS, toGet);
|
||||
|
||||
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] = _i2cPort->read();
|
||||
temp[1] = _i2cPort->read();
|
||||
temp[0] = _i2cPort->read();
|
||||
|
||||
//Convert array to long
|
||||
memcpy(&tempLong, temp, sizeof(tempLong));
|
||||
|
||||
tempLong &= 0x3FFFF; //Zero out all but 18 bits
|
||||
|
||||
sense.red[sense.head] = tempLong; //Store this reading into the sense array
|
||||
|
||||
if (activeLEDs > 1)
|
||||
{
|
||||
//Burst read three more bytes - IR
|
||||
temp[3] = 0;
|
||||
temp[2] = _i2cPort->read();
|
||||
temp[1] = _i2cPort->read();
|
||||
temp[0] = _i2cPort->read();
|
||||
|
||||
//Convert array to long
|
||||
memcpy(&tempLong, temp, sizeof(tempLong));
|
||||
|
||||
tempLong &= 0x3FFFF; //Zero out all but 18 bits
|
||||
|
||||
sense.IR[sense.head] = tempLong;
|
||||
}
|
||||
|
||||
if (activeLEDs > 2)
|
||||
{
|
||||
//Burst read three more bytes - Green
|
||||
temp[3] = 0;
|
||||
temp[2] = _i2cPort->read();
|
||||
temp[1] = _i2cPort->read();
|
||||
temp[0] = _i2cPort->read();
|
||||
|
||||
//Convert array to long
|
||||
memcpy(&tempLong, temp, sizeof(tempLong));
|
||||
|
||||
tempLong &= 0x3FFFF; //Zero out all but 18 bits
|
||||
|
||||
sense.green[sense.head] = tempLong;
|
||||
}
|
||||
|
||||
toGet -= activeLEDs * 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 MAX30105::safeCheck(uint8_t maxTimeToCheck)
|
||||
{
|
||||
uint32_t markTime = millis();
|
||||
|
||||
while(1)
|
||||
{
|
||||
if(millis() - markTime > maxTimeToCheck) return(false);
|
||||
|
||||
if(check() == true) //We found new data!
|
||||
return(true);
|
||||
|
||||
delay(1);
|
||||
}
|
||||
}
|
||||
|
||||
//Given a register, read it, mask it, and then set the thing
|
||||
void MAX30105::bitMask(uint8_t reg, uint8_t mask, uint8_t thing)
|
||||
{
|
||||
// Grab current register context
|
||||
uint8_t originalContents = readRegister8(_i2caddr, reg);
|
||||
|
||||
// Zero-out the portions of the register we're interested in
|
||||
originalContents = originalContents & mask;
|
||||
|
||||
// Change contents
|
||||
writeRegister8(_i2caddr, reg, originalContents | thing);
|
||||
}
|
||||
|
||||
//
|
||||
// Low-level I2C Communication
|
||||
//
|
||||
uint8_t MAX30105::readRegister8(uint8_t address, uint8_t reg) {
|
||||
_i2cPort->beginTransmission(address);
|
||||
_i2cPort->write(reg);
|
||||
_i2cPort->endTransmission(false);
|
||||
|
||||
_i2cPort->requestFrom((uint8_t)address, (uint8_t)1); // Request 1 byte
|
||||
if (_i2cPort->available())
|
||||
{
|
||||
return(_i2cPort->read());
|
||||
}
|
||||
|
||||
return (0); //Fail
|
||||
|
||||
}
|
||||
|
||||
void MAX30105::writeRegister8(uint8_t address, uint8_t reg, uint8_t value) {
|
||||
_i2cPort->beginTransmission(address);
|
||||
_i2cPort->write(reg);
|
||||
_i2cPort->write(value);
|
||||
_i2cPort->endTransmission();
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
/***************************************************
|
||||
This is a library written for the Maxim MAX30105 Optical Smoke Detector
|
||||
It should also work with the MAX30102. However, the MAX30102 does not have a Green LED.
|
||||
|
||||
These sensors use I2C to communicate, as well as a single (optional)
|
||||
interrupt line that is not currently supported in this driver.
|
||||
|
||||
Written by Peter Jansen and Nathan Seidle (SparkFun)
|
||||
BSD license, all text above must be included in any redistribution.
|
||||
*****************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#if (ARDUINO >= 100)
|
||||
#include "Arduino.h"
|
||||
#else
|
||||
#include "WProgram.h"
|
||||
#endif
|
||||
|
||||
#include <Wire.h>
|
||||
|
||||
#define MAX30105_ADDRESS 0x57 //7-bit I2C Address
|
||||
//Note that MAX30102 has the same I2C address and Part ID
|
||||
|
||||
#define I2C_SPEED_STANDARD 100000
|
||||
#define I2C_SPEED_FAST 400000
|
||||
|
||||
//Define the size of the I2C buffer based on the platform the user has
|
||||
#if defined(__AVR_ATmega328P__) || defined(__AVR_ATmega168__)
|
||||
|
||||
//I2C_BUFFER_LENGTH is defined in Wire.H
|
||||
#define I2C_BUFFER_LENGTH BUFFER_LENGTH
|
||||
|
||||
#elif defined(__SAMD21G18A__)
|
||||
|
||||
//SAMD21 uses RingBuffer.h
|
||||
#define I2C_BUFFER_LENGTH SERIAL_BUFFER_SIZE
|
||||
|
||||
#else
|
||||
|
||||
//The catch-all default is 32
|
||||
#define I2C_BUFFER_LENGTH 32
|
||||
|
||||
#endif
|
||||
|
||||
class MAX30105 {
|
||||
public:
|
||||
MAX30105(void);
|
||||
|
||||
boolean begin(TwoWire &wirePort = Wire, uint32_t i2cSpeed = I2C_SPEED_STANDARD, uint8_t i2caddr = MAX30105_ADDRESS);
|
||||
|
||||
uint32_t getRed(void); //Returns immediate red value
|
||||
uint32_t getIR(void); //Returns immediate IR value
|
||||
uint32_t getGreen(void); //Returns immediate green value
|
||||
bool safeCheck(uint8_t maxTimeToCheck); //Given a max amount of time, check for new data
|
||||
|
||||
// Configuration
|
||||
void softReset();
|
||||
void shutDown();
|
||||
void wakeUp();
|
||||
|
||||
void setLEDMode(uint8_t mode);
|
||||
|
||||
void setADCRange(uint8_t adcRange);
|
||||
void setSampleRate(uint8_t sampleRate);
|
||||
void setPulseWidth(uint8_t pulseWidth);
|
||||
|
||||
void setPulseAmplitudeRed(uint8_t value);
|
||||
void setPulseAmplitudeIR(uint8_t value);
|
||||
void setPulseAmplitudeGreen(uint8_t value);
|
||||
void setPulseAmplitudeProximity(uint8_t value);
|
||||
|
||||
void setProximityThreshold(uint8_t threshMSB);
|
||||
|
||||
//Multi-led configuration mode (page 22)
|
||||
void enableSlot(uint8_t slotNumber, uint8_t device); //Given slot number, assign a device to slot
|
||||
void disableSlots(void);
|
||||
|
||||
// Data Collection
|
||||
|
||||
//Interrupts (page 13, 14)
|
||||
uint8_t getINT1(void); //Returns the main interrupt group
|
||||
uint8_t getINT2(void); //Returns the temp ready interrupt
|
||||
void enableAFULL(void); //Enable/disable individual interrupts
|
||||
void disableAFULL(void);
|
||||
void enableDATARDY(void);
|
||||
void disableDATARDY(void);
|
||||
void enableALCOVF(void);
|
||||
void disableALCOVF(void);
|
||||
void enablePROXINT(void);
|
||||
void disablePROXINT(void);
|
||||
void enableDIETEMPRDY(void);
|
||||
void disableDIETEMPRDY(void);
|
||||
|
||||
//FIFO Configuration (page 18)
|
||||
void setFIFOAverage(uint8_t samples);
|
||||
void enableFIFORollover();
|
||||
void disableFIFORollover();
|
||||
void setFIFOAlmostFull(uint8_t samples);
|
||||
|
||||
//FIFO Reading
|
||||
uint16_t check(void); //Checks for new data and fills FIFO
|
||||
uint8_t available(void); //Tells caller how many new samples are available (head - tail)
|
||||
void nextSample(void); //Advances the tail of the sense array
|
||||
uint32_t getFIFORed(void); //Returns the FIFO sample pointed to by tail
|
||||
uint32_t getFIFOIR(void); //Returns the FIFO sample pointed to by tail
|
||||
uint32_t getFIFOGreen(void); //Returns the FIFO sample pointed to by tail
|
||||
|
||||
uint8_t getWritePointer(void);
|
||||
uint8_t getReadPointer(void);
|
||||
void clearFIFO(void); //Sets the read/write pointers to zero
|
||||
|
||||
//Proximity Mode Interrupt Threshold
|
||||
void setPROXINTTHRESH(uint8_t val);
|
||||
|
||||
// Die Temperature
|
||||
float readTemperature();
|
||||
float readTemperatureF();
|
||||
|
||||
// Detecting ID/Revision
|
||||
uint8_t getRevisionID();
|
||||
uint8_t readPartID();
|
||||
|
||||
// Setup the IC with user selectable settings
|
||||
void setup(byte powerLevel = 0x1F, byte sampleAverage = 4, byte ledMode = 3, int sampleRate = 400, int pulseWidth = 411, int adcRange = 4096);
|
||||
|
||||
// Low-level I2C communication
|
||||
uint8_t readRegister8(uint8_t address, uint8_t reg);
|
||||
void writeRegister8(uint8_t address, uint8_t reg, uint8_t value);
|
||||
|
||||
private:
|
||||
TwoWire *_i2cPort; //The generic connection to user's chosen I2C hardware
|
||||
uint8_t _i2caddr;
|
||||
|
||||
//activeLEDs is the number of channels turned on, and can be 1 to 3. 2 is common for Red+IR.
|
||||
byte activeLEDs; //Gets set during setup. Allows check() to calculate how many bytes to read from FIFO
|
||||
|
||||
uint8_t revisionID;
|
||||
|
||||
void readRevisionID();
|
||||
|
||||
void bitMask(uint8_t reg, uint8_t mask, uint8_t thing);
|
||||
|
||||
#define STORAGE_SIZE 4 //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];
|
||||
uint32_t green[STORAGE_SIZE];
|
||||
byte head;
|
||||
byte tail;
|
||||
} sense_struct; //This is our circular buffer of readings from the sensor
|
||||
|
||||
sense_struct sense;
|
||||
|
||||
};
|
||||
@@ -0,0 +1,167 @@
|
||||
/*
|
||||
Optical Heart Rate Detection (PBA Algorithm)
|
||||
By: Nathan Seidle
|
||||
SparkFun Electronics
|
||||
Date: October 2nd, 2016
|
||||
|
||||
Given a series of IR samples from the MAX30105 we discern when a heart beat is occurring
|
||||
|
||||
Let's have a brief chat about what this code does. We're going to try to detect
|
||||
heart-rate optically. This is tricky and prone to give false readings. We really don't
|
||||
want to get anyone hurt so use this code only as an example of how to process optical
|
||||
data. Build fun stuff with our MAX30105 breakout board but don't use it for actual
|
||||
medical diagnosis.
|
||||
|
||||
Excellent background on optical heart rate detection:
|
||||
http://www.ti.com/lit/an/slaa655/slaa655.pdf
|
||||
|
||||
Good reading:
|
||||
http://www.techforfuture.nl/fjc_documents/mitrabaratchi-measuringheartratewithopticalsensor.pdf
|
||||
https://fruct.org/publications/fruct13/files/Lau.pdf
|
||||
|
||||
This is an implementation of Maxim's PBA (Penpheral Beat Amplitude) algorithm. It's been
|
||||
converted to work within the Arduino framework.
|
||||
*/
|
||||
|
||||
/* Copyright (C) 2016 Maxim Integrated Products, Inc., All Rights Reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included
|
||||
* in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL MAXIM INTEGRATED BE LIABLE FOR ANY CLAIM, DAMAGES
|
||||
* OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
* Except as contained in this notice, the name of Maxim Integrated
|
||||
* Products, Inc. shall not be used except as stated in the Maxim Integrated
|
||||
* Products, Inc. Branding Policy.
|
||||
*
|
||||
* The mere transfer of this software does not imply any licenses
|
||||
* of trade secrets, proprietary technology, copyrights, patents,
|
||||
* trademarks, maskwork rights, or any other form of intellectual
|
||||
* property whatsoever. Maxim Integrated Products, Inc. retains all
|
||||
* ownership rights.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "heartRate.h"
|
||||
|
||||
int16_t IR_AC_Max = 20;
|
||||
int16_t IR_AC_Min = -20;
|
||||
|
||||
int16_t IR_AC_Signal_Current = 0;
|
||||
int16_t IR_AC_Signal_Previous;
|
||||
int16_t IR_AC_Signal_min = 0;
|
||||
int16_t IR_AC_Signal_max = 0;
|
||||
int16_t IR_Average_Estimated;
|
||||
|
||||
int16_t positiveEdge = 0;
|
||||
int16_t negativeEdge = 0;
|
||||
int32_t ir_avg_reg = 0;
|
||||
|
||||
int16_t cbuf[32];
|
||||
uint8_t offset = 0;
|
||||
|
||||
static const uint16_t FIRCoeffs[12] = {172, 321, 579, 927, 1360, 1858, 2390, 2916, 3391, 3768, 4012, 4096};
|
||||
|
||||
// Heart Rate Monitor functions takes a sample value and the sample number
|
||||
// Returns true if a beat is detected
|
||||
// A running average of four samples is recommended for display on the screen.
|
||||
bool checkForBeat(int32_t sample)
|
||||
{
|
||||
bool beatDetected = false;
|
||||
|
||||
// Save current state
|
||||
IR_AC_Signal_Previous = IR_AC_Signal_Current;
|
||||
|
||||
//This is good to view for debugging
|
||||
//Serial.print("Signal_Current: ");
|
||||
//Serial.println(IR_AC_Signal_Current);
|
||||
|
||||
// Process next data sample
|
||||
IR_Average_Estimated = averageDCEstimator(&ir_avg_reg, sample);
|
||||
IR_AC_Signal_Current = lowPassFIRFilter(sample - IR_Average_Estimated);
|
||||
|
||||
// Detect positive zero crossing (rising edge)
|
||||
if ((IR_AC_Signal_Previous < 0) & (IR_AC_Signal_Current >= 0))
|
||||
{
|
||||
|
||||
IR_AC_Max = IR_AC_Signal_max; //Adjust our AC max and min
|
||||
IR_AC_Min = IR_AC_Signal_min;
|
||||
|
||||
positiveEdge = 1;
|
||||
negativeEdge = 0;
|
||||
IR_AC_Signal_max = 0;
|
||||
|
||||
//if ((IR_AC_Max - IR_AC_Min) > 100 & (IR_AC_Max - IR_AC_Min) < 1000)
|
||||
if ((IR_AC_Max - IR_AC_Min) > 20 & (IR_AC_Max - IR_AC_Min) < 1000)
|
||||
{
|
||||
//Heart beat!!!
|
||||
beatDetected = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Detect negative zero crossing (falling edge)
|
||||
if ((IR_AC_Signal_Previous > 0) & (IR_AC_Signal_Current <= 0))
|
||||
{
|
||||
positiveEdge = 0;
|
||||
negativeEdge = 1;
|
||||
IR_AC_Signal_min = 0;
|
||||
}
|
||||
|
||||
// Find Maximum value in positive cycle
|
||||
if (positiveEdge & (IR_AC_Signal_Current > IR_AC_Signal_Previous))
|
||||
{
|
||||
IR_AC_Signal_max = IR_AC_Signal_Current;
|
||||
}
|
||||
|
||||
// Find Minimum value in negative cycle
|
||||
if (negativeEdge & (IR_AC_Signal_Current < IR_AC_Signal_Previous))
|
||||
{
|
||||
IR_AC_Signal_min = IR_AC_Signal_Current;
|
||||
}
|
||||
|
||||
return(beatDetected);
|
||||
}
|
||||
|
||||
// Average DC Estimator
|
||||
int16_t averageDCEstimator(int32_t *p, uint16_t x)
|
||||
{
|
||||
*p += ((((long) x << 15) - *p) >> 4);
|
||||
return (*p >> 15);
|
||||
}
|
||||
|
||||
// Low Pass FIR Filter
|
||||
int16_t lowPassFIRFilter(int16_t din)
|
||||
{
|
||||
cbuf[offset] = din;
|
||||
|
||||
int32_t z = mul16(FIRCoeffs[11], cbuf[(offset - 11) & 0x1F]);
|
||||
|
||||
for (uint8_t i = 0 ; i < 11 ; i++)
|
||||
{
|
||||
z += mul16(FIRCoeffs[i], cbuf[(offset - i) & 0x1F] + cbuf[(offset - 22 + i) & 0x1F]);
|
||||
}
|
||||
|
||||
offset++;
|
||||
offset %= 32; //Wrap condition
|
||||
|
||||
return(z >> 15);
|
||||
}
|
||||
|
||||
// Integer multiplier
|
||||
int32_t mul16(int16_t x, int16_t y)
|
||||
{
|
||||
return((long)x * (long)y);
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
Optical Heart Rate Detection (PBA Algorithm)
|
||||
By: Nathan Seidle
|
||||
SparkFun Electronics
|
||||
Date: October 2nd, 2016
|
||||
|
||||
Given a series of IR samples from the MAX30105 we discern when a heart beat is occurring
|
||||
|
||||
Let's have a brief chat about what this code does. We're going to try to detect
|
||||
heart-rate optically. This is tricky and prone to give false readings. We really don't
|
||||
want to get anyone hurt so use this code only as an example of how to process optical
|
||||
data. Build fun stuff with our MAX30105 breakout board but don't use it for actual
|
||||
medical diagnosis.
|
||||
|
||||
Excellent background on optical heart rate detection:
|
||||
http://www.ti.com/lit/an/slaa655/slaa655.pdf
|
||||
|
||||
Good reading:
|
||||
http://www.techforfuture.nl/fjc_documents/mitrabaratchi-measuringheartratewithopticalsensor.pdf
|
||||
https://fruct.org/publications/fruct13/files/Lau.pdf
|
||||
|
||||
This is an implementation of Maxim's PBA (Penpheral Beat Amplitude) algorithm. It's been
|
||||
converted to work within the Arduino framework.
|
||||
*/
|
||||
|
||||
/* Copyright (C) 2016 Maxim Integrated Products, Inc., All Rights Reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included
|
||||
* in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL MAXIM INTEGRATED BE LIABLE FOR ANY CLAIM, DAMAGES
|
||||
* OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
* Except as contained in this notice, the name of Maxim Integrated
|
||||
* Products, Inc. shall not be used except as stated in the Maxim Integrated
|
||||
* Products, Inc. Branding Policy.
|
||||
*
|
||||
* The mere transfer of this software does not imply any licenses
|
||||
* of trade secrets, proprietary technology, copyrights, patents,
|
||||
* trademarks, maskwork rights, or any other form of intellectual
|
||||
* property whatsoever. Maxim Integrated Products, Inc. retains all
|
||||
* ownership rights.
|
||||
*
|
||||
*/
|
||||
|
||||
#if (ARDUINO >= 100)
|
||||
#include "Arduino.h"
|
||||
#else
|
||||
#include "WProgram.h"
|
||||
#endif
|
||||
|
||||
bool checkForBeat(int32_t sample);
|
||||
int16_t averageDCEstimator(int32_t *p, uint16_t x);
|
||||
int16_t lowPassFIRFilter(int16_t din);
|
||||
int32_t mul16(int16_t x, int16_t y);
|
||||
@@ -0,0 +1,321 @@
|
||||
/** \file algorithm.cpp ******************************************************
|
||||
*
|
||||
* Project: MAXREFDES117#
|
||||
* Filename: algorithm.cpp
|
||||
* Description: This module calculates the heart rate/SpO2 level
|
||||
*
|
||||
*
|
||||
* --------------------------------------------------------------------
|
||||
*
|
||||
* This code follows the following naming conventions:
|
||||
*
|
||||
* char ch_pmod_value
|
||||
* char (array) s_pmod_s_string[16]
|
||||
* float f_pmod_value
|
||||
* int32_t n_pmod_value
|
||||
* int32_t (array) an_pmod_value[16]
|
||||
* int16_t w_pmod_value
|
||||
* int16_t (array) aw_pmod_value[16]
|
||||
* uint16_t uw_pmod_value
|
||||
* uint16_t (array) auw_pmod_value[16]
|
||||
* uint8_t uch_pmod_value
|
||||
* uint8_t (array) auch_pmod_buffer[16]
|
||||
* uint32_t un_pmod_value
|
||||
* int32_t * pn_pmod_value
|
||||
*
|
||||
* ------------------------------------------------------------------------- */
|
||||
/*******************************************************************************
|
||||
* Copyright (C) 2016 Maxim Integrated Products, Inc., All Rights Reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included
|
||||
* in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL MAXIM INTEGRATED BE LIABLE FOR ANY CLAIM, DAMAGES
|
||||
* OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
* Except as contained in this notice, the name of Maxim Integrated
|
||||
* Products, Inc. shall not be used except as stated in the Maxim Integrated
|
||||
* Products, Inc. Branding Policy.
|
||||
*
|
||||
* The mere transfer of this software does not imply any licenses
|
||||
* of trade secrets, proprietary technology, copyrights, patents,
|
||||
* trademarks, maskwork rights, or any other form of intellectual
|
||||
* property whatsoever. Maxim Integrated Products, Inc. retains all
|
||||
* ownership rights.
|
||||
*******************************************************************************
|
||||
*/
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "spo2_algorithm.h"
|
||||
|
||||
#if defined(__AVR_ATmega328P__) || defined(__AVR_ATmega168__)
|
||||
//Arduino Uno doesn't have enough SRAM to store 100 samples of IR led data and red led data in 32-bit format
|
||||
//To solve this problem, 16-bit MSB of the sampled data will be truncated. Samples become 16-bit data.
|
||||
void maxim_heart_rate_and_oxygen_saturation(uint16_t *pun_ir_buffer, int32_t n_ir_buffer_length, uint16_t *pun_red_buffer, int32_t *pn_spo2, int8_t *pch_spo2_valid,
|
||||
int32_t *pn_heart_rate, int8_t *pch_hr_valid)
|
||||
#else
|
||||
void maxim_heart_rate_and_oxygen_saturation(uint32_t *pun_ir_buffer, int32_t n_ir_buffer_length, uint32_t *pun_red_buffer, int32_t *pn_spo2, int8_t *pch_spo2_valid,
|
||||
int32_t *pn_heart_rate, int8_t *pch_hr_valid)
|
||||
#endif
|
||||
/**
|
||||
* \brief Calculate the heart rate and SpO2 level
|
||||
* \par Details
|
||||
* By detecting peaks of PPG cycle and corresponding AC/DC of red/infra-red signal, the an_ratio for the SPO2 is computed.
|
||||
* Since this algorithm is aiming for Arm M0/M3. formaula for SPO2 did not achieve the accuracy due to register overflow.
|
||||
* Thus, accurate SPO2 is precalculated and save longo uch_spo2_table[] per each an_ratio.
|
||||
*
|
||||
* \param[in] *pun_ir_buffer - IR sensor data buffer
|
||||
* \param[in] n_ir_buffer_length - IR sensor data buffer length
|
||||
* \param[in] *pun_red_buffer - Red sensor data buffer
|
||||
* \param[out] *pn_spo2 - Calculated SpO2 value
|
||||
* \param[out] *pch_spo2_valid - 1 if the calculated SpO2 value is valid
|
||||
* \param[out] *pn_heart_rate - Calculated heart rate value
|
||||
* \param[out] *pch_hr_valid - 1 if the calculated heart rate value is valid
|
||||
*
|
||||
* \retval None
|
||||
*/
|
||||
{
|
||||
uint32_t un_ir_mean;
|
||||
int32_t k, n_i_ratio_count;
|
||||
int32_t i, n_exact_ir_valley_locs_count, n_middle_idx;
|
||||
int32_t n_th1, n_npks;
|
||||
int32_t an_ir_valley_locs[15] ;
|
||||
int32_t n_peak_interval_sum;
|
||||
|
||||
int32_t n_y_ac, n_x_ac;
|
||||
int32_t n_spo2_calc;
|
||||
int32_t n_y_dc_max, n_x_dc_max;
|
||||
int32_t n_y_dc_max_idx = 0;
|
||||
int32_t n_x_dc_max_idx = 0;
|
||||
int32_t an_ratio[5], n_ratio_average;
|
||||
int32_t n_nume, n_denom ;
|
||||
|
||||
// calculates DC mean and subtract DC from ir
|
||||
un_ir_mean =0;
|
||||
for (k=0 ; k<n_ir_buffer_length ; k++ ) un_ir_mean += pun_ir_buffer[k] ;
|
||||
un_ir_mean =un_ir_mean/n_ir_buffer_length ;
|
||||
|
||||
// remove DC and invert signal so that we can use peak detector as valley detector
|
||||
for (k=0 ; k<n_ir_buffer_length ; k++ )
|
||||
an_x[k] = -1*(pun_ir_buffer[k] - un_ir_mean) ;
|
||||
|
||||
// 4 pt Moving Average
|
||||
for(k=0; k< BUFFER_SIZE-MA4_SIZE; k++){
|
||||
an_x[k]=( an_x[k]+an_x[k+1]+ an_x[k+2]+ an_x[k+3])/(int)4;
|
||||
}
|
||||
// calculate threshold
|
||||
n_th1=0;
|
||||
for ( k=0 ; k<BUFFER_SIZE ;k++){
|
||||
n_th1 += an_x[k];
|
||||
}
|
||||
n_th1= n_th1/ ( BUFFER_SIZE);
|
||||
if( n_th1<30) n_th1=30; // min allowed
|
||||
if( n_th1>60) n_th1=60; // max allowed
|
||||
|
||||
for ( k=0 ; k<15;k++) an_ir_valley_locs[k]=0;
|
||||
// since we flipped signal, we use peak detector as valley detector
|
||||
maxim_find_peaks( an_ir_valley_locs, &n_npks, an_x, BUFFER_SIZE, n_th1, 4, 15 );//peak_height, peak_distance, max_num_peaks
|
||||
n_peak_interval_sum =0;
|
||||
if (n_npks>=2){
|
||||
for (k=1; k<n_npks; k++) n_peak_interval_sum += (an_ir_valley_locs[k] -an_ir_valley_locs[k -1] ) ;
|
||||
n_peak_interval_sum =n_peak_interval_sum/(n_npks-1);
|
||||
*pn_heart_rate =(int32_t)( (FreqS*60)/ n_peak_interval_sum );
|
||||
*pch_hr_valid = 1;
|
||||
}
|
||||
else {
|
||||
*pn_heart_rate = -999; // unable to calculate because # of peaks are too small
|
||||
*pch_hr_valid = 0;
|
||||
}
|
||||
|
||||
// load raw value again for SPO2 calculation : RED(=y) and IR(=X)
|
||||
for (k=0 ; k<n_ir_buffer_length ; k++ ) {
|
||||
an_x[k] = pun_ir_buffer[k] ;
|
||||
an_y[k] = pun_red_buffer[k] ;
|
||||
}
|
||||
|
||||
// find precise min near an_ir_valley_locs
|
||||
n_exact_ir_valley_locs_count =n_npks;
|
||||
|
||||
//using exact_ir_valley_locs , find ir-red DC andir-red AC for SPO2 calibration an_ratio
|
||||
//finding AC/DC maximum of raw
|
||||
|
||||
n_ratio_average =0;
|
||||
n_i_ratio_count = 0;
|
||||
for(k=0; k< 5; k++) an_ratio[k]=0;
|
||||
for (k=0; k< n_exact_ir_valley_locs_count; k++){
|
||||
if (an_ir_valley_locs[k] > BUFFER_SIZE ){
|
||||
*pn_spo2 = -999 ; // do not use SPO2 since valley loc is out of range
|
||||
*pch_spo2_valid = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
// find max between two valley locations
|
||||
// and use an_ratio betwen AC compoent of Ir & Red and DC compoent of Ir & Red for SPO2
|
||||
for (k=0; k< n_exact_ir_valley_locs_count-1; k++){
|
||||
n_y_dc_max= -16777216 ;
|
||||
n_x_dc_max= -16777216;
|
||||
if (an_ir_valley_locs[k+1]-an_ir_valley_locs[k] >3){
|
||||
for (i=an_ir_valley_locs[k]; i< an_ir_valley_locs[k+1]; i++){
|
||||
if (an_x[i]> n_x_dc_max) {n_x_dc_max =an_x[i]; n_x_dc_max_idx=i;}
|
||||
if (an_y[i]> n_y_dc_max) {n_y_dc_max =an_y[i]; n_y_dc_max_idx=i;}
|
||||
}
|
||||
n_y_ac= (an_y[an_ir_valley_locs[k+1]] - an_y[an_ir_valley_locs[k] ] )*(n_y_dc_max_idx -an_ir_valley_locs[k]); //red
|
||||
n_y_ac= an_y[an_ir_valley_locs[k]] + n_y_ac/ (an_ir_valley_locs[k+1] - an_ir_valley_locs[k]) ;
|
||||
n_y_ac= an_y[n_y_dc_max_idx] - n_y_ac; // subracting linear DC compoenents from raw
|
||||
n_x_ac= (an_x[an_ir_valley_locs[k+1]] - an_x[an_ir_valley_locs[k] ] )*(n_x_dc_max_idx -an_ir_valley_locs[k]); // ir
|
||||
n_x_ac= an_x[an_ir_valley_locs[k]] + n_x_ac/ (an_ir_valley_locs[k+1] - an_ir_valley_locs[k]);
|
||||
n_x_ac= an_x[n_y_dc_max_idx] - n_x_ac; // subracting linear DC compoenents from raw
|
||||
n_nume=( n_y_ac *n_x_dc_max)>>7 ; //prepare X100 to preserve floating value
|
||||
n_denom= ( n_x_ac *n_y_dc_max)>>7;
|
||||
if (n_denom>0 && n_i_ratio_count <5 && n_nume != 0)
|
||||
{
|
||||
an_ratio[n_i_ratio_count]= (n_nume*100)/n_denom ; //formular is ( n_y_ac *n_x_dc_max) / ( n_x_ac *n_y_dc_max) ;
|
||||
n_i_ratio_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
// choose median value since PPG signal may varies from beat to beat
|
||||
maxim_sort_ascend(an_ratio, n_i_ratio_count);
|
||||
n_middle_idx= n_i_ratio_count/2;
|
||||
|
||||
if (n_middle_idx >1)
|
||||
n_ratio_average =( an_ratio[n_middle_idx-1] +an_ratio[n_middle_idx])/2; // use median
|
||||
else
|
||||
n_ratio_average = an_ratio[n_middle_idx ];
|
||||
|
||||
if( n_ratio_average>2 && n_ratio_average <184){
|
||||
n_spo2_calc= uch_spo2_table[n_ratio_average] ;
|
||||
*pn_spo2 = n_spo2_calc ;
|
||||
*pch_spo2_valid = 1;// float_SPO2 = -45.060*n_ratio_average* n_ratio_average/10000 + 30.354 *n_ratio_average/100 + 94.845 ; // for comparison with table
|
||||
}
|
||||
else{
|
||||
*pn_spo2 = -999 ; // do not use SPO2 since signal an_ratio is out of range
|
||||
*pch_spo2_valid = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void maxim_find_peaks( int32_t *pn_locs, int32_t *n_npks, int32_t *pn_x, int32_t n_size, int32_t n_min_height, int32_t n_min_distance, int32_t n_max_num )
|
||||
/**
|
||||
* \brief Find peaks
|
||||
* \par Details
|
||||
* Find at most MAX_NUM peaks above MIN_HEIGHT separated by at least MIN_DISTANCE
|
||||
*
|
||||
* \retval None
|
||||
*/
|
||||
{
|
||||
maxim_peaks_above_min_height( pn_locs, n_npks, pn_x, n_size, n_min_height );
|
||||
maxim_remove_close_peaks( pn_locs, n_npks, pn_x, n_min_distance );
|
||||
*n_npks = min( *n_npks, n_max_num );
|
||||
}
|
||||
|
||||
void maxim_peaks_above_min_height( int32_t *pn_locs, int32_t *n_npks, int32_t *pn_x, int32_t n_size, int32_t n_min_height )
|
||||
/**
|
||||
* \brief Find peaks above n_min_height
|
||||
* \par Details
|
||||
* Find all peaks above MIN_HEIGHT
|
||||
*
|
||||
* \retval None
|
||||
*/
|
||||
{
|
||||
int32_t i = 1, n_width;
|
||||
*n_npks = 0;
|
||||
|
||||
while (i < n_size-1){
|
||||
if (pn_x[i] > n_min_height && pn_x[i] > pn_x[i-1]){ // find left edge of potential peaks
|
||||
n_width = 1;
|
||||
while (i+n_width < n_size && pn_x[i] == pn_x[i+n_width]) // find flat peaks
|
||||
n_width++;
|
||||
if (pn_x[i] > pn_x[i+n_width] && (*n_npks) < 15 ){ // find right edge of peaks
|
||||
pn_locs[(*n_npks)++] = i;
|
||||
// for flat peaks, peak location is left edge
|
||||
i += n_width+1;
|
||||
}
|
||||
else
|
||||
i += n_width;
|
||||
}
|
||||
else
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
void maxim_remove_close_peaks(int32_t *pn_locs, int32_t *pn_npks, int32_t *pn_x, int32_t n_min_distance)
|
||||
/**
|
||||
* \brief Remove peaks
|
||||
* \par Details
|
||||
* Remove peaks separated by less than MIN_DISTANCE
|
||||
*
|
||||
* \retval None
|
||||
*/
|
||||
{
|
||||
|
||||
int32_t i, j, n_old_npks, n_dist;
|
||||
|
||||
/* Order peaks from large to small */
|
||||
maxim_sort_indices_descend( pn_x, pn_locs, *pn_npks );
|
||||
|
||||
for ( i = -1; i < *pn_npks; i++ ){
|
||||
n_old_npks = *pn_npks;
|
||||
*pn_npks = i+1;
|
||||
for ( j = i+1; j < n_old_npks; j++ ){
|
||||
n_dist = pn_locs[j] - ( i == -1 ? -1 : pn_locs[i] ); // lag-zero peak of autocorr is at index -1
|
||||
if ( n_dist > n_min_distance || n_dist < -n_min_distance )
|
||||
pn_locs[(*pn_npks)++] = pn_locs[j];
|
||||
}
|
||||
}
|
||||
|
||||
// Resort indices int32_to ascending order
|
||||
maxim_sort_ascend( pn_locs, *pn_npks );
|
||||
}
|
||||
|
||||
void maxim_sort_ascend(int32_t *pn_x, int32_t n_size)
|
||||
/**
|
||||
* \brief Sort array
|
||||
* \par Details
|
||||
* Sort array in ascending order (insertion sort algorithm)
|
||||
*
|
||||
* \retval None
|
||||
*/
|
||||
{
|
||||
int32_t i, j, n_temp;
|
||||
for (i = 1; i < n_size; i++) {
|
||||
n_temp = pn_x[i];
|
||||
for (j = i; j > 0 && n_temp < pn_x[j-1]; j--)
|
||||
pn_x[j] = pn_x[j-1];
|
||||
pn_x[j] = n_temp;
|
||||
}
|
||||
}
|
||||
|
||||
void maxim_sort_indices_descend( int32_t *pn_x, int32_t *pn_indx, int32_t n_size)
|
||||
/**
|
||||
* \brief Sort indices
|
||||
* \par Details
|
||||
* Sort indices according to descending order (insertion sort algorithm)
|
||||
*
|
||||
* \retval None
|
||||
*/
|
||||
{
|
||||
int32_t i, j, n_temp;
|
||||
for (i = 1; i < n_size; i++) {
|
||||
n_temp = pn_indx[i];
|
||||
for (j = i; j > 0 && pn_x[n_temp] > pn_x[pn_indx[j-1]]; j--)
|
||||
pn_indx[j] = pn_indx[j-1];
|
||||
pn_indx[j] = n_temp;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
/** \file algorithm.h ******************************************************
|
||||
*
|
||||
* Project: MAXREFDES117#
|
||||
* Filename: algorithm.h
|
||||
* Description: This module is the heart rate/SpO2 calculation algorithm header file
|
||||
*
|
||||
* Revision History:
|
||||
*\n 1-18-2016 Rev 01.00 SK Initial release.
|
||||
*\n
|
||||
*
|
||||
* --------------------------------------------------------------------
|
||||
*
|
||||
* This code follows the following naming conventions:
|
||||
*
|
||||
*\n char ch_pmod_value
|
||||
*\n char (array) s_pmod_s_string[16]
|
||||
*\n float f_pmod_value
|
||||
*\n int32_t n_pmod_value
|
||||
*\n int32_t (array) an_pmod_value[16]
|
||||
*\n int16_t w_pmod_value
|
||||
*\n int16_t (array) aw_pmod_value[16]
|
||||
*\n uint16_t uw_pmod_value
|
||||
*\n uint16_t (array) auw_pmod_value[16]
|
||||
*\n uint8_t uch_pmod_value
|
||||
*\n uint8_t (array) auch_pmod_buffer[16]
|
||||
*\n uint32_t un_pmod_value
|
||||
*\n int32_t * pn_pmod_value
|
||||
*
|
||||
* ------------------------------------------------------------------------- */
|
||||
/*******************************************************************************
|
||||
* Copyright (C) 2015 Maxim Integrated Products, Inc., All Rights Reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included
|
||||
* in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL MAXIM INTEGRATED BE LIABLE FOR ANY CLAIM, DAMAGES
|
||||
* OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
* Except as contained in this notice, the name of Maxim Integrated
|
||||
* Products, Inc. shall not be used except as stated in the Maxim Integrated
|
||||
* Products, Inc. Branding Policy.
|
||||
*
|
||||
* The mere transfer of this software does not imply any licenses
|
||||
* of trade secrets, proprietary technology, copyrights, patents,
|
||||
* trademarks, maskwork rights, or any other form of intellectual
|
||||
* property whatsoever. Maxim Integrated Products, Inc. retains all
|
||||
* ownership rights.
|
||||
*******************************************************************************
|
||||
*/
|
||||
#ifndef SPO2_ALGORITHM_H_
|
||||
#define SPO2_ALGORITHM_H_
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#define FreqS 25 //sampling frequency
|
||||
#define BUFFER_SIZE (FreqS * 4)
|
||||
#define MA4_SIZE 4 // DONOT CHANGE
|
||||
//#define min(x,y) ((x) < (y) ? (x) : (y)) //Defined in Arduino.h
|
||||
|
||||
//uch_spo2_table is approximated as -45.060*ratioAverage* ratioAverage + 30.354 *ratioAverage + 94.845 ;
|
||||
const uint8_t uch_spo2_table[184]={ 95, 95, 95, 96, 96, 96, 97, 97, 97, 97, 97, 98, 98, 98, 98, 98, 99, 99, 99, 99,
|
||||
99, 99, 99, 99, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100,
|
||||
100, 100, 100, 100, 99, 99, 99, 99, 99, 99, 99, 99, 98, 98, 98, 98, 98, 98, 97, 97,
|
||||
97, 97, 96, 96, 96, 96, 95, 95, 95, 94, 94, 94, 93, 93, 93, 92, 92, 92, 91, 91,
|
||||
90, 90, 89, 89, 89, 88, 88, 87, 87, 86, 86, 85, 85, 84, 84, 83, 82, 82, 81, 81,
|
||||
80, 80, 79, 78, 78, 77, 76, 76, 75, 74, 74, 73, 72, 72, 71, 70, 69, 69, 68, 67,
|
||||
66, 66, 65, 64, 63, 62, 62, 61, 60, 59, 58, 57, 56, 56, 55, 54, 53, 52, 51, 50,
|
||||
49, 48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 31, 30, 29,
|
||||
28, 27, 26, 25, 23, 22, 21, 20, 19, 17, 16, 15, 14, 12, 11, 10, 9, 7, 6, 5,
|
||||
3, 2, 1 } ;
|
||||
static int32_t an_x[ BUFFER_SIZE]; //ir
|
||||
static int32_t an_y[ BUFFER_SIZE]; //red
|
||||
|
||||
|
||||
#if defined(__AVR_ATmega328P__) || defined(__AVR_ATmega168__)
|
||||
//Arduino Uno doesn't have enough SRAM to store 100 samples of IR led data and red led data in 32-bit format
|
||||
//To solve this problem, 16-bit MSB of the sampled data will be truncated. Samples become 16-bit data.
|
||||
void maxim_heart_rate_and_oxygen_saturation(uint16_t *pun_ir_buffer, int32_t n_ir_buffer_length, uint16_t *pun_red_buffer, int32_t *pn_spo2, int8_t *pch_spo2_valid, int32_t *pn_heart_rate, int8_t *pch_hr_valid);
|
||||
#else
|
||||
void maxim_heart_rate_and_oxygen_saturation(uint32_t *pun_ir_buffer, int32_t n_ir_buffer_length, uint32_t *pun_red_buffer, int32_t *pn_spo2, int8_t *pch_spo2_valid, int32_t *pn_heart_rate, int8_t *pch_hr_valid);
|
||||
#endif
|
||||
|
||||
void maxim_find_peaks(int32_t *pn_locs, int32_t *n_npks, int32_t *pn_x, int32_t n_size, int32_t n_min_height, int32_t n_min_distance, int32_t n_max_num);
|
||||
void maxim_peaks_above_min_height(int32_t *pn_locs, int32_t *n_npks, int32_t *pn_x, int32_t n_size, int32_t n_min_height);
|
||||
void maxim_remove_close_peaks(int32_t *pn_locs, int32_t *pn_npks, int32_t *pn_x, int32_t n_min_distance);
|
||||
void maxim_sort_ascend(int32_t *pn_x, int32_t n_size);
|
||||
void maxim_sort_indices_descend(int32_t *pn_x, int32_t *pn_indx, int32_t n_size);
|
||||
|
||||
#endif /* ALGORITHM_H_ */
|
||||
|
||||
Reference in New Issue
Block a user