├── .gitattributes ├── .gitignore ├── LICENSE.md ├── README.md ├── examples ├── Example1_Basic_Readings │ └── Example1_Basic_Readings.ino ├── Example2_Presence_Sensing │ └── Example2_Presence_Sensing.ino ├── Example3_Temperature_Sense │ └── Example3_Temperature_Sense.ino ├── Example4_HeartBeat_Plotter │ └── Example4_HeartBeat_Plotter.ino ├── Example5_HeartRate │ ├── Example5_HeartRate.ino │ └── License.ino ├── Example6_FIFO_Readings │ └── Example6_FIFO_Readings.ino ├── Example7_Basic_Readings_Interrupts │ └── Example7_Basic_Readings_Interrupts.ino ├── Example8_SPO2 │ ├── Example8_SPO2.ino │ └── License.ino └── Example9_RateTesting │ └── Example9_RateTesting.ino ├── extras ├── HeartBeat.jpg └── HeartBeat1.jpg ├── keywords.txt ├── library.properties └── src ├── MAX30105.cpp ├── MAX30105.h ├── heartRate.cpp ├── heartRate.h ├── spo2_algorithm.cpp └── spo2_algorithm.h /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ################# 2 | ## SparkFun Useful stuff 3 | ################# 4 | 5 | ## AVR Development 6 | *.eep 7 | *.elf 8 | *.lst 9 | *.lss 10 | *.sym 11 | *.d 12 | *.o 13 | *.srec 14 | *.map 15 | 16 | ## Notepad++ backup files 17 | *.bak 18 | 19 | ## BOM files 20 | *bom* 21 | 22 | ################# 23 | ## Eclipse 24 | ################# 25 | 26 | *.pydevproject 27 | .project 28 | .metadata 29 | bin/ 30 | tmp/ 31 | *.tmp 32 | *.bak 33 | *.swp 34 | *~.nib 35 | local.properties 36 | .classpath 37 | .settings/ 38 | .loadpath 39 | 40 | # External tool builders 41 | .externalToolBuilders/ 42 | 43 | # Locally stored "Eclipse launch configurations" 44 | *.launch 45 | 46 | # CDT-specific 47 | .cproject 48 | 49 | # PDT-specific 50 | .buildpath 51 | 52 | 53 | ############# 54 | ## Eagle 55 | ############# 56 | 57 | # Ignore the board and schematic backup files and lock files 58 | *.b#? 59 | *.s#? 60 | *.l#? 61 | *.lck 62 | 63 | 64 | ################# 65 | ## Visual Studio 66 | ################# 67 | 68 | ## Ignore Visual Studio temporary files, build results, and 69 | ## files generated by popular Visual Studio add-ons. 70 | 71 | # User-specific files 72 | *.suo 73 | *.user 74 | *.sln.docstates 75 | 76 | # Build results 77 | [Dd]ebug/ 78 | [Rr]elease/ 79 | *_i.c 80 | *_p.c 81 | *.ilk 82 | *.meta 83 | *.obj 84 | *.pch 85 | *.pdb 86 | *.pgc 87 | *.pgd 88 | *.rsp 89 | *.sbr 90 | *.tlb 91 | *.tli 92 | *.tlh 93 | *.tmp 94 | *.vspscc 95 | .builds 96 | *.dotCover 97 | 98 | ## TODO: If you have NuGet Package Restore enabled, uncomment this 99 | #packages/ 100 | 101 | # Visual C++ cache files 102 | ipch/ 103 | *.aps 104 | *.ncb 105 | *.opensdf 106 | *.sdf 107 | 108 | # Visual Studio profiler 109 | *.psess 110 | *.vsp 111 | 112 | # ReSharper is a .NET coding add-in 113 | _ReSharper* 114 | 115 | # Installshield output folder 116 | [Ee]xpress 117 | 118 | # DocProject is a documentation generator add-in 119 | DocProject/buildhelp/ 120 | DocProject/Help/*.HxT 121 | DocProject/Help/*.HxC 122 | DocProject/Help/*.hhc 123 | DocProject/Help/*.hhk 124 | DocProject/Help/*.hhp 125 | DocProject/Help/Html2 126 | DocProject/Help/html 127 | 128 | # Click-Once directory 129 | publish 130 | 131 | # Others 132 | [Bb]in 133 | [Oo]bj 134 | sql 135 | TestResults 136 | *.Cache 137 | ClientBin 138 | stylecop.* 139 | ~$* 140 | *.dbmdl 141 | Generated_Code #added for RIA/Silverlight projects 142 | 143 | # Backup & report files from converting an old project file to a newer 144 | # Visual Studio version. Backup files are not needed, because we have git ;-) 145 | _UpgradeReport_Files/ 146 | Backup*/ 147 | UpgradeLog*.XML 148 | 149 | 150 | ############ 151 | ## Windows 152 | ############ 153 | 154 | # Windows image file caches 155 | Thumbs.db 156 | 157 | # Folder config file 158 | Desktop.ini 159 | 160 | 161 | ############# 162 | ## Mac OS 163 | ############# 164 | 165 | .DS_Store 166 | 167 | 168 | ############# 169 | ## Linux 170 | ############# 171 | 172 | # backup files (*.bak on Win) 173 | *~ 174 | 175 | 176 | ############# 177 | ## Python 178 | ############# 179 | 180 | *.py[co] 181 | 182 | # Packages 183 | *.egg 184 | *.egg-info 185 | dist 186 | build 187 | eggs 188 | parts 189 | bin 190 | var 191 | sdist 192 | develop-eggs 193 | .installed.cfg 194 | 195 | # Installer logs 196 | pip-log.txt 197 | 198 | # Unit test / coverage reports 199 | .coverage 200 | .tox 201 | 202 | #Translations 203 | *.mo 204 | 205 | #Mr Developer 206 | .mr.developer.cfg 207 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | SparkFun License Information 2 | ============================ 3 | 4 | SparkFun uses two different licenses for our files — one for hardware and one for code. 5 | 6 | Hardware 7 | --------- 8 | 9 | **SparkFun hardware is released under [Creative Commons Share-alike 4.0 International](http://creativecommons.org/licenses/by-sa/4.0/).** 10 | 11 | Note: This is a human-readable summary of (and not a substitute for) the [license](http://creativecommons.org/licenses/by-sa/4.0/legalcode). 12 | 13 | You are free to: 14 | 15 | Share — copy and redistribute the material in any medium or format 16 | Adapt — remix, transform, and build upon the material 17 | for any purpose, even commercially. 18 | The licensor cannot revoke these freedoms as long as you follow the license terms. 19 | Under the following terms: 20 | 21 | Attribution — You must give appropriate credit, provide a link to the license, and indicate if changes were made. You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use. 22 | ShareAlike — If you remix, transform, or build upon the material, you must distribute your contributions under the same license as the original. 23 | No additional restrictions — You may not apply legal terms or technological measures that legally restrict others from doing anything the license permits. 24 | Notices: 25 | 26 | You do not have to comply with the license for elements of the material in the public domain or where your use is permitted by an applicable exception or limitation. 27 | No warranties are given. The license may not give you all of the permissions necessary for your intended use. For example, other rights such as publicity, privacy, or moral rights may limit how you use the material. 28 | 29 | 30 | Code 31 | -------- 32 | 33 | **SparkFun code, firmware, and software is released under the MIT License(http://opensource.org/licenses/MIT).** 34 | 35 | The MIT License (MIT) 36 | 37 | Copyright (c) 2016 SparkFun Electronics 38 | 39 | Permission is hereby granted, free of charge, to any person obtaining a copy 40 | of this software and associated documentation files (the "Software"), to deal 41 | in the Software without restriction, including without limitation the rights 42 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 43 | copies of the Software, and to permit persons to whom the Software is 44 | furnished to do so, subject to the following conditions: 45 | 46 | The above copyright notice and this permission notice shall be included in all 47 | copies or substantial portions of the Software. 48 | 49 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 50 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 51 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 52 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 53 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 54 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 55 | SOFTWARE. 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | SparkFun MAX301x Particle Sensor Library 2 | =========================================================== 3 | 4 | ![SparkFun Reflectance, Particle, and Pulse Ox sensor - MAX30105](https://cdn.sparkfun.com/assets/parts/1/1/8/7/4/14045-02.jpg) 5 | 6 | [*SparkFun Reflectance, Particle, and Pulse Ox sensor - MAX30105 (SEN-14045)*](https://www.sparkfun.com/products/14045) 7 | 8 | ![A graph of a heartbeat](https://raw.githubusercontent.com/sparkfun/SparkFun_MAX3010x_Sensor_Library/master/extras/HeartBeat1.jpg) 9 | 10 | That's my heartbeat! 11 | 12 | This is a breadboard friendly breakout board for the Maxim MAX30105 reflectance, particle, and pulse ox sensor. 13 | 14 | This library should work with other MAX3010x sensors including the MAX30102, MAX30101, and MAX30100. 15 | 16 | Library written by Nathan Seidle ([SparkFun](http://www.sparkfun.com)) and Peter Jansen ([Open Sensing Lab](https://github.com/opensensinglab)). 17 | 18 | Repository Contents 19 | ------------------- 20 | 21 | * **/examples** - Example sketches for the library (.ino). Run these from the Arduino IDE. 22 | * **/src** - Source files for the library (.cpp, .h). 23 | * **keywords.txt** - Keywords from this library that will be highlighted in the Arduino IDE. 24 | * **library.properties** - General library properties for the Arduino package manager. 25 | 26 | Documentation 27 | -------------- 28 | 29 | * **[Installing an Arduino Library Guide](https://learn.sparkfun.com/tutorials/installing-an-arduino-library)** - Basic information on how to install an Arduino library. 30 | * **[Product Repository (MAX30105)](https://github.com/sparkfun/MAX30105_Particle_Sensor_Breakout)** - Main repository (including hardware files) for the MAX30105 particle sensor breakout board. 31 | * **[Product Repository (MAX30101)](https://github.com/sparkfun/SparkFun_Photodetector_Breakout_MAX30101_Qwiic)** - Main repository (including hardware files) for the SparkFun Photodetector (MAX30101) - Qwiic. 32 | * **[Hookup Guide (MAX30105)](https://learn.sparkfun.com/tutorials/max30105-particle-and-pulse-ox-sensor-hookup-guide)** - Basic hookup guide for the MAX30105 Particle Sensor breakout board. 33 | * **[Hookup Guide (MAX30101)](https://learn.sparkfun.com/tutorials/sparkfun-photodetector-max30101-hookup-guide)** - Basic hookup guide for the SparkFun Photodetector (MAX30101) - Qwiic. 34 | 35 | License Information 36 | ------------------- 37 | 38 | This product is _**open source**_! 39 | 40 | Various bits of the code have different licenses applied. Anything SparkFun wrote is beerware; if you see me (or any other SparkFun employee) at the local, and you've found our code helpful, please buy us a round! Anything Maxim wrote has its own license. Anything that was co-writing with Peter Jansen is BSD. 41 | 42 | Please use, reuse, and modify these files as you see fit. Please maintain attribution to SparkFun Electronics and release anything derivative under the same license. 43 | 44 | Distributed as-is; no warranty is given. 45 | 46 | - Your friends at SparkFun. 47 | -------------------------------------------------------------------------------- /examples/Example1_Basic_Readings/Example1_Basic_Readings.ino: -------------------------------------------------------------------------------- 1 | /* 2 | MAX30105 Breakout: Output all the raw Red/IR/Green readings 3 | By: Nathan Seidle @ SparkFun Electronics 4 | Date: October 2nd, 2016 5 | https://github.com/sparkfun/MAX30105_Breakout 6 | 7 | Outputs all Red/IR/Green values. 8 | 9 | Hardware Connections (Breakoutboard to Arduino): 10 | -5V = 5V (3.3V is allowed) 11 | -GND = GND 12 | -SDA = A4 (or SDA) 13 | -SCL = A5 (or SCL) 14 | -INT = Not connected 15 | 16 | The MAX30105 Breakout can handle 5V or 3.3V I2C logic. We recommend powering the board with 5V 17 | but it will also run at 3.3V. 18 | 19 | This code is released under the [MIT License](http://opensource.org/licenses/MIT). 20 | */ 21 | 22 | #include 23 | #include "MAX30105.h" 24 | 25 | MAX30105 particleSensor; 26 | 27 | #define debug Serial //Uncomment this line if you're using an Uno or ESP 28 | //#define debug SerialUSB //Uncomment this line if you're using a SAMD21 29 | 30 | void setup() 31 | { 32 | debug.begin(9600); 33 | debug.println("MAX30105 Basic Readings Example"); 34 | 35 | // Initialize sensor 36 | if (particleSensor.begin() == false) 37 | { 38 | debug.println("MAX30105 was not found. Please check wiring/power. "); 39 | while (1); 40 | } 41 | 42 | particleSensor.setup(); //Configure sensor. Use 6.4mA for LED drive 43 | } 44 | 45 | void loop() 46 | { 47 | debug.print(" R["); 48 | debug.print(particleSensor.getRed()); 49 | debug.print("] IR["); 50 | debug.print(particleSensor.getIR()); 51 | debug.print("] G["); 52 | debug.print(particleSensor.getGreen()); 53 | debug.print("]"); 54 | 55 | debug.println(); 56 | } 57 | -------------------------------------------------------------------------------- /examples/Example2_Presence_Sensing/Example2_Presence_Sensing.ino: -------------------------------------------------------------------------------- 1 | /* 2 | MAX30105 Breakout: Take IR reading to sense presence 3 | By: Nathan Seidle @ SparkFun Electronics 4 | Date: October 2nd, 2016 5 | https://github.com/sparkfun/MAX30105_Breakout 6 | 7 | This takes an average reading at power up and if the reading changes more than 100 8 | then print 'Something is there!'. 9 | 10 | Hardware Connections (Breakoutboard to Arduino): 11 | -5V = 5V (3.3V is allowed) 12 | -GND = GND 13 | -SDA = A4 (or SDA) 14 | -SCL = A5 (or SCL) 15 | -INT = Not connected 16 | 17 | The MAX30105 Breakout can handle 5V or 3.3V I2C logic. We recommend powering the board with 5V 18 | but it will also run at 3.3V. 19 | 20 | */ 21 | 22 | #include 23 | #include "MAX30105.h" 24 | 25 | MAX30105 particleSensor; 26 | 27 | long samplesTaken = 0; //Counter for calculating the Hz or read rate 28 | long unblockedValue; //Average IR at power up 29 | long startTime; //Used to calculate measurement rate 30 | 31 | void setup() 32 | { 33 | Serial.begin(9600); 34 | Serial.println("MAX30105 Presence Sensing Example"); 35 | 36 | // Initialize sensor 37 | if (particleSensor.begin(Wire, I2C_SPEED_FAST) == false) //Use default I2C port, 400kHz speed 38 | { 39 | Serial.println("MAX30105 was not found. Please check wiring/power. "); 40 | while (1); 41 | } 42 | 43 | //Setup to sense up to 18 inches, max LED brightness 44 | byte ledBrightness = 0xFF; //Options: 0=Off to 255=50mA 45 | byte sampleAverage = 4; //Options: 1, 2, 4, 8, 16, 32 46 | byte ledMode = 2; //Options: 1 = Red only, 2 = Red + IR, 3 = Red + IR + Green 47 | int sampleRate = 400; //Options: 50, 100, 200, 400, 800, 1000, 1600, 3200 48 | int pulseWidth = 411; //Options: 69, 118, 215, 411 49 | int adcRange = 2048; //Options: 2048, 4096, 8192, 16384 50 | 51 | particleSensor.setup(ledBrightness, sampleAverage, ledMode, sampleRate, pulseWidth, adcRange); //Configure sensor with these settings 52 | 53 | particleSensor.setPulseAmplitudeRed(0); //Turn off Red LED 54 | particleSensor.setPulseAmplitudeGreen(0); //Turn off Green LED 55 | 56 | //Take an average of IR readings at power up 57 | unblockedValue = 0; 58 | for (byte x = 0 ; x < 32 ; x++) 59 | { 60 | unblockedValue += particleSensor.getIR(); //Read the IR value 61 | } 62 | unblockedValue /= 32; 63 | 64 | startTime = millis(); 65 | } 66 | 67 | void loop() 68 | { 69 | samplesTaken++; 70 | 71 | Serial.print("IR["); 72 | Serial.print(particleSensor.getIR()); 73 | Serial.print("] Hz["); 74 | Serial.print((float)samplesTaken / ((millis() - startTime) / 1000.0), 2); 75 | Serial.print("]"); 76 | 77 | long currentDelta = particleSensor.getIR() - unblockedValue; 78 | 79 | Serial.print(" delta["); 80 | Serial.print(currentDelta); 81 | Serial.print("]"); 82 | 83 | if (currentDelta > (long)100) 84 | { 85 | Serial.print(" Something is there!"); 86 | } 87 | 88 | Serial.println(); 89 | } 90 | -------------------------------------------------------------------------------- /examples/Example3_Temperature_Sense/Example3_Temperature_Sense.ino: -------------------------------------------------------------------------------- 1 | /* 2 | MAX3010 Breakout: Read the onboard temperature sensor 3 | By: Nathan Seidle @ SparkFun Electronics 4 | Date: October 20th, 2016 5 | https://github.com/sparkfun/MAX30105_Breakout 6 | 7 | This demo outputs the onboard temperature sensor. The temp sensor is accurate to +/-1 C but 8 | has an astonishing precision of 0.0625 C. 9 | 10 | Hardware Connections (Breakoutboard to Arduino): 11 | -5V = 5V (3.3V is allowed) 12 | -GND = GND 13 | -SDA = A4 (or SDA) 14 | -SCL = A5 (or SCL) 15 | -INT = Not connected 16 | 17 | The MAX30105 Breakout can handle 5V or 3.3V I2C logic. We recommend powering the board with 5V 18 | but it will also run at 3.3V. 19 | */ 20 | 21 | #include 22 | 23 | #include "MAX30105.h" //Get it here: http://librarymanager/All#SparkFun_MAX30105 24 | MAX30105 particleSensor; 25 | 26 | void setup() 27 | { 28 | Serial.begin(9600); 29 | Serial.println("Initializing..."); 30 | 31 | // Initialize sensor 32 | if (particleSensor.begin(Wire, I2C_SPEED_FAST) == false) //Use default I2C port, 400kHz speed 33 | { 34 | Serial.println("MAX30105 was not found. Please check wiring/power. "); 35 | while (1); 36 | } 37 | 38 | //The LEDs are very low power and won't affect the temp reading much but 39 | //you may want to turn off the LEDs to avoid any local heating 40 | particleSensor.setup(0); //Configure sensor. Turn off LEDs 41 | //particleSensor.setup(); //Configure sensor. Use 25mA for LED drive 42 | 43 | particleSensor.enableDIETEMPRDY(); //Enable the temp ready interrupt. This is required. 44 | } 45 | 46 | void loop() 47 | { 48 | float temperature = particleSensor.readTemperature(); 49 | 50 | Serial.print("temperatureC="); 51 | Serial.print(temperature, 4); 52 | 53 | float temperatureF = particleSensor.readTemperatureF(); //Because I am a bad global citizen 54 | 55 | Serial.print(" temperatureF="); 56 | Serial.print(temperatureF, 4); 57 | 58 | Serial.println(); 59 | } 60 | -------------------------------------------------------------------------------- /examples/Example4_HeartBeat_Plotter/Example4_HeartBeat_Plotter.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Heart beat plotting! 3 | By: Nathan Seidle @ SparkFun Electronics 4 | Date: October 20th, 2016 5 | https://github.com/sparkfun/MAX30105_Breakout 6 | 7 | Shows the user's heart beat on Arduino's serial plotter 8 | 9 | Instructions: 10 | 1) Load code onto Redboard 11 | 2) Attach sensor to your finger with a rubber band (see below) 12 | 3) Open Tools->'Serial Plotter' 13 | 4) Make sure the drop down is set to 115200 baud 14 | 5) Checkout the blips! 15 | 6) Feel the pulse on your neck and watch it mimic the blips 16 | 17 | It is best to attach the sensor to your finger using a rubber band or other tightening 18 | device. Humans are generally bad at applying constant pressure to a thing. When you 19 | press your finger against the sensor it varies enough to cause the blood in your 20 | finger to flow differently which causes the sensor readings to go wonky. 21 | 22 | Hardware Connections (Breakoutboard to Arduino): 23 | -5V = 5V (3.3V is allowed) 24 | -GND = GND 25 | -SDA = A4 (or SDA) 26 | -SCL = A5 (or SCL) 27 | -INT = Not connected 28 | 29 | The MAX30105 Breakout can handle 5V or 3.3V I2C logic. We recommend powering the board with 5V 30 | but it will also run at 3.3V. 31 | */ 32 | 33 | #include 34 | #include "MAX30105.h" 35 | 36 | MAX30105 particleSensor; 37 | 38 | void setup() 39 | { 40 | Serial.begin(115200); 41 | Serial.println("Initializing..."); 42 | 43 | // Initialize sensor 44 | if (!particleSensor.begin(Wire, I2C_SPEED_FAST)) //Use default I2C port, 400kHz speed 45 | { 46 | Serial.println("MAX30105 was not found. Please check wiring/power. "); 47 | while (1); 48 | } 49 | 50 | //Setup to sense a nice looking saw tooth on the plotter 51 | byte ledBrightness = 0x1F; //Options: 0=Off to 255=50mA 52 | byte sampleAverage = 8; //Options: 1, 2, 4, 8, 16, 32 53 | byte ledMode = 3; //Options: 1 = Red only, 2 = Red + IR, 3 = Red + IR + Green 54 | int sampleRate = 100; //Options: 50, 100, 200, 400, 800, 1000, 1600, 3200 55 | int pulseWidth = 411; //Options: 69, 118, 215, 411 56 | int adcRange = 4096; //Options: 2048, 4096, 8192, 16384 57 | 58 | particleSensor.setup(ledBrightness, sampleAverage, ledMode, sampleRate, pulseWidth, adcRange); //Configure sensor with these settings 59 | 60 | //Arduino plotter auto-scales annoyingly. To get around this, pre-populate 61 | //the plotter with 500 of an average reading from the sensor 62 | 63 | //Take an average of IR readings at power up 64 | const byte avgAmount = 64; 65 | long baseValue = 0; 66 | for (byte x = 0 ; x < avgAmount ; x++) 67 | { 68 | baseValue += particleSensor.getIR(); //Read the IR value 69 | } 70 | baseValue /= avgAmount; 71 | 72 | //Pre-populate the plotter so that the Y scale is close to IR values 73 | for (int x = 0 ; x < 500 ; x++) 74 | Serial.println(baseValue); 75 | } 76 | 77 | void loop() 78 | { 79 | Serial.println(particleSensor.getIR()); //Send raw data to plotter 80 | } 81 | -------------------------------------------------------------------------------- /examples/Example5_HeartRate/Example5_HeartRate.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Optical Heart Rate Detection (PBA Algorithm) using the MAX30105 Breakout 3 | By: Nathan Seidle @ SparkFun Electronics 4 | Date: October 2nd, 2016 5 | https://github.com/sparkfun/MAX30105_Breakout 6 | 7 | This is a demo to show the reading of heart rate or beats per minute (BPM) using 8 | a Penpheral Beat Amplitude (PBA) algorithm. 9 | 10 | It is best to attach the sensor to your finger using a rubber band or other tightening 11 | device. Humans are generally bad at applying constant pressure to a thing. When you 12 | press your finger against the sensor it varies enough to cause the blood in your 13 | finger to flow differently which causes the sensor readings to go wonky. 14 | 15 | Hardware Connections (Breakoutboard to Arduino): 16 | -5V = 5V (3.3V is allowed) 17 | -GND = GND 18 | -SDA = A4 (or SDA) 19 | -SCL = A5 (or SCL) 20 | -INT = Not connected 21 | 22 | The MAX30105 Breakout can handle 5V or 3.3V I2C logic. We recommend powering the board with 5V 23 | but it will also run at 3.3V. 24 | */ 25 | 26 | #include 27 | #include "MAX30105.h" 28 | 29 | #include "heartRate.h" 30 | 31 | MAX30105 particleSensor; 32 | 33 | const byte RATE_SIZE = 4; //Increase this for more averaging. 4 is good. 34 | byte rates[RATE_SIZE]; //Array of heart rates 35 | byte rateSpot = 0; 36 | long lastBeat = 0; //Time at which the last beat occurred 37 | 38 | float beatsPerMinute; 39 | int beatAvg; 40 | 41 | void setup() 42 | { 43 | Serial.begin(115200); 44 | Serial.println("Initializing..."); 45 | 46 | // Initialize sensor 47 | if (!particleSensor.begin(Wire, I2C_SPEED_FAST)) //Use default I2C port, 400kHz speed 48 | { 49 | Serial.println("MAX30105 was not found. Please check wiring/power. "); 50 | while (1); 51 | } 52 | Serial.println("Place your index finger on the sensor with steady pressure."); 53 | 54 | particleSensor.setup(); //Configure sensor with default settings 55 | particleSensor.setPulseAmplitudeRed(0x0A); //Turn Red LED to low to indicate sensor is running 56 | particleSensor.setPulseAmplitudeGreen(0); //Turn off Green LED 57 | } 58 | 59 | void loop() 60 | { 61 | long irValue = particleSensor.getIR(); 62 | 63 | if (checkForBeat(irValue) == true) 64 | { 65 | //We sensed a beat! 66 | long delta = millis() - lastBeat; 67 | lastBeat = millis(); 68 | 69 | beatsPerMinute = 60 / (delta / 1000.0); 70 | 71 | if (beatsPerMinute < 255 && beatsPerMinute > 20) 72 | { 73 | rates[rateSpot++] = (byte)beatsPerMinute; //Store this reading in the array 74 | rateSpot %= RATE_SIZE; //Wrap variable 75 | 76 | //Take average of readings 77 | beatAvg = 0; 78 | for (byte x = 0 ; x < RATE_SIZE ; x++) 79 | beatAvg += rates[x]; 80 | beatAvg /= RATE_SIZE; 81 | } 82 | } 83 | 84 | Serial.print("IR="); 85 | Serial.print(irValue); 86 | Serial.print(", BPM="); 87 | Serial.print(beatsPerMinute); 88 | Serial.print(", Avg BPM="); 89 | Serial.print(beatAvg); 90 | 91 | if (irValue < 50000) 92 | Serial.print(" No finger?"); 93 | 94 | Serial.println(); 95 | } 96 | 97 | 98 | -------------------------------------------------------------------------------- /examples/Example5_HeartRate/License.ino: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2016 Maxim Integrated Products, Inc., All Rights Reserved. 2 | * 3 | * Permission is hereby granted, free of charge, to any person obtaining a 4 | * copy of this software and associated documentation files (the "Software"), 5 | * to deal in the Software without restriction, including without limitation 6 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | * and/or sell copies of the Software, and to permit persons to whom the 8 | * Software is furnished to do so, subject to the following conditions: 9 | * 10 | * The above copyright notice and this permission notice shall be included 11 | * in all copies or substantial portions of the Software. 12 | * 13 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 14 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 15 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 16 | * IN NO EVENT SHALL MAXIM INTEGRATED BE LIABLE FOR ANY CLAIM, DAMAGES 17 | * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 19 | * OTHER DEALINGS IN THE SOFTWARE. 20 | * 21 | * Except as contained in this notice, the name of Maxim Integrated 22 | * Products, Inc. shall not be used except as stated in the Maxim Integrated 23 | * Products, Inc. Branding Policy. 24 | * 25 | * The mere transfer of this software does not imply any licenses 26 | * of trade secrets, proprietary technology, copyrights, patents, 27 | * trademarks, maskwork rights, or any other form of intellectual 28 | * property whatsoever. Maxim Integrated Products, Inc. retains all 29 | * ownership rights. 30 | * 31 | */ 32 | -------------------------------------------------------------------------------- /examples/Example6_FIFO_Readings/Example6_FIFO_Readings.ino: -------------------------------------------------------------------------------- 1 | /* 2 | MAX30105 Breakout: Take readings from the FIFO 3 | By: Nathan Seidle @ SparkFun Electronics 4 | Date: October 2nd, 2016 5 | https://github.com/sparkfun/MAX30105_Breakout 6 | 7 | Outputs all Red/IR/Green values at 25Hz by polling the FIFO 8 | 9 | Hardware Connections (Breakoutboard to Arduino): 10 | -5V = 5V (3.3V is allowed) 11 | -GND = GND 12 | -SDA = A4 (or SDA) 13 | -SCL = A5 (or SCL) 14 | -INT = Not connected 15 | 16 | The MAX30105 Breakout can handle 5V or 3.3V I2C logic. We recommend powering the board with 5V 17 | but it will also run at 3.3V. 18 | 19 | This code is released under the [MIT License](http://opensource.org/licenses/MIT). 20 | */ 21 | 22 | #include 23 | #include "MAX30105.h" 24 | 25 | MAX30105 particleSensor; 26 | 27 | long startTime; 28 | long samplesTaken = 0; //Counter for calculating the Hz or read rate 29 | 30 | void setup() 31 | { 32 | Serial.begin(115200); 33 | Serial.println("Initializing..."); 34 | 35 | // Initialize sensor 36 | if (particleSensor.begin(Wire, I2C_SPEED_FAST) == false) //Use default I2C port, 400kHz speed 37 | { 38 | Serial.println("MAX30105 was not found. Please check wiring/power. "); 39 | while (1); 40 | } 41 | 42 | //Setup to sense up to 18 inches, max LED brightness 43 | byte ledBrightness = 0xFF; //Options: 0=Off to 255=50mA 44 | byte sampleAverage = 4; //Options: 1, 2, 4, 8, 16, 32 45 | byte ledMode = 2; //Options: 1 = Red only, 2 = Red + IR, 3 = Red + IR + Green 46 | int sampleRate = 400; //Options: 50, 100, 200, 400, 800, 1000, 1600, 3200 47 | int pulseWidth = 411; //Options: 69, 118, 215, 411 48 | int adcRange = 2048; //Options: 2048, 4096, 8192, 16384 49 | 50 | particleSensor.setup(ledBrightness, sampleAverage, ledMode, sampleRate, pulseWidth, adcRange); //Configure sensor with these settings 51 | // particleSensor.setup(); //Configure sensor. Use 6.4mA for LED drive 52 | 53 | startTime = millis(); 54 | } 55 | 56 | void loop() 57 | { 58 | particleSensor.check(); //Check the sensor, read up to 3 samples 59 | 60 | while (particleSensor.available()) //do we have new data? 61 | { 62 | samplesTaken++; 63 | 64 | Serial.print(" R["); 65 | Serial.print(particleSensor.getFIFORed()); 66 | Serial.print("] IR["); 67 | Serial.print(particleSensor.getFIFOIR()); 68 | Serial.print("] G["); 69 | Serial.print(particleSensor.getFIFOGreen()); 70 | Serial.print("] Hz["); 71 | Serial.print((float)samplesTaken / ((millis() - startTime) / 1000.0), 2); 72 | Serial.print("]"); 73 | 74 | Serial.println(); 75 | 76 | particleSensor.nextSample(); //We're finished with this sample so move to next sample 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /examples/Example7_Basic_Readings_Interrupts/Example7_Basic_Readings_Interrupts.ino: -------------------------------------------------------------------------------- 1 | /* 2 | MAX30105 Breakout: Output all the raw Red/IR/Green readings, check INT pin and interrupt register 3 | By: Nathan Seidle @ SparkFun Electronics 4 | Date: October 2nd, 2016 5 | https://github.com/sparkfun/MAX30105_Breakout 6 | 7 | Outputs all Red/IR/Green values as fast as possible 8 | Checks the interrupt pin to see if an interrupt occurred 9 | Checks the interrupt register to see if a bit was set 10 | 11 | Hardware Connections (Breakoutboard to Arduino): 12 | -5V = 5V (3.3V is allowed) 13 | -GND = GND 14 | -SDA = A4 (or SDA) 15 | -SCL = A5 (or SCL) 16 | -INT = Not connected 17 | 18 | The MAX30105 Breakout can handle 5V or 3.3V I2C logic. We recommend powering the board with 5V 19 | but it will also run at 3.3V. 20 | */ 21 | 22 | #include 23 | #include "MAX30105.h" 24 | 25 | MAX30105 particleSensor; 26 | 27 | long startTime; 28 | long samplesTaken = 0; //Counter for calculating the Hz or read rate 29 | 30 | byte interruptPin = 3; //Connect INT pin on breakout board to pin 3 31 | 32 | void setup() 33 | { 34 | pinMode(interruptPin, INPUT); 35 | 36 | Serial.begin(115200); 37 | Serial.println("Initializing..."); 38 | 39 | // Initialize sensor 40 | if (!particleSensor.begin(Wire, I2C_SPEED_FAST)) //Use default I2C port, 400kHz speed 41 | { 42 | Serial.println("MAX30105 was not found. Please check wiring/power. "); 43 | while (1); 44 | } 45 | 46 | //Let's configure the sensor to run fast so we can over-run the buffer and cause an interrupt 47 | byte ledBrightness = 0x7F; //Options: 0=Off to 255=50mA 48 | byte sampleAverage = 1; //Options: 1, 2, 4, 8, 16, 32 49 | byte ledMode = 3; //Options: 1 = Red only, 2 = Red + IR, 3 = Red + IR + Green 50 | int sampleRate = 400; //Options: 50, 100, 200, 400, 800, 1000, 1600, 3200 51 | int pulseWidth = 69; //Options: 69, 118, 215, 411 52 | int adcRange = 4096; //Options: 2048, 4096, 8192, 16384 53 | 54 | particleSensor.setup(ledBrightness, sampleAverage, ledMode, sampleRate, pulseWidth, adcRange); //Configure sensor with these settings 55 | 56 | particleSensor.enableAFULL(); //Enable the almost full interrupt (default is 32 samples) 57 | 58 | particleSensor.setFIFOAlmostFull(3); //Set almost full int to fire at 29 samples 59 | 60 | startTime = millis(); 61 | } 62 | 63 | void loop() 64 | { 65 | particleSensor.check(); //Check the sensor, read up to 3 samples 66 | 67 | while (particleSensor.available()) //do we have new data? 68 | { 69 | samplesTaken++; 70 | 71 | Serial.print(" R["); 72 | Serial.print(particleSensor.getRed()); 73 | Serial.print("] IR["); 74 | Serial.print(particleSensor.getIR()); 75 | Serial.print("] G["); 76 | Serial.print(particleSensor.getGreen()); 77 | Serial.print("] Hz["); 78 | Serial.print((float)samplesTaken / ((millis() - startTime) / 1000.0), 2); 79 | Serial.print("]"); 80 | 81 | if (digitalRead(interruptPin) == LOW) //Hardware way of reading interrupts 82 | { 83 | Serial.print(" INT!"); 84 | } 85 | 86 | byte flags = particleSensor.getINT1(); //Software way of reading interrupts 87 | if (flags) 88 | { 89 | Serial.print(" I["); 90 | Serial.print(flags, BIN); 91 | Serial.print("]"); 92 | } 93 | 94 | Serial.println(); 95 | 96 | particleSensor.nextSample(); //We're finished with this sample so move to next sample 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /examples/Example8_SPO2/Example8_SPO2.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Optical SP02 Detection (SPK Algorithm) using the MAX30105 Breakout 3 | By: Nathan Seidle @ SparkFun Electronics 4 | Date: October 19th, 2016 5 | https://github.com/sparkfun/MAX30105_Breakout 6 | 7 | This demo shows heart rate and SPO2 levels. 8 | 9 | It is best to attach the sensor to your finger using a rubber band or other tightening 10 | device. Humans are generally bad at applying constant pressure to a thing. When you 11 | press your finger against the sensor it varies enough to cause the blood in your 12 | finger to flow differently which causes the sensor readings to go wonky. 13 | 14 | This example is based on MAXREFDES117 and RD117_LILYPAD.ino from Maxim. Their example 15 | was modified to work with the SparkFun MAX30105 library and to compile under Arduino 1.6.11 16 | Please see license file for more info. 17 | 18 | Hardware Connections (Breakoutboard to Arduino): 19 | -5V = 5V (3.3V is allowed) 20 | -GND = GND 21 | -SDA = A4 (or SDA) 22 | -SCL = A5 (or SCL) 23 | -INT = Not connected 24 | 25 | The MAX30105 Breakout can handle 5V or 3.3V I2C logic. We recommend powering the board with 5V 26 | but it will also run at 3.3V. 27 | */ 28 | 29 | #include 30 | #include "MAX30105.h" 31 | #include "spo2_algorithm.h" 32 | 33 | MAX30105 particleSensor; 34 | 35 | #define MAX_BRIGHTNESS 255 36 | 37 | #if defined(__AVR_ATmega328P__) || defined(__AVR_ATmega168__) 38 | //Arduino Uno doesn't have enough SRAM to store 100 samples of IR led data and red led data in 32-bit format 39 | //To solve this problem, 16-bit MSB of the sampled data will be truncated. Samples become 16-bit data. 40 | uint16_t irBuffer[100]; //infrared LED sensor data 41 | uint16_t redBuffer[100]; //red LED sensor data 42 | #else 43 | uint32_t irBuffer[100]; //infrared LED sensor data 44 | uint32_t redBuffer[100]; //red LED sensor data 45 | #endif 46 | 47 | int32_t bufferLength; //data length 48 | int32_t spo2; //SPO2 value 49 | int8_t validSPO2; //indicator to show if the SPO2 calculation is valid 50 | int32_t heartRate; //heart rate value 51 | int8_t validHeartRate; //indicator to show if the heart rate calculation is valid 52 | 53 | byte pulseLED = 11; //Must be on PWM pin 54 | byte readLED = 13; //Blinks with each data read 55 | 56 | void setup() 57 | { 58 | Serial.begin(115200); // initialize serial communication at 115200 bits per second: 59 | 60 | pinMode(pulseLED, OUTPUT); 61 | pinMode(readLED, OUTPUT); 62 | 63 | // Initialize sensor 64 | if (!particleSensor.begin(Wire, I2C_SPEED_FAST)) //Use default I2C port, 400kHz speed 65 | { 66 | Serial.println(F("MAX30105 was not found. Please check wiring/power.")); 67 | while (1); 68 | } 69 | 70 | Serial.println(F("Attach sensor to finger with rubber band. Press any key to start conversion")); 71 | while (Serial.available() == 0) ; //wait until user presses a key 72 | Serial.read(); 73 | 74 | byte ledBrightness = 60; //Options: 0=Off to 255=50mA 75 | byte sampleAverage = 4; //Options: 1, 2, 4, 8, 16, 32 76 | byte ledMode = 2; //Options: 1 = Red only, 2 = Red + IR, 3 = Red + IR + Green 77 | byte sampleRate = 100; //Options: 50, 100, 200, 400, 800, 1000, 1600, 3200 78 | int pulseWidth = 411; //Options: 69, 118, 215, 411 79 | int adcRange = 4096; //Options: 2048, 4096, 8192, 16384 80 | 81 | particleSensor.setup(ledBrightness, sampleAverage, ledMode, sampleRate, pulseWidth, adcRange); //Configure sensor with these settings 82 | } 83 | 84 | void loop() 85 | { 86 | bufferLength = 100; //buffer length of 100 stores 4 seconds of samples running at 25sps 87 | 88 | //read the first 100 samples, and determine the signal range 89 | for (byte i = 0 ; i < bufferLength ; i++) 90 | { 91 | while (particleSensor.available() == false) //do we have new data? 92 | particleSensor.check(); //Check the sensor for new data 93 | 94 | redBuffer[i] = particleSensor.getRed(); 95 | irBuffer[i] = particleSensor.getIR(); 96 | particleSensor.nextSample(); //We're finished with this sample so move to next sample 97 | 98 | Serial.print(F("red=")); 99 | Serial.print(redBuffer[i], DEC); 100 | Serial.print(F(", ir=")); 101 | Serial.println(irBuffer[i], DEC); 102 | } 103 | 104 | //calculate heart rate and SpO2 after first 100 samples (first 4 seconds of samples) 105 | maxim_heart_rate_and_oxygen_saturation(irBuffer, bufferLength, redBuffer, &spo2, &validSPO2, &heartRate, &validHeartRate); 106 | 107 | //Continuously taking samples from MAX30102. Heart rate and SpO2 are calculated every 1 second 108 | while (1) 109 | { 110 | //dumping the first 25 sets of samples in the memory and shift the last 75 sets of samples to the top 111 | for (byte i = 25; i < 100; i++) 112 | { 113 | redBuffer[i - 25] = redBuffer[i]; 114 | irBuffer[i - 25] = irBuffer[i]; 115 | } 116 | 117 | //take 25 sets of samples before calculating the heart rate. 118 | for (byte i = 75; i < 100; i++) 119 | { 120 | while (particleSensor.available() == false) //do we have new data? 121 | particleSensor.check(); //Check the sensor for new data 122 | 123 | digitalWrite(readLED, !digitalRead(readLED)); //Blink onboard LED with every data read 124 | 125 | redBuffer[i] = particleSensor.getRed(); 126 | irBuffer[i] = particleSensor.getIR(); 127 | particleSensor.nextSample(); //We're finished with this sample so move to next sample 128 | 129 | //send samples and calculation result to terminal program through UART 130 | Serial.print(F("red=")); 131 | Serial.print(redBuffer[i], DEC); 132 | Serial.print(F(", ir=")); 133 | Serial.print(irBuffer[i], DEC); 134 | 135 | Serial.print(F(", HR=")); 136 | Serial.print(heartRate, DEC); 137 | 138 | Serial.print(F(", HRvalid=")); 139 | Serial.print(validHeartRate, DEC); 140 | 141 | Serial.print(F(", SPO2=")); 142 | Serial.print(spo2, DEC); 143 | 144 | Serial.print(F(", SPO2Valid=")); 145 | Serial.println(validSPO2, DEC); 146 | } 147 | 148 | //After gathering 25 new samples recalculate HR and SP02 149 | maxim_heart_rate_and_oxygen_saturation(irBuffer, bufferLength, redBuffer, &spo2, &validSPO2, &heartRate, &validHeartRate); 150 | } 151 | } 152 | 153 | -------------------------------------------------------------------------------- /examples/Example8_SPO2/License.ino: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2016 Maxim Integrated Products, Inc., All Rights Reserved. 2 | * 3 | * Permission is hereby granted, free of charge, to any person obtaining a 4 | * copy of this software and associated documentation files (the "Software"), 5 | * to deal in the Software without restriction, including without limitation 6 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | * and/or sell copies of the Software, and to permit persons to whom the 8 | * Software is furnished to do so, subject to the following conditions: 9 | * 10 | * The above copyright notice and this permission notice shall be included 11 | * in all copies or substantial portions of the Software. 12 | * 13 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 14 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 15 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 16 | * IN NO EVENT SHALL MAXIM INTEGRATED BE LIABLE FOR ANY CLAIM, DAMAGES 17 | * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 19 | * OTHER DEALINGS IN THE SOFTWARE. 20 | * 21 | * Except as contained in this notice, the name of Maxim Integrated 22 | * Products, Inc. shall not be used except as stated in the Maxim Integrated 23 | * Products, Inc. Branding Policy. 24 | * 25 | * The mere transfer of this software does not imply any licenses 26 | * of trade secrets, proprietary technology, copyrights, patents, 27 | * trademarks, maskwork rights, or any other form of intellectual 28 | * property whatsoever. Maxim Integrated Products, Inc. retains all 29 | * ownership rights. 30 | * 31 | */ 32 | -------------------------------------------------------------------------------- /examples/Example9_RateTesting/Example9_RateTesting.ino: -------------------------------------------------------------------------------- 1 | /* 2 | MAX30105 Breakout: Take readings from the FIFO 3 | By: Nathan Seidle @ SparkFun Electronics 4 | Date: April 8th, 2018 5 | https://github.com/sparkfun/MAX30105_Breakout 6 | 7 | Push the MAX30105 as fast as it will go! 8 | 9 | We used a Teensy 3.2 for testing. This will configure the MAX3010x and 10 | output at approximately 3200Hz. 11 | 12 | On an Uno the fastest we can read is 2700Hz. 13 | 14 | Setting required: 15 | Sample average has a direct impact on max read amount. Set to 1 for max speed. 16 | The pulsewidth must be as short as possible. Set to 69. 17 | ledMode must be 1 for 3200Hz. If ledMode is set to 2 max is 1600Hz. 18 | Run at 400kHz I2C communication speed. 19 | Print serial at 115200. 20 | 21 | Any serial printing will slow the reading of data and may cause the FIFO to overflow. 22 | Keep your prints small. 23 | 24 | Hardware Connections (Breakoutboard to Arduino): 25 | -5V = 5V (3.3V is allowed) 26 | -GND = GND 27 | -SDA = A4 (or SDA) - Pin 18 on Teensy 28 | -SCL = A5 (or SCL) - Pin 19 on Teensy 29 | -INT = Not connected 30 | 31 | The MAX30105 Breakout can handle 5V or 3.3V I2C logic. We recommend powering the board with 5V 32 | but it will also run at 3.3V. 33 | 34 | This code is released under the [MIT License](http://opensource.org/licenses/MIT). 35 | */ 36 | 37 | #include 38 | #include "MAX30105.h" 39 | 40 | MAX30105 particleSensor; 41 | 42 | void setup() 43 | { 44 | Serial.begin(115200); 45 | while(!Serial); //We must wait for Teensy to come online 46 | Serial.println("Max sample rate example"); 47 | 48 | // Initialize sensor 49 | if (particleSensor.begin(Wire, I2C_SPEED_FAST) == false) //Use default I2C port, 400kHz speed 50 | { 51 | Serial.println("MAX30105 was not found. Please check wiring/power. "); 52 | while (1); 53 | } 54 | 55 | //Setup to sense up to 18 inches, max LED brightness 56 | byte ledBrightness = 0xFF; //Options: 0=Off to 255=50mA 57 | byte sampleAverage = 1; //Options: 1, 2, 4, 8, 16, 32 58 | byte ledMode = 1; //Options: 1 = Red only, 2 = Red + IR, 3 = Red + IR + Green 59 | int sampleRate = 3200; //Options: 50, 100, 200, 400, 800, 1000, 1600, 3200 60 | int pulseWidth = 69; //Options: 69, 118, 215, 411 61 | int adcRange = 16384; //Options: 2048, 4096, 8192, 16384 62 | 63 | particleSensor.setup(ledBrightness, sampleAverage, ledMode, sampleRate, pulseWidth, adcRange); //Configure sensor with these settings 64 | } 65 | 66 | void loop() 67 | { 68 | byte samplesTaken = 0; 69 | long startTime = micros(); 70 | 71 | while(samplesTaken < 10) 72 | { 73 | particleSensor.check(); //Check the sensor, read up to 3 samples 74 | while (particleSensor.available()) //do we have new data? 75 | { 76 | samplesTaken++; 77 | particleSensor.getFIFOIR(); 78 | particleSensor.nextSample(); //We're finished with this sample so move to next sample 79 | } 80 | } 81 | 82 | long endTime = micros(); 83 | 84 | Serial.print("samples["); 85 | Serial.print(samplesTaken); 86 | 87 | Serial.print("] Hz["); 88 | Serial.print((float)samplesTaken / ((endTime - startTime) / 1000000.0), 2); 89 | Serial.print("]"); 90 | 91 | Serial.println(); 92 | } 93 | -------------------------------------------------------------------------------- /extras/HeartBeat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparkfun/SparkFun_MAX3010x_Sensor_Library/72d5308df500ae1a64cc9d63e950c68c96dc78d5/extras/HeartBeat.jpg -------------------------------------------------------------------------------- /extras/HeartBeat1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparkfun/SparkFun_MAX3010x_Sensor_Library/72d5308df500ae1a64cc9d63e950c68c96dc78d5/extras/HeartBeat1.jpg -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | ####################################### 2 | # Syntax Coloring Map 3 | ####################################### 4 | 5 | ####################################### 6 | # Datatypes (KEYWORD1) 7 | ####################################### 8 | 9 | MAX30105 KEYWORD1 10 | 11 | ####################################### 12 | # Methods and Functions (KEYWORD2) 13 | ####################################### 14 | 15 | begin KEYWORD2 16 | setup KEYWORD2 17 | available KEYWORD2 18 | getRed KEYWORD2 19 | getIR KEYWORD2 20 | getGreen KEYWORD2 21 | readTemperature KEYWORD2 22 | readTemperatureF KEYWORD2 23 | 24 | check KEYWORD2 25 | getRed KEYWORD2 26 | getIR KEYWORD2 27 | getGreen KEYWORD2 28 | 29 | softReset KEYWORD2 30 | shutDown KEYWORD2 31 | wakeUp KEYWORD2 32 | setLEDMode KEYWORD2 33 | setADCRange KEYWORD2 34 | setSampleRate KEYWORD2 35 | setPulseWidth KEYWORD2 36 | setPulseAmplitudeRed KEYWORD2 37 | setPulseAmplitudeIR KEYWORD2 38 | setPulseAmplitudeGreen KEYWORD2 39 | setPulseAmplitudeProximity KEYWORD2 40 | setProximityThreshold KEYWORD2 41 | enableSlot KEYWORD2 42 | disableSlots KEYWORD2 43 | getINT1 KEYWORD2 44 | getINT2 KEYWORD2 45 | enableAFULL KEYWORD2 46 | disableAFULL KEYWORD2 47 | enableDATARDY KEYWORD2 48 | disableDATARDY KEYWORD2 49 | enableALCOVF KEYWORD2 50 | disableALCOVF KEYWORD2 51 | enablePROXINT KEYWORD2 52 | disablePROXINT KEYWORD2 53 | enableDIETEMPRDY KEYWORD2 54 | disableDIETEMPRDY KEYWORD2 55 | 56 | setFIFOAverage KEYWORD2 57 | enableFIFORollover KEYWORD2 58 | disableFIFORollover KEYWORD2 59 | setFIFOAlmostFull KEYWORD2 60 | 61 | getFIFORed KEYWORD2 62 | getFIFOIR KEYWORD2 63 | getFIFOGreen KEYWORD2 64 | getWritePointer KEYWORD2 65 | getReadPointer KEYWORD2 66 | clearFIFO KEYWORD2 67 | available KEYWORD2 68 | 69 | nextSample KEYWORD2 70 | 71 | setPROXINTTHRESH KEYWORD2 72 | 73 | getRevisionID KEYWORD2 74 | readPartID KEYWORD2 75 | 76 | readRegister8 KEYWORD2 77 | writeRegister8 KEYWORD2 78 | 79 | ####################################### 80 | # Constants (LITERAL1) 81 | ####################################### -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=SparkFun MAX3010x Pulse and Proximity Sensor Library 2 | version=1.1.2 3 | author=SparkFun Electronics 4 | maintainer=SparkFun Electronics 5 | sentence=Library for the MAX30102 Pulse and MAX30105 Proximity Breakout 6 | paragraph=An Arduino Library for the MAX3015 particle sensor and MAX30102 Pulse Ox sensor 7 | category=Sensors 8 | url=https://github.com/sparkfun/SparkFun_MAX3010x_Sensor_Library 9 | architectures=* 10 | -------------------------------------------------------------------------------- /src/MAX30105.cpp: -------------------------------------------------------------------------------- 1 | /*************************************************** 2 | This is a library written for the Maxim MAX30105 Optical Smoke Detector 3 | It should also work with the MAX30102. However, the MAX30102 does not have a Green LED. 4 | 5 | These sensors use I2C to communicate, as well as a single (optional) 6 | interrupt line that is not currently supported in this driver. 7 | 8 | Written by Peter Jansen and Nathan Seidle (SparkFun) 9 | BSD license, all text above must be included in any redistribution. 10 | *****************************************************/ 11 | 12 | #include "MAX30105.h" 13 | 14 | // Status Registers 15 | static const uint8_t MAX30105_INTSTAT1 = 0x00; 16 | static const uint8_t MAX30105_INTSTAT2 = 0x01; 17 | static const uint8_t MAX30105_INTENABLE1 = 0x02; 18 | static const uint8_t MAX30105_INTENABLE2 = 0x03; 19 | 20 | // FIFO Registers 21 | static const uint8_t MAX30105_FIFOWRITEPTR = 0x04; 22 | static const uint8_t MAX30105_FIFOOVERFLOW = 0x05; 23 | static const uint8_t MAX30105_FIFOREADPTR = 0x06; 24 | static const uint8_t MAX30105_FIFODATA = 0x07; 25 | 26 | // Configuration Registers 27 | static const uint8_t MAX30105_FIFOCONFIG = 0x08; 28 | static const uint8_t MAX30105_MODECONFIG = 0x09; 29 | static const uint8_t MAX30105_PARTICLECONFIG = 0x0A; // Note, sometimes listed as "SPO2" config in datasheet (pg. 11) 30 | static const uint8_t MAX30105_LED1_PULSEAMP = 0x0C; 31 | static const uint8_t MAX30105_LED2_PULSEAMP = 0x0D; 32 | static const uint8_t MAX30105_LED3_PULSEAMP = 0x0E; 33 | static const uint8_t MAX30105_LED_PROX_AMP = 0x10; 34 | static const uint8_t MAX30105_MULTILEDCONFIG1 = 0x11; 35 | static const uint8_t MAX30105_MULTILEDCONFIG2 = 0x12; 36 | 37 | // Die Temperature Registers 38 | static const uint8_t MAX30105_DIETEMPINT = 0x1F; 39 | static const uint8_t MAX30105_DIETEMPFRAC = 0x20; 40 | static const uint8_t MAX30105_DIETEMPCONFIG = 0x21; 41 | 42 | // Proximity Function Registers 43 | static const uint8_t MAX30105_PROXINTTHRESH = 0x30; 44 | 45 | // Part ID Registers 46 | static const uint8_t MAX30105_REVISIONID = 0xFE; 47 | static const uint8_t MAX30105_PARTID = 0xFF; // Should always be 0x15. Identical to MAX30102. 48 | 49 | // MAX30105 Commands 50 | // Interrupt configuration (pg 13, 14) 51 | static const uint8_t MAX30105_INT_A_FULL_MASK = (byte)~0b10000000; 52 | static const uint8_t MAX30105_INT_A_FULL_ENABLE = 0x80; 53 | static const uint8_t MAX30105_INT_A_FULL_DISABLE = 0x00; 54 | 55 | static const uint8_t MAX30105_INT_DATA_RDY_MASK = (byte)~0b01000000; 56 | static const uint8_t MAX30105_INT_DATA_RDY_ENABLE = 0x40; 57 | static const uint8_t MAX30105_INT_DATA_RDY_DISABLE = 0x00; 58 | 59 | static const uint8_t MAX30105_INT_ALC_OVF_MASK = (byte)~0b00100000; 60 | static const uint8_t MAX30105_INT_ALC_OVF_ENABLE = 0x20; 61 | static const uint8_t MAX30105_INT_ALC_OVF_DISABLE = 0x00; 62 | 63 | static const uint8_t MAX30105_INT_PROX_INT_MASK = (byte)~0b00010000; 64 | static const uint8_t MAX30105_INT_PROX_INT_ENABLE = 0x10; 65 | static const uint8_t MAX30105_INT_PROX_INT_DISABLE = 0x00; 66 | 67 | static const uint8_t MAX30105_INT_DIE_TEMP_RDY_MASK = (byte)~0b00000010; 68 | static const uint8_t MAX30105_INT_DIE_TEMP_RDY_ENABLE = 0x02; 69 | static const uint8_t MAX30105_INT_DIE_TEMP_RDY_DISABLE = 0x00; 70 | 71 | static const uint8_t MAX30105_SAMPLEAVG_MASK = (byte)~0b11100000; 72 | static const uint8_t MAX30105_SAMPLEAVG_1 = 0x00; 73 | static const uint8_t MAX30105_SAMPLEAVG_2 = 0x20; 74 | static const uint8_t MAX30105_SAMPLEAVG_4 = 0x40; 75 | static const uint8_t MAX30105_SAMPLEAVG_8 = 0x60; 76 | static const uint8_t MAX30105_SAMPLEAVG_16 = 0x80; 77 | static const uint8_t MAX30105_SAMPLEAVG_32 = 0xA0; 78 | 79 | static const uint8_t MAX30105_ROLLOVER_MASK = 0xEF; 80 | static const uint8_t MAX30105_ROLLOVER_ENABLE = 0x10; 81 | static const uint8_t MAX30105_ROLLOVER_DISABLE = 0x00; 82 | 83 | static const uint8_t MAX30105_A_FULL_MASK = 0xF0; 84 | 85 | // Mode configuration commands (page 19) 86 | static const uint8_t MAX30105_SHUTDOWN_MASK = 0x7F; 87 | static const uint8_t MAX30105_SHUTDOWN = 0x80; 88 | static const uint8_t MAX30105_WAKEUP = 0x00; 89 | 90 | static const uint8_t MAX30105_RESET_MASK = 0xBF; 91 | static const uint8_t MAX30105_RESET = 0x40; 92 | 93 | static const uint8_t MAX30105_MODE_MASK = 0xF8; 94 | static const uint8_t MAX30105_MODE_REDONLY = 0x02; 95 | static const uint8_t MAX30105_MODE_REDIRONLY = 0x03; 96 | static const uint8_t MAX30105_MODE_MULTILED = 0x07; 97 | 98 | // Particle sensing configuration commands (pgs 19-20) 99 | static const uint8_t MAX30105_ADCRANGE_MASK = 0x9F; 100 | static const uint8_t MAX30105_ADCRANGE_2048 = 0x00; 101 | static const uint8_t MAX30105_ADCRANGE_4096 = 0x20; 102 | static const uint8_t MAX30105_ADCRANGE_8192 = 0x40; 103 | static const uint8_t MAX30105_ADCRANGE_16384 = 0x60; 104 | 105 | static const uint8_t MAX30105_SAMPLERATE_MASK = 0xE3; 106 | static const uint8_t MAX30105_SAMPLERATE_50 = 0x00; 107 | static const uint8_t MAX30105_SAMPLERATE_100 = 0x04; 108 | static const uint8_t MAX30105_SAMPLERATE_200 = 0x08; 109 | static const uint8_t MAX30105_SAMPLERATE_400 = 0x0C; 110 | static const uint8_t MAX30105_SAMPLERATE_800 = 0x10; 111 | static const uint8_t MAX30105_SAMPLERATE_1000 = 0x14; 112 | static const uint8_t MAX30105_SAMPLERATE_1600 = 0x18; 113 | static const uint8_t MAX30105_SAMPLERATE_3200 = 0x1C; 114 | 115 | static const uint8_t MAX30105_PULSEWIDTH_MASK = 0xFC; 116 | static const uint8_t MAX30105_PULSEWIDTH_69 = 0x00; 117 | static const uint8_t MAX30105_PULSEWIDTH_118 = 0x01; 118 | static const uint8_t MAX30105_PULSEWIDTH_215 = 0x02; 119 | static const uint8_t MAX30105_PULSEWIDTH_411 = 0x03; 120 | 121 | //Multi-LED Mode configuration (pg 22) 122 | static const uint8_t MAX30105_SLOT1_MASK = 0xF8; 123 | static const uint8_t MAX30105_SLOT2_MASK = 0x8F; 124 | static const uint8_t MAX30105_SLOT3_MASK = 0xF8; 125 | static const uint8_t MAX30105_SLOT4_MASK = 0x8F; 126 | 127 | static const uint8_t SLOT_NONE = 0x00; 128 | static const uint8_t SLOT_RED_LED = 0x01; 129 | static const uint8_t SLOT_IR_LED = 0x02; 130 | static const uint8_t SLOT_GREEN_LED = 0x03; 131 | static const uint8_t SLOT_NONE_PILOT = 0x04; 132 | static const uint8_t SLOT_RED_PILOT = 0x05; 133 | static const uint8_t SLOT_IR_PILOT = 0x06; 134 | static const uint8_t SLOT_GREEN_PILOT = 0x07; 135 | 136 | static const uint8_t MAX_30105_EXPECTEDPARTID = 0x15; 137 | 138 | MAX30105::MAX30105() { 139 | // Constructor 140 | } 141 | 142 | boolean MAX30105::begin(TwoWire &wirePort, uint32_t i2cSpeed, uint8_t i2caddr) { 143 | 144 | _i2cPort = &wirePort; //Grab which port the user wants us to use 145 | 146 | _i2cPort->begin(); 147 | _i2cPort->setClock(i2cSpeed); 148 | 149 | _i2caddr = i2caddr; 150 | 151 | // Step 1: Initial Communication and Verification 152 | // Check that a MAX30105 is connected 153 | if (readPartID() != MAX_30105_EXPECTEDPARTID) { 154 | // Error -- Part ID read from MAX30105 does not match expected part ID. 155 | // This may mean there is a physical connectivity problem (broken wire, unpowered, etc). 156 | return false; 157 | } 158 | 159 | // Populate revision ID 160 | readRevisionID(); 161 | 162 | return true; 163 | } 164 | 165 | // 166 | // Configuration 167 | // 168 | 169 | //Begin Interrupt configuration 170 | uint8_t MAX30105::getINT1(void) { 171 | return (readRegister8(_i2caddr, MAX30105_INTSTAT1)); 172 | } 173 | uint8_t MAX30105::getINT2(void) { 174 | return (readRegister8(_i2caddr, MAX30105_INTSTAT2)); 175 | } 176 | 177 | void MAX30105::enableAFULL(void) { 178 | bitMask(MAX30105_INTENABLE1, MAX30105_INT_A_FULL_MASK, MAX30105_INT_A_FULL_ENABLE); 179 | } 180 | void MAX30105::disableAFULL(void) { 181 | bitMask(MAX30105_INTENABLE1, MAX30105_INT_A_FULL_MASK, MAX30105_INT_A_FULL_DISABLE); 182 | } 183 | 184 | void MAX30105::enableDATARDY(void) { 185 | bitMask(MAX30105_INTENABLE1, MAX30105_INT_DATA_RDY_MASK, MAX30105_INT_DATA_RDY_ENABLE); 186 | } 187 | void MAX30105::disableDATARDY(void) { 188 | bitMask(MAX30105_INTENABLE1, MAX30105_INT_DATA_RDY_MASK, MAX30105_INT_DATA_RDY_DISABLE); 189 | } 190 | 191 | void MAX30105::enableALCOVF(void) { 192 | bitMask(MAX30105_INTENABLE1, MAX30105_INT_ALC_OVF_MASK, MAX30105_INT_ALC_OVF_ENABLE); 193 | } 194 | void MAX30105::disableALCOVF(void) { 195 | bitMask(MAX30105_INTENABLE1, MAX30105_INT_ALC_OVF_MASK, MAX30105_INT_ALC_OVF_DISABLE); 196 | } 197 | 198 | void MAX30105::enablePROXINT(void) { 199 | bitMask(MAX30105_INTENABLE1, MAX30105_INT_PROX_INT_MASK, MAX30105_INT_PROX_INT_ENABLE); 200 | } 201 | void MAX30105::disablePROXINT(void) { 202 | bitMask(MAX30105_INTENABLE1, MAX30105_INT_PROX_INT_MASK, MAX30105_INT_PROX_INT_DISABLE); 203 | } 204 | 205 | void MAX30105::enableDIETEMPRDY(void) { 206 | bitMask(MAX30105_INTENABLE2, MAX30105_INT_DIE_TEMP_RDY_MASK, MAX30105_INT_DIE_TEMP_RDY_ENABLE); 207 | } 208 | void MAX30105::disableDIETEMPRDY(void) { 209 | bitMask(MAX30105_INTENABLE2, MAX30105_INT_DIE_TEMP_RDY_MASK, MAX30105_INT_DIE_TEMP_RDY_DISABLE); 210 | } 211 | 212 | //End Interrupt configuration 213 | 214 | void MAX30105::softReset(void) { 215 | bitMask(MAX30105_MODECONFIG, MAX30105_RESET_MASK, MAX30105_RESET); 216 | 217 | // Poll for bit to clear, reset is then complete 218 | // Timeout after 100ms 219 | unsigned long startTime = millis(); 220 | while (millis() - startTime < 100) 221 | { 222 | uint8_t response = readRegister8(_i2caddr, MAX30105_MODECONFIG); 223 | if ((response & MAX30105_RESET) == 0) break; //We're done! 224 | delay(1); //Let's not over burden the I2C bus 225 | } 226 | } 227 | 228 | void MAX30105::shutDown(void) { 229 | // Put IC into low power mode (datasheet pg. 19) 230 | // During shutdown the IC will continue to respond to I2C commands but will 231 | // not update with or take new readings (such as temperature) 232 | bitMask(MAX30105_MODECONFIG, MAX30105_SHUTDOWN_MASK, MAX30105_SHUTDOWN); 233 | } 234 | 235 | void MAX30105::wakeUp(void) { 236 | // Pull IC out of low power mode (datasheet pg. 19) 237 | bitMask(MAX30105_MODECONFIG, MAX30105_SHUTDOWN_MASK, MAX30105_WAKEUP); 238 | } 239 | 240 | void MAX30105::setLEDMode(uint8_t mode) { 241 | // Set which LEDs are used for sampling -- Red only, RED+IR only, or custom. 242 | // See datasheet, page 19 243 | bitMask(MAX30105_MODECONFIG, MAX30105_MODE_MASK, mode); 244 | } 245 | 246 | void MAX30105::setADCRange(uint8_t adcRange) { 247 | // adcRange: one of MAX30105_ADCRANGE_2048, _4096, _8192, _16384 248 | bitMask(MAX30105_PARTICLECONFIG, MAX30105_ADCRANGE_MASK, adcRange); 249 | } 250 | 251 | void MAX30105::setSampleRate(uint8_t sampleRate) { 252 | // sampleRate: one of MAX30105_SAMPLERATE_50, _100, _200, _400, _800, _1000, _1600, _3200 253 | bitMask(MAX30105_PARTICLECONFIG, MAX30105_SAMPLERATE_MASK, sampleRate); 254 | } 255 | 256 | void MAX30105::setPulseWidth(uint8_t pulseWidth) { 257 | // pulseWidth: one of MAX30105_PULSEWIDTH_69, _188, _215, _411 258 | bitMask(MAX30105_PARTICLECONFIG, MAX30105_PULSEWIDTH_MASK, pulseWidth); 259 | } 260 | 261 | // NOTE: Amplitude values: 0x00 = 0mA, 0x7F = 25.4mA, 0xFF = 50mA (typical) 262 | // See datasheet, page 21 263 | void MAX30105::setPulseAmplitudeRed(uint8_t amplitude) { 264 | writeRegister8(_i2caddr, MAX30105_LED1_PULSEAMP, amplitude); 265 | } 266 | 267 | void MAX30105::setPulseAmplitudeIR(uint8_t amplitude) { 268 | writeRegister8(_i2caddr, MAX30105_LED2_PULSEAMP, amplitude); 269 | } 270 | 271 | void MAX30105::setPulseAmplitudeGreen(uint8_t amplitude) { 272 | writeRegister8(_i2caddr, MAX30105_LED3_PULSEAMP, amplitude); 273 | } 274 | 275 | void MAX30105::setPulseAmplitudeProximity(uint8_t amplitude) { 276 | writeRegister8(_i2caddr, MAX30105_LED_PROX_AMP, amplitude); 277 | } 278 | 279 | void MAX30105::setProximityThreshold(uint8_t threshMSB) { 280 | // Set the IR ADC count that will trigger the beginning of particle-sensing mode. 281 | // The threshMSB signifies only the 8 most significant-bits of the ADC count. 282 | // See datasheet, page 24. 283 | writeRegister8(_i2caddr, MAX30105_PROXINTTHRESH, threshMSB); 284 | } 285 | 286 | //Given a slot number assign a thing to it 287 | //Devices are SLOT_RED_LED or SLOT_RED_PILOT (proximity) 288 | //Assigning a SLOT_RED_LED will pulse LED 289 | //Assigning a SLOT_RED_PILOT will ?? 290 | void MAX30105::enableSlot(uint8_t slotNumber, uint8_t device) { 291 | 292 | uint8_t originalContents; 293 | 294 | switch (slotNumber) { 295 | case (1): 296 | bitMask(MAX30105_MULTILEDCONFIG1, MAX30105_SLOT1_MASK, device); 297 | break; 298 | case (2): 299 | bitMask(MAX30105_MULTILEDCONFIG1, MAX30105_SLOT2_MASK, device << 4); 300 | break; 301 | case (3): 302 | bitMask(MAX30105_MULTILEDCONFIG2, MAX30105_SLOT3_MASK, device); 303 | break; 304 | case (4): 305 | bitMask(MAX30105_MULTILEDCONFIG2, MAX30105_SLOT4_MASK, device << 4); 306 | break; 307 | default: 308 | //Shouldn't be here! 309 | break; 310 | } 311 | } 312 | 313 | //Clears all slot assignments 314 | void MAX30105::disableSlots(void) { 315 | writeRegister8(_i2caddr, MAX30105_MULTILEDCONFIG1, 0); 316 | writeRegister8(_i2caddr, MAX30105_MULTILEDCONFIG2, 0); 317 | } 318 | 319 | // 320 | // FIFO Configuration 321 | // 322 | 323 | //Set sample average (Table 3, Page 18) 324 | void MAX30105::setFIFOAverage(uint8_t numberOfSamples) { 325 | bitMask(MAX30105_FIFOCONFIG, MAX30105_SAMPLEAVG_MASK, numberOfSamples); 326 | } 327 | 328 | //Resets all points to start in a known state 329 | //Page 15 recommends clearing FIFO before beginning a read 330 | void MAX30105::clearFIFO(void) { 331 | writeRegister8(_i2caddr, MAX30105_FIFOWRITEPTR, 0); 332 | writeRegister8(_i2caddr, MAX30105_FIFOOVERFLOW, 0); 333 | writeRegister8(_i2caddr, MAX30105_FIFOREADPTR, 0); 334 | } 335 | 336 | //Enable roll over if FIFO over flows 337 | void MAX30105::enableFIFORollover(void) { 338 | bitMask(MAX30105_FIFOCONFIG, MAX30105_ROLLOVER_MASK, MAX30105_ROLLOVER_ENABLE); 339 | } 340 | 341 | //Disable roll over if FIFO over flows 342 | void MAX30105::disableFIFORollover(void) { 343 | bitMask(MAX30105_FIFOCONFIG, MAX30105_ROLLOVER_MASK, MAX30105_ROLLOVER_DISABLE); 344 | } 345 | 346 | //Set number of samples to trigger the almost full interrupt (Page 18) 347 | //Power on default is 32 samples 348 | //Note it is reverse: 0x00 is 32 samples, 0x0F is 17 samples 349 | void MAX30105::setFIFOAlmostFull(uint8_t numberOfSamples) { 350 | bitMask(MAX30105_FIFOCONFIG, MAX30105_A_FULL_MASK, numberOfSamples); 351 | } 352 | 353 | //Read the FIFO Write Pointer 354 | uint8_t MAX30105::getWritePointer(void) { 355 | return (readRegister8(_i2caddr, MAX30105_FIFOWRITEPTR)); 356 | } 357 | 358 | //Read the FIFO Read Pointer 359 | uint8_t MAX30105::getReadPointer(void) { 360 | return (readRegister8(_i2caddr, MAX30105_FIFOREADPTR)); 361 | } 362 | 363 | 364 | // Die Temperature 365 | // Returns temp in C 366 | float MAX30105::readTemperature() { 367 | 368 | //DIE_TEMP_RDY interrupt must be enabled 369 | //See issue 19: https://github.com/sparkfun/SparkFun_MAX3010x_Sensor_Library/issues/19 370 | 371 | // Step 1: Config die temperature register to take 1 temperature sample 372 | writeRegister8(_i2caddr, MAX30105_DIETEMPCONFIG, 0x01); 373 | 374 | // Poll for bit to clear, reading is then complete 375 | // Timeout after 100ms 376 | unsigned long startTime = millis(); 377 | while (millis() - startTime < 100) 378 | { 379 | //uint8_t response = readRegister8(_i2caddr, MAX30105_DIETEMPCONFIG); //Original way 380 | //if ((response & 0x01) == 0) break; //We're done! 381 | 382 | //Check to see if DIE_TEMP_RDY interrupt is set 383 | uint8_t response = readRegister8(_i2caddr, MAX30105_INTSTAT2); 384 | if ((response & MAX30105_INT_DIE_TEMP_RDY_ENABLE) > 0) break; //We're done! 385 | delay(1); //Let's not over burden the I2C bus 386 | } 387 | //TODO How do we want to fail? With what type of error? 388 | //? if(millis() - startTime >= 100) return(-999.0); 389 | 390 | // Step 2: Read die temperature register (integer) 391 | int8_t tempInt = readRegister8(_i2caddr, MAX30105_DIETEMPINT); 392 | uint8_t tempFrac = readRegister8(_i2caddr, MAX30105_DIETEMPFRAC); //Causes the clearing of the DIE_TEMP_RDY interrupt 393 | 394 | // Step 3: Calculate temperature (datasheet pg. 23) 395 | return (float)tempInt + ((float)tempFrac * 0.0625); 396 | } 397 | 398 | // Returns die temp in F 399 | float MAX30105::readTemperatureF() { 400 | float temp = readTemperature(); 401 | 402 | if (temp != -999.0) temp = temp * 1.8 + 32.0; 403 | 404 | return (temp); 405 | } 406 | 407 | // Set the PROX_INT_THRESHold 408 | void MAX30105::setPROXINTTHRESH(uint8_t val) { 409 | writeRegister8(_i2caddr, MAX30105_PROXINTTHRESH, val); 410 | } 411 | 412 | 413 | // 414 | // Device ID and Revision 415 | // 416 | uint8_t MAX30105::readPartID() { 417 | return readRegister8(_i2caddr, MAX30105_PARTID); 418 | } 419 | 420 | void MAX30105::readRevisionID() { 421 | revisionID = readRegister8(_i2caddr, MAX30105_REVISIONID); 422 | } 423 | 424 | uint8_t MAX30105::getRevisionID() { 425 | return revisionID; 426 | } 427 | 428 | 429 | //Setup the sensor 430 | //The MAX30105 has many settings. By default we select: 431 | // Sample Average = 4 432 | // Mode = MultiLED 433 | // ADC Range = 16384 (62.5pA per LSB) 434 | // Sample rate = 50 435 | //Use the default setup if you are just getting started with the MAX30105 sensor 436 | void MAX30105::setup(byte powerLevel, byte sampleAverage, byte ledMode, int sampleRate, int pulseWidth, int adcRange) { 437 | softReset(); //Reset all configuration, threshold, and data registers to POR values 438 | 439 | //FIFO Configuration 440 | //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 441 | //The chip will average multiple samples of same type together if you wish 442 | if (sampleAverage == 1) setFIFOAverage(MAX30105_SAMPLEAVG_1); //No averaging per FIFO record 443 | else if (sampleAverage == 2) setFIFOAverage(MAX30105_SAMPLEAVG_2); 444 | else if (sampleAverage == 4) setFIFOAverage(MAX30105_SAMPLEAVG_4); 445 | else if (sampleAverage == 8) setFIFOAverage(MAX30105_SAMPLEAVG_8); 446 | else if (sampleAverage == 16) setFIFOAverage(MAX30105_SAMPLEAVG_16); 447 | else if (sampleAverage == 32) setFIFOAverage(MAX30105_SAMPLEAVG_32); 448 | else setFIFOAverage(MAX30105_SAMPLEAVG_4); 449 | 450 | //setFIFOAlmostFull(2); //Set to 30 samples to trigger an 'Almost Full' interrupt 451 | enableFIFORollover(); //Allow FIFO to wrap/roll over 452 | //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 453 | 454 | //Mode Configuration 455 | //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 456 | if (ledMode == 3) setLEDMode(MAX30105_MODE_MULTILED); //Watch all three LED channels 457 | else if (ledMode == 2) setLEDMode(MAX30105_MODE_REDIRONLY); //Red and IR 458 | else setLEDMode(MAX30105_MODE_REDONLY); //Red only 459 | activeLEDs = ledMode; //Used to control how many bytes to read from FIFO buffer 460 | //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 461 | 462 | //Particle Sensing Configuration 463 | //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 464 | if(adcRange < 4096) setADCRange(MAX30105_ADCRANGE_2048); //7.81pA per LSB 465 | else if(adcRange < 8192) setADCRange(MAX30105_ADCRANGE_4096); //15.63pA per LSB 466 | else if(adcRange < 16384) setADCRange(MAX30105_ADCRANGE_8192); //31.25pA per LSB 467 | else if(adcRange == 16384) setADCRange(MAX30105_ADCRANGE_16384); //62.5pA per LSB 468 | else setADCRange(MAX30105_ADCRANGE_2048); 469 | 470 | if (sampleRate < 100) setSampleRate(MAX30105_SAMPLERATE_50); //Take 50 samples per second 471 | else if (sampleRate < 200) setSampleRate(MAX30105_SAMPLERATE_100); 472 | else if (sampleRate < 400) setSampleRate(MAX30105_SAMPLERATE_200); 473 | else if (sampleRate < 800) setSampleRate(MAX30105_SAMPLERATE_400); 474 | else if (sampleRate < 1000) setSampleRate(MAX30105_SAMPLERATE_800); 475 | else if (sampleRate < 1600) setSampleRate(MAX30105_SAMPLERATE_1000); 476 | else if (sampleRate < 3200) setSampleRate(MAX30105_SAMPLERATE_1600); 477 | else if (sampleRate == 3200) setSampleRate(MAX30105_SAMPLERATE_3200); 478 | else setSampleRate(MAX30105_SAMPLERATE_50); 479 | 480 | //The longer the pulse width the longer range of detection you'll have 481 | //At 69us and 0.4mA it's about 2 inches 482 | //At 411us and 0.4mA it's about 6 inches 483 | if (pulseWidth < 118) setPulseWidth(MAX30105_PULSEWIDTH_69); //Page 26, Gets us 15 bit resolution 484 | else if (pulseWidth < 215) setPulseWidth(MAX30105_PULSEWIDTH_118); //16 bit resolution 485 | else if (pulseWidth < 411) setPulseWidth(MAX30105_PULSEWIDTH_215); //17 bit resolution 486 | else if (pulseWidth == 411) setPulseWidth(MAX30105_PULSEWIDTH_411); //18 bit resolution 487 | else setPulseWidth(MAX30105_PULSEWIDTH_69); 488 | //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 489 | 490 | //LED Pulse Amplitude Configuration 491 | //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 492 | //Default is 0x1F which gets us 6.4mA 493 | //powerLevel = 0x02, 0.4mA - Presence detection of ~4 inch 494 | //powerLevel = 0x1F, 6.4mA - Presence detection of ~8 inch 495 | //powerLevel = 0x7F, 25.4mA - Presence detection of ~8 inch 496 | //powerLevel = 0xFF, 50.0mA - Presence detection of ~12 inch 497 | 498 | setPulseAmplitudeRed(powerLevel); 499 | setPulseAmplitudeIR(powerLevel); 500 | setPulseAmplitudeGreen(powerLevel); 501 | setPulseAmplitudeProximity(powerLevel); 502 | //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 503 | 504 | //Multi-LED Mode Configuration, Enable the reading of the three LEDs 505 | //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 506 | enableSlot(1, SLOT_RED_LED); 507 | if (ledMode > 1) enableSlot(2, SLOT_IR_LED); 508 | if (ledMode > 2) enableSlot(3, SLOT_GREEN_LED); 509 | //enableSlot(1, SLOT_RED_PILOT); 510 | //enableSlot(2, SLOT_IR_PILOT); 511 | //enableSlot(3, SLOT_GREEN_PILOT); 512 | //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 513 | 514 | clearFIFO(); //Reset the FIFO before we begin checking the sensor 515 | } 516 | 517 | // 518 | // Data Collection 519 | // 520 | 521 | //Tell caller how many samples are available 522 | uint8_t MAX30105::available(void) 523 | { 524 | int8_t numberOfSamples = sense.head - sense.tail; 525 | if (numberOfSamples < 0) numberOfSamples += STORAGE_SIZE; 526 | 527 | return (numberOfSamples); 528 | } 529 | 530 | //Report the most recent red value 531 | uint32_t MAX30105::getRed(void) 532 | { 533 | //Check the sensor for new data for 250ms 534 | if(safeCheck(250)) 535 | return (sense.red[sense.head]); 536 | else 537 | return(0); //Sensor failed to find new data 538 | } 539 | 540 | //Report the most recent IR value 541 | uint32_t MAX30105::getIR(void) 542 | { 543 | //Check the sensor for new data for 250ms 544 | if(safeCheck(250)) 545 | return (sense.IR[sense.head]); 546 | else 547 | return(0); //Sensor failed to find new data 548 | } 549 | 550 | //Report the most recent Green value 551 | uint32_t MAX30105::getGreen(void) 552 | { 553 | //Check the sensor for new data for 250ms 554 | if(safeCheck(250)) 555 | return (sense.green[sense.head]); 556 | else 557 | return(0); //Sensor failed to find new data 558 | } 559 | 560 | //Report the next Red value in the FIFO 561 | uint32_t MAX30105::getFIFORed(void) 562 | { 563 | return (sense.red[sense.tail]); 564 | } 565 | 566 | //Report the next IR value in the FIFO 567 | uint32_t MAX30105::getFIFOIR(void) 568 | { 569 | return (sense.IR[sense.tail]); 570 | } 571 | 572 | //Report the next Green value in the FIFO 573 | uint32_t MAX30105::getFIFOGreen(void) 574 | { 575 | return (sense.green[sense.tail]); 576 | } 577 | 578 | //Advance the tail 579 | void MAX30105::nextSample(void) 580 | { 581 | if(available()) //Only advance the tail if new data is available 582 | { 583 | sense.tail++; 584 | sense.tail %= STORAGE_SIZE; //Wrap condition 585 | } 586 | } 587 | 588 | //Polls the sensor for new data 589 | //Call regularly 590 | //If new data is available, it updates the head and tail in the main struct 591 | //Returns number of new samples obtained 592 | uint16_t MAX30105::check(void) 593 | { 594 | //Read register FIDO_DATA in (3-byte * number of active LED) chunks 595 | //Until FIFO_RD_PTR = FIFO_WR_PTR 596 | 597 | byte readPointer = getReadPointer(); 598 | byte writePointer = getWritePointer(); 599 | 600 | int numberOfSamples = 0; 601 | 602 | //Do we have new data? 603 | if (readPointer != writePointer) 604 | { 605 | //Calculate the number of readings we need to get from sensor 606 | numberOfSamples = writePointer - readPointer; 607 | if (numberOfSamples < 0) numberOfSamples += 32; //Wrap condition 608 | 609 | //We now have the number of readings, now calc bytes to read 610 | //For this example we are just doing Red and IR (3 bytes each) 611 | int bytesLeftToRead = numberOfSamples * activeLEDs * 3; 612 | 613 | //Get ready to read a burst of data from the FIFO register 614 | _i2cPort->beginTransmission(MAX30105_ADDRESS); 615 | _i2cPort->write(MAX30105_FIFODATA); 616 | _i2cPort->endTransmission(); 617 | 618 | //We may need to read as many as 288 bytes so we read in blocks no larger than I2C_BUFFER_LENGTH 619 | //I2C_BUFFER_LENGTH changes based on the platform. 64 bytes for SAMD21, 32 bytes for Uno. 620 | //Wire.requestFrom() is limited to BUFFER_LENGTH which is 32 on the Uno 621 | while (bytesLeftToRead > 0) 622 | { 623 | int toGet = bytesLeftToRead; 624 | if (toGet > I2C_BUFFER_LENGTH) 625 | { 626 | //If toGet is 32 this is bad because we read 6 bytes (Red+IR * 3 = 6) at a time 627 | //32 % 6 = 2 left over. We don't want to request 32 bytes, we want to request 30. 628 | //32 % 9 (Red+IR+GREEN) = 5 left over. We want to request 27. 629 | 630 | toGet = I2C_BUFFER_LENGTH - (I2C_BUFFER_LENGTH % (activeLEDs * 3)); //Trim toGet to be a multiple of the samples we need to read 631 | } 632 | 633 | bytesLeftToRead -= toGet; 634 | 635 | //Request toGet number of bytes from sensor 636 | _i2cPort->requestFrom(MAX30105_ADDRESS, toGet); 637 | 638 | while (toGet > 0) 639 | { 640 | sense.head++; //Advance the head of the storage struct 641 | sense.head %= STORAGE_SIZE; //Wrap condition 642 | 643 | byte temp[sizeof(uint32_t)]; //Array of 4 bytes that we will convert into long 644 | uint32_t tempLong; 645 | 646 | //Burst read three bytes - RED 647 | temp[3] = 0; 648 | temp[2] = _i2cPort->read(); 649 | temp[1] = _i2cPort->read(); 650 | temp[0] = _i2cPort->read(); 651 | 652 | //Convert array to long 653 | memcpy(&tempLong, temp, sizeof(tempLong)); 654 | 655 | tempLong &= 0x3FFFF; //Zero out all but 18 bits 656 | 657 | sense.red[sense.head] = tempLong; //Store this reading into the sense array 658 | 659 | if (activeLEDs > 1) 660 | { 661 | //Burst read three more bytes - IR 662 | temp[3] = 0; 663 | temp[2] = _i2cPort->read(); 664 | temp[1] = _i2cPort->read(); 665 | temp[0] = _i2cPort->read(); 666 | 667 | //Convert array to long 668 | memcpy(&tempLong, temp, sizeof(tempLong)); 669 | 670 | tempLong &= 0x3FFFF; //Zero out all but 18 bits 671 | 672 | sense.IR[sense.head] = tempLong; 673 | } 674 | 675 | if (activeLEDs > 2) 676 | { 677 | //Burst read three more bytes - Green 678 | temp[3] = 0; 679 | temp[2] = _i2cPort->read(); 680 | temp[1] = _i2cPort->read(); 681 | temp[0] = _i2cPort->read(); 682 | 683 | //Convert array to long 684 | memcpy(&tempLong, temp, sizeof(tempLong)); 685 | 686 | tempLong &= 0x3FFFF; //Zero out all but 18 bits 687 | 688 | sense.green[sense.head] = tempLong; 689 | } 690 | 691 | toGet -= activeLEDs * 3; 692 | } 693 | 694 | } //End while (bytesLeftToRead > 0) 695 | 696 | } //End readPtr != writePtr 697 | 698 | return (numberOfSamples); //Let the world know how much new data we found 699 | } 700 | 701 | //Check for new data but give up after a certain amount of time 702 | //Returns true if new data was found 703 | //Returns false if new data was not found 704 | bool MAX30105::safeCheck(uint8_t maxTimeToCheck) 705 | { 706 | uint32_t markTime = millis(); 707 | 708 | while(1) 709 | { 710 | if(millis() - markTime > maxTimeToCheck) return(false); 711 | 712 | if(check() == true) //We found new data! 713 | return(true); 714 | 715 | delay(1); 716 | } 717 | } 718 | 719 | //Given a register, read it, mask it, and then set the thing 720 | void MAX30105::bitMask(uint8_t reg, uint8_t mask, uint8_t thing) 721 | { 722 | // Grab current register context 723 | uint8_t originalContents = readRegister8(_i2caddr, reg); 724 | 725 | // Zero-out the portions of the register we're interested in 726 | originalContents = originalContents & mask; 727 | 728 | // Change contents 729 | writeRegister8(_i2caddr, reg, originalContents | thing); 730 | } 731 | 732 | // 733 | // Low-level I2C Communication 734 | // 735 | uint8_t MAX30105::readRegister8(uint8_t address, uint8_t reg) { 736 | _i2cPort->beginTransmission(address); 737 | _i2cPort->write(reg); 738 | _i2cPort->endTransmission(false); 739 | 740 | _i2cPort->requestFrom((uint8_t)address, (uint8_t)1); // Request 1 byte 741 | if (_i2cPort->available()) 742 | { 743 | return(_i2cPort->read()); 744 | } 745 | 746 | return (0); //Fail 747 | 748 | } 749 | 750 | void MAX30105::writeRegister8(uint8_t address, uint8_t reg, uint8_t value) { 751 | _i2cPort->beginTransmission(address); 752 | _i2cPort->write(reg); 753 | _i2cPort->write(value); 754 | _i2cPort->endTransmission(); 755 | } 756 | -------------------------------------------------------------------------------- /src/MAX30105.h: -------------------------------------------------------------------------------- 1 | /*************************************************** 2 | This is a library written for the Maxim MAX30105 Optical Smoke Detector 3 | It should also work with the MAX30102. However, the MAX30102 does not have a Green LED. 4 | 5 | These sensors use I2C to communicate, as well as a single (optional) 6 | interrupt line that is not currently supported in this driver. 7 | 8 | Written by Peter Jansen and Nathan Seidle (SparkFun) 9 | BSD license, all text above must be included in any redistribution. 10 | *****************************************************/ 11 | 12 | #pragma once 13 | 14 | #if (ARDUINO >= 100) 15 | #include "Arduino.h" 16 | #else 17 | #include "WProgram.h" 18 | #endif 19 | 20 | #include 21 | 22 | #define MAX30105_ADDRESS 0x57 //7-bit I2C Address 23 | //Note that MAX30102 has the same I2C address and Part ID 24 | 25 | #define I2C_SPEED_STANDARD 100000 26 | #define I2C_SPEED_FAST 400000 27 | 28 | //Define the size of the I2C buffer based on the platform the user has 29 | #if defined(__AVR_ATmega328P__) || defined(__AVR_ATmega168__) 30 | 31 | //I2C_BUFFER_LENGTH is defined in Wire.H 32 | #define I2C_BUFFER_LENGTH BUFFER_LENGTH 33 | 34 | #elif defined(__SAMD21G18A__) 35 | 36 | //SAMD21 uses RingBuffer.h 37 | #define I2C_BUFFER_LENGTH SERIAL_BUFFER_SIZE 38 | 39 | #else 40 | 41 | //The catch-all default is 32 42 | #define I2C_BUFFER_LENGTH 32 43 | 44 | #endif 45 | 46 | class MAX30105 { 47 | public: 48 | MAX30105(void); 49 | 50 | boolean begin(TwoWire &wirePort = Wire, uint32_t i2cSpeed = I2C_SPEED_STANDARD, uint8_t i2caddr = MAX30105_ADDRESS); 51 | 52 | uint32_t getRed(void); //Returns immediate red value 53 | uint32_t getIR(void); //Returns immediate IR value 54 | uint32_t getGreen(void); //Returns immediate green value 55 | bool safeCheck(uint8_t maxTimeToCheck); //Given a max amount of time, check for new data 56 | 57 | // Configuration 58 | void softReset(); 59 | void shutDown(); 60 | void wakeUp(); 61 | 62 | void setLEDMode(uint8_t mode); 63 | 64 | void setADCRange(uint8_t adcRange); 65 | void setSampleRate(uint8_t sampleRate); 66 | void setPulseWidth(uint8_t pulseWidth); 67 | 68 | void setPulseAmplitudeRed(uint8_t value); 69 | void setPulseAmplitudeIR(uint8_t value); 70 | void setPulseAmplitudeGreen(uint8_t value); 71 | void setPulseAmplitudeProximity(uint8_t value); 72 | 73 | void setProximityThreshold(uint8_t threshMSB); 74 | 75 | //Multi-led configuration mode (page 22) 76 | void enableSlot(uint8_t slotNumber, uint8_t device); //Given slot number, assign a device to slot 77 | void disableSlots(void); 78 | 79 | // Data Collection 80 | 81 | //Interrupts (page 13, 14) 82 | uint8_t getINT1(void); //Returns the main interrupt group 83 | uint8_t getINT2(void); //Returns the temp ready interrupt 84 | void enableAFULL(void); //Enable/disable individual interrupts 85 | void disableAFULL(void); 86 | void enableDATARDY(void); 87 | void disableDATARDY(void); 88 | void enableALCOVF(void); 89 | void disableALCOVF(void); 90 | void enablePROXINT(void); 91 | void disablePROXINT(void); 92 | void enableDIETEMPRDY(void); 93 | void disableDIETEMPRDY(void); 94 | 95 | //FIFO Configuration (page 18) 96 | void setFIFOAverage(uint8_t samples); 97 | void enableFIFORollover(); 98 | void disableFIFORollover(); 99 | void setFIFOAlmostFull(uint8_t samples); 100 | 101 | //FIFO Reading 102 | uint16_t check(void); //Checks for new data and fills FIFO 103 | uint8_t available(void); //Tells caller how many new samples are available (head - tail) 104 | void nextSample(void); //Advances the tail of the sense array 105 | uint32_t getFIFORed(void); //Returns the FIFO sample pointed to by tail 106 | uint32_t getFIFOIR(void); //Returns the FIFO sample pointed to by tail 107 | uint32_t getFIFOGreen(void); //Returns the FIFO sample pointed to by tail 108 | 109 | uint8_t getWritePointer(void); 110 | uint8_t getReadPointer(void); 111 | void clearFIFO(void); //Sets the read/write pointers to zero 112 | 113 | //Proximity Mode Interrupt Threshold 114 | void setPROXINTTHRESH(uint8_t val); 115 | 116 | // Die Temperature 117 | float readTemperature(); 118 | float readTemperatureF(); 119 | 120 | // Detecting ID/Revision 121 | uint8_t getRevisionID(); 122 | uint8_t readPartID(); 123 | 124 | // Setup the IC with user selectable settings 125 | void setup(byte powerLevel = 0x1F, byte sampleAverage = 4, byte ledMode = 3, int sampleRate = 400, int pulseWidth = 411, int adcRange = 4096); 126 | 127 | // Low-level I2C communication 128 | uint8_t readRegister8(uint8_t address, uint8_t reg); 129 | void writeRegister8(uint8_t address, uint8_t reg, uint8_t value); 130 | 131 | private: 132 | TwoWire *_i2cPort; //The generic connection to user's chosen I2C hardware 133 | uint8_t _i2caddr; 134 | 135 | //activeLEDs is the number of channels turned on, and can be 1 to 3. 2 is common for Red+IR. 136 | byte activeLEDs; //Gets set during setup. Allows check() to calculate how many bytes to read from FIFO 137 | 138 | uint8_t revisionID; 139 | 140 | void readRevisionID(); 141 | 142 | void bitMask(uint8_t reg, uint8_t mask, uint8_t thing); 143 | 144 | #define STORAGE_SIZE 4 //Each long is 4 bytes so limit this to fit on your micro 145 | typedef struct Record 146 | { 147 | uint32_t red[STORAGE_SIZE]; 148 | uint32_t IR[STORAGE_SIZE]; 149 | uint32_t green[STORAGE_SIZE]; 150 | byte head; 151 | byte tail; 152 | } sense_struct; //This is our circular buffer of readings from the sensor 153 | 154 | sense_struct sense; 155 | 156 | }; 157 | -------------------------------------------------------------------------------- /src/heartRate.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Optical Heart Rate Detection (PBA Algorithm) 3 | By: Nathan Seidle 4 | SparkFun Electronics 5 | Date: October 2nd, 2016 6 | 7 | Given a series of IR samples from the MAX30105 we discern when a heart beat is occurring 8 | 9 | Let's have a brief chat about what this code does. We're going to try to detect 10 | heart-rate optically. This is tricky and prone to give false readings. We really don't 11 | want to get anyone hurt so use this code only as an example of how to process optical 12 | data. Build fun stuff with our MAX30105 breakout board but don't use it for actual 13 | medical diagnosis. 14 | 15 | Excellent background on optical heart rate detection: 16 | http://www.ti.com/lit/an/slaa655/slaa655.pdf 17 | 18 | Good reading: 19 | http://www.techforfuture.nl/fjc_documents/mitrabaratchi-measuringheartratewithopticalsensor.pdf 20 | https://fruct.org/publications/fruct13/files/Lau.pdf 21 | 22 | This is an implementation of Maxim's PBA (Penpheral Beat Amplitude) algorithm. It's been 23 | converted to work within the Arduino framework. 24 | */ 25 | 26 | /* Copyright (C) 2016 Maxim Integrated Products, Inc., All Rights Reserved. 27 | * 28 | * Permission is hereby granted, free of charge, to any person obtaining a 29 | * copy of this software and associated documentation files (the "Software"), 30 | * to deal in the Software without restriction, including without limitation 31 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 32 | * and/or sell copies of the Software, and to permit persons to whom the 33 | * Software is furnished to do so, subject to the following conditions: 34 | * 35 | * The above copyright notice and this permission notice shall be included 36 | * in all copies or substantial portions of the Software. 37 | * 38 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 39 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 40 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 41 | * IN NO EVENT SHALL MAXIM INTEGRATED BE LIABLE FOR ANY CLAIM, DAMAGES 42 | * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 43 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 44 | * OTHER DEALINGS IN THE SOFTWARE. 45 | * 46 | * Except as contained in this notice, the name of Maxim Integrated 47 | * Products, Inc. shall not be used except as stated in the Maxim Integrated 48 | * Products, Inc. Branding Policy. 49 | * 50 | * The mere transfer of this software does not imply any licenses 51 | * of trade secrets, proprietary technology, copyrights, patents, 52 | * trademarks, maskwork rights, or any other form of intellectual 53 | * property whatsoever. Maxim Integrated Products, Inc. retains all 54 | * ownership rights. 55 | * 56 | */ 57 | 58 | #include "heartRate.h" 59 | 60 | int16_t IR_AC_Max = 20; 61 | int16_t IR_AC_Min = -20; 62 | 63 | int16_t IR_AC_Signal_Current = 0; 64 | int16_t IR_AC_Signal_Previous; 65 | int16_t IR_AC_Signal_min = 0; 66 | int16_t IR_AC_Signal_max = 0; 67 | int16_t IR_Average_Estimated; 68 | 69 | int16_t positiveEdge = 0; 70 | int16_t negativeEdge = 0; 71 | int32_t ir_avg_reg = 0; 72 | 73 | int16_t cbuf[32]; 74 | uint8_t offset = 0; 75 | 76 | static const uint16_t FIRCoeffs[12] = {172, 321, 579, 927, 1360, 1858, 2390, 2916, 3391, 3768, 4012, 4096}; 77 | 78 | // Heart Rate Monitor functions takes a sample value and the sample number 79 | // Returns true if a beat is detected 80 | // A running average of four samples is recommended for display on the screen. 81 | bool checkForBeat(int32_t sample) 82 | { 83 | bool beatDetected = false; 84 | 85 | // Save current state 86 | IR_AC_Signal_Previous = IR_AC_Signal_Current; 87 | 88 | //This is good to view for debugging 89 | //Serial.print("Signal_Current: "); 90 | //Serial.println(IR_AC_Signal_Current); 91 | 92 | // Process next data sample 93 | IR_Average_Estimated = averageDCEstimator(&ir_avg_reg, sample); 94 | IR_AC_Signal_Current = lowPassFIRFilter(sample - IR_Average_Estimated); 95 | 96 | // Detect positive zero crossing (rising edge) 97 | if ((IR_AC_Signal_Previous < 0) & (IR_AC_Signal_Current >= 0)) 98 | { 99 | 100 | IR_AC_Max = IR_AC_Signal_max; //Adjust our AC max and min 101 | IR_AC_Min = IR_AC_Signal_min; 102 | 103 | positiveEdge = 1; 104 | negativeEdge = 0; 105 | IR_AC_Signal_max = 0; 106 | 107 | //if ((IR_AC_Max - IR_AC_Min) > 100 & (IR_AC_Max - IR_AC_Min) < 1000) 108 | if ((IR_AC_Max - IR_AC_Min) > 20 & (IR_AC_Max - IR_AC_Min) < 1000) 109 | { 110 | //Heart beat!!! 111 | beatDetected = true; 112 | } 113 | } 114 | 115 | // Detect negative zero crossing (falling edge) 116 | if ((IR_AC_Signal_Previous > 0) & (IR_AC_Signal_Current <= 0)) 117 | { 118 | positiveEdge = 0; 119 | negativeEdge = 1; 120 | IR_AC_Signal_min = 0; 121 | } 122 | 123 | // Find Maximum value in positive cycle 124 | if (positiveEdge & (IR_AC_Signal_Current > IR_AC_Signal_Previous)) 125 | { 126 | IR_AC_Signal_max = IR_AC_Signal_Current; 127 | } 128 | 129 | // Find Minimum value in negative cycle 130 | if (negativeEdge & (IR_AC_Signal_Current < IR_AC_Signal_Previous)) 131 | { 132 | IR_AC_Signal_min = IR_AC_Signal_Current; 133 | } 134 | 135 | return(beatDetected); 136 | } 137 | 138 | // Average DC Estimator 139 | int16_t averageDCEstimator(int32_t *p, uint16_t x) 140 | { 141 | *p += ((((long) x << 15) - *p) >> 4); 142 | return (*p >> 15); 143 | } 144 | 145 | // Low Pass FIR Filter 146 | int16_t lowPassFIRFilter(int16_t din) 147 | { 148 | cbuf[offset] = din; 149 | 150 | int32_t z = mul16(FIRCoeffs[11], cbuf[(offset - 11) & 0x1F]); 151 | 152 | for (uint8_t i = 0 ; i < 11 ; i++) 153 | { 154 | z += mul16(FIRCoeffs[i], cbuf[(offset - i) & 0x1F] + cbuf[(offset - 22 + i) & 0x1F]); 155 | } 156 | 157 | offset++; 158 | offset %= 32; //Wrap condition 159 | 160 | return(z >> 15); 161 | } 162 | 163 | // Integer multiplier 164 | int32_t mul16(int16_t x, int16_t y) 165 | { 166 | return((long)x * (long)y); 167 | } 168 | -------------------------------------------------------------------------------- /src/heartRate.h: -------------------------------------------------------------------------------- 1 | /* 2 | Optical Heart Rate Detection (PBA Algorithm) 3 | By: Nathan Seidle 4 | SparkFun Electronics 5 | Date: October 2nd, 2016 6 | 7 | Given a series of IR samples from the MAX30105 we discern when a heart beat is occurring 8 | 9 | Let's have a brief chat about what this code does. We're going to try to detect 10 | heart-rate optically. This is tricky and prone to give false readings. We really don't 11 | want to get anyone hurt so use this code only as an example of how to process optical 12 | data. Build fun stuff with our MAX30105 breakout board but don't use it for actual 13 | medical diagnosis. 14 | 15 | Excellent background on optical heart rate detection: 16 | http://www.ti.com/lit/an/slaa655/slaa655.pdf 17 | 18 | Good reading: 19 | http://www.techforfuture.nl/fjc_documents/mitrabaratchi-measuringheartratewithopticalsensor.pdf 20 | https://fruct.org/publications/fruct13/files/Lau.pdf 21 | 22 | This is an implementation of Maxim's PBA (Penpheral Beat Amplitude) algorithm. It's been 23 | converted to work within the Arduino framework. 24 | */ 25 | 26 | /* Copyright (C) 2016 Maxim Integrated Products, Inc., All Rights Reserved. 27 | * 28 | * Permission is hereby granted, free of charge, to any person obtaining a 29 | * copy of this software and associated documentation files (the "Software"), 30 | * to deal in the Software without restriction, including without limitation 31 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 32 | * and/or sell copies of the Software, and to permit persons to whom the 33 | * Software is furnished to do so, subject to the following conditions: 34 | * 35 | * The above copyright notice and this permission notice shall be included 36 | * in all copies or substantial portions of the Software. 37 | * 38 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 39 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 40 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 41 | * IN NO EVENT SHALL MAXIM INTEGRATED BE LIABLE FOR ANY CLAIM, DAMAGES 42 | * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 43 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 44 | * OTHER DEALINGS IN THE SOFTWARE. 45 | * 46 | * Except as contained in this notice, the name of Maxim Integrated 47 | * Products, Inc. shall not be used except as stated in the Maxim Integrated 48 | * Products, Inc. Branding Policy. 49 | * 50 | * The mere transfer of this software does not imply any licenses 51 | * of trade secrets, proprietary technology, copyrights, patents, 52 | * trademarks, maskwork rights, or any other form of intellectual 53 | * property whatsoever. Maxim Integrated Products, Inc. retains all 54 | * ownership rights. 55 | * 56 | */ 57 | 58 | #if (ARDUINO >= 100) 59 | #include "Arduino.h" 60 | #else 61 | #include "WProgram.h" 62 | #endif 63 | 64 | bool checkForBeat(int32_t sample); 65 | int16_t averageDCEstimator(int32_t *p, uint16_t x); 66 | int16_t lowPassFIRFilter(int16_t din); 67 | int32_t mul16(int16_t x, int16_t y); 68 | -------------------------------------------------------------------------------- /src/spo2_algorithm.cpp: -------------------------------------------------------------------------------- 1 | /** \file algorithm.cpp ****************************************************** 2 | * 3 | * Project: MAXREFDES117# 4 | * Filename: algorithm.cpp 5 | * Description: This module calculates the heart rate/SpO2 level 6 | * 7 | * 8 | * -------------------------------------------------------------------- 9 | * 10 | * This code follows the following naming conventions: 11 | * 12 | * char ch_pmod_value 13 | * char (array) s_pmod_s_string[16] 14 | * float f_pmod_value 15 | * int32_t n_pmod_value 16 | * int32_t (array) an_pmod_value[16] 17 | * int16_t w_pmod_value 18 | * int16_t (array) aw_pmod_value[16] 19 | * uint16_t uw_pmod_value 20 | * uint16_t (array) auw_pmod_value[16] 21 | * uint8_t uch_pmod_value 22 | * uint8_t (array) auch_pmod_buffer[16] 23 | * uint32_t un_pmod_value 24 | * int32_t * pn_pmod_value 25 | * 26 | * ------------------------------------------------------------------------- */ 27 | /******************************************************************************* 28 | * Copyright (C) 2016 Maxim Integrated Products, Inc., All Rights Reserved. 29 | * 30 | * Permission is hereby granted, free of charge, to any person obtaining a 31 | * copy of this software and associated documentation files (the "Software"), 32 | * to deal in the Software without restriction, including without limitation 33 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 34 | * and/or sell copies of the Software, and to permit persons to whom the 35 | * Software is furnished to do so, subject to the following conditions: 36 | * 37 | * The above copyright notice and this permission notice shall be included 38 | * in all copies or substantial portions of the Software. 39 | * 40 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 41 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 42 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 43 | * IN NO EVENT SHALL MAXIM INTEGRATED BE LIABLE FOR ANY CLAIM, DAMAGES 44 | * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 45 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 46 | * OTHER DEALINGS IN THE SOFTWARE. 47 | * 48 | * Except as contained in this notice, the name of Maxim Integrated 49 | * Products, Inc. shall not be used except as stated in the Maxim Integrated 50 | * Products, Inc. Branding Policy. 51 | * 52 | * The mere transfer of this software does not imply any licenses 53 | * of trade secrets, proprietary technology, copyrights, patents, 54 | * trademarks, maskwork rights, or any other form of intellectual 55 | * property whatsoever. Maxim Integrated Products, Inc. retains all 56 | * ownership rights. 57 | ******************************************************************************* 58 | */ 59 | 60 | #include "Arduino.h" 61 | #include "spo2_algorithm.h" 62 | 63 | #if defined(__AVR_ATmega328P__) || defined(__AVR_ATmega168__) 64 | //Arduino Uno doesn't have enough SRAM to store 100 samples of IR led data and red led data in 32-bit format 65 | //To solve this problem, 16-bit MSB of the sampled data will be truncated. Samples become 16-bit data. 66 | 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, 67 | int32_t *pn_heart_rate, int8_t *pch_hr_valid) 68 | #else 69 | 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, 70 | int32_t *pn_heart_rate, int8_t *pch_hr_valid) 71 | #endif 72 | /** 73 | * \brief Calculate the heart rate and SpO2 level 74 | * \par Details 75 | * By detecting peaks of PPG cycle and corresponding AC/DC of red/infra-red signal, the an_ratio for the SPO2 is computed. 76 | * Since this algorithm is aiming for Arm M0/M3. formaula for SPO2 did not achieve the accuracy due to register overflow. 77 | * Thus, accurate SPO2 is precalculated and save longo uch_spo2_table[] per each an_ratio. 78 | * 79 | * \param[in] *pun_ir_buffer - IR sensor data buffer 80 | * \param[in] n_ir_buffer_length - IR sensor data buffer length 81 | * \param[in] *pun_red_buffer - Red sensor data buffer 82 | * \param[out] *pn_spo2 - Calculated SpO2 value 83 | * \param[out] *pch_spo2_valid - 1 if the calculated SpO2 value is valid 84 | * \param[out] *pn_heart_rate - Calculated heart rate value 85 | * \param[out] *pch_hr_valid - 1 if the calculated heart rate value is valid 86 | * 87 | * \retval None 88 | */ 89 | { 90 | uint32_t un_ir_mean; 91 | int32_t k, n_i_ratio_count; 92 | int32_t i, n_exact_ir_valley_locs_count, n_middle_idx; 93 | int32_t n_th1, n_npks; 94 | int32_t an_ir_valley_locs[15] ; 95 | int32_t n_peak_interval_sum; 96 | 97 | int32_t n_y_ac, n_x_ac; 98 | int32_t n_spo2_calc; 99 | int32_t n_y_dc_max, n_x_dc_max; 100 | int32_t n_y_dc_max_idx = 0; 101 | int32_t n_x_dc_max_idx = 0; 102 | int32_t an_ratio[5], n_ratio_average; 103 | int32_t n_nume, n_denom ; 104 | 105 | // calculates DC mean and subtract DC from ir 106 | un_ir_mean =0; 107 | for (k=0 ; k60) n_th1=60; // max allowed 126 | 127 | for ( k=0 ; k<15;k++) an_ir_valley_locs[k]=0; 128 | // since we flipped signal, we use peak detector as valley detector 129 | maxim_find_peaks( an_ir_valley_locs, &n_npks, an_x, BUFFER_SIZE, n_th1, 4, 15 );//peak_height, peak_distance, max_num_peaks 130 | n_peak_interval_sum =0; 131 | if (n_npks>=2){ 132 | for (k=1; k BUFFER_SIZE ){ 159 | *pn_spo2 = -999 ; // do not use SPO2 since valley loc is out of range 160 | *pch_spo2_valid = 0; 161 | return; 162 | } 163 | } 164 | // find max between two valley locations 165 | // and use an_ratio betwen AC compoent of Ir & Red and DC compoent of Ir & Red for SPO2 166 | for (k=0; k< n_exact_ir_valley_locs_count-1; k++){ 167 | n_y_dc_max= -16777216 ; 168 | n_x_dc_max= -16777216; 169 | if (an_ir_valley_locs[k+1]-an_ir_valley_locs[k] >3){ 170 | for (i=an_ir_valley_locs[k]; i< an_ir_valley_locs[k+1]; i++){ 171 | if (an_x[i]> n_x_dc_max) {n_x_dc_max =an_x[i]; n_x_dc_max_idx=i;} 172 | if (an_y[i]> n_y_dc_max) {n_y_dc_max =an_y[i]; n_y_dc_max_idx=i;} 173 | } 174 | 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 175 | n_y_ac= an_y[an_ir_valley_locs[k]] + n_y_ac/ (an_ir_valley_locs[k+1] - an_ir_valley_locs[k]) ; 176 | n_y_ac= an_y[n_y_dc_max_idx] - n_y_ac; // subracting linear DC compoenents from raw 177 | 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 178 | n_x_ac= an_x[an_ir_valley_locs[k]] + n_x_ac/ (an_ir_valley_locs[k+1] - an_ir_valley_locs[k]); 179 | n_x_ac= an_x[n_y_dc_max_idx] - n_x_ac; // subracting linear DC compoenents from raw 180 | n_nume=( n_y_ac *n_x_dc_max)>>7 ; //prepare X100 to preserve floating value 181 | n_denom= ( n_x_ac *n_y_dc_max)>>7; 182 | if (n_denom>0 && n_i_ratio_count <5 && n_nume != 0) 183 | { 184 | 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) ; 185 | n_i_ratio_count++; 186 | } 187 | } 188 | } 189 | // choose median value since PPG signal may varies from beat to beat 190 | maxim_sort_ascend(an_ratio, n_i_ratio_count); 191 | n_middle_idx= n_i_ratio_count/2; 192 | 193 | if (n_middle_idx >1) 194 | n_ratio_average =( an_ratio[n_middle_idx-1] +an_ratio[n_middle_idx])/2; // use median 195 | else 196 | n_ratio_average = an_ratio[n_middle_idx ]; 197 | 198 | if( n_ratio_average>2 && n_ratio_average <184){ 199 | n_spo2_calc= uch_spo2_table[n_ratio_average] ; 200 | *pn_spo2 = n_spo2_calc ; 201 | *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 202 | } 203 | else{ 204 | *pn_spo2 = -999 ; // do not use SPO2 since signal an_ratio is out of range 205 | *pch_spo2_valid = 0; 206 | } 207 | } 208 | 209 | 210 | 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 ) 211 | /** 212 | * \brief Find peaks 213 | * \par Details 214 | * Find at most MAX_NUM peaks above MIN_HEIGHT separated by at least MIN_DISTANCE 215 | * 216 | * \retval None 217 | */ 218 | { 219 | maxim_peaks_above_min_height( pn_locs, n_npks, pn_x, n_size, n_min_height ); 220 | maxim_remove_close_peaks( pn_locs, n_npks, pn_x, n_min_distance ); 221 | *n_npks = min( *n_npks, n_max_num ); 222 | } 223 | 224 | 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 ) 225 | /** 226 | * \brief Find peaks above n_min_height 227 | * \par Details 228 | * Find all peaks above MIN_HEIGHT 229 | * 230 | * \retval None 231 | */ 232 | { 233 | int32_t i = 1, n_width; 234 | *n_npks = 0; 235 | 236 | while (i < n_size-1){ 237 | if (pn_x[i] > n_min_height && pn_x[i] > pn_x[i-1]){ // find left edge of potential peaks 238 | n_width = 1; 239 | while (i+n_width < n_size && pn_x[i] == pn_x[i+n_width]) // find flat peaks 240 | n_width++; 241 | if (pn_x[i] > pn_x[i+n_width] && (*n_npks) < 15 ){ // find right edge of peaks 242 | pn_locs[(*n_npks)++] = i; 243 | // for flat peaks, peak location is left edge 244 | i += n_width+1; 245 | } 246 | else 247 | i += n_width; 248 | } 249 | else 250 | i++; 251 | } 252 | } 253 | 254 | void maxim_remove_close_peaks(int32_t *pn_locs, int32_t *pn_npks, int32_t *pn_x, int32_t n_min_distance) 255 | /** 256 | * \brief Remove peaks 257 | * \par Details 258 | * Remove peaks separated by less than MIN_DISTANCE 259 | * 260 | * \retval None 261 | */ 262 | { 263 | 264 | int32_t i, j, n_old_npks, n_dist; 265 | 266 | /* Order peaks from large to small */ 267 | maxim_sort_indices_descend( pn_x, pn_locs, *pn_npks ); 268 | 269 | for ( i = -1; i < *pn_npks; i++ ){ 270 | n_old_npks = *pn_npks; 271 | *pn_npks = i+1; 272 | for ( j = i+1; j < n_old_npks; j++ ){ 273 | n_dist = pn_locs[j] - ( i == -1 ? -1 : pn_locs[i] ); // lag-zero peak of autocorr is at index -1 274 | if ( n_dist > n_min_distance || n_dist < -n_min_distance ) 275 | pn_locs[(*pn_npks)++] = pn_locs[j]; 276 | } 277 | } 278 | 279 | // Resort indices int32_to ascending order 280 | maxim_sort_ascend( pn_locs, *pn_npks ); 281 | } 282 | 283 | void maxim_sort_ascend(int32_t *pn_x, int32_t n_size) 284 | /** 285 | * \brief Sort array 286 | * \par Details 287 | * Sort array in ascending order (insertion sort algorithm) 288 | * 289 | * \retval None 290 | */ 291 | { 292 | int32_t i, j, n_temp; 293 | for (i = 1; i < n_size; i++) { 294 | n_temp = pn_x[i]; 295 | for (j = i; j > 0 && n_temp < pn_x[j-1]; j--) 296 | pn_x[j] = pn_x[j-1]; 297 | pn_x[j] = n_temp; 298 | } 299 | } 300 | 301 | void maxim_sort_indices_descend( int32_t *pn_x, int32_t *pn_indx, int32_t n_size) 302 | /** 303 | * \brief Sort indices 304 | * \par Details 305 | * Sort indices according to descending order (insertion sort algorithm) 306 | * 307 | * \retval None 308 | */ 309 | { 310 | int32_t i, j, n_temp; 311 | for (i = 1; i < n_size; i++) { 312 | n_temp = pn_indx[i]; 313 | for (j = i; j > 0 && pn_x[n_temp] > pn_x[pn_indx[j-1]]; j--) 314 | pn_indx[j] = pn_indx[j-1]; 315 | pn_indx[j] = n_temp; 316 | } 317 | } 318 | 319 | 320 | 321 | 322 | -------------------------------------------------------------------------------- /src/spo2_algorithm.h: -------------------------------------------------------------------------------- 1 | /** \file algorithm.h ****************************************************** 2 | * 3 | * Project: MAXREFDES117# 4 | * Filename: algorithm.h 5 | * Description: This module is the heart rate/SpO2 calculation algorithm header file 6 | * 7 | * Revision History: 8 | *\n 1-18-2016 Rev 01.00 SK Initial release. 9 | *\n 10 | * 11 | * -------------------------------------------------------------------- 12 | * 13 | * This code follows the following naming conventions: 14 | * 15 | *\n char ch_pmod_value 16 | *\n char (array) s_pmod_s_string[16] 17 | *\n float f_pmod_value 18 | *\n int32_t n_pmod_value 19 | *\n int32_t (array) an_pmod_value[16] 20 | *\n int16_t w_pmod_value 21 | *\n int16_t (array) aw_pmod_value[16] 22 | *\n uint16_t uw_pmod_value 23 | *\n uint16_t (array) auw_pmod_value[16] 24 | *\n uint8_t uch_pmod_value 25 | *\n uint8_t (array) auch_pmod_buffer[16] 26 | *\n uint32_t un_pmod_value 27 | *\n int32_t * pn_pmod_value 28 | * 29 | * ------------------------------------------------------------------------- */ 30 | /******************************************************************************* 31 | * Copyright (C) 2015 Maxim Integrated Products, Inc., All Rights Reserved. 32 | * 33 | * Permission is hereby granted, free of charge, to any person obtaining a 34 | * copy of this software and associated documentation files (the "Software"), 35 | * to deal in the Software without restriction, including without limitation 36 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 37 | * and/or sell copies of the Software, and to permit persons to whom the 38 | * Software is furnished to do so, subject to the following conditions: 39 | * 40 | * The above copyright notice and this permission notice shall be included 41 | * in all copies or substantial portions of the Software. 42 | * 43 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 44 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 45 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 46 | * IN NO EVENT SHALL MAXIM INTEGRATED BE LIABLE FOR ANY CLAIM, DAMAGES 47 | * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 48 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 49 | * OTHER DEALINGS IN THE SOFTWARE. 50 | * 51 | * Except as contained in this notice, the name of Maxim Integrated 52 | * Products, Inc. shall not be used except as stated in the Maxim Integrated 53 | * Products, Inc. Branding Policy. 54 | * 55 | * The mere transfer of this software does not imply any licenses 56 | * of trade secrets, proprietary technology, copyrights, patents, 57 | * trademarks, maskwork rights, or any other form of intellectual 58 | * property whatsoever. Maxim Integrated Products, Inc. retains all 59 | * ownership rights. 60 | ******************************************************************************* 61 | */ 62 | #ifndef SPO2_ALGORITHM_H_ 63 | #define SPO2_ALGORITHM_H_ 64 | 65 | #include 66 | 67 | #define FreqS 25 //sampling frequency 68 | #define BUFFER_SIZE (FreqS * 4) 69 | #define MA4_SIZE 4 // DONOT CHANGE 70 | //#define min(x,y) ((x) < (y) ? (x) : (y)) //Defined in Arduino.h 71 | 72 | //uch_spo2_table is approximated as -45.060*ratioAverage* ratioAverage + 30.354 *ratioAverage + 94.845 ; 73 | 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, 74 | 99, 99, 99, 99, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 75 | 100, 100, 100, 100, 99, 99, 99, 99, 99, 99, 99, 99, 98, 98, 98, 98, 98, 98, 97, 97, 76 | 97, 97, 96, 96, 96, 96, 95, 95, 95, 94, 94, 94, 93, 93, 93, 92, 92, 92, 91, 91, 77 | 90, 90, 89, 89, 89, 88, 88, 87, 87, 86, 86, 85, 85, 84, 84, 83, 82, 82, 81, 81, 78 | 80, 80, 79, 78, 78, 77, 76, 76, 75, 74, 74, 73, 72, 72, 71, 70, 69, 69, 68, 67, 79 | 66, 66, 65, 64, 63, 62, 62, 61, 60, 59, 58, 57, 56, 56, 55, 54, 53, 52, 51, 50, 80 | 49, 48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 31, 30, 29, 81 | 28, 27, 26, 25, 23, 22, 21, 20, 19, 17, 16, 15, 14, 12, 11, 10, 9, 7, 6, 5, 82 | 3, 2, 1 } ; 83 | static int32_t an_x[ BUFFER_SIZE]; //ir 84 | static int32_t an_y[ BUFFER_SIZE]; //red 85 | 86 | 87 | #if defined(__AVR_ATmega328P__) || defined(__AVR_ATmega168__) 88 | //Arduino Uno doesn't have enough SRAM to store 100 samples of IR led data and red led data in 32-bit format 89 | //To solve this problem, 16-bit MSB of the sampled data will be truncated. Samples become 16-bit data. 90 | 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); 91 | #else 92 | 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); 93 | #endif 94 | 95 | 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); 96 | 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); 97 | void maxim_remove_close_peaks(int32_t *pn_locs, int32_t *pn_npks, int32_t *pn_x, int32_t n_min_distance); 98 | void maxim_sort_ascend(int32_t *pn_x, int32_t n_size); 99 | void maxim_sort_indices_descend(int32_t *pn_x, int32_t *pn_indx, int32_t n_size); 100 | 101 | #endif /* ALGORITHM_H_ */ 102 | 103 | --------------------------------------------------------------------------------