zinnet yazıcı
/
max30105Example
maxrefdes117
Revision 0:78a2573ad768, committed 2019-08-06
- Comitter:
- zinnetyazicii53
- Date:
- Tue Aug 06 12:19:46 2019 +0000
- Commit message:
- commit
Changed in this revision
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.hgignore Tue Aug 06 12:19:46 2019 +0000 @@ -0,0 +1,6 @@ +mbed-os +.build +/mdot/ +/mdot-library/ +/BUILD/ +/.git/
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MAX30105/MAX30105.cpp Tue Aug 06 12:19:46 2019 +0000 @@ -0,0 +1,781 @@ + +#include "mbed.h" +#include "MAX30105.h" +#include "millis.h" + + +//****************************************************************************** + +MAX30105::MAX30105(I2C &i2c): _i2c(i2c) +{ +} + +//****************************************************************************** + +int MAX30105::writeRegValue(uint8_t reg, char value) +{ + char cmdData[2] = { (char)reg, value }; + + if (_i2c.write(MAX30105_ADDRESS, cmdData, sizeof(cmdData)) != 0) { + return MAX30105_ERROR; + } + + return MAX30105_NO_ERROR; +} + +//****************************************************************************** + +int MAX30105::writeReg(uint8_t reg ) +{ + char cmdData[1] = { (char)reg }; + + if (_i2c.write(MAX30105_ADDRESS, cmdData, sizeof(cmdData)) != 0) { + return MAX30105_ERROR; + } + + return MAX30105_NO_ERROR; +} + + +//****************************************************************************** + +int MAX30105::readReg(uint8_t reg, char *value) +{ + char cmdData[1] = { (char)reg }; + + if (_i2c.write(MAX30105_ADDRESS, cmdData, sizeof(cmdData)) != 0) { + return MAX30105_ERROR; + } + + if (_i2c.read(MAX30105_ADDRESS, value, 1) != 0) { + return MAX30105_ERROR; + } + + return MAX30105_NO_ERROR; +} + + +//****************************************************************************** + +uint8_t MAX30105::readRegister8(uint8_t address, uint8_t reg){ + _i2c.write(reg); + char *data = new char[5]; + _i2c.read(address,data,5); + return (uint8_t)data; + +} + + +//****************************************************************************** +void MAX30105::writeRegister8(uint8_t address, uint8_t reg, uint8_t value){ + + writeRegValue(reg, value); + +} + + +//****************************************************************************** + +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); + + wait(1); + } +} + + +//****************************************************************************** + +// 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); +} +//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! + wait(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); +} + + +//****************************************************************************** + +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; + } +} + + +//****************************************************************************** + +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)); +} + +//****************************************************************************** + +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! + wait(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(uint8_t powerLevel, uint8_t sampleAverage, uint8_t 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 uint8_ts 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-uint8_t * number of active LED) chunks + //Until FIFO_RD_PTR = FIFO_WR_PTR + + uint8_t readPointer = getReadPointer(); + uint8_t 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 uint8_ts to read + //For this example we are just doing Red and IR (3 uint8_ts each) + int uint8_tsLeftToRead = numberOfSamples * activeLEDs * 3; + + //Get ready to read a burst of data from the FIFO register + //buraya bak + _i2c.writeReg(MAX30105_FIFODATA); + + + //We may need to read as many as 288 uint8_ts so we read in blocks no larger than I2C_BUFFER_LENGTH + //I2C_BUFFER_LENGTH changes based on the platform. 64 uint8_ts for SAMD21, 32 uint8_ts for Uno. + //Wire.requestFrom() is limited to BUFFER_LENGTH which is 32 on the Uno + while (uint8_tsLeftToRead > 0) + { + int toGet = uint8_tsLeftToRead; + if (toGet > I2C_BUFFER_LENGTH) + { + //If toGet is 32 this is bad because we read 6 uint8_ts (Red+IR * 3 = 6) at a time + //32 % 6 = 2 left over. We don't want to request 32 uint8_ts, 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 + } + + uint8_tsLeftToRead -= toGet; + + //Request toGet number of uint8_ts from sensor + + //buraya bak + _i2c.requestFrom(MAX30105_ADDRESS, toGet); + + while (toGet > 0) + { + sense.head++; //Advance the head of the storage struct + sense.head %= STORAGE_SIZE; //Wrap condition + + uint8_t temp[sizeof(uint32_t)]; //Array of 4 uint8_ts that we will convert into long + uint32_t tempLong; + + //Burst read three uint8_ts - RED + + //buraya bak + + 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 uint8_ts - 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 uint8_ts - 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 (uint8_tsLeftToRead > 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 + + +//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); +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MAX30105/MAX30105.h Tue Aug 06 12:19:46 2019 +0000 @@ -0,0 +1,269 @@ + +#ifndef _MAX30105_H_ +#define _MAX30105_H_ + +#include "mbed.h" + + +#define MAX30105_ADDRESS 0x57 //7-bit I2C Address //87 //1010111 + +//#define MAX30105_I2C_ADDR 0xAE << 1 //8-BİT //174//10101110 // + +//Note that MAX30102 has the same I2C address and Part ID + + +#define MAX30105_NO_ERROR 0 +#define MAX30105_ERROR -1 +#define MAX30105_TEMP_ERROR -999.0 + +//#define I2C_SPEED_STANDARD 100000 +//#define I2C_SPEED_FAST 400000 + +#define I2C_BUFFER_LENGTH 32 + + + +class MAX30105 { + public: + + + MAX30105(I2C &i2c); + 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(uint8_t powerLevel = 0x1F, uint8_t sampleAverage = 4, uint8_t 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); + int writeRegValue(uint8_t reg, char value); + int writeReg(uint8_t reg); + int readReg(uint8_t reg, char *value); + + private: + I2C _i2c; //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. + uint8_t activeLEDs; //Gets set during setup. Allows check() to calculate how many uint8_ts 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 uint8_ts 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]; + uint8_t head; + uint8_t tail; + } sense_struct; //This is our circular buffer of readings from the sensor + + sense_struct sense; + + + + // 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 = (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; + +}; +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/README Tue Aug 06 12:19:46 2019 +0000 @@ -0,0 +1,22 @@ +Instructions for building Dot-Examples + +1. Import dot-examples +$ mbed import http://os.mbed.com/teams/MultiTech/code/Dot-Examples/ + +2. cd to the Dot-Examples/examples and import the dot library stack +Choose either the stable or dev library for your dot device +e.g. to get the latest development library for the xDot +$ mbed add http://os.mbed.com/teams/MultiTech/code/libxDot-dev-mbed5/ + +3. Update mbed-os revision to match that of the dot library you just imported. +This information can be found in the library's commit history. +e.g. +$ cd Dot-Examples/mbed-os +$ mbed update mbed-os-5.7.6 + +4. Modify the Dot-Examples/examples/example_config.h to select the channel plan and which example to build +By default, the OTA example is selected to build with the US channel plan + +5. Once the example is selected, modify the example source file to match the configuration of your gateway. +Make sure the network_name, network_passphrase, frequency_sub_band (US), public_network, and join_delay settings match that of your gateway +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/heartRate.cpp Tue Aug 06 12:19:46 2019 +0000 @@ -0,0 +1,105 @@ +#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}; + +bool heartRate::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 heartRate::averageDCEstimator(int32_t *p, uint16_t x) +{ + *p += ((((long) x << 15) - *p) >> 4); + return (*p >> 15); +} + +// Low Pass FIR Filter +int16_t heartRate::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 heartRate::mul16(int16_t x, int16_t y) +{ + return((long)x * (long)y); +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/heartRate.h Tue Aug 06 12:19:46 2019 +0000 @@ -0,0 +1,36 @@ +#include "mbed.h" + +#ifndef HEARTRATE_H_ +#define HEARTRATE_H_ + + +class heartRate { +public: + 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); + +private: + + /*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};*/ + + +}; + +#endif \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/main.cpp Tue Aug 06 12:19:46 2019 +0000 @@ -0,0 +1,61 @@ + +#include "MAX30105.h" +#include "heartRate.h" +#include "spo2_algorithm.h" +#include "mbed.h" + +I2C i2c(I2C_SDA, I2C_SCL); +MAX30105 max30105(i2c); + +Serial pc(USBTX, USBRX); +MAX30105 particleSensor; + +DigitalIn INT(PA_5); + +void restart(); +void measureBPM(); + +void(* resetFunc) (void) = 0; + +bool nofinger=false; +bool programStarted=false; + +int RATE_SIZE=0; //Increase this for more averaging. 4 is good. +int rate[1000]; //Array of heart rates +long lastBeat = 0; //Time at which the last beat occurred + +float beatsPerMinute; +int beatAvg; + +long now=0; +int bpm = 0; +int mins=0; +int ten_secs=10; +int avg=0; + +long t= Timer(); + +void setup () +{ + pc.baud(9600); + pc.format(8,SerialBase::None,1); + wait(1); + particleSensor.setup(); + } + + int main(){ + setup(); + pc.printf(" R["); + pc.printf("%i",particleSensor.getRed()); + pc.printf("] IR["); + pc.printf("%i",particleSensor.getIR()); + pc.printf("] G["); + pc.printf("%i",particleSensor.getGreen()); + pc.printf("]"); + +} + + + + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mbed-os.lib Tue Aug 06 12:19:46 2019 +0000 @@ -0,0 +1,1 @@ +https://github.com/ARMmbed/mbed-os/#c966348d3f9ca80843be7cdc9b748f06ea73ced0
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/millis.cpp Tue Aug 06 12:19:46 2019 +0000 @@ -0,0 +1,29 @@ +#include "mbed.h" +#include "millis.h" + +static volatile uint32_t millisValue = 0; + +static Ticker ticker; + +void millisTicker () +{ + millisValue ++; +} + +uint32_t millis () +{ + return millisValue; +} + +void setMillis (uint32_t theValue) { + millisValue = theValue; +} + +void startMillis () { + ticker.attach (millisTicker, 0.001); +} + +void stopMillis () { + ticker.detach (); +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/millis.h Tue Aug 06 12:19:46 2019 +0000 @@ -0,0 +1,14 @@ + +#ifndef _MILLIS_H_ +#define _MILLIS_H_ + + + void millisTicker (); + + uint32_t millis (); + + void startMillis (); + + void stopMillis (); + +#endif /* _MILLIS_H_ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/setup.sh Tue Aug 06 12:19:46 2019 +0000 @@ -0,0 +1,3 @@ +if [ ! -d ISL29011 ]; then + hg clone https://developer.mbed.org/teams/Multi-Hackers/code/ISL29011/ +fi
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/spo2_algorithm.cpp Tue Aug 06 12:19:46 2019 +0000 @@ -0,0 +1,321 @@ +#include "spo2_algorithm.h" + + + +const uint16_t auw_hamm[31]={41,276,512,276,41 }; //Hamm= long16(512* hamming(5)'); +//SPO2table is computed 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_dx[ BUFFER_SIZE-MA4_SIZE]; // delta +static int32_t an_x[ BUFFER_SIZE]; //ir +static int32_t an_y[ BUFFER_SIZE]; //red + +void spo2_algorithm::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) +/** +* \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 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 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 ,un_only_once ; + int32_t k ,n_i_ratio_count; + int32_t i,s ,m, n_exact_ir_valley_locs_count ,n_middle_idx; + int32_t n_th1, n_npks,n_c_min; + int32_t an_ir_valley_locs[15] ; + int32_t an_exact_ir_valley_locs[15] ; + int32_t an_dx_peak_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, n_x_dc_max_idx; + int32_t an_ratio[5],n_ratio_average; + int32_t n_nume, n_denom ; + // remove DC of ir signal + 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 ; + for (k=0 ; k<n_ir_buffer_length ; k++ ) an_x[k] = pun_ir_buffer[k] - un_ir_mean ; + + // 4 pt Moving Average + for(k=0; k< BUFFER_SIZE-MA4_SIZE; k++){ + n_denom= ( an_x[k]+an_x[k+1]+ an_x[k+2]+ an_x[k+3]); + an_x[k]= n_denom/(int32_t)4; + } + + // get difference of smoothed IR signal + + for( k=0; k<BUFFER_SIZE-MA4_SIZE-1; k++) + an_dx[k]= (an_x[k+1]- an_x[k]); + + // 2-pt Moving Average to an_dx + for(k=0; k< BUFFER_SIZE-MA4_SIZE-2; k++){ + an_dx[k] = ( an_dx[k]+an_dx[k+1])/2 ; + } + + // hamming window + // flip wave form so that we can detect valley with peak detector + for ( i=0 ; i<BUFFER_SIZE-HAMMING_SIZE-MA4_SIZE-2 ;i++){ + s= 0; + for( k=i; k<i+ HAMMING_SIZE ;k++){ + s -= an_dx[k] *auw_hamm[k-i] ; + } + an_dx[i]= s/ (int32_t)1146; // divide by sum of auw_hamm + } + + + n_th1=0; // threshold calculation + for ( k=0 ; k<BUFFER_SIZE-HAMMING_SIZE ;k++){ + n_th1 += ((an_dx[k]>0)? an_dx[k] : ((int32_t)0-an_dx[k])) ; + } + n_th1= n_th1/ ( BUFFER_SIZE-HAMMING_SIZE); + // peak location is acutally index for sharpest location of raw signal since we flipped the signal + maxim_find_peaks(an_dx_peak_locs, &n_npks, an_dx, BUFFER_SIZE-HAMMING_SIZE, n_th1, 8, 5 );//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_dx_peak_locs[k]-an_dx_peak_locs[k -1]); + n_peak_interval_sum=n_peak_interval_sum/(n_npks-1); + *pn_heart_rate=(int32_t)(6000/n_peak_interval_sum);// beats per minutes + *pch_hr_valid = 1; + } + else { + *pn_heart_rate = -999; + *pch_hr_valid = 0; + } + + for ( k=0 ; k<n_npks ;k++) + an_ir_valley_locs[k]=an_dx_peak_locs[k]+HAMMING_SIZE/2; + + + // raw value : RED(=y) and IR(=X) + // we need to assess DC and AC value of ir and red PPG. + 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 =0; + for(k=0 ; k<n_npks ;k++){ + un_only_once =1; + m=an_ir_valley_locs[k]; + n_c_min= 16777216;//2^24; + if (m+5 < BUFFER_SIZE-HAMMING_SIZE && m-5 >0){ + for(i= m-5;i<m+5; i++) + if (an_x[i]<n_c_min){ + if (un_only_once >0){ + un_only_once =0; + } + n_c_min= an_x[i] ; + an_exact_ir_valley_locs[k]=i; + } + if (un_only_once ==0) + n_exact_ir_valley_locs_count ++ ; + } + } + if (n_exact_ir_valley_locs_count <2 ){ + *pn_spo2 = -999 ; // do not use SPO2 since signal ratio is out of range + *pch_spo2_valid = 0; + return; + } + // 4 pt MA + 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])/(int32_t)4; + an_y[k]=( an_y[k]+an_y[k+1]+ an_y[k+2]+ an_y[k+3])/(int32_t)4; + } + + //using an_exact_ir_valley_locs , find ir-red DC andir-red AC for SPO2 calibration ratio + //finding AC/DC maximum of raw ir * red between two valley locations + 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_exact_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 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_exact_ir_valley_locs[k+1]-an_exact_ir_valley_locs[k] >10){ + for (i=an_exact_ir_valley_locs[k]; i< an_exact_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_exact_ir_valley_locs[k+1]] - an_y[an_exact_ir_valley_locs[k] ] )*(n_y_dc_max_idx -an_exact_ir_valley_locs[k]); //red + n_y_ac= an_y[an_exact_ir_valley_locs[k]] + n_y_ac/ (an_exact_ir_valley_locs[k+1] - an_exact_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_exact_ir_valley_locs[k+1]] - an_x[an_exact_ir_valley_locs[k] ] )*(n_x_dc_max_idx -an_exact_ir_valley_locs[k]); // ir + n_x_ac= an_x[an_exact_ir_valley_locs[k]] + n_x_ac/ (an_exact_ir_valley_locs[k+1] - an_exact_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++; + } + } + } + + 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 ratio is out of range + *pch_spo2_valid = 0; + } +} + + +void spo2_algorithm::maxim_find_peaks(int32_t *pn_locs, int32_t *pn_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, pn_npks, pn_x, n_size, n_min_height ); + maxim_remove_close_peaks( pn_locs, pn_npks, pn_x, n_min_distance ); + *pn_npks = min( *pn_npks, n_max_num ); +} + +void spo2_algorithm::maxim_peaks_above_min_height(int32_t *pn_locs, int32_t *pn_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; + *pn_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] && (*pn_npks) < 15 ){ // find right edge of peaks + pn_locs[(*pn_npks)++] = i; + // for flat peaks, peak location is left edge + i += n_width+1; + } + else + i += n_width; + } + else + i++; + } +} + + +void spo2_algorithm::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 longo ascending order + maxim_sort_ascend( pn_locs, *pn_npks ); +} + +void spo2_algorithm::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 spo2_algorithm::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; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/spo2_algorithm.h Tue Aug 06 12:19:46 2019 +0000 @@ -0,0 +1,31 @@ +#ifndef SPO2_ALGORITHM_H_ +#define SPO2_ALGORITHM_H_ + +#include "mbed.h" + +#define FS 25 //sampling frequency +#define BUFFER_SIZE (FS * 4) +#define MA4_SIZE 4 // DONOT CHANGE +#define HAMMING_SIZE 5 +#define min(x,y) ((x) < (y) ? (x) : (y)) //Defined in Arduino.h + +#define true 1 +#define false 0 +//#define FS 100 +//#define BUFFER_SIZE (FS* 5) +#define HR_FIFO_SIZE 7 +#define MA4_SIZE 4 // DO NOT CHANGE +#define HAMMING_SIZE 5// DO NOT CHANGE +#define min(x,y) ((x) < (y) ? (x) : (y)) + + +class spo2_algorithm{ + +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); +void maxim_find_peaks( int32_t *pn_locs, int32_t *pn_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 *pn_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