├── res ├── MAX86150.pdf ├── Health_Sensor_ECG_PPG_MAX86150-1.png ├── Health_Sensor_ECG_PPG_MAX86150-2.png ├── Health_Sensor_ECG_PPG_MAX86150-3.png ├── Health_Sensor_ECG_PPG_MAX86150-4.png ├── Health_Sensor_ECG_PPG_MAX86150-5.png ├── Health_Sensor_ECG_PPG_MAX86150-6.png ├── Health_Sensor_ECG_PPG_MAX86150-7.png ├── Health_Sensor_ECG_PPG_MAX86150-8.png └── Health_Sensor_ECG_PPG_MAX86150-9.png ├── hardware └── ECG_max86150_v1.7 │ ├── ECG_max86150_v1.7_B.png │ └── ECG_max86150_v1.7_T.png ├── code ├── processing │ └── Health_Sensor_Plot_V3_2_Auto_Serial_CSV │ │ ├── data │ │ ├── Helvetica-Light-15.vlw │ │ ├── Helvetica-BoldOblique-72.vlw │ │ └── Helvetica-LightOblique-20.vlw │ │ └── Health_Sensor_Plot_V3_2_Auto_Serial_CSV.pde └── arduino │ ├── WiFi Transmission │ ├── ESPNOW_MACADDRESS_RX │ │ └── ESPNOW_MACADDRESS.ino │ ├── ECG_PPG_MAX86150_V5_WiFi_RX │ │ └── ESPNOW_RX.ino │ └── ECG_PPG_MAX86150_V5_WiFi_TX │ │ └── ECG_PPG_MAX86150_V5_WiFi_TX.ino │ └── ECG_PPG_MAX86150_V4 │ └── ECG_PPG_MAX86150_V4.ino ├── .gitignore ├── LICENSE ├── README.md └── src ├── max86150.h └── max86150.cpp /res/MAX86150.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mic-Tsai/Sensor_ECG_PPG_MAX86150_Dev-Board/HEAD/res/MAX86150.pdf -------------------------------------------------------------------------------- /res/Health_Sensor_ECG_PPG_MAX86150-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mic-Tsai/Sensor_ECG_PPG_MAX86150_Dev-Board/HEAD/res/Health_Sensor_ECG_PPG_MAX86150-1.png -------------------------------------------------------------------------------- /res/Health_Sensor_ECG_PPG_MAX86150-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mic-Tsai/Sensor_ECG_PPG_MAX86150_Dev-Board/HEAD/res/Health_Sensor_ECG_PPG_MAX86150-2.png -------------------------------------------------------------------------------- /res/Health_Sensor_ECG_PPG_MAX86150-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mic-Tsai/Sensor_ECG_PPG_MAX86150_Dev-Board/HEAD/res/Health_Sensor_ECG_PPG_MAX86150-3.png -------------------------------------------------------------------------------- /res/Health_Sensor_ECG_PPG_MAX86150-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mic-Tsai/Sensor_ECG_PPG_MAX86150_Dev-Board/HEAD/res/Health_Sensor_ECG_PPG_MAX86150-4.png -------------------------------------------------------------------------------- /res/Health_Sensor_ECG_PPG_MAX86150-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mic-Tsai/Sensor_ECG_PPG_MAX86150_Dev-Board/HEAD/res/Health_Sensor_ECG_PPG_MAX86150-5.png -------------------------------------------------------------------------------- /res/Health_Sensor_ECG_PPG_MAX86150-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mic-Tsai/Sensor_ECG_PPG_MAX86150_Dev-Board/HEAD/res/Health_Sensor_ECG_PPG_MAX86150-6.png -------------------------------------------------------------------------------- /res/Health_Sensor_ECG_PPG_MAX86150-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mic-Tsai/Sensor_ECG_PPG_MAX86150_Dev-Board/HEAD/res/Health_Sensor_ECG_PPG_MAX86150-7.png -------------------------------------------------------------------------------- /res/Health_Sensor_ECG_PPG_MAX86150-8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mic-Tsai/Sensor_ECG_PPG_MAX86150_Dev-Board/HEAD/res/Health_Sensor_ECG_PPG_MAX86150-8.png -------------------------------------------------------------------------------- /res/Health_Sensor_ECG_PPG_MAX86150-9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mic-Tsai/Sensor_ECG_PPG_MAX86150_Dev-Board/HEAD/res/Health_Sensor_ECG_PPG_MAX86150-9.png -------------------------------------------------------------------------------- /hardware/ECG_max86150_v1.7/ECG_max86150_v1.7_B.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mic-Tsai/Sensor_ECG_PPG_MAX86150_Dev-Board/HEAD/hardware/ECG_max86150_v1.7/ECG_max86150_v1.7_B.png -------------------------------------------------------------------------------- /hardware/ECG_max86150_v1.7/ECG_max86150_v1.7_T.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mic-Tsai/Sensor_ECG_PPG_MAX86150_Dev-Board/HEAD/hardware/ECG_max86150_v1.7/ECG_max86150_v1.7_T.png -------------------------------------------------------------------------------- /code/processing/Health_Sensor_Plot_V3_2_Auto_Serial_CSV/data/Helvetica-Light-15.vlw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mic-Tsai/Sensor_ECG_PPG_MAX86150_Dev-Board/HEAD/code/processing/Health_Sensor_Plot_V3_2_Auto_Serial_CSV/data/Helvetica-Light-15.vlw -------------------------------------------------------------------------------- /code/processing/Health_Sensor_Plot_V3_2_Auto_Serial_CSV/data/Helvetica-BoldOblique-72.vlw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mic-Tsai/Sensor_ECG_PPG_MAX86150_Dev-Board/HEAD/code/processing/Health_Sensor_Plot_V3_2_Auto_Serial_CSV/data/Helvetica-BoldOblique-72.vlw -------------------------------------------------------------------------------- /code/processing/Health_Sensor_Plot_V3_2_Auto_Serial_CSV/data/Helvetica-LightOblique-20.vlw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mic-Tsai/Sensor_ECG_PPG_MAX86150_Dev-Board/HEAD/code/processing/Health_Sensor_Plot_V3_2_Auto_Serial_CSV/data/Helvetica-LightOblique-20.vlw -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | -------------------------------------------------------------------------------- /code/arduino/WiFi Transmission/ESPNOW_MACADDRESS_RX/ESPNOW_MACADDRESS.ino: -------------------------------------------------------------------------------- 1 | // Complete Instructions to Get and Change ESP MAC Address: https://RandomNerdTutorials.com/get-change-esp32-esp8266-mac-address-arduino/ 2 | 3 | #include 4 | 5 | void setup(){ 6 | Serial.begin(38400); 7 | Serial.println(); 8 | Serial.print("ESP8266 Board MAC Address: "); 9 | Serial.println(WiFi.macAddress()); 10 | } 11 | 12 | void loop(){ 13 | 14 | } 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Mic.Tsai 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /code/arduino/WiFi Transmission/ECG_PPG_MAX86150_V5_WiFi_RX/ESPNOW_RX.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Rui Santos 3 | Complete project details at https://RandomNerdTutorials.com/esp-now-esp8266-nodemcu-arduino-ide/ 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files. 7 | 8 | The above copyright notice and this permission notice shall be included in all 9 | copies or substantial portions of the Software. 10 | */ 11 | 12 | #include 13 | #include 14 | 15 | // Structure example to receive data 16 | // Must match the sender structure 17 | typedef struct struct_message { 18 | int a; 19 | int b; 20 | int c; 21 | int d; 22 | } struct_message; 23 | 24 | // Create a struct_message called myData 25 | struct_message myData; 26 | 27 | // Callback function that will be executed when data is received 28 | void OnDataRecv(uint8_t * mac, uint8_t *incomingData, uint8_t len) { 29 | memcpy(&myData, incomingData, sizeof(myData)); 30 | 31 | /* 32 | Serial.print("Bytes received: "); 33 | Serial.println(len); 34 | Serial.print("Char: "); 35 | Serial.println(myData.a); 36 | Serial.print("Int: "); 37 | Serial.println(myData.b); 38 | Serial.print("Float: "); 39 | Serial.println(myData.c); 40 | Serial.print("String: "); 41 | Serial.println(myData.d); 42 | Serial.print("Bool: "); 43 | Serial.println(myData.e); 44 | Serial.println(); 45 | */ 46 | 47 | 48 | Serial.print(millis()); 49 | Serial.print(","); 50 | Serial.print(myData.a - myData.b); 51 | Serial.print(","); 52 | Serial.print(myData.c - myData.d); 53 | Serial.println(","); 54 | } 55 | 56 | void setup() { 57 | // Initialize Serial Monitor 58 | Serial.begin(38400); 59 | 60 | // Set device as a Wi-Fi Station 61 | WiFi.mode(WIFI_STA); 62 | 63 | // Init ESP-NOW 64 | if (esp_now_init() != 0) { 65 | Serial.println("Error initializing ESP-NOW"); 66 | return; 67 | } 68 | 69 | // Once ESPNow is successfully Init, we will register for recv CB to 70 | // get recv packer info 71 | esp_now_set_self_role(ESP_NOW_ROLE_SLAVE); 72 | esp_now_register_recv_cb(OnDataRecv); 73 | } 74 | 75 | void loop() { 76 | 77 | } 78 | 79 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![*Sensor_MAX86150*](https://github.com/Mic-Tsai/Health_Sensor_ecg_ppg_max86150/blob/master/res/Health_Sensor_ECG_PPG_MAX86150-1.png) 2 | ## Sensor_ECG_PPG_MAX86150 | Dev-Board 3 | 4 | [![Donate](https://img.shields.io/badge/paypal-donate-yellow.svg)](https://paypal.me/mictsai?locale.x=zh_TW) 5 | 6 | Arduino based sensor learning project. The feature combines ECG/PPG data with heart rate calculation, plotting the ECG/PPG curve which used Processing on your computer. 7 | 8 | - Bias Cancellation | Store raw data in time, when new data coming that bias level will calculate as hysteresis filter. 9 | 10 | - Heart Rate Detect | Setting upper/lower threshold once your ECG/PPG value cross u/l threshold, check windows will output the BPM results (Both PPG/ECG). 11 | 12 | ## 13 | 14 | ### Wireless Support ### 15 | 16 | This board also support WiFi transmission via ESPNOW (ESP8266 Broadcast), and need power by battery. And you need 2x esp8266 board. 17 | 18 | 1) Read receiver esp8266's mac address. 19 | 2) Fill it into the transmitter sketch. "ECG_PPG_MAX86150_V5_WiFi_TX" 20 | 21 | 22 | 23 | **A Known Issue** 24 | 25 | Sampling rate from MAX86150 set as 200sps, but ESP8266's multiplex with ESP-NOW and sensor hub reading task, it will low down the output sample rate to ~150sps. So, when you measuring your ECG/PPG in real-time plot, it may show the missing R-R interval. (R-peak missing!) Don't worry it just the lower sample rate caused. 26 | 27 | 28 | ``` 29 | Arduino tool kit, and require the following material: 30 | - MCU: ESP8266 31 | - Display: SD1306_128x64_OLED 32 | - Sensor: MAX86150 33 | ``` 34 | ## 35 | 36 | For coding example, you need the following library: 37 | 38 | * [Adafruit_SSD1306](https://github.com/adafruit/Adafruit_SSD1306) 39 | * [protocentral_max86150_ecg_ppg](https://github.com/Protocentral/protocentral_max86150_ecg_ppg) 40 | 41 | ## 42 | 43 | Any question or need technical support: 44 | 45 | * Contact me via mail (xbcke12345@gmail.com) 46 | * If this project helping. Please generously support =) 47 | 48 | [![Donate](https://img.shields.io/badge/paypal-donate-yellow.svg)](https://paypal.me/mictsai?locale.x=zh_TW) 49 | 50 | 51 | ## 52 | ![*Sensor_MAX86150*](https://github.com/Mic-Tsai/Health_Sensor_ecg_ppg_max86150/blob/master/res/Health_Sensor_ECG_PPG_MAX86150-2.png) 53 | ![*Sensor_MAX86150*](https://github.com/Mic-Tsai/Health_Sensor_ecg_ppg_max86150/blob/master/res/Health_Sensor_ECG_PPG_MAX86150-3.png) 54 | ![*Sensor_MAX86150*](https://github.com/Mic-Tsai/Health_Sensor_ecg_ppg_max86150/blob/master/res/Health_Sensor_ECG_PPG_MAX86150-4.png) 55 | ![*Sensor_MAX86150*](https://github.com/Mic-Tsai/Health_Sensor_ecg_ppg_max86150/blob/master/res/Health_Sensor_ECG_PPG_MAX86150-5.png) 56 | ![*Sensor_MAX86150*](https://github.com/Mic-Tsai/Health_Sensor_ecg_ppg_max86150/blob/master/res/Health_Sensor_ECG_PPG_MAX86150-6.png) 57 | ![*Sensor_MAX86150*](https://github.com/Mic-Tsai/Health_Sensor_ecg_ppg_max86150/blob/master/res/Health_Sensor_ECG_PPG_MAX86150-7.png) 58 | ![*Sensor_MAX86150*](https://github.com/Mic-Tsai/Health_Sensor_ecg_ppg_max86150/blob/master/res/Health_Sensor_ECG_PPG_MAX86150-8.png) 59 | ![*Sensor_MAX86150*](https://github.com/Mic-Tsai/Health_Sensor_ecg_ppg_max86150/blob/master/res/Health_Sensor_ECG_PPG_MAX86150-9.png) 60 | ## 61 | 62 | 63 | >### License Information 64 | >>This product is open source! Both, our hardware and software are open source and licensed under the following: 65 | >>#### Hardware 66 | >>>All hardware is released under [Creative Commons Share-alike 4.0 International](http://creativecommons.org/licenses/by-sa/4.0/) 67 | >>#### Software 68 | >>>All software is released under the MIT License [http://opensource.org/licenses/MIT](http://opensource.org/licenses/MIT) 69 | >>#### Documentation 70 | >>>The documentation on this page is released under [Creative Commons Share-alike 4.0 International](http://creativecommons.org/licenses/by-sa/4.0/) 71 | -------------------------------------------------------------------------------- /src/max86150.h: -------------------------------------------------------------------------------- 1 | /*************************************************** 2 | Arduino library written for the Maxim MAX86150 ECG and PPG integrated sensor 3 | 4 | Written by Ashwin Whitchurch, ProtoCentral Electronics (www.protocentral.com) 5 | 6 | https://github.com/protocentral/protocentral_max86150 7 | 8 | Based on code written by Peter Jansen and Nathan Seidle (SparkFun) for the MAX30105 sensor 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 MAX86150_ADDRESS 0x5E //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 MAX86150 { 47 | public: 48 | MAX86150(void); 49 | 50 | boolean begin(TwoWire &wirePort = Wire, uint32_t i2cSpeed = I2C_SPEED_STANDARD, uint8_t i2caddr = MAX86150_ADDRESS); 51 | 52 | uint32_t getRed(void); //Returns immediate red value 53 | uint32_t getIR(void); //Returns immediate IR value 54 | int32_t getECG(void); //Returns immediate ECG 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 setPulseAmplitudeProximity(uint8_t value); 71 | 72 | void setProximityThreshold(uint8_t threshMSB); 73 | 74 | //Multi-led configuration mode (page 22) 75 | void enableSlot(uint8_t slotNumber, uint8_t device); //Given slot number, assign a device to slot 76 | void disableSlots(void); 77 | 78 | // Data Collection 79 | 80 | //Interrupts (page 13, 14) 81 | uint8_t getINT1(void); //Returns the main interrupt group 82 | uint8_t getINT2(void); //Returns the temp ready interrupt 83 | void enableAFULL(void); //Enable/disable individual interrupts 84 | void disableAFULL(void); 85 | void enableDATARDY(void); 86 | void disableDATARDY(void); 87 | void enableALCOVF(void); 88 | void disableALCOVF(void); 89 | void enablePROXINT(void); 90 | void disablePROXINT(void); 91 | void enableDIETEMPRDY(void); 92 | void disableDIETEMPRDY(void); 93 | 94 | //FIFO Configuration (page 18) 95 | void setFIFOAverage(uint8_t samples); 96 | void enableFIFORollover(); 97 | void disableFIFORollover(); 98 | void setFIFOAlmostFull(uint8_t samples); 99 | 100 | //FIFO Reading 101 | uint16_t check(void); //Checks for new data and fills FIFO 102 | uint8_t available(void); //Tells caller how many new samples are available (head - tail) 103 | void nextSample(void); //Advances the tail of the sense array 104 | uint32_t getFIFORed(void); //Returns the FIFO sample pointed to by tail 105 | uint32_t getFIFOIR(void); //Returns the FIFO sample pointed to by tail 106 | int32_t getFIFOECG(void); //Returns the FIFO sample pointed to by tail 107 | 108 | uint8_t getWritePointer(void); 109 | uint8_t getReadPointer(void); 110 | void clearFIFO(void); //Sets the read/write pointers to zero 111 | 112 | //Proximity Mode Interrupt Threshold 113 | void setPROXINTTHRESH(uint8_t val); 114 | 115 | // Die Temperature 116 | float readTemperature(); 117 | float readTemperatureF(); 118 | 119 | // Detecting ID/Revision 120 | uint8_t getRevisionID(); 121 | uint8_t readPartID(); 122 | uint8_t readRegLED(); 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 activeDevices; //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 | int32_t ecg[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 | -------------------------------------------------------------------------------- /code/arduino/ECG_PPG_MAX86150_V4/ECG_PPG_MAX86150_V4.ino: -------------------------------------------------------------------------------- 1 | // ################################################################################### 2 | // # Project: ECG Health Sensor 3 | // # Engineer: Mic.Tsai 4 | // # Date: 1 June 2020 5 | // # Objective: Dev.board 6 | // # Usage: ESP8266 7 | // # Modified: Mode Select with filter / ECG / PPG 8 | // ################################################################################### 9 | 10 | /* 11 | See the output on the Arduino Plotter utlity by: 12 | 1) Program the code to your Arduino 13 | 2) Place your left hand finger and the right hand finger on the two ECG electrode pads 14 | 3) In the Arduino IDE, Open Tools->'Serial Plotter' 15 | 4) Make sure the drop down is set to 38400 baud 16 | 5) See your ECG and heartbeat 17 | This code is released under the [MIT License](http://opensource.org/licenses/MIT). 18 | 19 | -VCC = 5V 20 | -GND = GND 21 | -SDA = A4 (or SDA) 22 | -SCL = A5 (or SCL) 23 | -INT = Not connected 24 | */ 25 | 26 | #include 27 | #include "max86150.h" 28 | 29 | // # LCD install 30 | #include 31 | #include 32 | Adafruit_SSD1306 display(128, 64, &Wire, -1); 33 | 34 | byte TestledBrightness = 50; //Options: 0=Off to 255=50mA //0x1F 35 | 36 | // # ECG sensor 37 | int16_t ecgsigned16; 38 | int16_t redunsigned16; 39 | uint16_t ppgunsigned16; 40 | 41 | MAX86150 max86150Sensor; 42 | 43 | int TimerLast=0; 44 | int TimerNow=0; 45 | int Time=0; 46 | 47 | int Filter_Value; 48 | int Filter_Value2; 49 | int Value; 50 | int Value2; 51 | 52 | // # BTN Mode Select 53 | const int buttonPin = 16; // the pin that the pushbutton is attached to 54 | 55 | // Variables will change: 56 | int buttonPushCounter = 0; // counter for the number of button presses 57 | int buttonState = 0; // current state of the button 58 | int lastButtonState = 0; // previous state of the button 59 | 60 | // PPG 61 | // # Plot 變數宣告區 62 | int a=0; 63 | int lasta=0; 64 | int lastb=0; 65 | int LastTime=0; 66 | int ThisTime; 67 | bool BPMTiming=false; 68 | bool BeatComplete=false; 69 | int BPM=0; 70 | 71 | // # 上限下限 72 | #define UpperThreshold 520 73 | #define LowerThreshold 450 74 | 75 | // # Bias 76 | int LevelSea = 0; 77 | 78 | // # 平均心跳 79 | const byte RATE_SIZE = 36; //Increase this for more averaging. 18 is good. 80 | byte rates[RATE_SIZE]; //Array of heart rates 81 | byte rateSpot = 0; 82 | long lastBeat = 0; //Time at which the last beat occurred 83 | 84 | float beatsPerMinute; 85 | int beatAvg; 86 | int PPGAVG; 87 | 88 | void setup() 89 | { 90 | pinMode(buttonPin, INPUT); 91 | Serial.begin(38400); 92 | Serial.println("MAX86150 Basic Readings Example"); 93 | 94 | // Initialize sensor 95 | if (max86150Sensor.begin(Wire, I2C_SPEED_FAST) == false) 96 | { 97 | Serial.println("MAX86150 was not found. Please check wiring/power. "); 98 | while (1); 99 | } 100 | 101 | //Setup to sense a nice looking saw tooth on the plotter 102 | byte ledBrightness = 255; //Options: 0=Off to 255=50mA 103 | byte sampleAverage = 32; //Options: 1, 2, 4, 8, 16, 32 104 | byte ledMode = 2; //Options: 1 = Red only, 2 = Red + IR, 3 = Red + IR + Green 105 | int sampleRate = 3200; //Options: 50, 100, 200, 400, 800, 1000, 1600, 3200 106 | int pulseWidth = 411; //Options: 69, 118, 215, 411 107 | int adcRange = 16384; //Options: 2048, 4096, 8192, 16384 108 | 109 | max86150Sensor.setup(ledBrightness, sampleAverage, ledMode, sampleRate, pulseWidth, adcRange); //Configure sensor with these settings 110 | 111 | 112 | //Display show... 113 | // initialize with the I2C addr 0x3C 114 | Serial.print("Initializing...Display"); 115 | if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3D for 128x64 116 | Serial.println(F("SSD1306 allocation failed")); 117 | for(;;); 118 | } 119 | 120 | // Clear the buffer. 121 | display.clearDisplay(); 122 | display.setTextSize(2); 123 | 124 | // Display "8WA - ECG" 125 | display.setTextSize(2); display.setTextColor(WHITE); display.setCursor(0,0); display.println("ECG"); 126 | display.setTextSize(1); display.setCursor(0,20); display.println("ECG Health Senser_v1"); 127 | display.display(); 128 | delay(1000); 129 | 130 | // Display "By Mic.Tsai - BU8" 131 | display.setTextSize(1); display.setTextColor(WHITE); 132 | display.setCursor(73,45); display.println("Engineer:"); 133 | display.setCursor(80,55); display.println("Mic.Tsai"); 134 | display.display(); 135 | delay(1000); 136 | 137 | Serial.println("OK!"); 138 | 139 | display.clearDisplay(); 140 | display.display(); 141 | } 142 | 143 | void loop() 144 | { 145 | //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 146 | // Status change 147 | 148 | buttonState = digitalRead(buttonPin); 149 | 150 | if (buttonState != lastButtonState) { 151 | if (buttonState == HIGH) { 152 | buttonPushCounter++; 153 | } 154 | delay(100); 155 | } 156 | lastButtonState = buttonState; 157 | //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 158 | if (buttonPushCounter > 11) { 159 | buttonPushCounter = 0; 160 | } 161 | //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 162 | 163 | 164 | //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 165 | //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 166 | 167 | 168 | //>>>>>>>>>>>>>>>>>>>>>>>> Mode 0 >>>>>>>>>>>>>>>>>>>>>>>>>>> 169 | //>>>>>>>>>>>>> Plot Demo = TIME + ECG + PPG >>>>>>>>>>>>>>> 170 | if (buttonPushCounter == 0) { 171 | 172 | display.clearDisplay(); 173 | 174 | // Display "8WA - ECG" 175 | display.setTextSize(2); display.setTextColor(WHITE); display.setCursor(0,0); display.println("ECG/PPG"); 176 | display.setTextSize(1); display.setCursor(0,20); display.println("ECG Health Senser_v1"); 177 | display.setTextSize(1); display.setTextColor(WHITE); 178 | display.setCursor(2,55); display.println("Ploting..."); 179 | 180 | // Mode Demo 181 | display.setTextSize(1); display.setTextColor(WHITE); 182 | display.setCursor(90,5); display.println("Demo"); 183 | display.display(); 184 | delay(100); 185 | 186 | buttonPushCounter++; 187 | } 188 | 189 | if (buttonPushCounter == 1) { 190 | 191 | ECG(); 192 | Filter_Value = Filter(); 193 | 194 | ppgunsigned16 = (uint16_t) (max86150Sensor.getFIFORed()>>2); 195 | Filter_Value2 = Filter2(); 196 | 197 | Serial.print(millis()); 198 | Serial.print(","); 199 | Serial.print(ecgsigned16 - Filter_Value); 200 | Serial.print(","); 201 | Serial.print(ppgunsigned16 - Filter_Value2); 202 | Serial.println(","); 203 | } 204 | 205 | if (buttonPushCounter == 2) { 206 | 207 | display.clearDisplay(); 208 | 209 | // Display "8WA - ECG" 210 | display.setTextSize(2); display.setTextColor(WHITE); display.setCursor(0,0); display.println("ECG/PPG"); 211 | display.setTextSize(1); display.setCursor(0,20); display.println("ECG Health Senser_v1"); 212 | display.setTextSize(1); display.setTextColor(WHITE); 213 | display.setCursor(2,55); display.println("Ploting..."); 214 | 215 | // Mode Demo 216 | display.setTextSize(1); display.setTextColor(WHITE); 217 | display.setCursor(90,5); display.println("Demo2"); 218 | display.display(); 219 | delay(100); 220 | 221 | buttonPushCounter++; 222 | } 223 | 224 | if (buttonPushCounter == 3) { 225 | 226 | ECG(); 227 | Filter_Value = Filter(); 228 | 229 | ppgunsigned16 = (uint16_t) (max86150Sensor.getFIFORed()>>2); 230 | Filter_Value2 = Filter2(); 231 | 232 | Serial.print(ecgsigned16 - Filter_Value); 233 | Serial.print(","); 234 | Serial.println(ppgunsigned16 - Filter_Value2); 235 | 236 | // Serial.print(ecgsigned16 - Filter_Value); 237 | // Serial.print(","); 238 | // Serial.println(ppgunsigned16 - Filter_Value2); 239 | } 240 | 241 | if (buttonPushCounter == 4) { 242 | 243 | display.clearDisplay(); 244 | 245 | // Display "8WA - ECG" 246 | display.setTextSize(2); display.setTextColor(WHITE); display.setCursor(0,0); display.println("ECG"); 247 | display.setTextSize(1); display.setCursor(0,20); display.println("ECG Health Senser_v1"); 248 | display.setTextSize(1); display.setTextColor(WHITE); 249 | display.setCursor(2,55); display.println("Ploting..."); 250 | 251 | // Mode Demo 252 | display.setTextSize(1); display.setTextColor(WHITE); 253 | display.setCursor(60,5); display.println("Raw ECG"); 254 | display.display(); 255 | delay(100); 256 | 257 | buttonPushCounter++; 258 | } 259 | 260 | //>>>>>>>>>>>>>>>>>>>>>>>> Mode 2 >>>>>>>>>>>>>>>>>>>>>>>>>>> 261 | //>>>>>>>>>>>>>>>>>>>>>>> Raw ECG >>>>>>>>>>>>>>>>>>>>>>>>>> 262 | 263 | if (buttonPushCounter == 5) { 264 | ECG(); 265 | Filter_Value = Filter(); 266 | ppgunsigned16 = (uint16_t) (max86150Sensor.getFIFORed()>>2); 267 | Serial.print(ecgsigned16); 268 | Serial.print(","); 269 | Serial.println(Filter_Value); 270 | PlotShow(); 271 | } 272 | 273 | if (buttonPushCounter == 6) { 274 | 275 | display.clearDisplay(); 276 | 277 | // Display "8WA - ECG" 278 | display.setTextSize(2); display.setTextColor(WHITE); display.setCursor(0,0); display.println("ECG"); 279 | display.setTextSize(1); display.setCursor(0,20); display.println("ECG Health Senser_v1"); 280 | display.setTextSize(1); display.setTextColor(WHITE); 281 | display.setCursor(2,55); display.println("Ploting..."); 282 | 283 | // Mode Demo 284 | display.setTextSize(1); display.setTextColor(WHITE); 285 | display.setCursor(60,5); display.println("Filter ECG"); 286 | display.display(); 287 | delay(100); 288 | 289 | buttonPushCounter++; 290 | } 291 | 292 | //>>>>>>>>>>>>>>>>>>>>>>>> Mode 3 >>>>>>>>>>>>>>>>>>>>>>>>>>> 293 | //>>>>>>>>>>>>>>>>>>>>> Filter ECG >>>>>>>>>>>>>>>>>>>>>>>>>> 294 | 295 | if (buttonPushCounter == 7) { 296 | 297 | ECG(); 298 | ECGFilter(); 299 | PlotShow(); 300 | } 301 | 302 | if (buttonPushCounter == 8) { 303 | 304 | display.clearDisplay(); 305 | 306 | // Display "8WA - ECG" 307 | display.setTextSize(2); display.setTextColor(WHITE); display.setCursor(0,0); display.println("PPG"); 308 | display.setTextSize(1); display.setCursor(0,20); display.println("ECG Health Senser_v1"); 309 | display.setTextSize(1); display.setTextColor(WHITE); 310 | display.setCursor(2,55); display.println("Ploting..."); 311 | 312 | // Mode Demo 313 | display.setTextSize(1); display.setTextColor(WHITE); 314 | display.setCursor(60,5); display.println("Raw PPG"); 315 | display.display(); 316 | delay(100); 317 | 318 | buttonPushCounter++; 319 | } 320 | 321 | //>>>>>>>>>>>>>>>>>>>>>>>> Mode 4 >>>>>>>>>>>>>>>>>>>>>>>>>>> 322 | //>>>>>>>>>>>>>>>>>>>>>> Raw PPG >>>>>>>>>>>>>>>>>>>>>>>>>>> 323 | 324 | if (buttonPushCounter == 9) { 325 | 326 | ECG(); 327 | Filter_Value = Filter(); 328 | ppgunsigned16 = (uint16_t) (max86150Sensor.getFIFORed()>>2); 329 | Filter_Value2 = Filter2(); 330 | Serial.print(ppgunsigned16); 331 | Serial.print(","); 332 | Serial.println(Filter_Value2); 333 | PlotShow(); 334 | } 335 | 336 | if (buttonPushCounter == 10) { 337 | 338 | display.clearDisplay(); 339 | 340 | // Display "8WA - PPG" 341 | display.setTextSize(2); display.setTextColor(WHITE); display.setCursor(0,0); display.println("PPG"); 342 | display.setTextSize(1); display.setCursor(0,20); display.println("ECG Health Senser_v1"); 343 | display.setTextSize(1); display.setTextColor(WHITE); 344 | display.setCursor(2,55); display.println("Ploting..."); 345 | 346 | // Mode Demo 347 | display.setTextSize(1); display.setTextColor(WHITE); 348 | display.setCursor(60,5); display.println("Filter PPG"); 349 | display.display(); 350 | delay(100); 351 | 352 | buttonPushCounter++; 353 | } 354 | 355 | //>>>>>>>>>>>>>>>>>>>>>>>> Mode 3 >>>>>>>>>>>>>>>>>>>>>>>>>>> 356 | //>>>>>>>>>>>>>>>>>>>>> Filter PPG >>>>>>>>>>>>>>>>>>>>>>>>>> 357 | 358 | if (buttonPushCounter == 11) { 359 | 360 | ECG(); 361 | Filter_Value = Filter(); 362 | ppgunsigned16 = (uint16_t) (max86150Sensor.getFIFORed()>>2); 363 | Filter_Value2 = Filter2(); 364 | Serial.println(ppgunsigned16 - Filter_Value2); 365 | PlotShow(); 366 | } 367 | 368 | } 369 | //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 370 | //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 371 | 372 | //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 373 | //>>>>>>>>>>>>>>>>>>>>>>>>>>> Get ECG Data >>>>>>>>>>>>>>>>>>>>>>>>>>>>> 374 | //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 375 | void ECG() 376 | { 377 | ecgsigned16 = (int16_t) (max86150Sensor.getECG()>>2); 378 | } 379 | //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 380 | //>>>>>>>>>>>>>>>>>>>>>>>>>>> Get ECG Data >>>>>>>>>>>>>>>>>>>>>>>>>>>>> 381 | //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 382 | 383 | 384 | //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 385 | //>>>>>>>>>>>>>>>>>>>>>>>>>>> Get PPG Data >>>>>>>>>>>>>>>>>>>>>>>>>>>>> 386 | //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 387 | void PPG() 388 | { 389 | ppgunsigned16 = (uint16_t) (max86150Sensor.getFIFORed()>>2); 390 | } 391 | //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 392 | //>>>>>>>>>>>>>>>>>>>>>>>>>>> Get PPG Data >>>>>>>>>>>>>>>>>>>>>>>>>>>>> 393 | //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 394 | void ECGFilter() 395 | { 396 | Filter_Value = Filter(); 397 | Serial.println(ecgsigned16 - Filter_Value); 398 | } 399 | //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 400 | //>>>>>>>>>>>>>>>>>>>>>> Plot Timer Check >>>>>>>>>>>>>>>>>>>>>>>>>>>>> 401 | //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 402 | void PlotShow() 403 | { 404 | TimerNow = millis(); 405 | Time = TimerNow - TimerLast; 406 | 407 | if (Time < 42) 408 | { 409 | display.writeFillRect(0,53,60,20,BLACK); 410 | 411 | if (buttonPushCounter == 0) { 412 | display.writeFillRect(0,40,60,10,BLACK); 413 | } 414 | 415 | display.display(); 416 | } 417 | if (Time < 5000 && Time > 4950) 418 | { 419 | display.writeFillRect(0,53,60,20,WHITE); 420 | display.setTextSize(1); display.setTextColor(BLACK); 421 | display.setCursor(2,55); display.println("Ploting..."); 422 | 423 | if (buttonPushCounter == 0) { 424 | display.writeFillRect(0,40,40,10,WHITE); 425 | display.setTextSize(1); display.setTextColor(BLACK); 426 | display.setCursor(2,42); display.println("Filter"); 427 | } 428 | 429 | 430 | display.display(); 431 | } 432 | if (Time > 10000) 433 | { 434 | display.writeFillRect(0,53,60,20,BLACK); 435 | 436 | if (buttonPushCounter == 0) { 437 | display.writeFillRect(0,40,60,10,BLACK); 438 | } 439 | 440 | display.display(); 441 | TimerLast = TimerNow; 442 | } 443 | // Serial.print(" , "); 444 | // Serial.println(Time); 445 | } 446 | //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 447 | //>>>>>>>>>>>>>>>>>>>>>> Plot Timer Check >>>>>>>>>>>>>>>>>>>>>>>>>>>>> 448 | //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 449 | 450 | 451 | // ecg data in 452 | int Get_AD() { 453 | return ecgsigned16; 454 | } 455 | 456 | // 一階滯後濾波法 457 | #define FILTER_A 0.01 458 | int Filter() { 459 | int NewValue; 460 | NewValue = Get_AD(); 461 | Value = (int)((float)NewValue * FILTER_A + (1.0 - FILTER_A) * (float)Value); 462 | return Value; 463 | } 464 | 465 | //>>>>>>>>>>>>>>>>>>>>>>>>>>>> PPG >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 466 | 467 | // ecg data in 468 | int Get_AD2() { 469 | return ppgunsigned16; 470 | } 471 | 472 | // 一階滯後濾波法 473 | #define FILTER_A2 0.01 474 | int Filter2() { 475 | int NewValue2; 476 | NewValue2 = Get_AD2(); 477 | Value2 = (int)((float)NewValue2 * FILTER_A2 + (1.0 - FILTER_A2) * (float)Value2); 478 | return Value2; 479 | } 480 | 481 | //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 482 | //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 483 | -------------------------------------------------------------------------------- /code/arduino/WiFi Transmission/ECG_PPG_MAX86150_V5_WiFi_TX/ECG_PPG_MAX86150_V5_WiFi_TX.ino: -------------------------------------------------------------------------------- 1 | // ################################################################################### 2 | // # Project: ECG Health Sensor 3 | // # Engineer: Mic.Tsai 4 | // # Date: 30 July 2020 5 | // # Objective: Dev.board 6 | // # Usage: ESP8266 7 | // # Modified: Mode Select with filter / ECG / PPG 8 | // ################################################################################### 9 | 10 | // ######################## 11 | #include 12 | #include 13 | // ######################## 14 | 15 | #include 16 | #include "max86150.h" 17 | 18 | // # LCD install 19 | #include 20 | #include 21 | Adafruit_SSD1306 display(128, 64, &Wire, -1); 22 | 23 | 24 | byte TestledBrightness = 255; //Options: 0=Off to 255=50mA //0x1F 25 | 26 | // # ECG sensor 27 | int16_t ecgsigned16; 28 | int16_t redunsigned16; 29 | uint16_t ppgunsigned16; 30 | 31 | MAX86150 max86150Sensor; 32 | 33 | int TimerLast=0; 34 | int TimerNow=0; 35 | int Time=0; 36 | 37 | int Filter_Value; 38 | int Filter_Value2; 39 | int Value; 40 | int Value2; 41 | 42 | // # BTN Mode Select 43 | const int buttonPin = 16; // the pin that the pushbutton is attached to 44 | 45 | // Variables will change: 46 | int buttonPushCounter = 0; // counter for the number of button presses 47 | int buttonState = 0; // current state of the button 48 | int lastButtonState = 0; // previous state of the button 49 | 50 | /* 51 | // PPG 52 | // # Plot 變數宣告區 53 | int a=0; 54 | int lasta=0; 55 | int lastb=0; 56 | int LastTime=0; 57 | int ThisTime; 58 | bool BPMTiming=false; 59 | bool BeatComplete=false; 60 | int BPM=0; 61 | */ 62 | 63 | // # 上限下限 64 | #define UpperThreshold 520 65 | #define LowerThreshold 450 66 | 67 | // # Bias 68 | int LevelSea = 0; 69 | 70 | // # 平均心跳 71 | const byte RATE_SIZE = 36; //Increase this for more averaging. 18 is good. 72 | byte rates[RATE_SIZE]; //Array of heart rates 73 | byte rateSpot = 0; 74 | long lastBeat = 0; //Time at which the last beat occurred 75 | 76 | float beatsPerMinute; 77 | int beatAvg; 78 | int PPGAVG; 79 | 80 | 81 | // ######################## 82 | 83 | 84 | // REPLACE WITH RECEIVER MAC Address 85 | uint8_t broadcastAddress[] = {0xF4, 0xCF, 0xA2, 0x6D, 0x2E, 0xDF}; 86 | //uint8_t broadcastAddress[] = {0x5C, 0xCF, 0x7F, 0xB9, 0x8A, 0x6A}; 87 | // uint8_t broadcastAddress[] = {0xF4, 0xCF, 0xA2, 0x6D, 0x2E, 0xDF}; 88 | // uint8_t broadcastAddress[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; 89 | 90 | // Structure example to send data 91 | // Must match the receiver structure 92 | typedef struct struct_message { 93 | int a; 94 | int b; 95 | int c; 96 | int d; 97 | 98 | /* 99 | char a[32]; 100 | int b; 101 | float c; 102 | String d; 103 | bool e; 104 | */ 105 | 106 | } struct_message; 107 | 108 | // Create a struct_message called myData 109 | struct_message myData; 110 | 111 | unsigned long lastTime = 0; 112 | unsigned long timerDelay = 1; // send readings timer 113 | 114 | // Callback when data is sent 115 | void OnDataSent(uint8_t *mac_addr, uint8_t sendStatus) { 116 | Serial.print("Last Packet Send Status: "); 117 | if (sendStatus == 0){ 118 | Serial.println("Delivery success"); 119 | } 120 | else{ 121 | Serial.println("Delivery fail"); 122 | } 123 | } 124 | 125 | // ######################## 126 | 127 | void setup() 128 | { 129 | pinMode(buttonPin, INPUT); 130 | Serial.begin(38400); 131 | Serial.println("MAX86150 Basic Readings Example"); 132 | 133 | // Initialize sensor 134 | if (max86150Sensor.begin(Wire, I2C_SPEED_FAST) == false) 135 | { 136 | Serial.println("MAX86150 was not found. Please check wiring/power. "); 137 | while (1); 138 | } 139 | 140 | //Setup to sense a nice looking saw tooth on the plotter 141 | byte ledBrightness = 255; //Options: 0=Off to 255=50mA 142 | byte sampleAverage = 1; //Options: 1, 2, 4, 8, 16, 32 143 | byte ledMode = 2; //Options: 1 = Red only, 2 = Red + IR, 3 = Red + IR + Green 144 | int sampleRate = 50; //Options: 50, 100, 200, 400, 800, 1000, 1600, 3200 145 | int pulseWidth = 69; //Options: 69, 118, 215, 411 146 | int adcRange = 2048; //Options: 2048, 4096, 8192, 16384 147 | 148 | max86150Sensor.setup(ledBrightness, sampleAverage, ledMode, sampleRate, pulseWidth, adcRange); //Configure sensor with these settings 149 | 150 | //Setup LED register (0x00 0mA, 0xFF 100mA) 151 | // max86150Sensor.writeRegister8(0x5E,0x11,0xFF); 152 | // max86150Sensor.writeRegister8(0x5E,0x12,0xFF); 153 | 154 | /* 155 | //----------------------------------------------------------------- 156 | 157 | Serial.println("Read"); 158 | Serial.println(max86150Sensor.readRegister8(0x5E,0x11)); 159 | Serial.println(max86150Sensor.readRegister8(0x5E,0x12)); 160 | delay(1000); 161 | Serial.println("OK"); 162 | 163 | delay(1000); 164 | Serial.println("Write"); 165 | max86150Sensor.writeRegister8(0x5E,0x11,0x00); 166 | max86150Sensor.writeRegister8(0x5E,0x12,0x00); 167 | delay(1000); 168 | Serial.println("OK"); 169 | 170 | Serial.println("Read"); 171 | Serial.println(max86150Sensor.readRegister8(0x5E,0x11)); 172 | Serial.println(max86150Sensor.readRegister8(0x5E,0x12)); 173 | delay(1000); 174 | Serial.println("OK"); 175 | 176 | //----------------------------------------------------------------- 177 | */ 178 | 179 | //Display show... 180 | // initialize with the I2C addr 0x3C 181 | Serial.print("Initializing...Display"); 182 | if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3D for 128x64 183 | Serial.println(F("SSD1306 allocation failed")); 184 | for(;;); 185 | } 186 | 187 | // Clear the buffer. 188 | display.clearDisplay(); 189 | display.setTextSize(2); 190 | 191 | // Display "8WA - ECG" 192 | display.setTextSize(2); display.setTextColor(WHITE); display.setCursor(0,0); display.println("ECG"); 193 | display.setTextSize(1); display.setCursor(0,20); display.println("ECG Health Senser_v1"); 194 | display.display(); 195 | delay(1000); 196 | 197 | // Display "By Mic.Tsai - BU8" 198 | display.setTextSize(1); display.setTextColor(WHITE); 199 | display.setCursor(73,45); display.println("Engineer:"); 200 | display.setCursor(80,55); display.println("Mic.Tsai"); 201 | display.display(); 202 | delay(1000); 203 | 204 | Serial.println("OK!"); 205 | 206 | display.clearDisplay(); 207 | display.display(); 208 | 209 | 210 | // ######################## 211 | 212 | // Set device as a Wi-Fi Station 213 | WiFi.mode(WIFI_STA); 214 | 215 | // Init ESP-NOW 216 | if (esp_now_init() != 0) { 217 | Serial.println("Error initializing ESP-NOW"); 218 | return; 219 | } 220 | 221 | // Once ESPNow is successfully Init, we will register for Send CB to 222 | // get the status of Trasnmitted packet 223 | esp_now_set_self_role(ESP_NOW_ROLE_CONTROLLER); 224 | esp_now_register_send_cb(OnDataSent); 225 | 226 | // Register peer 227 | esp_now_add_peer(broadcastAddress, ESP_NOW_ROLE_SLAVE, 1, NULL, 0); 228 | 229 | // ######################## 230 | 231 | 232 | 233 | } 234 | 235 | void loop() 236 | { 237 | //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 238 | // Status change 239 | 240 | buttonState = digitalRead(buttonPin); 241 | 242 | if (buttonState != lastButtonState) { 243 | if (buttonState == HIGH) { 244 | buttonPushCounter++; 245 | } 246 | delay(100); 247 | } 248 | lastButtonState = buttonState; 249 | //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 250 | if (buttonPushCounter > 11) { 251 | buttonPushCounter = 0; 252 | } 253 | //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 254 | 255 | 256 | //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 257 | //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 258 | 259 | 260 | //>>>>>>>>>>>>>>>>>>>>>>>> Mode 0 >>>>>>>>>>>>>>>>>>>>>>>>>>> 261 | //>>>>>>>>>>>>> Plot Demo = TIME + ECG + PPG >>>>>>>>>>>>>>> 262 | if (buttonPushCounter == 0) { 263 | 264 | display.clearDisplay(); 265 | 266 | // Display "8WA - ECG" 267 | display.setTextSize(2); display.setTextColor(WHITE); display.setCursor(0,0); display.println("ECG/PPG"); 268 | display.setTextSize(1); display.setCursor(0,20); display.println("ECG Health Senser_v1"); 269 | display.setTextSize(1); display.setTextColor(WHITE); 270 | display.setCursor(2,55); display.println("Ploting..."); 271 | 272 | // Mode Demo 273 | display.setTextSize(1); display.setTextColor(WHITE); 274 | display.setCursor(90,5); display.println("Demo"); 275 | display.display(); 276 | delay(100); 277 | 278 | buttonPushCounter++; 279 | } 280 | 281 | if (buttonPushCounter == 1) { 282 | 283 | ECG(); 284 | Filter_Value = Filter(); 285 | 286 | ppgunsigned16 = (uint16_t) (max86150Sensor.getFIFORed()>>2); 287 | Filter_Value2 = Filter2(); 288 | 289 | Serial.print(millis()); 290 | Serial.print(","); 291 | Serial.print(ecgsigned16 - Filter_Value); 292 | Serial.print(","); 293 | Serial.print(ppgunsigned16 - Filter_Value2); 294 | Serial.println(","); 295 | 296 | 297 | // ######################## 298 | 299 | // Set values to send 300 | myData.a = ecgsigned16; 301 | myData.b = Filter_Value; 302 | myData.c = ppgunsigned16; 303 | myData.d = Filter_Value2; 304 | 305 | // Send message via ESP-NOW 306 | esp_now_send(broadcastAddress, (uint8_t *) &myData, sizeof(myData)); 307 | 308 | // ######################## 309 | 310 | } 311 | 312 | if (buttonPushCounter == 2) { 313 | 314 | display.clearDisplay(); 315 | 316 | // Display "8WA - ECG" 317 | display.setTextSize(2); display.setTextColor(WHITE); display.setCursor(0,0); display.println("ECG/PPG"); 318 | display.setTextSize(1); display.setCursor(0,20); display.println("ECG Health Senser_v1"); 319 | display.setTextSize(1); display.setTextColor(WHITE); 320 | display.setCursor(2,55); display.println("Ploting..."); 321 | 322 | // Mode Demo 323 | display.setTextSize(1); display.setTextColor(WHITE); 324 | display.setCursor(90,5); display.println("Demo2"); 325 | display.display(); 326 | delay(100); 327 | 328 | buttonPushCounter++; 329 | } 330 | 331 | if (buttonPushCounter == 3) { 332 | 333 | ECG(); 334 | Filter_Value = Filter(); 335 | 336 | ppgunsigned16 = (uint16_t) (max86150Sensor.getFIFORed()>>2); 337 | Filter_Value2 = Filter2(); 338 | 339 | Serial.print(ecgsigned16 - Filter_Value); 340 | Serial.print(","); 341 | Serial.println(ppgunsigned16 - Filter_Value2); 342 | 343 | // Serial.print(ecgsigned16 - Filter_Value); 344 | // Serial.print(","); 345 | // Serial.println(ppgunsigned16 - Filter_Value2); 346 | } 347 | 348 | if (buttonPushCounter == 4) { 349 | 350 | display.clearDisplay(); 351 | 352 | // Display "8WA - ECG" 353 | display.setTextSize(2); display.setTextColor(WHITE); display.setCursor(0,0); display.println("ECG"); 354 | display.setTextSize(1); display.setCursor(0,20); display.println("ECG Health Senser_v1"); 355 | display.setTextSize(1); display.setTextColor(WHITE); 356 | display.setCursor(2,55); display.println("Ploting..."); 357 | 358 | // Mode Demo 359 | display.setTextSize(1); display.setTextColor(WHITE); 360 | display.setCursor(60,5); display.println("Raw ECG"); 361 | display.display(); 362 | delay(100); 363 | 364 | buttonPushCounter++; 365 | } 366 | 367 | //>>>>>>>>>>>>>>>>>>>>>>>> Mode 2 >>>>>>>>>>>>>>>>>>>>>>>>>>> 368 | //>>>>>>>>>>>>>>>>>>>>>>> Raw ECG >>>>>>>>>>>>>>>>>>>>>>>>>> 369 | 370 | if (buttonPushCounter == 5) { 371 | ECG(); 372 | Filter_Value = Filter(); 373 | ppgunsigned16 = (uint16_t) (max86150Sensor.getFIFORed()>>2); 374 | Serial.print(ecgsigned16); 375 | Serial.print(","); 376 | Serial.println(Filter_Value); 377 | PlotShow(); 378 | } 379 | 380 | if (buttonPushCounter == 6) { 381 | 382 | display.clearDisplay(); 383 | 384 | // Display "8WA - ECG" 385 | display.setTextSize(2); display.setTextColor(WHITE); display.setCursor(0,0); display.println("ECG"); 386 | display.setTextSize(1); display.setCursor(0,20); display.println("ECG Health Senser_v1"); 387 | display.setTextSize(1); display.setTextColor(WHITE); 388 | display.setCursor(2,55); display.println("Ploting..."); 389 | 390 | // Mode Demo 391 | display.setTextSize(1); display.setTextColor(WHITE); 392 | display.setCursor(60,5); display.println("Filter ECG"); 393 | display.display(); 394 | delay(100); 395 | 396 | buttonPushCounter++; 397 | } 398 | 399 | //>>>>>>>>>>>>>>>>>>>>>>>> Mode 3 >>>>>>>>>>>>>>>>>>>>>>>>>>> 400 | //>>>>>>>>>>>>>>>>>>>>> Filter ECG >>>>>>>>>>>>>>>>>>>>>>>>>> 401 | 402 | if (buttonPushCounter == 7) { 403 | 404 | ECG(); 405 | ECGFilter(); 406 | PlotShow(); 407 | } 408 | 409 | if (buttonPushCounter == 8) { 410 | 411 | display.clearDisplay(); 412 | 413 | // Display "8WA - ECG" 414 | display.setTextSize(2); display.setTextColor(WHITE); display.setCursor(0,0); display.println("PPG"); 415 | display.setTextSize(1); display.setCursor(0,20); display.println("ECG Health Senser_v1"); 416 | display.setTextSize(1); display.setTextColor(WHITE); 417 | display.setCursor(2,55); display.println("Ploting..."); 418 | 419 | // Mode Demo 420 | display.setTextSize(1); display.setTextColor(WHITE); 421 | display.setCursor(60,5); display.println("Raw PPG"); 422 | display.display(); 423 | delay(100); 424 | 425 | buttonPushCounter++; 426 | } 427 | 428 | //>>>>>>>>>>>>>>>>>>>>>>>> Mode 4 >>>>>>>>>>>>>>>>>>>>>>>>>>> 429 | //>>>>>>>>>>>>>>>>>>>>>> Raw PPG >>>>>>>>>>>>>>>>>>>>>>>>>>> 430 | 431 | if (buttonPushCounter == 9) { 432 | 433 | ECG(); 434 | Filter_Value = Filter(); 435 | ppgunsigned16 = (uint16_t) (max86150Sensor.getFIFORed()>>2); 436 | Filter_Value2 = Filter2(); 437 | Serial.print(ppgunsigned16); 438 | Serial.print(","); 439 | Serial.println(Filter_Value2); 440 | PlotShow(); 441 | } 442 | 443 | if (buttonPushCounter == 10) { 444 | 445 | display.clearDisplay(); 446 | 447 | // Display "8WA - PPG" 448 | display.setTextSize(2); display.setTextColor(WHITE); display.setCursor(0,0); display.println("PPG"); 449 | display.setTextSize(1); display.setCursor(0,20); display.println("ECG Health Senser_v1"); 450 | display.setTextSize(1); display.setTextColor(WHITE); 451 | display.setCursor(2,55); display.println("Ploting..."); 452 | 453 | // Mode Demo 454 | display.setTextSize(1); display.setTextColor(WHITE); 455 | display.setCursor(60,5); display.println("Filter PPG"); 456 | display.display(); 457 | delay(100); 458 | 459 | buttonPushCounter++; 460 | } 461 | 462 | //>>>>>>>>>>>>>>>>>>>>>>>> Mode 3 >>>>>>>>>>>>>>>>>>>>>>>>>>> 463 | //>>>>>>>>>>>>>>>>>>>>> Filter PPG >>>>>>>>>>>>>>>>>>>>>>>>>> 464 | 465 | if (buttonPushCounter == 11) { 466 | 467 | ECG(); 468 | Filter_Value = Filter(); 469 | ppgunsigned16 = (uint16_t) (max86150Sensor.getFIFORed()>>2); 470 | Filter_Value2 = Filter2(); 471 | Serial.println(ppgunsigned16 - Filter_Value2); 472 | PlotShow(); 473 | } 474 | 475 | } 476 | //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 477 | //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 478 | 479 | //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 480 | //>>>>>>>>>>>>>>>>>>>>>>>>>>> Get ECG Data >>>>>>>>>>>>>>>>>>>>>>>>>>>>> 481 | //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 482 | void ECG() 483 | { 484 | ecgsigned16 = (int16_t) (max86150Sensor.getECG()>>2); 485 | } 486 | //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 487 | //>>>>>>>>>>>>>>>>>>>>>>>>>>> Get ECG Data >>>>>>>>>>>>>>>>>>>>>>>>>>>>> 488 | //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 489 | 490 | 491 | //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 492 | //>>>>>>>>>>>>>>>>>>>>>>>>>>> Get PPG Data >>>>>>>>>>>>>>>>>>>>>>>>>>>>> 493 | //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 494 | void PPG() 495 | { 496 | ppgunsigned16 = (uint16_t) (max86150Sensor.getFIFORed()>>2); 497 | } 498 | //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 499 | //>>>>>>>>>>>>>>>>>>>>>>>>>>> Get PPG Data >>>>>>>>>>>>>>>>>>>>>>>>>>>>> 500 | //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 501 | void ECGFilter() 502 | { 503 | Filter_Value = Filter(); 504 | Serial.println(ecgsigned16 - Filter_Value); 505 | } 506 | //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 507 | //>>>>>>>>>>>>>>>>>>>>>> Plot Timer Check >>>>>>>>>>>>>>>>>>>>>>>>>>>>> 508 | //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 509 | void PlotShow() 510 | { 511 | TimerNow = millis(); 512 | Time = TimerNow - TimerLast; 513 | 514 | if (Time < 42) 515 | { 516 | display.writeFillRect(0,53,60,20,BLACK); 517 | 518 | if (buttonPushCounter == 0) { 519 | display.writeFillRect(0,40,60,10,BLACK); 520 | } 521 | 522 | display.display(); 523 | } 524 | if (Time < 5000 && Time > 4950) 525 | { 526 | display.writeFillRect(0,53,60,20,WHITE); 527 | display.setTextSize(1); display.setTextColor(BLACK); 528 | display.setCursor(2,55); display.println("Ploting..."); 529 | 530 | if (buttonPushCounter == 0) { 531 | display.writeFillRect(0,40,40,10,WHITE); 532 | display.setTextSize(1); display.setTextColor(BLACK); 533 | display.setCursor(2,42); display.println("Filter"); 534 | } 535 | 536 | 537 | display.display(); 538 | } 539 | if (Time > 10000) 540 | { 541 | display.writeFillRect(0,53,60,20,BLACK); 542 | 543 | if (buttonPushCounter == 0) { 544 | display.writeFillRect(0,40,60,10,BLACK); 545 | } 546 | 547 | display.display(); 548 | TimerLast = TimerNow; 549 | } 550 | // Serial.print(" , "); 551 | // Serial.println(Time); 552 | } 553 | //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 554 | //>>>>>>>>>>>>>>>>>>>>>> Plot Timer Check >>>>>>>>>>>>>>>>>>>>>>>>>>>>> 555 | //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 556 | 557 | 558 | // ecg data in 559 | int Get_AD() { 560 | return ecgsigned16; 561 | } 562 | 563 | // 一階滯後濾波法 564 | #define FILTER_A 0.01 565 | int Filter() { 566 | int NewValue; 567 | NewValue = Get_AD(); 568 | Value = (int)((float)NewValue * FILTER_A + (1.0 - FILTER_A) * (float)Value); 569 | return Value; 570 | } 571 | 572 | //>>>>>>>>>>>>>>>>>>>>>>>>>>>> PPG >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 573 | 574 | // ecg data in 575 | int Get_AD2() { 576 | return ppgunsigned16; 577 | } 578 | 579 | // 一階滯後濾波法 580 | #define FILTER_A2 0.01 581 | int Filter2() { 582 | int NewValue2; 583 | NewValue2 = Get_AD2(); 584 | Value2 = (int)((float)NewValue2 * FILTER_A2 + (1.0 - FILTER_A2) * (float)Value2); 585 | return Value2; 586 | } 587 | 588 | //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 589 | //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 590 | -------------------------------------------------------------------------------- /code/processing/Health_Sensor_Plot_V3_2_Auto_Serial_CSV/Health_Sensor_Plot_V3_2_Auto_Serial_CSV.pde: -------------------------------------------------------------------------------- 1 | // Base on ArduinoScope 2 | // ################################################################################### 3 | // # Project: ECG Health Sensor Plots 4 | // # Engineer: Mic.Tsai 5 | // # Date: 10 June 2020 6 | // # Objective: Dev.board 7 | // # Usage: ESP8266 8 | // # Modified: Mode Select with filter and PPG 9 | // ################################################################################### 10 | 11 | import java.io.BufferedWriter; 12 | import java.io.FileWriter; 13 | String outFilename = "/Users/mic/Desktop/ECG+PPG_Data.csv"; 14 | 15 | 16 | import processing.serial.*; 17 | Serial ArduinoPort; // Create object from Serial class 18 | 19 | int NumOfScopes,NumOfInput=2; 20 | int data_span=10000; 21 | Strage dfs = new Strage(); 22 | Scope[] sp; 23 | 24 | int fontsize=16; 25 | PFont myFont; 26 | PFont myFont2; 27 | PFont myFont3; 28 | 29 | //============================= # ECG BPM calculate 30 | int LastTimeECG=0; 31 | int ThisTimeECG; 32 | float inByteECG = 0; 33 | int BPMECG = 0; 34 | int beat_oldECG = 0; 35 | float[] beatsECG = new float[500]; // Used to calculate average BPM 36 | int averagebuffervalueecg=5; 37 | int beatIndexECG; 38 | boolean belowThresholdECG = true; 39 | 40 | int valssECG; 41 | int timestampAllECG; 42 | 43 | boolean BPMTimingECG=false; 44 | boolean BeatCompleteECG=false; 45 | 46 | float beatsPerMinuteECG; 47 | int beatAvgECG; 48 | int averageECG = 0; 49 | 50 | //============================= # ECG BPM calculate 51 | 52 | //============================= # PPG BPM calculate 53 | int LastTime=0; 54 | int ThisTime; 55 | float inByte = 0; 56 | int BPM = 0; 57 | int beat_old = 0; 58 | float[] beats = new float[500]; // Used to calculate average BPM 59 | int averagebuffervalueppg=5; 60 | int beatIndex; 61 | boolean belowThreshold = true; 62 | 63 | int valssPPG; 64 | int timestampAll; 65 | 66 | boolean BPMTiming=false; 67 | boolean BeatComplete=false; 68 | 69 | float beatsPerMinute; 70 | int beatAvg; 71 | int averageppg = 0; 72 | 73 | //============================= # PPG BPM calculate 74 | 75 | //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Set ECG Limit>>>>>>> 76 | // # ECG limit 77 | float UpperThresholdECG = 750 ; 78 | float LowerThresholdECG = 0 ; 79 | //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Set ECG Limit>>>>>>> 80 | 81 | //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Set PPG Limit>>>>>>> 82 | // # PPG limit 83 | float UpperThreshold = 100 ; 84 | float LowerThreshold = 0 ; 85 | //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Set PPG Limit>>>>>>> 86 | 87 | void setup() 88 | { 89 | //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Set COM PORT Here>>>>>>> 90 | boolean portNr = activateSerialPort("tty.usbserial", 38400); // Search Phrase and speed 91 | ArduinoPort.bufferUntil(10); 92 | //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Set COM PORT Here>>>>>>> 93 | 94 | //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Set Screen Here>>>>>>> 95 | // Screen 96 | size(800, 500); 97 | //size(700, 500); 98 | //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Set Screen Here>>>>>>> 99 | 100 | NumOfScopes=2; 101 | sp = new Scope[NumOfScopes]; 102 | sp[0]= new Scope(0,50,10,width-100,height/2-35,1500,-1500,1000,10); 103 | sp[1]= new Scope(1,50,height/2+15,width-100,height/2-35,300,-300,1000,10); 104 | 105 | myFont = loadFont("Helvetica-Light-15.vlw"); 106 | myFont2 = loadFont("Helvetica-LightOblique-20.vlw"); 107 | myFont3 = loadFont("Helvetica-BoldOblique-72.vlw"); 108 | textFont(myFont,fontsize); 109 | } 110 | 111 | 112 | 113 | boolean activateSerialPort(String partOfPortName, int SerialSpeed) { 114 | boolean portIsActivated = false; 115 | int portIndex = -1; 116 | 117 | for(int i = 0; i < Serial.list().length; i++) { // go thru all serial ports 118 | print(i); 119 | 120 | if(Serial.list()[i].indexOf(partOfPortName) > 0) { // chech if name match 121 | portIndex = i; 122 | print("*"); 123 | } 124 | println("\t" + Serial.list()[i]); 125 | } 126 | 127 | if(portIndex < 0 ) { 128 | println("Error: No serial port found - " + partOfPortName); 129 | }else{ 130 | ArduinoPort = new Serial(this, Serial.list()[portIndex], SerialSpeed); 131 | println("Serial conection: " + Serial.list()[portIndex] + " speed: " + SerialSpeed); // activate 132 | } 133 | return portIsActivated; 134 | } 135 | 136 | 137 | class Scope{ 138 | int input_id; // corresponding input 139 | int posx,posy; // screen position of the scope 140 | int sizex,sizey; // pixel size of the scope 141 | float yu,yl; // range of y is [yl,yu] 142 | int tspan; // 143 | int tspany; // 144 | int ngx,ngy; // number of grids 145 | float maxposx,maxposy,minposx,minposy,maxx,minx,maxy,miny; 146 | 147 | Scope(int did,int px,int py,int sx,int sy,float syu,float syl,int ts,int ts2){ 148 | input_id=did; 149 | posx=px; 150 | posy=py; 151 | sizex=sx; 152 | sizey=sy; 153 | yu=syu; 154 | yl=syl; 155 | tspan=ts; 156 | tspany=ts2; //add x 157 | ngx=10; 158 | ngy=4; 159 | } 160 | 161 | void grid(){ 162 | pushStyle(); 163 | fill(255,196); 164 | stroke(0,0,150); 165 | for(float gx=sizex; gx>=0; gx-= (float)sizex/ngx){ 166 | line(posx+gx,posy,posx+gx,posy+sizey); 167 | textAlign(CENTER,TOP); 168 | text((int)map(gx,sizex,0,0,-tspan),posx+gx,posy+sizey+2); 169 | } 170 | for(float gy=sizey; gy>=0; gy-= (float)sizey/ngy){ 171 | line(posx,posy+gy,posx+sizex,posy+gy); 172 | textAlign(RIGHT,CENTER); 173 | text((int)map(gy,0,sizey,yu,yl),posx,posy+gy); 174 | } 175 | popStyle(); 176 | } 177 | 178 | int curx,cury; 179 | 180 | // draw cursor 181 | void cur() 182 | { 183 | // return if mouse cursor is not in this scope 184 | if(constrain(mouseX,posx,posx+sizex)!=mouseX 185 | || constrain(mouseY,posy,posy+sizey)!=mouseY) return; 186 | 187 | pushStyle(); 188 | 189 | // draw cross cursor 190 | stroke(255,0,0,196); 191 | fill(255,0,0,196); 192 | line(mouseX,posy,mouseX,posy+sizey); 193 | line(posx,mouseY,posx+sizex,mouseY); 194 | 195 | // draw measure if mouse is dragged 196 | if(mousePressed){ 197 | line(curx,posy,curx,posy+sizey); 198 | line(posx,cury,posx+sizex,cury); 199 | textAlign(RIGHT,BOTTOM); 200 | text((int)map(curx,posx,posx+sizex,-tspan,0)+"ms, "+(int)map(cury,posy,posy+sizey,yu,yl),curx,cury); 201 | textAlign(LEFT,TOP); 202 | text("("+nfp((int)map(mouseX-curx,0,sizex,0,tspan),1)+"ms, "+nfp((int)map(mouseY-cury,0,sizey,0,-(yu-yl)),1)+")\n"+nf(1000/map(mouseX-curx,0,sizex,0,tspan),1,2)+"Hz\n"+nf(TWO_PI*1000/map(mouseX-curx,0,sizex,0,tspan),1,2)+"rad/sec",mouseX,mouseY+2); 203 | } 204 | else{ 205 | curx=mouseX; 206 | cury=mouseY; 207 | textAlign(RIGHT,BOTTOM); 208 | text((int)map(curx,posx,posx+sizex,-tspan,0)+"ms, "+(int)map(cury,posy,posy+sizey,yu,yl),curx,cury); 209 | } 210 | popStyle(); 211 | } 212 | 213 | // draw min&max tick 214 | void minmax(){ 215 | pushStyle(); 216 | fill(255,128); 217 | stroke(0,0,100); 218 | textAlign(RIGHT,CENTER); 219 | line(posx,maxposy,posx+sizex,maxposy); 220 | text((int)maxy,posx,maxposy); 221 | line(posx,minposy,posx+sizex,minposy); 222 | text((int)miny,posx,minposy); 223 | textAlign(LEFT,CENTER); 224 | textAlign(CENTER,TOP); 225 | text("max",maxposx,maxposy); 226 | textAlign(CENTER,BOTTOM); 227 | text("min",minposx,minposy+20); 228 | popStyle(); 229 | } 230 | 231 | // draw scope 232 | void Plot(){ 233 | 234 | float sx,sy,ex,ey; 235 | int nof=0; 236 | DataFrame df_last = dfs.get(0); 237 | 238 | maxy=-1e10; // -inf 239 | miny=1e10; // +inf 240 | 241 | // draw background (for transparency) 242 | pushStyle(); 243 | noStroke(); 244 | fill(0,0,64,64); 245 | rect(posx,posy,sizex,sizey); 246 | popStyle(); 247 | 248 | // draw data plot 249 | pushStyle(); 250 | stroke(0,255,0); 251 | smooth(); 252 | strokeWeight(1); 253 | for(int idx=0;(dfs.get(idx).t>max(df_last.t-tspan,0)) && -idx>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Draw Data View>>>>>>> 308 | 309 | textSize(14); 310 | 311 | /* 312 | //# Raw Data check 313 | text(beatsPerMinuteECG , width -600, 25); 314 | text(valssECG , width -600, 45); 315 | text(ThisTime , width -600, 65); 316 | */ 317 | 318 | //# ECG Part 319 | text( "/" , width-46, 220); 320 | text( BPMECG , width-40, 220); 321 | 322 | textFont(myFont2,16); 323 | textSize(16); text( "Avg" , width-163, 200); 324 | textSize(20); text( "BPM:" , width-165, 220); 325 | textFont(myFont3,16); 326 | textSize(50); text( averageECG , width-110, 220); 327 | textSize(25); text("ECG" , width -430, 32); 328 | 329 | textFont(myFont,16); 330 | 331 | 332 | //# PPG Part 333 | text( "/" , width-46, 475); 334 | text( BPM , width-40, 475); 335 | 336 | textFont(myFont2,16); 337 | textSize(16); text( "Avg" , width-163, 455); 338 | textSize(20); text( "BPM:" , width-165, 475); 339 | textFont(myFont3,16); 340 | textSize(50); text( averageppg , width-110, 475); 341 | textSize(25); text("PPG" , width -430, 286); 342 | 343 | textFont(myFont,14); 344 | 345 | 346 | appendTextToFile(outFilename, "Time:" + "," + millis() + "," 347 | 348 | + "ECG:" + "," + valssECG + "," + "ECG_BPM:" + "," + averageECG + "," 349 | 350 | + "PPG:" + "," + valssPPG + "," + "PPG_BPM:" + "," + averageppg + "," 351 | 352 | ); 353 | 354 | 355 | //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Draw Data View>>>>>>> 356 | } 357 | 358 | 359 | 360 | //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Save CSV>>>>>>> 361 | 362 | 363 | /** 364 | * Appends text to the end of a text file located in the data directory, 365 | * creates the file if it does not exist. 366 | * Can be used for big files with lots of rows, 367 | * existing lines will not be rewritten 368 | */ 369 | void appendTextToFile(String filename, String text){ 370 | File f = new File(dataPath(filename)); 371 | if(!f.exists()){ 372 | createFile(f); 373 | } 374 | try { 375 | PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(f, true))); 376 | out.println(text); 377 | out.close(); 378 | }catch (IOException e){ 379 | e.printStackTrace(); 380 | } 381 | } 382 | 383 | /** 384 | * Creates a new file including all subfolders 385 | */ 386 | void createFile(File f){ 387 | File parentDir = f.getParentFile(); 388 | try{ 389 | parentDir.mkdirs(); 390 | f.createNewFile(); 391 | }catch(Exception e){ 392 | e.printStackTrace(); 393 | } 394 | } 395 | 396 | //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Save CSV>>>>>>> 397 | 398 | 399 | // input data buffer class 400 | // (now using ring buffer) 401 | class Strage{ 402 | int cur; 403 | DataFrame[] DataFrames; 404 | 405 | Strage(){ 406 | cur=0; 407 | DataFrames=new DataFrame[data_span]; 408 | for(int idx=0;idx 0) { 471 | String datline=myPort.readString(); 472 | splitdata=parseInt(datline.split(",")); 473 | if((splitdata.length==NumOfInput+2)){ 474 | timestamp=splitdata[0]; 475 | for(int idx=0;idx= 45) 510 | { 511 | averageECG = averageppgbuffECG; 512 | } 513 | 514 | //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<UpperThresholdECG)&(BPMTimingECG)) 523 | BeatCompleteECG=true; 524 | 525 | // beatsPerMinuteECG = BPMECG; 526 | //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< ECG BPM calculate 527 | 528 | 529 | //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< PPG BPM calculate 530 | // PPG BPM calculation check 531 | if(valssPPG= 45) 550 | { 551 | averageppg = averageppgbuff; 552 | } 553 | //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<UpperThreshold)&(BPMTiming)) 562 | BeatComplete=true; 563 | 564 | //beatsPerMinute = BPM; 565 | 566 | //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< PPG BPM calculate 567 | 568 | if(isactive){ 569 | if((timestamp-dfs.get(0).t)<0){ 570 | dfs.cur--; 571 | } 572 | if((timestamp-dfs.get(0).t) > ((float)sp[0].tspan / sp[0].sizex/2.0) ){ 573 | dfs.push( new DataFrame(timestamp,vals)); 574 | } 575 | } 576 | 577 | } 578 | } 579 | } 580 | 581 | // keyboard user interface 582 | void keyPressed(){ 583 | switch(key){ 584 | // activate/deactivate scope update 585 | case ' ': 586 | isactive=!isactive; 587 | break; 588 | // save record 589 | case 's': 590 | // dfs.save(); 591 | break; 592 | case CODED: 593 | switch(keyCode){ 594 | // Increse time span 595 | case RIGHT: 596 | for(int i=0;i>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> END >>>>>>> 629 | 630 | /* Arduino code 631 | void setup() 632 | { 633 | Serial.begin(38400); 634 | } 635 | 636 | void loop() 637 | { 638 | Serial.print(millis()); 639 | Serial.print(","); 640 | Serial.print(analogRead(0)); 641 | Serial.print(","); 642 | Serial.print(analogRead(1)); 643 | Serial.println(","); 644 | } 645 | */ 646 | -------------------------------------------------------------------------------- /src/max86150.cpp: -------------------------------------------------------------------------------- 1 | /*************************************************** 2 | Arduino library written for the Maxim MAX86150 ECG and PPG integrated sensor 3 | 4 | Written by Ashwin Whitchurch, ProtoCentral Electronics (www.protocentral.com) 5 | 6 | Based on code written by Peter Jansen and Nathan Seidle (SparkFun) for the MAX30105 sensor 7 | BSD license, all text above must be included in any redistribution. 8 | *****************************************************/ 9 | 10 | #include "max86150.h" 11 | 12 | static const uint8_t MAX86150_INTSTAT1 = 0x00; 13 | static const uint8_t MAX86150_INTSTAT2 = 0x01; 14 | static const uint8_t MAX86150_INTENABLE1 = 0x02; 15 | static const uint8_t MAX86150_INTENABLE2 = 0x03; 16 | 17 | static const uint8_t MAX86150_FIFOWRITEPTR = 0x04; 18 | static const uint8_t MAX86150_FIFOOVERFLOW = 0x05; 19 | static const uint8_t MAX86150_FIFOREADPTR = 0x06; 20 | static const uint8_t MAX86150_FIFODATA = 0x07; 21 | 22 | static const uint8_t MAX86150_FIFOCONFIG = 0x08; 23 | static const uint8_t MAX86150_FIFOCONTROL1= 0x09; 24 | static const uint8_t MAX86150_FIFOCONTROL2 = 0x0A; 25 | 26 | static const uint8_t MAX86150_SYSCONTROL = 0x0D; 27 | static const uint8_t MAX86150_PPGCONFIG1 = 0x0E; 28 | static const uint8_t MAX86150_PPGCONFIG2 = 0x0F; 29 | static const uint8_t MAX86150_LED_PROX_AMP = 0x10; 30 | 31 | static const uint8_t MAX86150_LED1_PULSEAMP = 0x11; 32 | static const uint8_t MAX86150_LED2_PULSEAMP = 0x12; 33 | static const uint8_t MAX86150_LED_RANGE = 0x14; 34 | static const uint8_t MAX86150_LED_PILOT_PA = 0x15; 35 | 36 | static const uint8_t MAX86150_ECG_CONFIG1 = 0x3C; 37 | static const uint8_t MAX86150_ECG_CONFIG3 = 0x3E; 38 | static const uint8_t MAX86150_PROXINTTHRESH = 0x10; 39 | 40 | static const uint8_t MAX86150_PARTID = 0xFF; 41 | 42 | // MAX86150 Commands 43 | static const uint8_t MAX86150_INT_A_FULL_MASK = (byte)~0b10000000; 44 | static const uint8_t MAX86150_INT_A_FULL_ENABLE = 0x80; 45 | static const uint8_t MAX86150_INT_A_FULL_DISABLE = 0x00; 46 | 47 | static const uint8_t MAX86150_INT_DATA_RDY_MASK = (byte)~0b01000000; 48 | static const uint8_t MAX86150_INT_DATA_RDY_ENABLE = 0x40; 49 | static const uint8_t MAX86150_INT_DATA_RDY_DISABLE = 0x00; 50 | 51 | static const uint8_t MAX86150_INT_ALC_OVF_MASK = (byte)~0b00100000; 52 | static const uint8_t MAX86150_INT_ALC_OVF_ENABLE = 0x20; 53 | static const uint8_t MAX86150_INT_ALC_OVF_DISABLE = 0x00; 54 | 55 | static const uint8_t MAX86150_INT_PROX_INT_MASK = (byte)~0b00010000; 56 | static const uint8_t MAX86150_INT_PROX_INT_ENABLE = 0x10; 57 | static const uint8_t MAX86150_INT_PROX_INT_DISABLE = 0x00; 58 | 59 | static const uint8_t MAX86150_SAMPLEAVG_MASK = (byte)~0b11100000; 60 | static const uint8_t MAX86150_SAMPLEAVG_1 = 0x00; 61 | static const uint8_t MAX86150_SAMPLEAVG_2 = 0x20; 62 | static const uint8_t MAX86150_SAMPLEAVG_4 = 0x40; 63 | static const uint8_t MAX86150_SAMPLEAVG_8 = 0x60; 64 | static const uint8_t MAX86150_SAMPLEAVG_16 = 0x80; 65 | static const uint8_t MAX86150_SAMPLEAVG_32 = 0xA0; 66 | 67 | static const uint8_t MAX86150_ROLLOVER_MASK = 0xEF; 68 | static const uint8_t MAX86150_ROLLOVER_ENABLE = 0x10; 69 | static const uint8_t MAX86150_ROLLOVER_DISABLE = 0x00; 70 | 71 | static const uint8_t MAX86150_A_FULL_MASK = 0xF0; 72 | 73 | static const uint8_t MAX86150_SHUTDOWN_MASK = 0x7F; 74 | static const uint8_t MAX86150_SHUTDOWN = 0x80; 75 | static const uint8_t MAX86150_WAKEUP = 0x00; 76 | 77 | static const uint8_t MAX86150_RESET_MASK = 0xFE; 78 | static const uint8_t MAX86150_RESET = 0x01; 79 | 80 | static const uint8_t MAX86150_MODE_MASK = 0xF8; 81 | static const uint8_t MAX86150_MODE_REDONLY = 0x02; 82 | static const uint8_t MAX86150_MODE_REDIRONLY = 0x03; 83 | static const uint8_t MAX86150_MODE_MULTILED = 0x07; 84 | 85 | static const uint8_t MAX86150_ADCRANGE_MASK = 0x9F; 86 | static const uint8_t MAX86150_ADCRANGE_2048 = 0x00; 87 | static const uint8_t MAX86150_ADCRANGE_4096 = 0x20; 88 | static const uint8_t MAX86150_ADCRANGE_8192 = 0x40; 89 | static const uint8_t MAX86150_ADCRANGE_16384 = 0x60; 90 | 91 | static const uint8_t MAX86150_SAMPLERATE_MASK = 0xE3; 92 | static const uint8_t MAX86150_SAMPLERATE_50 = 0x00; 93 | static const uint8_t MAX86150_SAMPLERATE_100 = 0x04; 94 | static const uint8_t MAX86150_SAMPLERATE_200 = 0x08; 95 | static const uint8_t MAX86150_SAMPLERATE_400 = 0x0C; 96 | static const uint8_t MAX86150_SAMPLERATE_800 = 0x10; 97 | static const uint8_t MAX86150_SAMPLERATE_1000 = 0x14; 98 | static const uint8_t MAX86150_SAMPLERATE_1600 = 0x18; 99 | static const uint8_t MAX86150_SAMPLERATE_3200 = 0x1C; 100 | 101 | static const uint8_t MAX86150_PULSEWIDTH_MASK = 0xFC; 102 | static const uint8_t MAX86150_PULSEWIDTH_69 = 0x00; 103 | static const uint8_t MAX86150_PULSEWIDTH_118 = 0x01; 104 | static const uint8_t MAX86150_PULSEWIDTH_215 = 0x02; 105 | static const uint8_t MAX86150_PULSEWIDTH_411 = 0x03; 106 | 107 | static const uint8_t MAX86150_SLOT1_MASK = 0xF0; 108 | static const uint8_t MAX86150_SLOT2_MASK = 0x0F; 109 | static const uint8_t MAX86150_SLOT3_MASK = 0xF0; 110 | static const uint8_t MAX86150_SLOT4_MASK = 0x0F; 111 | 112 | static const uint8_t SLOT_NONE = 0x00; 113 | static const uint8_t SLOT_RED_LED = 0x01; 114 | static const uint8_t SLOT_IR_LED = 0x02; 115 | static const uint8_t SLOT_RED_PILOT = 0x09; 116 | static const uint8_t SLOT_IR_PILOT = 0x0A; 117 | static const uint8_t SLOT_ECG = 0x0D; 118 | 119 | static const uint8_t MAX_30105_EXPECTEDPARTID = 0x1E; 120 | 121 | MAX86150::MAX86150() { 122 | // Constructor 123 | } 124 | 125 | boolean MAX86150::begin(TwoWire &wirePort, uint32_t i2cSpeed, uint8_t i2caddr) 126 | { 127 | _i2cPort = &wirePort; //Grab which port the user wants us to use 128 | 129 | _i2cPort->begin(); 130 | _i2cPort->setClock(i2cSpeed); 131 | 132 | _i2caddr = i2caddr; 133 | 134 | // Step 1: Initial Communication and Verification 135 | // Check that a MAX86150 is connected 136 | if (readPartID() != MAX_30105_EXPECTEDPARTID) { 137 | // Error -- Part ID read from MAX86150 does not match expected part ID. 138 | // This may mean there is a physical connectivity problem (broken wire, unpowered, etc). 139 | return false; 140 | } 141 | return true; 142 | } 143 | 144 | // 145 | // Configuration 146 | // 147 | 148 | //Begin Interrupt configuration 149 | uint8_t MAX86150::getINT1(void) 150 | { 151 | return (readRegister8(_i2caddr, MAX86150_INTSTAT1)); 152 | } 153 | uint8_t MAX86150::getINT2(void) { 154 | return (readRegister8(_i2caddr, MAX86150_INTSTAT2)); 155 | } 156 | 157 | void MAX86150::enableAFULL(void) { 158 | bitMask(MAX86150_INTENABLE1, MAX86150_INT_A_FULL_MASK, MAX86150_INT_A_FULL_ENABLE); 159 | } 160 | void MAX86150::disableAFULL(void) { 161 | bitMask(MAX86150_INTENABLE1, MAX86150_INT_A_FULL_MASK, MAX86150_INT_A_FULL_DISABLE); 162 | } 163 | 164 | void MAX86150::enableDATARDY(void) { 165 | bitMask(MAX86150_INTENABLE1, MAX86150_INT_DATA_RDY_MASK, MAX86150_INT_DATA_RDY_ENABLE); 166 | } 167 | void MAX86150::disableDATARDY(void) { 168 | bitMask(MAX86150_INTENABLE1, MAX86150_INT_DATA_RDY_MASK, MAX86150_INT_DATA_RDY_DISABLE); 169 | } 170 | 171 | void MAX86150::enableALCOVF(void) { 172 | bitMask(MAX86150_INTENABLE1, MAX86150_INT_ALC_OVF_MASK, MAX86150_INT_ALC_OVF_ENABLE); 173 | } 174 | void MAX86150::disableALCOVF(void) { 175 | bitMask(MAX86150_INTENABLE1, MAX86150_INT_ALC_OVF_MASK, MAX86150_INT_ALC_OVF_DISABLE); 176 | } 177 | 178 | void MAX86150::enablePROXINT(void) { 179 | bitMask(MAX86150_INTENABLE1, MAX86150_INT_PROX_INT_MASK, MAX86150_INT_PROX_INT_ENABLE); 180 | } 181 | void MAX86150::disablePROXINT(void) { 182 | bitMask(MAX86150_INTENABLE1, MAX86150_INT_PROX_INT_MASK, MAX86150_INT_PROX_INT_DISABLE); 183 | } 184 | //End Interrupt configuration 185 | 186 | void MAX86150::softReset(void) { 187 | bitMask(MAX86150_SYSCONTROL, MAX86150_RESET_MASK, MAX86150_RESET); 188 | 189 | // Poll for bit to clear, reset is then complete 190 | // Timeout after 100ms 191 | unsigned long startTime = millis(); 192 | while (millis() - startTime < 100) 193 | { 194 | uint8_t response = readRegister8(_i2caddr, MAX86150_SYSCONTROL); 195 | if ((response & MAX86150_RESET) == 0) break; //We're done! 196 | delay(1); //Let's not over burden the I2C bus 197 | } 198 | } 199 | 200 | void MAX86150::shutDown(void) { 201 | // Put IC into low power mode (datasheet pg. 19) 202 | // During shutdown the IC will continue to respond to I2C commands but will 203 | // not update with or take new readings (such as temperature) 204 | bitMask(MAX86150_SYSCONTROL, MAX86150_SHUTDOWN_MASK, MAX86150_SHUTDOWN); 205 | } 206 | 207 | void MAX86150::wakeUp(void) { 208 | // Pull IC out of low power mode (datasheet pg. 19) 209 | bitMask(MAX86150_SYSCONTROL, MAX86150_SHUTDOWN_MASK, MAX86150_WAKEUP); 210 | } 211 | 212 | void MAX86150::setLEDMode(uint8_t mode) { 213 | // Set which LEDs are used for sampling -- Red only, RED+IR only, or custom. 214 | // See datasheet, page 19 215 | //bitMask(MAX86150_PPGCONFIG1, MAX86150_MODE_MASK, mode); 216 | } 217 | 218 | void MAX86150::setADCRange(uint8_t adcRange) { 219 | // adcRange: one of MAX86150_ADCRANGE_2048, _4096, _8192, _16384 220 | //bitMask(MAX86150_PARTICLECONFIG, MAX86150_ADCRANGE_MASK, adcRange); 221 | } 222 | 223 | void MAX86150::setSampleRate(uint8_t sampleRate) { 224 | // sampleRate: one of MAX86150_SAMPLERATE_50, _100, _200, _400, _800, _1000, _1600, _3200 225 | //bitMask(MAX86150_PARTICLECONFIG, MAX86150_SAMPLERATE_MASK, sampleRate); 226 | } 227 | 228 | void MAX86150::setPulseWidth(uint8_t pulseWidth) { 229 | // pulseWidth: one of MAX86150_PULSEWIDTH_69, _188, _215, _411 230 | //bitMask(MAX86150_PPGCONFIG1, MAX86150_PULSEWIDTH_MASK, pulseWidth); 231 | } 232 | 233 | // NOTE: Amplitude values: 0x00 = 0mA, 0x7F = 25.4mA, 0xFF = 50mA (typical) 234 | // See datasheet, page 21 235 | void MAX86150::setPulseAmplitudeRed(uint8_t amplitude) 236 | { 237 | writeRegister8(_i2caddr, MAX86150_LED2_PULSEAMP, amplitude); 238 | } 239 | 240 | void MAX86150::setPulseAmplitudeIR(uint8_t amplitude) 241 | { 242 | writeRegister8(_i2caddr, MAX86150_LED1_PULSEAMP, amplitude); 243 | } 244 | 245 | void MAX86150::setPulseAmplitudeProximity(uint8_t amplitude) { 246 | writeRegister8(_i2caddr, MAX86150_LED_PROX_AMP, amplitude); 247 | } 248 | 249 | void MAX86150::setProximityThreshold(uint8_t threshMSB) 250 | { 251 | // The threshMSB signifies only the 8 most significant-bits of the ADC count. 252 | writeRegister8(_i2caddr, MAX86150_PROXINTTHRESH, threshMSB); 253 | } 254 | 255 | //Given a slot number assign a thing to it 256 | //Devices are SLOT_RED_LED or SLOT_RED_PILOT (proximity) 257 | //Assigning a SLOT_RED_LED will pulse LED 258 | //Assigning a SLOT_RED_PILOT will ?? 259 | void MAX86150::enableSlot(uint8_t slotNumber, uint8_t device) 260 | { 261 | uint8_t originalContents; 262 | 263 | switch (slotNumber) { 264 | case (1): 265 | bitMask(MAX86150_FIFOCONTROL1, MAX86150_SLOT1_MASK, device); 266 | break; 267 | case (2): 268 | bitMask(MAX86150_FIFOCONTROL1, MAX86150_SLOT2_MASK, device << 4); 269 | break; 270 | case (3): 271 | bitMask(MAX86150_FIFOCONTROL2, MAX86150_SLOT3_MASK, device); 272 | break; 273 | case (4): 274 | bitMask(MAX86150_FIFOCONTROL2, MAX86150_SLOT4_MASK, device << 4); 275 | break; 276 | default: 277 | //Shouldn't be here! 278 | break; 279 | } 280 | } 281 | 282 | //Clears all slot assignments 283 | void MAX86150::disableSlots(void) 284 | { 285 | writeRegister8(_i2caddr, MAX86150_FIFOCONTROL1, 0); 286 | writeRegister8(_i2caddr, MAX86150_FIFOCONTROL2, 0); 287 | } 288 | 289 | // 290 | // FIFO Configuration 291 | // 292 | 293 | void MAX86150::setFIFOAverage(uint8_t numberOfSamples) 294 | { 295 | bitMask(MAX86150_FIFOCONFIG, MAX86150_SAMPLEAVG_MASK, numberOfSamples); 296 | } 297 | 298 | //Resets all points to start in a known state 299 | void MAX86150::clearFIFO(void) { 300 | writeRegister8(_i2caddr, MAX86150_FIFOWRITEPTR, 0); 301 | writeRegister8(_i2caddr, MAX86150_FIFOOVERFLOW, 0); 302 | writeRegister8(_i2caddr, MAX86150_FIFOREADPTR, 0); 303 | } 304 | 305 | //Enable roll over if FIFO over flows 306 | void MAX86150::enableFIFORollover(void) { 307 | bitMask(MAX86150_FIFOCONFIG, MAX86150_ROLLOVER_MASK, MAX86150_ROLLOVER_ENABLE); 308 | } 309 | 310 | //Disable roll over if FIFO over flows 311 | void MAX86150::disableFIFORollover(void) { 312 | bitMask(MAX86150_FIFOCONFIG, MAX86150_ROLLOVER_MASK, MAX86150_ROLLOVER_DISABLE); 313 | } 314 | 315 | //Power on default is 32 samples 316 | //Note it is reverse: 0x00 is 32 samples, 0x0F is 17 samples 317 | void MAX86150::setFIFOAlmostFull(uint8_t numberOfSamples) { 318 | bitMask(MAX86150_FIFOCONFIG, MAX86150_A_FULL_MASK, numberOfSamples); 319 | } 320 | 321 | //Read the FIFO Write Pointer 322 | uint8_t MAX86150::getWritePointer(void) { 323 | return (readRegister8(_i2caddr, MAX86150_FIFOWRITEPTR)); 324 | } 325 | 326 | //Read the FIFO Read Pointer 327 | uint8_t MAX86150::getReadPointer(void) { 328 | return (readRegister8(_i2caddr, MAX86150_FIFOREADPTR)); 329 | } 330 | 331 | // Set the PROX_INT_THRESHold 332 | void MAX86150::setPROXINTTHRESH(uint8_t val) { 333 | writeRegister8(_i2caddr, MAX86150_PROXINTTHRESH, val); 334 | } 335 | 336 | // 337 | // Device ID and Revision 338 | // 339 | uint8_t MAX86150::readPartID() { 340 | return readRegister8(_i2caddr, MAX86150_PARTID); 341 | } 342 | 343 | //Setup the sensor 344 | //The MAX86150 has many settings. By default we select: 345 | // Sample Average = 4 346 | // Mode = MultiLED 347 | // ADC Range = 16384 (62.5pA per LSB) 348 | // Sample rate = 50 349 | //Use the default setup if you are just getting started with the MAX86150 sensor 350 | void MAX86150::setup(byte powerLevel, byte sampleAverage, byte ledMode, int sampleRate, int pulseWidth, int adcRange) 351 | { 352 | activeDevices=3; 353 | writeRegister8(_i2caddr,MAX86150_SYSCONTROL,0x01); 354 | delay(100); 355 | writeRegister8(_i2caddr,MAX86150_FIFOCONFIG,0x7F); 356 | 357 | //FIFO Configuration 358 | //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 359 | //The chip will average multiple samples of same type together if you wish 360 | if (sampleAverage == 1) setFIFOAverage(MAX86150_SAMPLEAVG_1); //No averaging per FIFO record 361 | else if (sampleAverage == 2) setFIFOAverage(MAX86150_SAMPLEAVG_2); 362 | else if (sampleAverage == 4) setFIFOAverage(MAX86150_SAMPLEAVG_4); 363 | else if (sampleAverage == 8) setFIFOAverage(MAX86150_SAMPLEAVG_8); 364 | else if (sampleAverage == 16) setFIFOAverage(MAX86150_SAMPLEAVG_16); 365 | else if (sampleAverage == 32) setFIFOAverage(MAX86150_SAMPLEAVG_32); 366 | else setFIFOAverage(MAX86150_SAMPLEAVG_4); 367 | 368 | uint16_t FIFOCode = 0x00; 369 | 370 | FIFOCode = FIFOCode<<4 | 0x0009;// : FIFOCode; //insert ECG front of ETI in FIFO 371 | FIFOCode = FIFOCode<<8 | 0x0021;//) : FIFOCode; //insert Red(2) and IR (1) in front of ECG in FIFO 372 | 373 | 374 | //FIFO Control 1 = FD2|FD1, FIFO Control 2 = FD4|FD3 375 | 376 | writeRegister8(_i2caddr,MAX86150_FIFOCONTROL1,(0b00100001)); 377 | //writeRegister8(_i2caddr,MAX86150_FIFOCONTROL1,(0b00001001)); 378 | writeRegister8(_i2caddr,MAX86150_FIFOCONTROL2,(0b00001001)); 379 | //writeRegister8(_i2caddr,MAX86150_FIFOCONTROL2,(0b00000000)); 380 | //writeRegister8(_i2caddr,MAX86150_FIFOCONTROL1, (char)(FIFOCode & 0x00FF) ); 381 | //writeRegister8(_i2caddr,MAX86150_FIFOCONTROL2, (char)(FIFOCode >>8) ); 382 | 383 | writeRegister8(_i2caddr,MAX86150_PPGCONFIG1,0b11010001); 384 | //writeRegister8(_i2caddr,MAX86150_PPGCONFIG1,0b11100111); 385 | 386 | writeRegister8(_i2caddr,MAX86150_PPGCONFIG2, 0x06); 387 | writeRegister8(_i2caddr,MAX86150_LED_RANGE, 0x00 ); // PPG_ADC_RGE: 32768nA 388 | 389 | writeRegister8(_i2caddr,MAX86150_SYSCONTROL,0x04);//start FIFO 390 | 391 | writeRegister8(_i2caddr,MAX86150_ECG_CONFIG1,0b00000011); 392 | writeRegister8(_i2caddr,MAX86150_ECG_CONFIG3,0b00001101); 393 | 394 | setPulseAmplitudeRed(0xFF); 395 | setPulseAmplitudeIR(0xFF); 396 | 397 | //Multi-LED Mode Configuration, Enable the reading of the three LEDs 398 | //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 399 | //enableSlot(1, SLOT_RED_LED); 400 | //if (ledMode > 1) 401 | //enableSlot(2, SLOT_IR_LED); 402 | //if (ledMode > 2) 403 | //enableSlot(3, SLOT_ECG); 404 | //enableSlot(1, SLOT_RED_PILOT); 405 | //enableSlot(2, SLOT_IR_PILOT); 406 | //enableSlot(3, SLOT_GREEN_PILOT); 407 | //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 408 | 409 | clearFIFO(); //Reset the FIFO before we begin checking the sensor 410 | } 411 | 412 | //Tell caller how many samples are available 413 | uint8_t MAX86150::available(void) 414 | { 415 | int8_t numberOfSamples = sense.head - sense.tail; 416 | if (numberOfSamples < 0) numberOfSamples += STORAGE_SIZE; 417 | 418 | return (numberOfSamples); 419 | } 420 | 421 | //Report the most recent red value 422 | uint32_t MAX86150::getRed(void) 423 | { 424 | //Check the sensor for new data for 250ms 425 | if(safeCheck(250)) 426 | return (sense.red[sense.head]); 427 | else 428 | return(0); //Sensor failed to find new data 429 | } 430 | 431 | //Report the most recent IR value 432 | uint32_t MAX86150::getIR(void) 433 | { 434 | //Check the sensor for new data for 250ms 435 | if(safeCheck(250)) 436 | return (sense.IR[sense.head]); 437 | else 438 | return(0); //Sensor failed to find new data 439 | } 440 | 441 | //Report the most recent Green value 442 | int32_t MAX86150::getECG(void) 443 | { 444 | //Check the sensor for new data for 250ms 445 | if(safeCheck(250)) 446 | return (sense.ecg[sense.head]); 447 | else 448 | return(0); //Sensor failed to find new data 449 | } 450 | 451 | //Report the next Red value in the FIFO 452 | uint32_t MAX86150::getFIFORed(void) 453 | { 454 | return (sense.red[sense.tail]); 455 | } 456 | 457 | //Report the next IR value in the FIFO 458 | uint32_t MAX86150::getFIFOIR(void) 459 | { 460 | return (sense.IR[sense.tail]); 461 | } 462 | 463 | //Report the next Green value in the FIFO 464 | int32_t MAX86150::getFIFOECG(void) 465 | { 466 | return (sense.ecg[sense.tail]); 467 | } 468 | 469 | //Advance the tail 470 | void MAX86150::nextSample(void) 471 | { 472 | if(available()) //Only advance the tail if new data is available 473 | { 474 | sense.tail++; 475 | sense.tail %= STORAGE_SIZE; //Wrap condition 476 | } 477 | } 478 | 479 | //Polls the sensor for new data 480 | //Call regularly 481 | //If new data is available, it updates the head and tail in the main struct 482 | //Returns number of new samples obtained 483 | uint16_t MAX86150::check(void) 484 | { 485 | //Read register FIDO_DATA in (3-byte * number of active LED) chunks 486 | //Until FIFO_RD_PTR = FIFO_WR_PTR 487 | 488 | byte readPointer = getReadPointer(); 489 | byte writePointer = getWritePointer(); 490 | 491 | int numberOfSamples = 0; 492 | 493 | //Do we have new data? 494 | if (readPointer != writePointer) 495 | { 496 | //Calculate the number of readings we need to get from sensor 497 | numberOfSamples = writePointer - readPointer; 498 | if (numberOfSamples < 0) numberOfSamples += 32; //Wrap condition 499 | 500 | //We now have the number of readings, now calc bytes to read 501 | //For this example we are just doing Red and IR (3 bytes each) 502 | int bytesLeftToRead = numberOfSamples * activeDevices * 3; 503 | 504 | //Get ready to read a burst of data from the FIFO register 505 | _i2cPort->beginTransmission(_i2caddr); 506 | _i2cPort->write(MAX86150_FIFODATA); 507 | _i2cPort->endTransmission(); 508 | 509 | //We may need to read as many as 288 bytes so we read in blocks no larger than I2C_BUFFER_LENGTH 510 | //I2C_BUFFER_LENGTH changes based on the platform. 64 bytes for SAMD21, 32 bytes for Uno. 511 | //Wire.requestFrom() is limited to BUFFER_LENGTH which is 32 on the Uno 512 | while (bytesLeftToRead > 0) 513 | { 514 | int toGet = bytesLeftToRead; 515 | if (toGet > I2C_BUFFER_LENGTH) 516 | { 517 | //If toGet is 32 this is bad because we read 6 bytes (Red+IR * 3 = 6) at a time 518 | //32 % 6 = 2 left over. We don't want to request 32 bytes, we want to request 30. 519 | //32 % 9 (Red+IR+GREEN) = 5 left over. We want to request 27. 520 | 521 | toGet = I2C_BUFFER_LENGTH - (I2C_BUFFER_LENGTH % (activeDevices * 3)); //Trim toGet to be a multiple of the samples we need to read 522 | } 523 | 524 | bytesLeftToRead -= toGet; 525 | 526 | //Request toGet number of bytes from sensor 527 | _i2cPort->requestFrom(_i2caddr, toGet); 528 | 529 | while (toGet > 0) 530 | { 531 | sense.head++; //Advance the head of the storage struct 532 | sense.head %= STORAGE_SIZE; //Wrap condition 533 | 534 | byte temp[sizeof(uint32_t)]; //Array of 4 bytes that we will convert into long 535 | uint32_t tempLong; 536 | 537 | //Burst read three bytes - RED 538 | temp[3] = 0; 539 | temp[2] = _i2cPort->read(); 540 | temp[1] = _i2cPort->read(); 541 | temp[0] = _i2cPort->read(); 542 | 543 | //Convert array to long 544 | memcpy(&tempLong, temp, sizeof(tempLong)); 545 | 546 | tempLong &= 0x7FFFF; //Zero out all but 18 bits 547 | 548 | sense.red[sense.head] = tempLong; //Store this reading into the sense array 549 | 550 | if (activeDevices > 1) 551 | { 552 | //Burst read three more bytes - IR 553 | temp[3] = 0; 554 | temp[2] = _i2cPort->read(); 555 | temp[1] = _i2cPort->read(); 556 | temp[0] = _i2cPort->read(); 557 | 558 | //Convert array to long 559 | memcpy(&tempLong, temp, sizeof(tempLong)); 560 | //Serial.println(tempLong); 561 | tempLong &= 0x7FFFF; //Zero out all but 18 bits 562 | 563 | sense.IR[sense.head] = tempLong; 564 | } 565 | 566 | if (activeDevices > 2) 567 | { 568 | //Burst read three more bytes - ECG 569 | int32_t tempLongSigned; 570 | 571 | temp[3] = 0; 572 | temp[2] = _i2cPort->read(); 573 | temp[1] = _i2cPort->read(); 574 | temp[0] = _i2cPort->read(); 575 | //Serial.println(tempLong); 576 | //Convert array to long 577 | memcpy(&tempLongSigned, temp, sizeof(tempLongSigned)); 578 | 579 | //tempLong &= 0x3FFFF; //Zero out all but 18 bits 580 | 581 | sense.ecg[sense.head] = tempLongSigned; 582 | } 583 | 584 | toGet -= activeDevices * 3; 585 | } 586 | } //End while (bytesLeftToRead > 0) 587 | } //End readPtr != writePtr 588 | return (numberOfSamples); //Let the world know how much new data we found 589 | } 590 | 591 | //Check for new data but give up after a certain amount of time 592 | //Returns true if new data was found 593 | //Returns false if new data was not found 594 | bool MAX86150::safeCheck(uint8_t maxTimeToCheck) 595 | { 596 | uint32_t markTime = millis(); 597 | 598 | while(1) 599 | { 600 | if(millis() - markTime > maxTimeToCheck) return(false); 601 | 602 | if(check() == true) //We found new data! 603 | return(true); 604 | 605 | delay(1); 606 | } 607 | } 608 | 609 | //Given a register, read it, mask it, and then set the thing 610 | void MAX86150::bitMask(uint8_t reg, uint8_t mask, uint8_t thing) 611 | { 612 | // Grab current register context 613 | uint8_t originalContents = readRegister8(_i2caddr, reg); 614 | 615 | // Zero-out the portions of the register we're interested in 616 | originalContents = originalContents & mask; 617 | 618 | // Change contents 619 | writeRegister8(_i2caddr, reg, originalContents | thing); 620 | } 621 | 622 | uint8_t MAX86150::readRegister8(uint8_t address, uint8_t reg) { 623 | 624 | uint8_t tempData = 0; 625 | _i2cPort->beginTransmission(address); 626 | _i2cPort->write(reg); 627 | _i2cPort->endTransmission(false); 628 | 629 | _i2cPort->requestFrom((uint8_t)address, (uint8_t)1); // Request 1 byte 630 | if (_i2cPort->available()) 631 | { 632 | 633 | return(_i2cPort->read()); 634 | } 635 | return (0); //Fail 636 | } 637 | 638 | void MAX86150::writeRegister8(uint8_t address, uint8_t reg, uint8_t value) { 639 | _i2cPort->beginTransmission(address); 640 | _i2cPort->write(reg); 641 | _i2cPort->write(value); 642 | _i2cPort->endTransmission(); 643 | } 644 | --------------------------------------------------------------------------------