├── License.md ├── MAX30100.cpp ├── MAX30100.h ├── README.md └── max30100.ino /License.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Raivis Strogonovs (https://morf.lv) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MAX30100.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2017 Raivis Strogonovs (https://morf.lv) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | 24 | 25 | */ 26 | 27 | #include "MAX30100.h" 28 | 29 | MAX30100::MAX30100( 30 | Mode mode, 31 | SamplingRate samplingRate, 32 | LEDPulseWidth pulseWidth, 33 | LEDCurrent IrLedCurrent, 34 | bool highResMode, 35 | bool debug) 36 | { 37 | this->debug = debug; 38 | currentPulseDetectorState = PULSE_IDLE; 39 | 40 | setMode( mode ); 41 | 42 | //Check table 8 in datasheet on page 19. You can't just throw in sample rate and pulse width randomly. 100hz + 1600us is max for that resolution 43 | setSamplingRate( samplingRate ); 44 | setLEDPulseWidth( pulseWidth ); 45 | 46 | redLEDCurrent = (uint8_t)STARTING_RED_LED_CURRENT; 47 | lastREDLedCurrentCheck = 0; 48 | 49 | this->IrLedCurrent = IrLedCurrent; 50 | setLEDCurrents(redLEDCurrent, IrLedCurrent ); 51 | setHighresModeEnabled(highResMode); 52 | 53 | 54 | dcFilterIR.w = 0; 55 | dcFilterIR.result = 0; 56 | 57 | dcFilterRed.w = 0; 58 | dcFilterRed.result = 0; 59 | 60 | 61 | lpbFilterIR.v[0] = 0; 62 | lpbFilterIR.v[1] = 0; 63 | lpbFilterIR.result = 0; 64 | 65 | meanDiffIR.index = 0; 66 | meanDiffIR.sum = 0; 67 | meanDiffIR.count = 0; 68 | 69 | 70 | valuesBPM[0] = 0; 71 | valuesBPMSum = 0; 72 | valuesBPMCount = 0; 73 | bpmIndex = 0; 74 | 75 | 76 | irACValueSqSum = 0; 77 | redACValueSqSum = 0; 78 | samplesRecorded = 0; 79 | pulsesDetected = 0; 80 | currentSaO2Value = 0; 81 | 82 | lastBeatThreshold = 0; 83 | 84 | } 85 | 86 | pulseoxymeter_t MAX30100::update() 87 | { 88 | pulseoxymeter_t result = { 89 | /*bool pulseDetected*/ false, 90 | /*float heartBPM*/ 0.0, 91 | /*float irCardiogram*/ 0.0, 92 | /*float irDcValue*/ 0.0, 93 | /*float redDcValue*/ 0.0, 94 | /*float SaO2*/ currentSaO2Value, 95 | /*uint32_t lastBeatThreshold*/ 0, 96 | /*float dcFilteredIR*/ 0.0, 97 | /*float dcFilteredRed*/ 0.0 98 | }; 99 | 100 | 101 | fifo_t rawData = readFIFO(); 102 | 103 | dcFilterIR = dcRemoval( (float)rawData.rawIR, dcFilterIR.w, ALPHA ); 104 | dcFilterRed = dcRemoval( (float)rawData.rawRed, dcFilterRed.w, ALPHA ); 105 | 106 | float meanDiffResIR = meanDiff( dcFilterIR.result, &meanDiffIR); 107 | lowPassButterworthFilter( meanDiffResIR/*-dcFilterIR.result*/, &lpbFilterIR ); 108 | 109 | irACValueSqSum += dcFilterIR.result * dcFilterIR.result; 110 | redACValueSqSum += dcFilterRed.result * dcFilterRed.result; 111 | samplesRecorded++; 112 | 113 | if( detectPulse( lpbFilterIR.result ) && samplesRecorded > 0 ) 114 | { 115 | result.pulseDetected=true; 116 | pulsesDetected++; 117 | 118 | float ratioRMS = log( sqrt(redACValueSqSum/samplesRecorded) ) / log( sqrt(irACValueSqSum/samplesRecorded) ); 119 | 120 | if( debug == true ) 121 | { 122 | Serial.print("RMS Ratio: "); 123 | Serial.println(ratioRMS); 124 | } 125 | 126 | //This is my adjusted standard model, so it shows 0.89 as 94% saturation. It is probably far from correct, requires proper empircal calibration 127 | currentSaO2Value = 110.0 - 18.0 * ratioRMS; 128 | result.SaO2 = currentSaO2Value; 129 | 130 | if( pulsesDetected % RESET_SPO2_EVERY_N_PULSES == 0) 131 | { 132 | irACValueSqSum = 0; 133 | redACValueSqSum = 0; 134 | samplesRecorded = 0; 135 | } 136 | } 137 | 138 | balanceIntesities( dcFilterRed.w, dcFilterIR.w ); 139 | 140 | 141 | result.heartBPM = currentBPM; 142 | result.irCardiogram = lpbFilterIR.result; 143 | result.irDcValue = dcFilterIR.w; 144 | result.redDcValue = dcFilterRed.w; 145 | result.lastBeatThreshold = lastBeatThreshold; 146 | result.dcFilteredIR = dcFilterIR.result; 147 | result.dcFilteredRed = dcFilterRed.result; 148 | 149 | 150 | return result; 151 | } 152 | 153 | bool MAX30100::detectPulse(float sensor_value) 154 | { 155 | static float prev_sensor_value = 0; 156 | static uint8_t values_went_down = 0; 157 | static uint32_t currentBeat = 0; 158 | static uint32_t lastBeat = 0; 159 | 160 | if(sensor_value > PULSE_MAX_THRESHOLD) 161 | { 162 | currentPulseDetectorState = PULSE_IDLE; 163 | prev_sensor_value = 0; 164 | lastBeat = 0; 165 | currentBeat = 0; 166 | values_went_down = 0; 167 | lastBeatThreshold = 0; 168 | return false; 169 | } 170 | 171 | switch(currentPulseDetectorState) 172 | { 173 | case PULSE_IDLE: 174 | if(sensor_value >= PULSE_MIN_THRESHOLD) { 175 | currentPulseDetectorState = PULSE_TRACE_UP; 176 | values_went_down = 0; 177 | } 178 | break; 179 | 180 | case PULSE_TRACE_UP: 181 | if(sensor_value > prev_sensor_value) 182 | { 183 | currentBeat = millis(); 184 | lastBeatThreshold = sensor_value; 185 | } 186 | else 187 | { 188 | 189 | if(debug == true) 190 | { 191 | Serial.print("Peak reached: "); 192 | Serial.print(sensor_value); 193 | Serial.print(" "); 194 | Serial.println(prev_sensor_value); 195 | } 196 | 197 | uint32_t beatDuration = currentBeat - lastBeat; 198 | lastBeat = currentBeat; 199 | 200 | float rawBPM = 0; 201 | if(beatDuration > 0) 202 | rawBPM = 60000.0 / (float)beatDuration; 203 | if(debug == true) 204 | Serial.println(rawBPM); 205 | 206 | //This method sometimes glitches, it's better to go through whole moving average everytime 207 | //IT's a neat idea to optimize the amount of work for moving avg. but while placing, removing finger it can screw up 208 | //valuesBPMSum -= valuesBPM[bpmIndex]; 209 | //valuesBPM[bpmIndex] = rawBPM; 210 | //valuesBPMSum += valuesBPM[bpmIndex]; 211 | 212 | valuesBPM[bpmIndex] = rawBPM; 213 | valuesBPMSum = 0; 214 | for(int i=0; i= RED_LED_CURRENT_ADJUSTMENT_MS) 273 | { 274 | //Serial.println( redLedDC - IRLedDC ); 275 | if( IRLedDC - redLedDC > MAGIC_ACCEPTABLE_INTENSITY_DIFF && redLEDCurrent < MAX30100_LED_CURRENT_50MA) 276 | { 277 | redLEDCurrent++; 278 | setLEDCurrents( redLEDCurrent, IrLedCurrent ); 279 | if(debug == true) 280 | Serial.println("RED LED Current +"); 281 | } 282 | else if(redLedDC - IRLedDC > MAGIC_ACCEPTABLE_INTENSITY_DIFF && redLEDCurrent > 0) 283 | { 284 | redLEDCurrent--; 285 | setLEDCurrents( redLEDCurrent, IrLedCurrent ); 286 | if(debug == true) 287 | Serial.println("RED LED Current -"); 288 | } 289 | 290 | lastREDLedCurrentCheck = millis(); 291 | } 292 | } 293 | 294 | 295 | // Writes val to address register on device 296 | void MAX30100::writeRegister(byte address, byte val) 297 | { 298 | Wire.beginTransmission(MAX30100_DEVICE); // start transmission to device 299 | Wire.write(address); // send register address 300 | Wire.write(val); // send value to write 301 | Wire.endTransmission(); // end transmission 302 | } 303 | 304 | uint8_t MAX30100::readRegister(uint8_t address) 305 | { 306 | Wire.beginTransmission(MAX30100_DEVICE); 307 | Wire.write(address); 308 | Wire.endTransmission(false); 309 | Wire.requestFrom(MAX30100_DEVICE, 1); 310 | 311 | return Wire.read(); 312 | } 313 | 314 | // Reads num bytes starting from address register on device in to _buff array 315 | void MAX30100::readFrom(byte address, int num, byte _buff[]) 316 | { 317 | Wire.beginTransmission(MAX30100_DEVICE); // start transmission to device 318 | Wire.write(address); // sends address to read from 319 | Wire.endTransmission(false); // end transmission 320 | 321 | Wire.requestFrom(MAX30100_DEVICE, num); // request 6 bytes from device Registers: DATAX0, DATAX1, DATAY0, DATAY1, DATAZ0, DATAZ1 322 | 323 | int i = 0; 324 | while(Wire.available()) // device may send less than requested (abnormal) 325 | { 326 | _buff[i++] = Wire.read(); // receive a byte 327 | } 328 | 329 | Wire.endTransmission(); // end transmission 330 | } 331 | 332 | void MAX30100::setMode(Mode mode) 333 | { 334 | byte currentModeReg = readRegister( MAX30100_MODE_CONF ); 335 | writeRegister( MAX30100_MODE_CONF, (currentModeReg & 0xF8) | mode ); 336 | } 337 | 338 | void MAX30100::setHighresModeEnabled(bool enabled) 339 | { 340 | uint8_t previous = readRegister(MAX30100_SPO2_CONF); 341 | if (enabled) { 342 | writeRegister(MAX30100_SPO2_CONF, previous | MAX30100_SPO2_HI_RES_EN); 343 | } else { 344 | writeRegister(MAX30100_SPO2_CONF, previous & ~MAX30100_SPO2_HI_RES_EN); 345 | } 346 | } 347 | 348 | void MAX30100::setSamplingRate(SamplingRate rate) 349 | { 350 | byte currentSpO2Reg = readRegister( MAX30100_SPO2_CONF ); 351 | writeRegister( MAX30100_SPO2_CONF, ( currentSpO2Reg & 0xE3 ) | (rate<<2) ); 352 | } 353 | 354 | void MAX30100::setLEDPulseWidth(LEDPulseWidth pw) 355 | { 356 | byte currentSpO2Reg = readRegister( MAX30100_SPO2_CONF ); 357 | writeRegister( MAX30100_SPO2_CONF, ( currentSpO2Reg & 0xFC ) | pw ); 358 | } 359 | 360 | void MAX30100::setLEDCurrents( byte redLedCurrent, byte IRLedCurrent ) 361 | { 362 | writeRegister( MAX30100_LED_CONF, (redLedCurrent << 4) | IRLedCurrent ); 363 | } 364 | 365 | float MAX30100::readTemperature() 366 | { 367 | byte currentModeReg = readRegister( MAX30100_MODE_CONF ); 368 | writeRegister( MAX30100_MODE_CONF, currentModeReg | MAX30100_MODE_TEMP_EN ); 369 | 370 | delay(100); //This can be changed to a while loop, there is an interrupt flag for when temperature has been read. 371 | 372 | int8_t temp = (int8_t)readRegister( MAX30100_TEMP_INT ); 373 | float tempFraction = (float)readRegister( MAX30100_TEMP_FRACTION ) * 0.0625; 374 | 375 | return (float)temp + tempFraction; 376 | } 377 | 378 | fifo_t MAX30100::readFIFO() 379 | { 380 | fifo_t result; 381 | 382 | byte buffer[4]; 383 | readFrom( MAX30100_FIFO_DATA, 4, buffer ); 384 | result.rawIR = (buffer[0] << 8) | buffer[1]; 385 | result.rawRed = (buffer[2] << 8) | buffer[3]; 386 | 387 | return result; 388 | } 389 | 390 | dcFilter_t MAX30100::dcRemoval(float x, float prev_w, float alpha) 391 | { 392 | dcFilter_t filtered; 393 | filtered.w = x + alpha * prev_w; 394 | filtered.result = filtered.w - prev_w; 395 | 396 | return filtered; 397 | } 398 | 399 | void MAX30100::lowPassButterworthFilter( float x, butterworthFilter_t * filterResult ) 400 | { 401 | filterResult->v[0] = filterResult->v[1]; 402 | 403 | //Fs = 100Hz and Fc = 10Hz 404 | filterResult->v[1] = (2.452372752527856026e-1 * x) + (0.50952544949442879485 * filterResult->v[0]); 405 | 406 | //Fs = 100Hz and Fc = 4Hz 407 | //filterResult->v[1] = (1.367287359973195227e-1 * x) + (0.72654252800536101020 * filterResult->v[0]); //Very precise butterworth filter 408 | 409 | filterResult->result = filterResult->v[0] + filterResult->v[1]; 410 | } 411 | 412 | float MAX30100::meanDiff(float M, meanDiffFilter_t* filterValues) 413 | { 414 | float avg = 0; 415 | 416 | filterValues->sum -= filterValues->values[filterValues->index]; 417 | filterValues->values[filterValues->index] = M; 418 | filterValues->sum += filterValues->values[filterValues->index]; 419 | 420 | filterValues->index++; 421 | filterValues->index = filterValues->index % MEAN_FILTER_SIZE; 422 | 423 | if(filterValues->count < MEAN_FILTER_SIZE) 424 | filterValues->count++; 425 | 426 | avg = filterValues->sum / filterValues->count; 427 | return avg - M; 428 | } 429 | 430 | void MAX30100::printRegisters() 431 | { 432 | Serial.println(readRegister(MAX30100_INT_STATUS), HEX); 433 | Serial.println(readRegister(MAX30100_INT_ENABLE), HEX); 434 | Serial.println(readRegister(MAX30100_FIFO_WRITE), HEX); 435 | Serial.println(readRegister(MAX30100_FIFO_OVERFLOW_COUNTER), HEX); 436 | Serial.println(readRegister(MAX30100_FIFO_READ), HEX); 437 | Serial.println(readRegister(MAX30100_FIFO_DATA), HEX); 438 | Serial.println(readRegister(MAX30100_MODE_CONF), HEX); 439 | Serial.println(readRegister(MAX30100_SPO2_CONF), HEX); 440 | Serial.println(readRegister(MAX30100_LED_CONF), HEX); 441 | Serial.println(readRegister(MAX30100_TEMP_INT), HEX); 442 | Serial.println(readRegister(MAX30100_TEMP_FRACTION), HEX); 443 | Serial.println(readRegister(MAX30100_REV_ID), HEX); 444 | Serial.println(readRegister(MAX30100_PART_ID), HEX); 445 | } 446 | 447 | -------------------------------------------------------------------------------- /MAX30100.h: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2017 Raivis Strogonovs (https://morf.lv) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | 24 | 25 | */ 26 | 27 | #ifndef MAX30100_H 28 | #define MAX30100_H 29 | 30 | #include 31 | #include 32 | #include 33 | /*----------------------------------------------*/ 34 | /* Config defines, you can tailor to your needs */ 35 | /*----------------------------------------------*/ 36 | 37 | /* MAX30100 parameters */ 38 | #define DEFAULT_OPERATING_MODE MAX30100_MODE_SPO2_HR 39 | /*!!!IMPORTANT 40 | * You can't just throw these two values at random. Check Check table 8 in datasheet on page 19. 41 | * 100hz + 1600us is max for that resolution 42 | */ 43 | #define DEFAULT_SAMPLING_RATE MAX30100_SAMPLING_RATE_100HZ 44 | #define DEFAULT_LED_PULSE_WIDTH MAX30100_PULSE_WIDTH_1600US_ADC_16 45 | 46 | #define DEFAULT_IR_LED_CURRENT MAX30100_LED_CURRENT_50MA 47 | #define STARTING_RED_LED_CURRENT MAX30100_LED_CURRENT_27_1MA 48 | 49 | 50 | /* Adjust RED LED current balancing*/ 51 | #define MAGIC_ACCEPTABLE_INTENSITY_DIFF 65000 52 | #define RED_LED_CURRENT_ADJUSTMENT_MS 500 53 | 54 | /* SaO2 parameters */ 55 | #define RESET_SPO2_EVERY_N_PULSES 4 56 | 57 | /* Filter parameters */ 58 | #define ALPHA 0.95 //dc filter alpha value 59 | #define MEAN_FILTER_SIZE 15 60 | 61 | /* Pulse detection parameters */ 62 | #define PULSE_MIN_THRESHOLD 100 //300 is good for finger, but for wrist you need like 20, and there is shitloads of noise 63 | #define PULSE_MAX_THRESHOLD 2000 64 | #define PULSE_GO_DOWN_THRESHOLD 1 65 | 66 | #define PULSE_BPM_SAMPLE_SIZE 10 //Moving average size 67 | 68 | 69 | 70 | 71 | 72 | /* Enums, data structures and typdefs. DO NOT EDIT */ 73 | struct pulseoxymeter_t { 74 | bool pulseDetected; 75 | float heartBPM; 76 | 77 | float irCardiogram; 78 | 79 | float irDcValue; 80 | float redDcValue; 81 | 82 | float SaO2; 83 | 84 | uint32_t lastBeatThreshold; 85 | 86 | float dcFilteredIR; 87 | float dcFilteredRed; 88 | }; 89 | 90 | typedef enum PulseStateMachine { 91 | PULSE_IDLE, 92 | PULSE_TRACE_UP, 93 | PULSE_TRACE_DOWN 94 | } PulseStateMachine; 95 | 96 | struct fifo_t { 97 | uint16_t rawIR; 98 | uint16_t rawRed; 99 | }; 100 | 101 | struct dcFilter_t { 102 | float w; 103 | float result; 104 | }; 105 | 106 | struct butterworthFilter_t 107 | { 108 | float v[2]; 109 | float result; 110 | }; 111 | 112 | struct meanDiffFilter_t 113 | { 114 | float values[MEAN_FILTER_SIZE]; 115 | byte index; 116 | float sum; 117 | byte count; 118 | }; 119 | 120 | /* MAX30100 register and bit defines, DO NOT EDIT */ 121 | #define MAX30100_DEVICE 0x57 122 | 123 | //Part ID Registers 124 | #define MAX30100_REV_ID 0xFE 125 | #define MAX30100_PART_ID 0xFF 126 | 127 | //status registers 128 | #define MAX30100_INT_STATUS 0x00 129 | #define MAX30100_INT_ENABLE 0x01 130 | 131 | //Fifo registers 132 | #define MAX30100_FIFO_WRITE 0x02 133 | #define MAX30100_FIFO_OVERFLOW_COUNTER 0x03 134 | #define MAX30100_FIFO_READ 0x04 135 | #define MAX30100_FIFO_DATA 0x05 136 | 137 | //Config registers 138 | #define MAX30100_MODE_CONF 0x06 139 | #define MAX30100_SPO2_CONF 0x07 140 | #define MAX30100_LED_CONF 0x09 141 | 142 | //Temperature registers 143 | #define MAX30100_TEMP_INT 0x16 144 | #define MAX30100_TEMP_FRACTION 0x17 145 | 146 | 147 | //Bit defines MODE Regsiter 148 | #define MAX30100_MODE_SHDN (1<<7) 149 | #define MAX30100_MODE_RESET (1<<6) 150 | #define MAX30100_MODE_TEMP_EN (1<<3) 151 | 152 | typedef enum Mode { 153 | MAX30100_MODE_HR_ONLY = 0x02, 154 | MAX30100_MODE_SPO2_HR = 0x03 155 | } Mode; 156 | 157 | //Bit defines SpO2 register 158 | #define MAX30100_SPO2_HI_RES_EN (1 << 6) 159 | typedef enum SamplingRate { 160 | MAX30100_SAMPLING_RATE_50HZ = 0x00, 161 | MAX30100_SAMPLING_RATE_100HZ = 0x01, 162 | MAX30100_SAMPLING_RATE_167HZ = 0x02, 163 | MAX30100_SAMPLING_RATE_200HZ = 0x03, 164 | MAX30100_SAMPLING_RATE_400HZ = 0x04, 165 | MAX30100_SAMPLING_RATE_600HZ = 0x05, 166 | MAX30100_SAMPLING_RATE_800HZ = 0x06, 167 | MAX30100_SAMPLING_RATE_1000HZ = 0x07 168 | } SamplingRate; 169 | 170 | typedef enum LEDPulseWidth { 171 | MAX30100_PULSE_WIDTH_200US_ADC_13 = 0x00, 172 | MAX30100_PULSE_WIDTH_400US_ADC_14 = 0x01, 173 | MAX30100_PULSE_WIDTH_800US_ADC_15 = 0x02, 174 | MAX30100_PULSE_WIDTH_1600US_ADC_16 = 0x03, 175 | } LEDPulseWidth; 176 | 177 | typedef enum LEDCurrent { 178 | MAX30100_LED_CURRENT_0MA = 0x00, 179 | MAX30100_LED_CURRENT_4_4MA = 0x01, 180 | MAX30100_LED_CURRENT_7_6MA = 0x02, 181 | MAX30100_LED_CURRENT_11MA = 0x03, 182 | MAX30100_LED_CURRENT_14_2MA = 0x04, 183 | MAX30100_LED_CURRENT_17_4MA = 0x05, 184 | MAX30100_LED_CURRENT_20_8MA = 0x06, 185 | MAX30100_LED_CURRENT_24MA = 0x07, 186 | MAX30100_LED_CURRENT_27_1MA = 0x08, 187 | MAX30100_LED_CURRENT_30_6MA = 0x09, 188 | MAX30100_LED_CURRENT_33_8MA = 0x0A, 189 | MAX30100_LED_CURRENT_37MA = 0x0B, 190 | MAX30100_LED_CURRENT_40_2MA = 0x0C, 191 | MAX30100_LED_CURRENT_43_6MA = 0x0D, 192 | MAX30100_LED_CURRENT_46_8MA = 0x0E, 193 | MAX30100_LED_CURRENT_50MA = 0x0F 194 | } LEDCurrent; 195 | 196 | class MAX30100 197 | { 198 | public: 199 | MAX30100( Mode mode = DEFAULT_OPERATING_MODE, 200 | SamplingRate samplingRate = DEFAULT_SAMPLING_RATE, 201 | LEDPulseWidth pulseWidth = DEFAULT_LED_PULSE_WIDTH, 202 | LEDCurrent IrLedCurrent = DEFAULT_IR_LED_CURRENT, 203 | bool highResMode = true, 204 | bool debug = false 205 | ); 206 | 207 | pulseoxymeter_t update(); 208 | 209 | void setMode(Mode mode); 210 | void setHighresModeEnabled(bool enabled); 211 | void setSamplingRate(SamplingRate rate); 212 | void setLEDPulseWidth(LEDPulseWidth pw); 213 | void setLEDCurrents( byte redLedCurrent, byte IRLedCurrent ); 214 | float readTemperature(); 215 | fifo_t readFIFO(); 216 | void printRegisters(); 217 | 218 | dcFilter_t dcRemoval(float x, float prev_w, float alpha); 219 | void lowPassButterworthFilter( float x, butterworthFilter_t * filterResult ); 220 | float meanDiff(float M, meanDiffFilter_t* filterValues); 221 | 222 | private: 223 | bool detectPulse(float sensor_value); 224 | void balanceIntesities( float redLedDC, float IRLedDC ); 225 | 226 | void writeRegister(byte address, byte val); 227 | uint8_t readRegister(uint8_t address); 228 | void readFrom(byte address, int num, byte _buff[]); 229 | 230 | private: 231 | bool debug; 232 | 233 | uint8_t redLEDCurrent; 234 | float lastREDLedCurrentCheck; 235 | 236 | uint8_t currentPulseDetectorState; 237 | float currentBPM; 238 | float valuesBPM[PULSE_BPM_SAMPLE_SIZE]; 239 | float valuesBPMSum; 240 | uint8_t valuesBPMCount; 241 | uint8_t bpmIndex; 242 | uint32_t lastBeatThreshold; 243 | 244 | fifo_t prevFifo; 245 | 246 | dcFilter_t dcFilterIR; 247 | dcFilter_t dcFilterRed; 248 | butterworthFilter_t lpbFilterIR; 249 | meanDiffFilter_t meanDiffIR; 250 | 251 | float irACValueSqSum; 252 | float redACValueSqSum; 253 | uint16_t samplesRecorded; 254 | uint16_t pulsesDetected; 255 | float currentSaO2Value; 256 | 257 | LEDCurrent IrLedCurrent; 258 | }; 259 | 260 | #endif 261 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MAX30100 2 | Driver for MAX30100 using arduino 3 | 4 | 5 | This code is part of tutorial on my blog: https://morf.lv/implementing-pulse-oximeter-using-max30100 6 | 7 | # Sample Usage 8 | ``` 9 | #include "MAX30100.h" 10 | 11 | MAX30100* pulseOxymeter; 12 | 13 | void setup() { 14 | Wire.begin(); 15 | Serial.begin(115200); 16 | Serial.println("Pulse oxymeter test!"); 17 | 18 | //pulseOxymeter = new MAX30100( DEFAULT_OPERATING_MODE, DEFAULT_SAMPLING_RATE, DEFAULT_LED_PULSE_WIDTH, DEFAULT_IR_LED_CURRENT, true, true ); 19 | pulseOxymeter = new MAX30100(); 20 | pinMode(2, OUTPUT); 21 | 22 | } 23 | 24 | void loop() { 25 | //You have to call update with frequency at least 37Hz. But the closer you call it to 100Hz the better, the filter will work. 26 | pulseoxymeter_t result = pulseOxymeter->update(); 27 | 28 | 29 | if( result.pulseDetected == true ) 30 | { 31 | Serial.println("BEAT"); 32 | 33 | Serial.print( "BPM: " ); 34 | Serial.print( result.heartBPM ); 35 | Serial.print( " | " ); 36 | 37 | Serial.print( "SaO2: " ); 38 | Serial.print( result.SaO2 ); 39 | Serial.println( "%" ); 40 | } 41 | } 42 | ``` 43 | -------------------------------------------------------------------------------- /max30100.ino: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | MIT License 4 | 5 | Copyright (c) 2017 Raivis Strogonovs (https://morf.lv) 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | 25 | */ 26 | #include 27 | #include 28 | #include 29 | 30 | #include "MAX30100.h" 31 | 32 | MAX30100* pulseOxymeter; 33 | 34 | void setup() { 35 | Wire.begin(); 36 | Serial.begin(115200); 37 | Serial.println("Pulse oxymeter test!"); 38 | 39 | //pulseOxymeter = new MAX30100( DEFAULT_OPERATING_MODE, DEFAULT_SAMPLING_RATE, DEFAULT_LED_PULSE_WIDTH, DEFAULT_IR_LED_CURRENT, true, true ); 40 | pulseOxymeter = new MAX30100(); 41 | pinMode(2, OUTPUT); 42 | 43 | //pulseOxymeter->printRegisters(); 44 | } 45 | 46 | void loop() { 47 | //return; 48 | //You have to call update with frequency at least 37Hz. But the closer you call it to 100Hz the better, the filter will work. 49 | pulseoxymeter_t result = pulseOxymeter->update(); 50 | 51 | 52 | if( result.pulseDetected == true ) 53 | { 54 | Serial.println("BEAT"); 55 | 56 | Serial.print( "BPM: " ); 57 | Serial.print( result.heartBPM ); 58 | Serial.print( " | " ); 59 | 60 | Serial.print( "SaO2: " ); 61 | Serial.print( result.SaO2 ); 62 | Serial.println( "%" ); 63 | 64 | Serial.print("{P2|BPM|255,40,0|"); 65 | Serial.print(result.heartBPM); 66 | Serial.print("|SaO2|0,0,255|"); 67 | Serial.print(result.SaO2); 68 | Serial.println("}"); 69 | } 70 | 71 | 72 | 73 | //These are special packets for FlexiPlot plotting tool 74 | Serial.print("{P0|IR|0,0,255|"); 75 | Serial.print(result.dcFilteredIR); 76 | Serial.print("|RED|255,0,0|"); 77 | Serial.print(result.dcFilteredRed); 78 | Serial.println("}"); 79 | 80 | Serial.print("{P1|RED|255,0,255|"); 81 | Serial.print(result.irCardiogram); 82 | Serial.print("|BEAT|0,0,255|"); 83 | Serial.print(result.lastBeatThreshold); 84 | Serial.println("}"); 85 | 86 | 87 | 88 | delay(10); 89 | 90 | //Basic way of determening execution of the loop via oscoliscope 91 | digitalWrite( 2, !digitalRead(2) ); 92 | } 93 | 94 | 95 | 96 | 97 | --------------------------------------------------------------------------------