├── .gitignore ├── .vscode ├── c_cpp_properties.json ├── launch.json ├── settings.json └── tasks.json ├── Learning ├── README.md └── get-started │ ├── .build-test-rules.yml │ ├── README.md │ ├── blink │ ├── CMakeLists.txt │ ├── README.md │ ├── main │ │ ├── CMakeLists.txt │ │ ├── Kconfig.projbuild │ │ ├── blink_example_main.c │ │ └── idf_component.yml │ ├── pytest_blink.py │ ├── sdkconfig.defaults │ ├── sdkconfig.defaults.esp32 │ ├── sdkconfig.defaults.esp32c3 │ ├── sdkconfig.defaults.esp32c5 │ ├── sdkconfig.defaults.esp32c6 │ ├── sdkconfig.defaults.esp32h2 │ ├── sdkconfig.defaults.esp32s2 │ └── sdkconfig.defaults.esp32s3 │ ├── hello_world │ ├── CMakeLists.txt │ ├── README.md │ ├── main │ │ ├── CMakeLists.txt │ │ └── hello_world_main.c │ ├── pytest_hello_world.py │ └── sdkconfig.ci │ └── sample_project │ ├── CMakeLists.txt │ ├── README.md │ └── main │ ├── CMakeLists.txt │ └── main.c ├── Old_C_Version ├── CMakeLists.txt ├── README.md ├── images │ ├── esp32max30102Library.png │ ├── max30102Fifo.png │ ├── max30102Modecontrol.png │ ├── max30102Registers1.png │ ├── max30102Registers2.png │ ├── max30102SpO2.png │ └── max30102Terminal.png └── main │ ├── CMakeLists.txt │ ├── main.c │ ├── max30102-registers.h │ ├── max30102.c │ └── max30102.h └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | sdkconfig 3 | sdkconfig.old 4 | .vscode/ -------------------------------------------------------------------------------- /.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Linux", 5 | "cStandard": "c11", 6 | "cppStandard": "c++17", 7 | "includePath": [ 8 | "${config:idf.espIdfPath}/components/**", 9 | "${config:idf.espIdfPathWin}/components/**", 10 | "${workspaceFolder}/**" 11 | ], 12 | "browse": { 13 | "path": [ 14 | "${config:idf.espIdfPath}/components", 15 | "${config:idf.espIdfPathWin}/components", 16 | "${workspaceFolder}" 17 | ], 18 | "limitSymbolsToIncludedHeaders": false 19 | }, 20 | "compileCommands": "${workspaceFolder}/build/compile_commands.json" 21 | } 22 | ], 23 | "version": 4 24 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "espidf", 9 | "name": "Launch", 10 | "request": "launch", 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "C_Cpp.clang_format_style": "Visual Studio", 3 | "editor.formatOnSave": false, 4 | "[cpp]": { 5 | "editor.quickSuggestions": { 6 | "comments": "on", 7 | "strings": "on", 8 | "other": "on" 9 | } 10 | }, 11 | "[c]": { 12 | "editor.quickSuggestions": { 13 | "comments": "on", 14 | "strings": "on", 15 | "other": "on" 16 | } 17 | }, 18 | "C_Cpp.intelliSenseEngineFallback": "Enabled", 19 | "idf.port": "/dev/ttyUSB0", 20 | "git.ignoreLimitWarning": true 21 | } 22 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "Build - Build project", 6 | "type": "shell", 7 | "command": "${config:idf.pythonBinPath} ${config:idf.espIdfPath}/tools/idf.py build", 8 | "windows": { 9 | "command": "${config:idf.pythonBinPathWin} ${config:idf.espIdfPathWin}\\tools\\idf.py build" 10 | }, 11 | "options": { 12 | "env": { 13 | "PATH": "${config:idf.customExtraPaths}" 14 | } 15 | }, 16 | "problemMatcher": [ 17 | { 18 | "owner": "cpp", 19 | "fileLocation": ["relative", "${workspaceFolder}"], 20 | "pattern": { 21 | "regexp": "^\\.\\.(.*):(\\d+):(\\d+):\\s+(warning|error):\\s+(.*)$", 22 | "file": 1, 23 | "line": 2, 24 | "column": 3, 25 | "severity": 4, 26 | "message": 5 27 | } 28 | }, 29 | { 30 | "owner": "cpp", 31 | "fileLocation": "absolute", 32 | "pattern": { 33 | "regexp": "^[^\\.](.*):(\\d+):(\\d+):\\s+(warning|error):\\s+(.*)$", 34 | "file": 1, 35 | "line": 2, 36 | "column": 3, 37 | "severity": 4, 38 | "message": 5 39 | } 40 | } 41 | ], 42 | "group": { 43 | "kind": "build", 44 | "isDefault": true 45 | } 46 | }, 47 | { 48 | "label": "Set ESP-IDF Target", 49 | "type": "shell", 50 | "command": "${command:espIdf.setTarget}", 51 | "problemMatcher": { 52 | "owner": "cpp", 53 | "fileLocation": "absolute", 54 | "pattern": { 55 | "regexp": "^(.*):(//d+):(//d+)://s+(warning|error)://s+(.*)$", 56 | "file": 1, 57 | "line": 2, 58 | "column": 3, 59 | "severity": 4, 60 | "message": 5 61 | } 62 | }, 63 | }, 64 | { 65 | "label": "Clean - Clean the project", 66 | "type": "shell", 67 | "command": "${config:idf.pythonBinPath} ${config:idf.espIdfPath}/tools/idf.py fullclean", 68 | "windows": { 69 | "command": "${config:idf.pythonBinPathWin} ${config:idf.espIdfPathWin}\\tools\\idf.py fullclean" 70 | }, 71 | "options": { 72 | "env": { 73 | "PATH": "${config:idf.customExtraPaths}" 74 | } 75 | }, 76 | "problemMatcher": [ 77 | { 78 | "owner": "cpp", 79 | "fileLocation": ["relative", "${workspaceFolder}"], 80 | "pattern": { 81 | "regexp": "^\\.\\.(.*):(\\d+):(\\d+):\\s+(warning|error):\\s+(.*)$", 82 | "file": 1, 83 | "line": 2, 84 | "column": 3, 85 | "severity": 4, 86 | "message": 5 87 | } 88 | }, 89 | { 90 | "owner": "cpp", 91 | "fileLocation": "absolute", 92 | "pattern": { 93 | "regexp": "^[^\\.](.*):(\\d+):(\\d+):\\s+(warning|error):\\s+(.*)$", 94 | "file": 1, 95 | "line": 2, 96 | "column": 3, 97 | "severity": 4, 98 | "message": 5 99 | } 100 | } 101 | ], 102 | }, 103 | { 104 | "label": "Flash - Flash the device", 105 | "type": "shell", 106 | "command": "${config:idf.pythonBinPath} ${config:idf.espIdfPath}/tools/idf.py -p ${config:idf.port} -b ${config:idf.baudRate} flash", 107 | "windows": { 108 | "command": "${config:idf.pythonBinPathWin} ${config:idf.espIdfPathWin}\\tools\\idf.py flash -p ${config:idf.portWin} -b ${config:idf.baudRate}" 109 | }, 110 | "options": { 111 | "env": { 112 | "PATH": "${config:idf.customExtraPaths}" 113 | } 114 | }, 115 | "problemMatcher": [ 116 | { 117 | "owner": "cpp", 118 | "fileLocation": ["relative", "${workspaceFolder}"], 119 | "pattern": { 120 | "regexp": "^\\.\\.(.*):(\\d+):(\\d+):\\s+(warning|error):\\s+(.*)$", 121 | "file": 1, 122 | "line": 2, 123 | "column": 3, 124 | "severity": 4, 125 | "message": 5 126 | } 127 | }, 128 | { 129 | "owner": "cpp", 130 | "fileLocation": "absolute", 131 | "pattern": { 132 | "regexp": "^[^\\.](.*):(\\d+):(\\d+):\\s+(warning|error):\\s+(.*)$", 133 | "file": 1, 134 | "line": 2, 135 | "column": 3, 136 | "severity": 4, 137 | "message": 5 138 | } 139 | } 140 | ], 141 | }, 142 | { 143 | "label": "Monitor: Start the monitor", 144 | "type": "shell", 145 | "command": "${config:idf.pythonBinPath} ${config:idf.espIdfPath}/tools/idf.py -p ${config:idf.port} -b ${config:idf.baudRate} monitor", 146 | "windows": { 147 | "command": "${config:idf.pythonBinPathWin} ${config:idf.espIdfPathWin}\\tools\\idf.py monitor -p ${config:idf.portWin} -b ${config:idf.baudRate}" 148 | }, 149 | "options": { 150 | "env": { 151 | "PATH": "${config:idf.customExtraPaths}", 152 | } 153 | }, 154 | "dependsOn": "Flash - Flash the device", 155 | }, 156 | { 157 | "label":"OpenOCD: Start openOCD", 158 | "type":"shell", 159 | "presentation": { 160 | "echo": true, 161 | "reveal": "never", 162 | "focus": false, 163 | "panel":"new" 164 | }, 165 | "command":"openocd -s ${command:espIdf.getOpenOcdScriptValue} ${command:espIdf.getOpenOcdConfigs}", 166 | "windows": { 167 | "command": "openocd.exe -s ${command:espIdf.getOpenOcdScriptValue} ${command:espIdf.getOpenOcdConfigs}" 168 | }, 169 | "options": { 170 | "env": { 171 | "PATH": "${config:idf.customExtraPaths}" 172 | } 173 | }, 174 | "problemMatcher": { 175 | "owner": "cpp", 176 | "fileLocation": "absolute", 177 | "pattern": { 178 | "regexp": "^(.*):(\\d+):(\\d+):\\s+(warning|error):\\s+(.*)$", 179 | "file": 1, 180 | "line": 2, 181 | "column": 3, 182 | "severity": 4, 183 | "message": 5 184 | } 185 | }, 186 | }, 187 | { 188 | "label": "adapter", 189 | "type": "shell", 190 | "command": "${config:idf.pythonBinPath}", 191 | "isBackground": true, 192 | "options": { 193 | "env": { 194 | "PATH": "${config:idf.customExtraPaths}", 195 | "PYTHONPATH": "${command:espIdf.getExtensionPath}/esp_debug_adapter/debug_adapter" 196 | } 197 | }, 198 | "problemMatcher": { 199 | "background": { 200 | "beginsPattern": "\bDEBUG_ADAPTER_STARTED\b", 201 | "endsPattern": "DEBUG_ADAPTER_READY2CONNECT", 202 | "activeOnStart": true 203 | }, 204 | "pattern": { 205 | "regexp": "(\\d+)-(\\d+)-(\\d+)\\s(\\d+):(\\d+):(\\d+),(\\d+)\\s-(.+)\\s(ERROR)", 206 | "file": 8, 207 | "line": 2, 208 | "column": 3, 209 | "severity": 4, 210 | "message": 9 211 | } 212 | }, 213 | "args": [ 214 | "${command:espIdf.getExtensionPath}/esp_debug_adapter/debug_adapter_main.py", 215 | "-e", "${workspaceFolder}/build/${command:espIdf.getProjectName}.elf", 216 | "-s", "${command:espIdf.getOpenOcdScriptValue}", 217 | "-ip", "localhost", 218 | "-dn", "${config:idf.adapterTargetName}", 219 | "-om", 220 | "connect_to_instance" 221 | ], 222 | "windows": { 223 | "args": [ 224 | "${command:espIdf.getExtensionPath}/esp_debug_adapter/debug_adapter_main.py", 225 | "-e", "${workspaceFolder}/build/${command:espIdf.getProjectName}.elf", 226 | "-s", "${command:espIdf.getOpenOcdScriptValue}", 227 | "-ip", "localhost", 228 | "-dn", "${config:idf.adapterTargetName}", 229 | "-om", 230 | "connect_to_instance" 231 | ] 232 | } 233 | } 234 | ] 235 | } 236 | -------------------------------------------------------------------------------- /Learning/README.md: -------------------------------------------------------------------------------- 1 | # MAX30102 Library 2 | Author: Joshua D. JOHN 3 | Date: 2024/07/26 4 | 5 | This project develops a library in C++ for the MAX30102 on ESP32. This documents the preparation and development of the project. 6 | 7 | ## Outline 8 | ### Setting up Dev Environment on Windows 9 | ### Loading up a Getting Started 10 | ### Loading up a C++ Getting Started 11 | ### Analyze how to I2C communication 12 | 13 | ## Setting up Dev Environment on Windows 14 | Please follow the instructions at 15 | https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/windows-setup.html 16 | 17 | The required software packages are downloaded and stored in Projects/Storage. 18 | 19 | ## Loading up a Getting Started 20 | Clone the esp32 repository to some folder 21 | https://github.com/espressif/esp-idf.git 22 | 23 | Follow the instructions here 24 | https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/windows-setup.html#get-started-windows-first-steps 25 | 26 | When the device is connected it should be visible under Device Manager->Ports as SiliconLabs CP210x 27 | 28 | Build the project 29 | ``` sh 30 | cd \hello_world 31 | idf.py set-target esp32 32 | idf.py menuconfig 33 | 34 | idf.py build 35 | 36 | idf.py -p COM5 flash 37 | 38 | idf.py -p COM5 monitor 39 | ``` 40 | Remember to press the button on the right to reset and trigger the flashing. 41 | 42 | ## Loading up a C++ Getting Started 43 | Follow the instructions here 44 | https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/cplusplus.html 45 | 46 | Clone esp-idf-cxx 47 | https://github.com/espressif/esp-idf-cxx.git 48 | 49 | Then go to your project directory, use `idf.py add-dependency espressif/esp-idf-cxx^1.0.0-beta ` (should only be done once) and you should be able to use CPP components. 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /Learning/get-started/.build-test-rules.yml: -------------------------------------------------------------------------------- 1 | # Documentation: .gitlab/ci/README.md#manifest-file-to-control-the-buildtest-apps 2 | 3 | examples/get-started/blink: 4 | disable: 5 | - if: SOC_GPSPI_SUPPORTED != 1 and SOC_RMT_SUPPORTED != 1 # The blink example relies on the RMT or GPSPI to drive the led strip 6 | depends_components: 7 | - esp_driver_gpio 8 | - esp_driver_spi 9 | - esp_driver_rmt 10 | 11 | examples/get-started/hello_world: 12 | enable: 13 | - if: INCLUDE_DEFAULT == 1 or IDF_TARGET in ["linux", "esp32c5", "esp32c61"] 14 | -------------------------------------------------------------------------------- /Learning/get-started/README.md: -------------------------------------------------------------------------------- 1 | # Get Started Examples 2 | 3 | Simple code to get started with ESP32 and ESP-IDF. 4 | 5 | See the [README.md](../README.md) file in the upper level [examples](../) directory for more information about examples. 6 | -------------------------------------------------------------------------------- /Learning/get-started/blink/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # The following five lines of boilerplate have to be in your project's 2 | # CMakeLists in this exact order for cmake to work correctly 3 | cmake_minimum_required(VERSION 3.16) 4 | 5 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 6 | project(blink) 7 | -------------------------------------------------------------------------------- /Learning/get-started/blink/README.md: -------------------------------------------------------------------------------- 1 | | Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-H2 | ESP32-P4 | ESP32-S2 | ESP32-S3 | 2 | | ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | 3 | 4 | # Blink Example 5 | 6 | (See the README.md file in the upper level 'examples' directory for more information about examples.) 7 | 8 | This example demonstrates how to blink a LED by using the GPIO driver or using the [led_strip](https://components.espressif.com/component/espressif/led_strip) library if the LED is addressable e.g. [WS2812](https://cdn-shop.adafruit.com/datasheets/WS2812B.pdf). The `led_strip` library is installed via [component manager](main/idf_component.yml). 9 | 10 | ## How to Use Example 11 | 12 | Before project configuration and build, be sure to set the correct chip target using `idf.py set-target `. 13 | 14 | ### Hardware Required 15 | 16 | * A development board with normal LED or addressable LED on-board (e.g., ESP32-S3-DevKitC, ESP32-C6-DevKitC etc.) 17 | * A USB cable for Power supply and programming 18 | 19 | See [Development Boards](https://www.espressif.com/en/products/devkits) for more information about it. 20 | 21 | ### Configure the Project 22 | 23 | Open the project configuration menu (`idf.py menuconfig`). 24 | 25 | In the `Example Configuration` menu: 26 | 27 | * Select the LED type in the `Blink LED type` option. 28 | * Use `GPIO` for regular LED 29 | * Use `LED strip` for addressable LED 30 | * If the LED type is `LED strip`, select the backend peripheral 31 | * `RMT` is only available for ESP targets with RMT peripheral supported 32 | * `SPI` is available for all ESP targets 33 | * Set the GPIO number used for the signal in the `Blink GPIO number` option. 34 | * Set the blinking period in the `Blink period in ms` option. 35 | 36 | ### Build and Flash 37 | 38 | Run `idf.py -p PORT flash monitor` to build, flash and monitor the project. 39 | 40 | (To exit the serial monitor, type ``Ctrl-]``.) 41 | 42 | See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects. 43 | 44 | ## Example Output 45 | 46 | As you run the example, you will see the LED blinking, according to the previously defined period. For the addressable LED, you can also change the LED color by setting the `led_strip_set_pixel(led_strip, 0, 16, 16, 16);` (LED Strip, Pixel Number, Red, Green, Blue) with values from 0 to 255 in the [source file](main/blink_example_main.c). 47 | 48 | ```text 49 | I (315) example: Example configured to blink addressable LED! 50 | I (325) example: Turning the LED OFF! 51 | I (1325) example: Turning the LED ON! 52 | I (2325) example: Turning the LED OFF! 53 | I (3325) example: Turning the LED ON! 54 | I (4325) example: Turning the LED OFF! 55 | I (5325) example: Turning the LED ON! 56 | I (6325) example: Turning the LED OFF! 57 | I (7325) example: Turning the LED ON! 58 | I (8325) example: Turning the LED OFF! 59 | ``` 60 | 61 | Note: The color order could be different according to the LED model. 62 | 63 | The pixel number indicates the pixel position in the LED strip. For a single LED, use 0. 64 | 65 | ## Troubleshooting 66 | 67 | * If the LED isn't blinking, check the GPIO or the LED type selection in the `Example Configuration` menu. 68 | 69 | For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon. 70 | -------------------------------------------------------------------------------- /Learning/get-started/blink/main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | idf_component_register(SRCS "blink_example_main.c" 2 | INCLUDE_DIRS ".") 3 | -------------------------------------------------------------------------------- /Learning/get-started/blink/main/Kconfig.projbuild: -------------------------------------------------------------------------------- 1 | menu "Example Configuration" 2 | 3 | orsource "$IDF_PATH/examples/common_components/env_caps/$IDF_TARGET/Kconfig.env_caps" 4 | 5 | choice BLINK_LED 6 | prompt "Blink LED type" 7 | default BLINK_LED_GPIO 8 | help 9 | Select the LED type. A normal level controlled LED or an addressable LED strip. 10 | The default selection is based on the Espressif DevKit boards. 11 | You can change the default selection according to your board. 12 | 13 | config BLINK_LED_GPIO 14 | bool "GPIO" 15 | config BLINK_LED_STRIP 16 | bool "LED strip" 17 | endchoice 18 | 19 | choice BLINK_LED_STRIP_BACKEND 20 | depends on BLINK_LED_STRIP 21 | prompt "LED strip backend peripheral" 22 | default BLINK_LED_STRIP_BACKEND_RMT if SOC_RMT_SUPPORTED 23 | default BLINK_LED_STRIP_BACKEND_SPI 24 | help 25 | Select the backend peripheral to drive the LED strip. 26 | 27 | config BLINK_LED_STRIP_BACKEND_RMT 28 | depends on SOC_RMT_SUPPORTED 29 | bool "RMT" 30 | config BLINK_LED_STRIP_BACKEND_SPI 31 | bool "SPI" 32 | endchoice 33 | 34 | config BLINK_GPIO 35 | int "Blink GPIO number" 36 | range ENV_GPIO_RANGE_MIN ENV_GPIO_OUT_RANGE_MAX 37 | default 8 38 | help 39 | GPIO number (IOxx) to blink on and off the LED. 40 | Some GPIOs are used for other purposes (flash connections, etc.) and cannot be used to blink. 41 | 42 | config BLINK_PERIOD 43 | int "Blink period in ms" 44 | range 10 3600000 45 | default 1000 46 | help 47 | Define the blinking period in milliseconds. 48 | 49 | endmenu 50 | -------------------------------------------------------------------------------- /Learning/get-started/blink/main/blink_example_main.c: -------------------------------------------------------------------------------- 1 | /* Blink Example 2 | 3 | This example code is in the Public Domain (or CC0 licensed, at your option.) 4 | 5 | Unless required by applicable law or agreed to in writing, this 6 | software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 7 | CONDITIONS OF ANY KIND, either express or implied. 8 | */ 9 | #include 10 | #include "freertos/FreeRTOS.h" 11 | #include "freertos/task.h" 12 | #include "driver/gpio.h" 13 | #include "esp_log.h" 14 | #include "led_strip.h" 15 | #include "sdkconfig.h" 16 | 17 | static const char *TAG = "example"; 18 | 19 | /* Use project configuration menu (idf.py menuconfig) to choose the GPIO to blink, 20 | or you can edit the following line and set a number here. 21 | */ 22 | #define BLINK_GPIO CONFIG_BLINK_GPIO 23 | 24 | static uint8_t s_led_state = 0; 25 | 26 | #ifdef CONFIG_BLINK_LED_STRIP 27 | 28 | static led_strip_handle_t led_strip; 29 | 30 | static void blink_led(void) 31 | { 32 | /* If the addressable LED is enabled */ 33 | if (s_led_state) { 34 | /* Set the LED pixel using RGB from 0 (0%) to 255 (100%) for each color */ 35 | led_strip_set_pixel(led_strip, 0, 16, 16, 16); 36 | /* Refresh the strip to send data */ 37 | led_strip_refresh(led_strip); 38 | } else { 39 | /* Set all LED off to clear all pixels */ 40 | led_strip_clear(led_strip); 41 | } 42 | } 43 | 44 | static void configure_led(void) 45 | { 46 | ESP_LOGI(TAG, "Example configured to blink addressable LED!"); 47 | /* LED strip initialization with the GPIO and pixels number*/ 48 | led_strip_config_t strip_config = { 49 | .strip_gpio_num = BLINK_GPIO, 50 | .max_leds = 1, // at least one LED on board 51 | }; 52 | #if CONFIG_BLINK_LED_STRIP_BACKEND_RMT 53 | led_strip_rmt_config_t rmt_config = { 54 | .resolution_hz = 10 * 1000 * 1000, // 10MHz 55 | .flags.with_dma = false, 56 | }; 57 | ESP_ERROR_CHECK(led_strip_new_rmt_device(&strip_config, &rmt_config, &led_strip)); 58 | #elif CONFIG_BLINK_LED_STRIP_BACKEND_SPI 59 | led_strip_spi_config_t spi_config = { 60 | .spi_bus = SPI2_HOST, 61 | .flags.with_dma = true, 62 | }; 63 | ESP_ERROR_CHECK(led_strip_new_spi_device(&strip_config, &spi_config, &led_strip)); 64 | #else 65 | #error "unsupported LED strip backend" 66 | #endif 67 | /* Set all LED off to clear all pixels */ 68 | led_strip_clear(led_strip); 69 | } 70 | 71 | #elif CONFIG_BLINK_LED_GPIO 72 | 73 | static void blink_led(void) 74 | { 75 | /* Set the GPIO level according to the state (LOW or HIGH)*/ 76 | gpio_set_level(BLINK_GPIO, s_led_state); 77 | } 78 | 79 | static void configure_led(void) 80 | { 81 | ESP_LOGI(TAG, "Example configured to blink GPIO LED!"); 82 | gpio_reset_pin(BLINK_GPIO); 83 | /* Set the GPIO as a push/pull output */ 84 | gpio_set_direction(BLINK_GPIO, GPIO_MODE_OUTPUT); 85 | } 86 | 87 | #else 88 | #error "unsupported LED type" 89 | #endif 90 | 91 | void app_main(void) 92 | { 93 | 94 | /* Configure the peripheral according to the LED type */ 95 | configure_led(); 96 | 97 | while (1) { 98 | ESP_LOGI(TAG, "Turning the LED %s!", s_led_state == true ? "ON" : "OFF"); 99 | blink_led(); 100 | /* Toggle the LED state */ 101 | s_led_state = !s_led_state; 102 | vTaskDelay(CONFIG_BLINK_PERIOD / portTICK_PERIOD_MS); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /Learning/get-started/blink/main/idf_component.yml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | espressif/led_strip: "^2.4.1" 3 | -------------------------------------------------------------------------------- /Learning/get-started/blink/pytest_blink.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD 2 | # SPDX-License-Identifier: CC0-1.0 3 | import logging 4 | import os 5 | 6 | import pytest 7 | from pytest_embedded_idf.dut import IdfDut 8 | 9 | 10 | @pytest.mark.supported_targets 11 | @pytest.mark.generic 12 | def test_blink(dut: IdfDut) -> None: 13 | # check and log bin size 14 | binary_file = os.path.join(dut.app.binary_path, 'blink.bin') 15 | bin_size = os.path.getsize(binary_file) 16 | logging.info('blink_bin_size : {}KB'.format(bin_size // 1024)) 17 | -------------------------------------------------------------------------------- /Learning/get-started/blink/sdkconfig.defaults: -------------------------------------------------------------------------------- 1 | CONFIG_BLINK_LED_GPIO=y 2 | CONFIG_BLINK_GPIO=8 3 | -------------------------------------------------------------------------------- /Learning/get-started/blink/sdkconfig.defaults.esp32: -------------------------------------------------------------------------------- 1 | CONFIG_BLINK_GPIO=5 2 | -------------------------------------------------------------------------------- /Learning/get-started/blink/sdkconfig.defaults.esp32c3: -------------------------------------------------------------------------------- 1 | CONFIG_BLINK_LED_STRIP=y 2 | -------------------------------------------------------------------------------- /Learning/get-started/blink/sdkconfig.defaults.esp32c5: -------------------------------------------------------------------------------- 1 | CONFIG_BLINK_GPIO=27 2 | CONFIG_BLINK_LED_STRIP=y 3 | -------------------------------------------------------------------------------- /Learning/get-started/blink/sdkconfig.defaults.esp32c6: -------------------------------------------------------------------------------- 1 | CONFIG_BLINK_LED_STRIP=y 2 | -------------------------------------------------------------------------------- /Learning/get-started/blink/sdkconfig.defaults.esp32h2: -------------------------------------------------------------------------------- 1 | CONFIG_BLINK_LED_STRIP=y 2 | -------------------------------------------------------------------------------- /Learning/get-started/blink/sdkconfig.defaults.esp32s2: -------------------------------------------------------------------------------- 1 | CONFIG_BLINK_LED_STRIP=y 2 | CONFIG_BLINK_GPIO=18 3 | -------------------------------------------------------------------------------- /Learning/get-started/blink/sdkconfig.defaults.esp32s3: -------------------------------------------------------------------------------- 1 | CONFIG_BLINK_LED_STRIP=y 2 | CONFIG_BLINK_GPIO=48 3 | -------------------------------------------------------------------------------- /Learning/get-started/hello_world/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # The following lines of boilerplate have to be in your project's 2 | # CMakeLists in this exact order for cmake to work correctly 3 | cmake_minimum_required(VERSION 3.16) 4 | 5 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 6 | project(hello_world) 7 | -------------------------------------------------------------------------------- /Learning/get-started/hello_world/README.md: -------------------------------------------------------------------------------- 1 | | Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-P4 | ESP32-S2 | ESP32-S3 | Linux | 2 | | ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | -------- | -------- | -------- | ----- | 3 | 4 | # Hello World Example 5 | 6 | Starts a FreeRTOS task to print "Hello World". 7 | 8 | (See the README.md file in the upper level 'examples' directory for more information about examples.) 9 | 10 | ## How to use example 11 | 12 | Follow detailed instructions provided specifically for this example. 13 | 14 | Select the instructions depending on Espressif chip installed on your development board: 15 | 16 | - [ESP32 Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/stable/get-started/index.html) 17 | - [ESP32-S2 Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s2/get-started/index.html) 18 | 19 | 20 | ## Example folder contents 21 | 22 | The project **hello_world** contains one source file in C language [hello_world_main.c](main/hello_world_main.c). The file is located in folder [main](main). 23 | 24 | ESP-IDF projects are built using CMake. The project build configuration is contained in `CMakeLists.txt` files that provide set of directives and instructions describing the project's source files and targets (executable, library, or both). 25 | 26 | Below is short explanation of remaining files in the project folder. 27 | 28 | ``` 29 | ├── CMakeLists.txt 30 | ├── pytest_hello_world.py Python script used for automated testing 31 | ├── main 32 | │ ├── CMakeLists.txt 33 | │ └── hello_world_main.c 34 | └── README.md This is the file you are currently reading 35 | ``` 36 | 37 | For more information on structure and contents of ESP-IDF projects, please refer to Section [Build System](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/build-system.html) of the ESP-IDF Programming Guide. 38 | 39 | ## Troubleshooting 40 | 41 | * Program upload failure 42 | 43 | * Hardware connection is not correct: run `idf.py -p PORT monitor`, and reboot your board to see if there are any output logs. 44 | * The baud rate for downloading is too high: lower your baud rate in the `menuconfig` menu, and try again. 45 | 46 | ## Technical support and feedback 47 | 48 | Please use the following feedback channels: 49 | 50 | * For technical queries, go to the [esp32.com](https://esp32.com/) forum 51 | * For a feature request or bug report, create a [GitHub issue](https://github.com/espressif/esp-idf/issues) 52 | 53 | We will get back to you as soon as possible. 54 | -------------------------------------------------------------------------------- /Learning/get-started/hello_world/main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | idf_component_register(SRCS "hello_world_main.c" 2 | PRIV_REQUIRES spi_flash 3 | INCLUDE_DIRS "") 4 | -------------------------------------------------------------------------------- /Learning/get-started/hello_world/main/hello_world_main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2010-2022 Espressif Systems (Shanghai) CO LTD 3 | * 4 | * SPDX-License-Identifier: CC0-1.0 5 | */ 6 | 7 | #include 8 | #include 9 | #include "sdkconfig.h" 10 | #include "freertos/FreeRTOS.h" 11 | #include "freertos/task.h" 12 | #include "esp_chip_info.h" 13 | #include "esp_flash.h" 14 | #include "esp_system.h" 15 | 16 | void app_main(void) 17 | { 18 | printf("Hello world!\n"); 19 | 20 | /* Print chip information */ 21 | esp_chip_info_t chip_info; 22 | uint32_t flash_size; 23 | esp_chip_info(&chip_info); 24 | printf("This is %s chip with %d CPU core(s), %s%s%s%s, ", 25 | CONFIG_IDF_TARGET, 26 | chip_info.cores, 27 | (chip_info.features & CHIP_FEATURE_WIFI_BGN) ? "WiFi/" : "", 28 | (chip_info.features & CHIP_FEATURE_BT) ? "BT" : "", 29 | (chip_info.features & CHIP_FEATURE_BLE) ? "BLE" : "", 30 | (chip_info.features & CHIP_FEATURE_IEEE802154) ? ", 802.15.4 (Zigbee/Thread)" : ""); 31 | 32 | unsigned major_rev = chip_info.revision / 100; 33 | unsigned minor_rev = chip_info.revision % 100; 34 | printf("silicon revision v%d.%d, ", major_rev, minor_rev); 35 | if(esp_flash_get_size(NULL, &flash_size) != ESP_OK) { 36 | printf("Get flash size failed"); 37 | return; 38 | } 39 | 40 | printf("%" PRIu32 "MB %s flash\n", flash_size / (uint32_t)(1024 * 1024), 41 | (chip_info.features & CHIP_FEATURE_EMB_FLASH) ? "embedded" : "external"); 42 | 43 | printf("Minimum free heap size: %" PRIu32 " bytes\n", esp_get_minimum_free_heap_size()); 44 | 45 | for (int i = 10; i >= 0; i--) { 46 | printf("Restarting in %d seconds...\n", i); 47 | vTaskDelay(1000 / portTICK_PERIOD_MS); 48 | } 49 | printf("Restarting now.\n"); 50 | fflush(stdout); 51 | esp_restart(); 52 | } 53 | -------------------------------------------------------------------------------- /Learning/get-started/hello_world/pytest_hello_world.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD 2 | # SPDX-License-Identifier: CC0-1.0 3 | import hashlib 4 | import logging 5 | from typing import Callable 6 | 7 | import pytest 8 | from pytest_embedded_idf.dut import IdfDut 9 | from pytest_embedded_qemu.app import QemuApp 10 | from pytest_embedded_qemu.dut import QemuDut 11 | 12 | 13 | @pytest.mark.supported_targets 14 | @pytest.mark.preview_targets 15 | @pytest.mark.generic 16 | def test_hello_world( 17 | dut: IdfDut, log_minimum_free_heap_size: Callable[..., None] 18 | ) -> None: 19 | dut.expect('Hello world!') 20 | log_minimum_free_heap_size() 21 | 22 | 23 | @pytest.mark.linux 24 | @pytest.mark.host_test 25 | def test_hello_world_linux(dut: IdfDut) -> None: 26 | dut.expect('Hello world!') 27 | 28 | 29 | @pytest.mark.linux 30 | @pytest.mark.host_test 31 | @pytest.mark.macos_shell 32 | def test_hello_world_macos(dut: IdfDut) -> None: 33 | dut.expect('Hello world!') 34 | 35 | 36 | def verify_elf_sha256_embedding(app: QemuApp, sha256_reported: str) -> None: 37 | sha256 = hashlib.sha256() 38 | with open(app.elf_file, 'rb') as f: 39 | sha256.update(f.read()) 40 | sha256_expected = sha256.hexdigest() 41 | 42 | logging.info(f'ELF file SHA256: {sha256_expected}') 43 | logging.info(f'ELF file SHA256 (reported by the app): {sha256_reported}') 44 | 45 | # the app reports only the first several hex characters of the SHA256, check that they match 46 | if not sha256_expected.startswith(sha256_reported): 47 | raise ValueError('ELF file SHA256 mismatch') 48 | 49 | 50 | @pytest.mark.esp32 # we only support qemu on esp32 for now 51 | @pytest.mark.host_test 52 | @pytest.mark.qemu 53 | def test_hello_world_host(app: QemuApp, dut: QemuDut) -> None: 54 | sha256_reported = ( 55 | dut.expect(r'ELF file SHA256:\s+([a-f0-9]+)').group(1).decode('utf-8') 56 | ) 57 | verify_elf_sha256_embedding(app, sha256_reported) 58 | 59 | dut.expect('Hello world!') 60 | -------------------------------------------------------------------------------- /Learning/get-started/hello_world/sdkconfig.ci: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoshDumo/esp32-max30102/72cec93b98cda09ba7481687a36e406502628dfe/Learning/get-started/hello_world/sdkconfig.ci -------------------------------------------------------------------------------- /Learning/get-started/sample_project/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # The following five lines of boilerplate have to be in your project's 2 | # CMakeLists in this exact order for cmake to work correctly 3 | cmake_minimum_required(VERSION 3.16) 4 | 5 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 6 | project(main) 7 | -------------------------------------------------------------------------------- /Learning/get-started/sample_project/README.md: -------------------------------------------------------------------------------- 1 | # _Sample project_ 2 | 3 | (For general overview of examples and their usage, see the `README.md` file in the upper level 'examples' directory.) 4 | 5 | > [!NOTE] 6 | > After you click any link to [ESP-IDF Programming Guide](https://docs.espressif.com/projects/esp-idf/en/latest/index.html), go to the top of the sidebar, then make sure you have the appropriate **Espressif chip** (target) and **ESP-IDF version** selected in the dropdown menus. 7 | 8 | This is the example of a simplest buildable project. It is also used by the command `idf.py create-project` which copies these files to the path specified by the user and sets the project name. 9 | 10 | This sample projects contains: 11 | 12 | ```sh 13 | ├── CMakeLists.txt # Build configuration declaring entire project 14 | ├── main 15 | │   ├── CMakeLists.txt # File that registers the main component 16 | │   └── main.c # Source file for the main component 17 | └── README.md # File you are currently browsing 18 | ``` 19 | 20 | If you want to develop a project for the legacy build system based on Make that requires `Makefile` and `component.mk` files, see [esp-idf-template](https://github.com/espressif/esp-idf-template). 21 | 22 | 23 | ## Usage 24 | 25 | For brief instructions on how to configure, build, and flash the project, see [Examples README](https://github.com/espressif/esp-idf/blob/master/examples/README.md#using-examples) > Using Examples. 26 | 27 | 28 | ## Further Development 29 | 30 | For further steps on how to develop the project, see the following: 31 | 32 | - Managing the project: 33 | - [IDF Frontend](https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html#start-a-new-project) document 34 | - ESP-IDF Getting Started video ([YouTube](https://youtu.be/J8zc8mMNKtc?t=340), [bilibili](https://www.bilibili.com/video/BV1114y1r7du/?t=336)) 35 | - [Overview](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/build-system.html#example-project) of an example project 36 | - [Build System](https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-guides/build-system.html) document 37 | - Writing code: 38 | - Find appropriate bits of code in [application examples](https://github.com/espressif/esp-idf/tree/master/examples) 39 | - Write your own code following the [API references](https://docs.espressif.com/projects/esp-idf/en/stable/api-reference/index.html) 40 | 41 | 42 | ## Documentation 43 | 44 | If you want to contribute this project as an ESP-IDF application example, please write this README based on the [example README template](https://github.com/espressif/esp-idf/blob/master/docs/TEMPLATE_EXAMPLE_README.md). 45 | -------------------------------------------------------------------------------- /Learning/get-started/sample_project/main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | idf_component_register(SRCS "main.c" 2 | INCLUDE_DIRS ".") 3 | -------------------------------------------------------------------------------- /Learning/get-started/sample_project/main/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void app_main(void) 4 | { 5 | 6 | } 7 | -------------------------------------------------------------------------------- /Old_C_Version/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # The following lines of boilerplate have to be in your project's 2 | # CMakeLists in this exact order for cmake to work correctly 3 | cmake_minimum_required(VERSION 3.5) 4 | 5 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 6 | project(esp32-max30102) -------------------------------------------------------------------------------- /Old_C_Version/README.md: -------------------------------------------------------------------------------- 1 | # Tutorial 2 | 3 | ![Main Slide Image](images/esp32max30102Library.png) 4 | This is an example of how a library for the MAX30102 Heart Rate and Pulse Oximetry sensor. We will do a partial walk through on how to implement the features and functionality as suggested in the [MAX30102 Datasheet](https://datasheets.maximintegrated.com/en/ds/MAX30102.pdf). 5 | 6 | ### Registers 7 | 8 | The MAX30102 is controlled/adjusted using its eight bit registers. A register map detailed in the datasheet. Here is a snippet. 9 | 10 | ![Register1](images/max30102Registers1.png) 11 | 12 | ![Register2](images/max30102Registers2.png) 13 | 14 | We can see the register types, what each bit in the register does, the register address, its state when the device is powered on or reset (POR), and whether it is read only or writable. For example, 15 | 16 | ###### Mode Configuration Register 17 | * Writable 18 | * All bits set to 0 on Power On or Reset 19 | * Address at 0x09 20 | * Bits 21 | * Most significant bit (MSB) B7 when set to 1, shuts down the sensor, 22 | * B6, when set to 1, resets the sensor, 23 | * B5, B4, B3 unused, default to 0, 24 | * Least Significant Bits (LSB) B2, B1 and B0 for choosing the operating mode. 25 | 26 | The register map is implemented in ESP32 by defining the register addresses. 27 | 28 | ``` c 29 | /** 30 | * MAX30102 internal registers definitions. 31 | */ 32 | #define MAX30102_DEVICE 0x57 33 | // Part ID 34 | #define MAX30102_REV_ID 0xFE 35 | #define MAX30102_PART_ID 0xFF 36 | // Status 37 | // - Interrupts 38 | #define MAX30102_INT_STATUS 0x00 39 | #define MAX30102_INT_STATUS_2 0x01 40 | #define MAX30102_INT_ENABLE 0x02 41 | #define MAX30102_INT_ENABLE_2 0x03 42 | // - FIFO 43 | #define MAX30102_FIFO_WRITE 0x04 44 | #define MAX30102_FIFO_OVERFLOW_COUNTER 0x05 45 | #define MAX30102_FIFO_READ 0x06 46 | #define MAX30102_FIFO_DATA 0x07 47 | // Configuration 48 | #define MAX30102_FIFO_CONF 0x08 49 | #define MAX30102_MODE_CONF 0x09 50 | #define MAX30102_SPO2_CONF 0x0A 51 | #define MAX30102_LED_CONF 0x0C 52 | #define MAX30102_LED_CONF_2 0x0D 53 | #define MAX30102_MULTILED_REG 0x11 54 | #define MAX30102_MULTILED_REG_2 0x12 55 | // Die Temperature 56 | #define MAX30102_TEMP_INT 0x1F 57 | #define MAX30102_TEMP_FRACTION 0x20 58 | #define MAX30102_TEMP_CONFIG 0x21 59 | ``` 60 | 61 | We now define functions that read status of each register, and set the registers that are writable. 62 | ### Setting a Register 63 | We will use the Mode Configuration as an example. The LSB bits B0, B1 and B2 select the mode, as given in the datasheet. 64 | 65 | ![ModeConf](images/max30102Modecontrol.png) 66 | 67 | This control table is implemented as an enum. 68 | 69 | ``` c 70 | /** 71 | * Operation Mode Enum. 72 | * Heart rate only or Oxygen saturation + Heart rate. 73 | */ 74 | typedef enum _max30102_mode_t { 75 | MAX30102_MODE_HR_ONLY = 0x02, 76 | MAX30102_MODE_SPO2 = 0x03, 77 | MAX30102_MODE_SPO2_HR = 0x07 78 | } max30102_mode_t; 79 | 80 | ``` 81 | 82 | A function is then defined to set the Mode Configuration register. 83 | 84 | ``` c 85 | esp_err_t max30102_set_mode(max30102_config_t* this, max30102_mode_t mode) { 86 | uint8_t current_mode_reg; 87 | //Tratar erros 88 | esp_err_t ret = max30102_read_register( this, 89 | MAX30102_MODE_CONF, 90 | ¤t_mode_reg ); 91 | if(ret != ESP_OK) return ret; 92 | printf("Setting the mode..."); 93 | printf("%x\n", (current_mode_reg & 0xF8) | mode ); 94 | return max30102_write_register( this, 95 | MAX30102_MODE_CONF, 96 | (current_mode_reg & 0xF8) | mode ); 97 | } 98 | ``` 99 | The function reads the current state of the register, then writes the chosen mode only (all other bits masked by the AND 0xF8) to the register. 100 | 101 | The MSB bits B6 and B7 of the register are set by the Initializing function. 102 | 103 | ### Reading a Register 104 | We will use the FIFO register for reading. This register contains the sensor data measured from the photodiode. 105 | 106 | ![FIFO](images/max30102Fifo.png) 107 | 108 | The FIFO register holds the data alternately between data read using the Red LED and the InfreRed LED, each taking 3 three bytes of data. So at each pass we read six bytes from the FIFO into a buffer, then split the data accordingly. 109 | ``` c 110 | esp_err_t max30102_read_fifo(max30102_config_t* this, max30102_fifo_t* fifo) { 111 | uint8_t buffer[6]; 112 | //Testar erros 113 | esp_err_t ret = max30102_read_from(this, MAX30102_FIFO_DATA, buffer, 6); 114 | if(ret != ESP_OK) return ret; 115 | fifo->raw_red = ((uint32_t)buffer[0] << 16) | (buffer[1] << 8) | buffer[2]; 116 | fifo->raw_ir = ((uint32_t)buffer[3] << 16) | (buffer[4] << 8) | buffer[5]; 117 | 118 | return ESP_OK; 119 | } 120 | 121 | ``` 122 | 123 | 124 | ### Digital Signal Processing 125 | Once the data is read, some digital signal processing steps are necessary to interpret the data. The DC component of the signal will need to be removed. The resulting signal is processed to detect the presences of peaks and troughs that form the heart beat. 126 | 127 | #### DC Removal 128 | 129 | ``` c 130 | max30102_dc_filter_t max30102_dc_removal( float x, 131 | float prev_w, 132 | float alpha ) 133 | { 134 | max30102_dc_filter_t filtered = {}; 135 | filtered.w = x + alpha * prev_w; 136 | filtered.result = filtered.w - prev_w; 137 | 138 | return filtered; 139 | } 140 | 141 | ``` 142 | #### Butterworth Filter 143 | 144 | ``` c 145 | void max30102_lpb_filter( max30102_config_t* this, float x ) 146 | { 147 | this->lpb_filter_ir.v[0] = this->lpb_filter_ir.v[1]; 148 | 149 | //Fs = 100Hz and Fc = 10Hz 150 | this->lpb_filter_ir.v[1] = (2.452372752527856026e-1 * x) + 151 | ( 0.50952544949442879485 * this->lpb_filter_ir.v[0] ); 152 | 153 | //Fs = 100Hz and Fc = 4Hz 154 | /*this->lpb_filter_ir.v[1] = (1.367287359973195227e-1 * x) 155 | + (0.72654252800536101020 * this->lpb_filter_ir.v[0]); 156 | //Very precise butterworth filter*/ 157 | 158 | this->lpb_filter_ir.result = this->lpb_filter_ir.v[0] + 159 | this->lpb_filter_ir.v[1]; 160 | } 161 | ``` 162 | #### SpO2 163 | 164 | This is implemented in the ``` c max30102_update ``` function. The basic equations are discussed in detail in the [MAX30102 Application Node](https://pdfserv.maximintegrated.com/en/an/AN6409.pdf). 165 | 166 | ![SpO2](images/max30102SpO2.png) 167 | 168 | ``` c 169 | float ratio_rms = log( sqrt( this->red_ac_sq_sum / 170 | (float)this->samples_recorded ) ) / 171 | log( sqrt( this->ir_ac_sq_sum / 172 | (float)this->samples_recorded ) ); 173 | 174 | if(this->debug) 175 | printf("RMS Ratio: %f\n", ratio_rms); 176 | 177 | this->current_spO2 = 104.0 - 17.0 * ratio_rms; 178 | data->spO2 = this->current_spO2; 179 | ``` 180 | 181 | 182 | ### Results 183 | 184 | A typical output from the simple example is shown, indicating the Heart Rate and oxygen saturation (SpO2) 185 | ![Terminal](images/max30102Terminal.png) 186 | 187 | ### References 188 | 189 | 1. [MAX30102 Datasheet](https://datasheets.maximintegrated.com/en/ds/MAX30102.pdf) 190 | 191 | 2. [MAX30102 Application Node](https://pdfserv.maximintegrated.com/en/an/AN6409.pdf) 192 | -------------------------------------------------------------------------------- /Old_C_Version/images/esp32max30102Library.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoshDumo/esp32-max30102/72cec93b98cda09ba7481687a36e406502628dfe/Old_C_Version/images/esp32max30102Library.png -------------------------------------------------------------------------------- /Old_C_Version/images/max30102Fifo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoshDumo/esp32-max30102/72cec93b98cda09ba7481687a36e406502628dfe/Old_C_Version/images/max30102Fifo.png -------------------------------------------------------------------------------- /Old_C_Version/images/max30102Modecontrol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoshDumo/esp32-max30102/72cec93b98cda09ba7481687a36e406502628dfe/Old_C_Version/images/max30102Modecontrol.png -------------------------------------------------------------------------------- /Old_C_Version/images/max30102Registers1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoshDumo/esp32-max30102/72cec93b98cda09ba7481687a36e406502628dfe/Old_C_Version/images/max30102Registers1.png -------------------------------------------------------------------------------- /Old_C_Version/images/max30102Registers2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoshDumo/esp32-max30102/72cec93b98cda09ba7481687a36e406502628dfe/Old_C_Version/images/max30102Registers2.png -------------------------------------------------------------------------------- /Old_C_Version/images/max30102SpO2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoshDumo/esp32-max30102/72cec93b98cda09ba7481687a36e406502628dfe/Old_C_Version/images/max30102SpO2.png -------------------------------------------------------------------------------- /Old_C_Version/images/max30102Terminal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoshDumo/esp32-max30102/72cec93b98cda09ba7481687a36e406502628dfe/Old_C_Version/images/max30102Terminal.png -------------------------------------------------------------------------------- /Old_C_Version/main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | idf_component_register( 2 | SRCS "max30102.c" "main.c" 3 | INCLUDE_DIRS "" 4 | ) 5 | -------------------------------------------------------------------------------- /Old_C_Version/main/main.c: -------------------------------------------------------------------------------- 1 | /* Hello World Example 2 | 3 | This example code is in the Public Domain (or CC0 licensed, at your option.) 4 | 5 | Unless required by applicable law or agreed to in writing, this 6 | software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 7 | CONDITIONS OF ANY KIND, either express or implied. 8 | */ 9 | #include 10 | #include "freertos/FreeRTOS.h" 11 | #include "freertos/task.h" 12 | #include "esp_system.h" 13 | #include "esp_spi_flash.h" 14 | #include "max30102.h" 15 | 16 | #define I2C_SDA 21 17 | #define I2C_SCL 22 18 | #define I2C_FRQ 100000 19 | #define I2C_PORT I2C_NUM_0 20 | 21 | max30102_config_t max30102 = {}; 22 | 23 | esp_err_t i2c_master_init(i2c_port_t i2c_port){ 24 | i2c_config_t conf = {}; 25 | conf.mode = I2C_MODE_MASTER; 26 | conf.sda_io_num = I2C_SDA; 27 | conf.scl_io_num = I2C_SCL; 28 | conf.sda_pullup_en = GPIO_PULLUP_ENABLE; 29 | conf.scl_pullup_en = GPIO_PULLUP_ENABLE; 30 | conf.master.clk_speed = I2C_FRQ; 31 | i2c_param_config(i2c_port, &conf); 32 | return i2c_driver_install(i2c_port, I2C_MODE_MASTER, 0, 0, 0); 33 | } 34 | 35 | void get_bpm(void* param) { 36 | printf("MAX30102 Test\n"); 37 | max30102_data_t result = {}; 38 | /*ESP_ERROR_CHECK(max30102_print_registers(&max30102));*/ 39 | while(true) { 40 | //Update sensor, saving to "result" 41 | ESP_ERROR_CHECK(max30102_update(&max30102, &result)); 42 | if(result.pulse_detected) { 43 | printf("BEAT\n"); 44 | printf("BPM: %f | SpO2: %f%%\n", result.heart_bpm, result.spO2); 45 | } 46 | //Update rate: 100Hz 47 | vTaskDelay(10/portTICK_PERIOD_MS); 48 | } 49 | } 50 | 51 | 52 | 53 | void app_main(void) 54 | { 55 | //Init I2C_NUM_0 56 | ESP_ERROR_CHECK(i2c_master_init(I2C_PORT)); 57 | //Init sensor at I2C_NUM_0 58 | ESP_ERROR_CHECK(max30102_init( &max30102, I2C_PORT, 59 | MAX30102_DEFAULT_OPERATING_MODE, 60 | MAX30102_DEFAULT_SAMPLING_RATE, 61 | MAX30102_DEFAULT_LED_PULSE_WIDTH, 62 | MAX30102_DEFAULT_IR_LED_CURRENT, 63 | MAX30102_DEFAULT_START_RED_LED_CURRENT, 64 | MAX30102_DEFAULT_MEAN_FILTER_SIZE, 65 | MAX30102_DEFAULT_PULSE_BPM_SAMPLE_SIZE, 66 | MAX30102_DEFAULT_ADC_RANGE, 67 | MAX30102_DEFAULT_SAMPLE_AVERAGING, 68 | MAX30102_DEFAULT_ROLL_OVER, 69 | MAX30102_DEFAULT_ALMOST_FULL, 70 | false )); 71 | 72 | //Start test task 73 | xTaskCreate(get_bpm, "Get BPM", 8192, NULL, 1, NULL); 74 | } 75 | -------------------------------------------------------------------------------- /Old_C_Version/main/max30102-registers.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file max30102-registers.h 3 | * 4 | * @author 5 | * Joshua D John 6 | * email: joshdumo@live.com 7 | * 8 | * 9 | * 10 | * @brief This is the "private" headers file for the MAX30102 ESP32 library. This is where there 11 | * are significant differences between the register structures with the MAX30100. 12 | * 13 | * Please do NOT include in your code. 14 | * 15 | * This work is adapted from Angelo Elias Dalzotto's library for the MAX30100 16 | */ 17 | 18 | #ifndef MAX30102_REGISTERS_H 19 | #define MAX30102_REGISTERS_H 20 | 21 | #include "max30102.h" 22 | 23 | /** 24 | * MAX30102 internal registers definitions. 25 | */ 26 | #define MAX30102_DEVICE 0x57 27 | // Part ID 28 | #define MAX30102_REV_ID 0xFE 29 | #define MAX30102_PART_ID 0xFF 30 | // Status 31 | // - Interrupts 32 | #define MAX30102_INT_STATUS 0x00 33 | #define MAX30102_INT_STATUS_2 0x01 34 | #define MAX30102_INT_ENABLE 0x02 35 | #define MAX30102_INT_ENABLE_2 0x03 36 | // - FIFO 37 | #define MAX30102_FIFO_WRITE 0x04 38 | #define MAX30102_FIFO_OVERFLOW_COUNTER 0x05 39 | #define MAX30102_FIFO_READ 0x06 40 | #define MAX30102_FIFO_DATA 0x07 41 | // Configuration 42 | #define MAX30102_FIFO_CONF 0x08 43 | #define MAX30102_MODE_CONF 0x09 44 | #define MAX30102_SPO2_CONF 0x0A 45 | #define MAX30102_LED_CONF 0x0C 46 | #define MAX30102_LED_CONF_2 0x0D 47 | #define MAX30102_MULTILED_REG 0x11 48 | #define MAX30102_MULTILED_REG_2 0x12 49 | // Die Temperature 50 | #define MAX30102_TEMP_INT 0x1F 51 | #define MAX30102_TEMP_FRACTION 0x20 52 | #define MAX30102_TEMP_CONFIG 0x21 53 | 54 | /** 55 | * Bit defines for mode configuration. 56 | */ 57 | #define MAX30102_MODE_SHDN (1<<7) 58 | #define MAX30102_MODE_RESET (1<<6) 59 | #define MAX30102_MODE_TEMP_EN (1<<1) 60 | #define MAX30102_SPO2_HI_RES_EN (1<<6) 61 | #define MAX30102_FIFO_ROLL_OVER_EN (1<<4) 62 | 63 | /** 64 | * Pulse state machine Enum. 65 | */ 66 | typedef enum _pulse_state_machine { 67 | MAX30102_PULSE_IDLE, 68 | MAX30102_PULSE_TRACE_UP, 69 | MAX30102_PULSE_TRACE_DOWN 70 | } pulse_state_machine; 71 | 72 | /** 73 | * "Private" functions declarations. 74 | * These functions will only be called by the library, and not by the user. 75 | */ 76 | 77 | /** 78 | * @brief Pulse detection algorithm. 79 | * 80 | * @details Called by the update function. 81 | * 82 | * @param this is the address of the configuration structure. 83 | * @param sensor_value is the value read from the sensor after the filters 84 | * 85 | * @returns true if detected. 86 | */ 87 | bool max30102_detect_pulse(max30102_config_t* this, float sensor_value); 88 | 89 | /** 90 | * @brief Balance intensities filter. 91 | * 92 | * @details DC filter for the raw values. 93 | * 94 | * @param this is the address of the configuration structure. 95 | * @param red_dc is the w in red led dc filter structure. 96 | * @param ir_dc is the w in ir led dc filter structure. 97 | * 98 | * @returns status of execution. 99 | */ 100 | esp_err_t max30102_balance_intensities(max30102_config_t* this, float red_dc, float ir_dc); 101 | 102 | /** 103 | * @brief Write to the MAX30102 register. 104 | * 105 | * @param this is the address of the configuration structure. 106 | * @param address is the register address. 107 | * @param val is the byte to write. 108 | * 109 | * @returns status of execution. 110 | */ 111 | esp_err_t max30102_write_register(max30102_config_t* this, uint8_t address, uint8_t val); 112 | 113 | /** 114 | * @brief Read from MAX30102 register. 115 | * 116 | * @param this is the address of the configuration structure. 117 | * @param address is the register address. 118 | * @param reg is the address to save the byte read. 119 | * 120 | * @returns status of execution. 121 | */ 122 | esp_err_t max30102_read_register(max30102_config_t* this, uint8_t address, uint8_t* reg); 123 | 124 | /** 125 | * @brief Read set of MAX30102 registers. 126 | * 127 | * @param this is the address of the configuration structure. 128 | * @param address is the register address. 129 | * @param data is the initial address to save. 130 | * @param size is the size of the register to read. 131 | * 132 | * @returns status of execution. 133 | */ 134 | esp_err_t max30102_read_from( max30102_config_t* this, uint8_t address, 135 | uint8_t* data, uint8_t size ); 136 | 137 | /** 138 | * @brief Read from MAX30102 FIFO. 139 | * 140 | * @param this is the address of the configuration structure. 141 | * @param fifo is the address to a FIFO structure. 142 | * 143 | * @returns status of execution. 144 | */ 145 | esp_err_t max30102_read_fifo(max30102_config_t* this, max30102_fifo_t* fifo); 146 | 147 | /** 148 | * @brief Removes the DC offset of the sensor value. 149 | * 150 | * @param this is the address of the configuration structure. 151 | * @param x is the raw value read from the led. 152 | * @param prev_w is the previous filtered w from the dc filter structure. 153 | * @param alpha is the dc filter alpha parameter. 154 | * 155 | * @returns a dc filter structure. 156 | */ 157 | max30102_dc_filter_t max30102_dc_removal(float x, float prev_w, float alpha); 158 | 159 | /** 160 | * @brief Applies the mean diff filter. 161 | * 162 | * @param this is the address of the configuration structure. 163 | * @param M is the filtered DC result of the dc filter structure. 164 | * 165 | * @returns a filtered value. 166 | */ 167 | float max30102_mean_diff(max30102_config_t* this, float M); 168 | 169 | /** 170 | * @brief Applies a low-pass butterworth filter. 171 | * 172 | * @param this is the address of the configuration structure. 173 | * @param x is the mean diff filtered value. 174 | */ 175 | void max30102_lpb_filter(max30102_config_t* this, float x); 176 | 177 | #endif 178 | 179 | /** 180 | * MIT License 181 | * 182 | * Permission is hereby granted, free of charge, to any person obtaining a copy 183 | * of this software and associated documentation files (the "Software"), to deal 184 | * in the Software without restriction, including without limitation the rights 185 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 186 | * copies of the Software, and to permit persons to whom the Software is 187 | * furnished to do so, subject to the following conditions: 188 | 189 | * The above copyright notice and this permission notice shall be included in all 190 | * copies or substantial portions of the Software. 191 | 192 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 193 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 194 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 195 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 196 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 197 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 198 | * SOFTWARE. 199 | */ -------------------------------------------------------------------------------- /Old_C_Version/main/max30102.c: -------------------------------------------------------------------------------- 1 | /** 2 | * @file max30102.c 3 | * 4 | * @author 5 | * Joshua D John 6 | * email: joshdumo@live.com 7 | * 8 | * 9 | * @brief 10 | * This is the source code for the library. Check out the implemetation of the processing that 11 | * converts the raw sensor data into HR and Sp02 12 | * 13 | * This work is adapted from Angelo Elias Dalzotto's library for the MAX30100 14 | */ 15 | 16 | #include 17 | #include "max30102.h" 18 | #include "max30102-registers.h" 19 | #include 20 | 21 | esp_err_t max30102_init( max30102_config_t* this, 22 | i2c_port_t i2c_num, 23 | max30102_mode_t mode, 24 | max30102_sampling_rate_t sampling_rate, 25 | max30102_pulse_width_t pulse_width, 26 | max30102_current_t ir_current, 27 | max30102_current_t start_red_current, 28 | uint8_t mean_filter_size, 29 | uint8_t pulse_bpm_sample_size, 30 | max30102_adc_range_t high_res_mode, 31 | max30102_sample_averaging_t sample_averaging, 32 | bool overflow_enable, 33 | max30102_almost_full_t almost_full, 34 | bool debug ) 35 | { 36 | this->i2c_num = i2c_num; 37 | 38 | this->acceptable_intense_diff = MAX30102_DEFAULT_ACCEPTABLE_INTENSITY_DIFF; 39 | this->red_current_adj_ms = MAX30102_DEFAULT_RED_LED_CURRENT_ADJUSTMENT_MS; 40 | this->reset_spo2_pulse_n = MAX30102_DEFAULT_RESET_SPO2_EVERY_N_PULSES; 41 | this->dc_alpha = MAX30102_DEFAULT_ALPHA; 42 | this->pulse_min_threshold = MAX30102_DEFAULT_PULSE_MIN_THRESHOLD; 43 | this->pulse_max_threshold = MAX30102_DEFAULT_PULSE_MAX_THRESHOLD; 44 | 45 | this->mean_filter_size = mean_filter_size; 46 | this->pulse_bpm_sample_size = pulse_bpm_sample_size; 47 | 48 | this->debug = debug; 49 | this->current_pulse_detector_state = MAX30102_PULSE_IDLE; 50 | 51 | this->mean_diff_ir.values = NULL; 52 | this->values_bpm = NULL; 53 | this->mean_diff_ir.values = malloc(sizeof(float)*mean_filter_size); 54 | this->values_bpm = malloc(sizeof(float)*pulse_bpm_sample_size); 55 | 56 | if(!(this->values_bpm) || !(this->mean_diff_ir.values)) return ESP_ERR_INVALID_RESPONSE; 57 | 58 | esp_err_t ret = max30102_set_mode(this, mode); 59 | if(ret != ESP_OK) return ret; 60 | 61 | ret = max30102_set_sampling_rate(this, sampling_rate); 62 | if(ret != ESP_OK) return ret; 63 | ret = max30102_set_pulse_width(this, pulse_width); 64 | if(ret != ESP_OK) return ret; 65 | 66 | this->red_current = (uint8_t)start_red_current; 67 | this->last_red_current_check = 0; 68 | 69 | this->ir_current = ir_current; 70 | ret = max30102_set_led_current(this, this->red_current, ir_current); 71 | if(ret != ESP_OK) return ret; 72 | ret = max30102_set_high_res(this, high_res_mode); 73 | if(ret != ESP_OK) return ret; 74 | 75 | ret = max30102_set_sample_averaging(this, sample_averaging); 76 | if(ret != ESP_OK) return ret; 77 | 78 | ret = max30102_set_roll_over(this, overflow_enable); 79 | if(ret != ESP_OK) return ret; 80 | 81 | ret = max30102_set_almost_full(this, almost_full); 82 | if(ret != ESP_OK) return ret; 83 | 84 | this->dc_filter_ir.w = 0; 85 | this->dc_filter_ir.result = 0; 86 | 87 | this->dc_filter_red.w = 0; 88 | this->dc_filter_red.result = 0; 89 | 90 | 91 | 92 | this->lpb_filter_ir.v[0] = 0; 93 | this->lpb_filter_ir.v[1] = 0; 94 | this->lpb_filter_ir.result = 0; 95 | 96 | memset(this->mean_diff_ir.values, 0, sizeof(float)*mean_filter_size); 97 | this->mean_diff_ir.index = 0; 98 | this->mean_diff_ir.sum = 0; 99 | this->mean_diff_ir.count = 0; 100 | 101 | 102 | memset(this->values_bpm, 0, sizeof(float)*pulse_bpm_sample_size); 103 | this->values_bpm_sum = 0; 104 | this->values_bpm_count = 0; 105 | this->bpm_index = 0; 106 | 107 | 108 | this->ir_ac_sq_sum = 0; 109 | this->red_ac_sq_sum = 0; 110 | this->samples_recorded = 0; 111 | this->pulses_detected = 0; 112 | this->current_spO2 = 0; 113 | 114 | this->last_beat_threshold = 0; 115 | return ESP_OK; 116 | } 117 | 118 | esp_err_t max30102_update(max30102_config_t* this, max30102_data_t* data) { 119 | data->pulse_detected = false; 120 | data->heart_bpm = 0.0; 121 | data->ir_cardiogram = 0.0; 122 | data->ir_dc_value = 0.0; 123 | data->red_dc_value = 0.0; 124 | data->spO2 = this->current_spO2; 125 | data->last_beat_threshold = 0; 126 | data->dc_filtered_ir = 0.0; 127 | data->dc_filtered_red = 0.0; 128 | 129 | max30102_fifo_t raw_data; 130 | esp_err_t ret = max30102_read_fifo(this, &raw_data); 131 | if(ret != ESP_OK) return ret; 132 | 133 | this->dc_filter_ir = max30102_dc_removal( (float)raw_data.raw_ir, 134 | this->dc_filter_ir.w, 135 | this->dc_alpha ); 136 | this->dc_filter_red = max30102_dc_removal( (float)raw_data.raw_red, 137 | this->dc_filter_red.w, 138 | this->dc_alpha ); 139 | 140 | float mean_diff_res_ir = max30102_mean_diff( this, 141 | this->dc_filter_ir.result ); 142 | 143 | max30102_lpb_filter(this, mean_diff_res_ir/*-dcFilterIR.result*/); 144 | 145 | this->ir_ac_sq_sum += this->dc_filter_ir.result * this->dc_filter_ir.result; 146 | this->red_ac_sq_sum+= this->dc_filter_red.result*this->dc_filter_red.result; 147 | this->samples_recorded++; 148 | 149 | if(max30102_detect_pulse(this, this->lpb_filter_ir.result) && this->samples_recorded) 150 | { 151 | data->pulse_detected=true; 152 | this->pulses_detected++; 153 | 154 | float ratio_rms = log( sqrt( this->red_ac_sq_sum / 155 | (float)this->samples_recorded ) ) / 156 | log( sqrt( this->ir_ac_sq_sum / 157 | (float)this->samples_recorded ) ); 158 | 159 | if(this->debug) 160 | printf("RMS Ratio: %f\n", ratio_rms); 161 | 162 | //This is my adjusted standard model, so it shows 0.89 as 94% saturation. 163 | //It is probably far from correct, requires proper empircal calibration. 164 | this->current_spO2 = 110.0 - 18.0 * ratio_rms; 165 | data->spO2 = this->current_spO2; 166 | 167 | if(!(this->pulses_detected % this->reset_spo2_pulse_n)) { 168 | this->ir_ac_sq_sum = 0; 169 | this->red_ac_sq_sum = 0; 170 | this->samples_recorded = 0; 171 | } 172 | } 173 | 174 | ret = max30102_balance_intensities( this, 175 | this->dc_filter_red.w, 176 | this->dc_filter_ir.w ); 177 | if(ret != ESP_OK) return ret; 178 | 179 | 180 | data->heart_bpm = this->current_bpm; 181 | data->ir_cardiogram = this->lpb_filter_ir.result; 182 | data->ir_dc_value = this->dc_filter_ir.w; 183 | data->red_dc_value = this->dc_filter_red.w; 184 | data->last_beat_threshold = this->last_beat_threshold; 185 | data->dc_filtered_ir = this->dc_filter_ir.result; 186 | data->dc_filtered_red = this->dc_filter_red.result; 187 | 188 | return ESP_OK; 189 | } 190 | 191 | bool max30102_detect_pulse(max30102_config_t* this, float sensor_value) { 192 | static float prev_sensor_value = 0; 193 | static uint8_t values_went_down = 0; 194 | static uint32_t current_beat = 0; 195 | static uint32_t last_beat = 0; 196 | 197 | if(sensor_value > this->pulse_max_threshold) { 198 | this->current_pulse_detector_state = MAX30102_PULSE_IDLE; 199 | prev_sensor_value = 0; 200 | last_beat = 0; 201 | current_beat = 0; 202 | values_went_down = 0; 203 | this->last_beat_threshold = 0; 204 | return false; 205 | } 206 | 207 | switch(this->current_pulse_detector_state) { 208 | case MAX30102_PULSE_IDLE: 209 | if(sensor_value >= this->pulse_min_threshold) { 210 | this->current_pulse_detector_state = MAX30102_PULSE_TRACE_UP; 211 | values_went_down = 0; 212 | } 213 | break; 214 | case MAX30102_PULSE_TRACE_UP: 215 | if(sensor_value > prev_sensor_value) { 216 | current_beat = (uint32_t)(xTaskGetTickCount() * portTICK_PERIOD_MS); 217 | this->last_beat_threshold = sensor_value; 218 | } else { 219 | if(this->debug) { 220 | printf("Peak reached: %f %f\n", 221 | sensor_value, 222 | prev_sensor_value); 223 | } 224 | 225 | uint32_t beat_duration = current_beat - last_beat; 226 | last_beat = current_beat; 227 | 228 | float raw_bpm = 0; 229 | if(beat_duration) 230 | raw_bpm = 60000.0 / (float)beat_duration; 231 | 232 | if(this->debug){ 233 | printf("Beat duration: %u\n", beat_duration); 234 | printf("Raw BPM: %f\n", raw_bpm); 235 | } 236 | 237 | this->current_pulse_detector_state = MAX30102_PULSE_TRACE_DOWN; 238 | 239 | // Reset filter after a while without pulses 240 | if(beat_duration > 2500){ // 2.5 seconds 241 | memset(this->values_bpm, 0, sizeof(float)*this->pulse_bpm_sample_size); 242 | this->values_bpm_sum = 0; 243 | this->values_bpm_count = 0; 244 | this->bpm_index = 0; 245 | 246 | if(this->debug) 247 | printf("Moving avg. resetted\n"); 248 | } 249 | 250 | // Test if out of bounds 251 | if(raw_bpm < 50 || raw_bpm > 220){ 252 | if(this->debug) 253 | printf("BPM out of bounds. Not adding to Moving Avg.\n"); 254 | 255 | return false; 256 | } 257 | 258 | // Optimized filter 259 | this->values_bpm_sum -= this->values_bpm[this->bpm_index]; 260 | this->values_bpm[this->bpm_index] = raw_bpm; 261 | this->values_bpm_sum += this->values_bpm[this->bpm_index++]; 262 | 263 | this->bpm_index %= this->pulse_bpm_sample_size; 264 | 265 | if(this->values_bpm_count < this->pulse_bpm_sample_size) 266 | this->values_bpm_count++; 267 | 268 | this->current_bpm = this->values_bpm_sum / this->values_bpm_count; 269 | 270 | if(this->debug) { 271 | printf("CurrentMoving Avg: "); 272 | 273 | for(int i = 0; i < this->values_bpm_count; i++) 274 | printf("%f ", this->values_bpm[i]); 275 | 276 | printf(" \n"); 277 | printf("AVg. BPM: %f\n", this->current_bpm); 278 | } 279 | 280 | return true; 281 | } 282 | break; 283 | case MAX30102_PULSE_TRACE_DOWN: 284 | if(sensor_value < prev_sensor_value) 285 | values_went_down++; 286 | 287 | if(sensor_value < this->pulse_min_threshold) 288 | this->current_pulse_detector_state = MAX30102_PULSE_IDLE; 289 | 290 | break; 291 | } 292 | 293 | prev_sensor_value = sensor_value; 294 | return false; 295 | } 296 | 297 | esp_err_t max30102_balance_intensities( max30102_config_t* this, 298 | float red_dc, 299 | float ir_dc ) 300 | { 301 | if( (uint32_t)(xTaskGetTickCount() * portTICK_PERIOD_MS) - 302 | this->last_red_current_check >= this->red_current_adj_ms ) 303 | { 304 | //printf("%f\n", red_dc - ir_dc); 305 | if( ir_dc - red_dc > this->acceptable_intense_diff && 306 | this->red_current < MAX30102_LED_CURRENT_50MA ) 307 | { 308 | this->red_current++; 309 | esp_err_t ret = max30102_set_led_current( this, 310 | this->red_current, 311 | this->ir_current ); 312 | if(ret != ESP_OK) return ret; 313 | if(this->debug) 314 | printf("RED LED Current +\n"); 315 | 316 | } else if( red_dc - ir_dc > this->acceptable_intense_diff && 317 | this->red_current > 0 ) 318 | { 319 | this->red_current--; 320 | esp_err_t ret = max30102_set_led_current( this, 321 | this->red_current, 322 | this->ir_current ); 323 | if(ret != ESP_OK) return ret; 324 | if(this->debug) 325 | printf("RED LED Current -\n"); 326 | } 327 | 328 | this->last_red_current_check = (uint32_t)(xTaskGetTickCount() * 329 | portTICK_PERIOD_MS ); 330 | 331 | } 332 | return ESP_OK; 333 | } 334 | 335 | // Writes val to address register on device 336 | esp_err_t max30102_write_register( max30102_config_t* this, 337 | uint8_t address, 338 | uint8_t val ) 339 | { 340 | // start transmission to device 341 | i2c_cmd_handle_t cmd = i2c_cmd_link_create(); 342 | i2c_master_start(cmd); 343 | i2c_master_write_byte(cmd, (MAX30102_DEVICE << 1) | I2C_MASTER_WRITE, true); 344 | 345 | i2c_master_write_byte(cmd, address, true); // send register address 346 | i2c_master_write_byte(cmd, val, true); // send value to write 347 | 348 | // end transmission 349 | i2c_master_stop(cmd); 350 | esp_err_t ret = i2c_master_cmd_begin( this->i2c_num, 351 | cmd, 352 | 1000 / portTICK_RATE_MS ); 353 | i2c_cmd_link_delete(cmd); 354 | return ret; 355 | } 356 | 357 | esp_err_t max30102_read_register( max30102_config_t* this, 358 | uint8_t address, 359 | uint8_t* reg ) 360 | { 361 | i2c_cmd_handle_t cmd = i2c_cmd_link_create(); 362 | i2c_master_start(cmd); 363 | i2c_master_write_byte(cmd, (MAX30102_DEVICE << 1) | I2C_MASTER_WRITE, true); 364 | i2c_master_write_byte(cmd, address, true); 365 | 366 | //i2c_master_stop(cmd); 367 | i2c_master_start(cmd); 368 | 369 | i2c_master_write_byte(cmd, (MAX30102_DEVICE << 1) | I2C_MASTER_READ, true); 370 | i2c_master_read_byte(cmd, reg, 1); //1 is NACK 371 | 372 | // end transmission 373 | i2c_master_stop(cmd); 374 | esp_err_t ret = i2c_master_cmd_begin( this->i2c_num, 375 | cmd, 376 | 1000 / portTICK_RATE_MS ); 377 | 378 | i2c_cmd_link_delete(cmd); 379 | return ret; 380 | } 381 | 382 | // Reads num bytes starting from address register on device in to _buff array 383 | esp_err_t max30102_read_from( max30102_config_t* this, 384 | uint8_t address, 385 | uint8_t* reg, 386 | uint8_t size ) 387 | { 388 | if(!size) 389 | return ESP_OK; 390 | 391 | i2c_cmd_handle_t cmd = i2c_cmd_link_create(); 392 | i2c_master_start(cmd); 393 | i2c_master_write_byte(cmd, (MAX30102_DEVICE << 1) | I2C_MASTER_WRITE, 1); 394 | i2c_master_write_byte(cmd, address, true); 395 | 396 | i2c_master_start(cmd); 397 | i2c_master_write_byte(cmd, (MAX30102_DEVICE << 1) | I2C_MASTER_READ, true); 398 | 399 | if(size > 1) 400 | i2c_master_read(cmd, reg, size-1, 0); //0 is ACK 401 | 402 | i2c_master_read_byte(cmd, reg+size-1, 1); //1 is NACK 403 | 404 | i2c_master_stop(cmd); 405 | esp_err_t ret = i2c_master_cmd_begin( this->i2c_num, 406 | cmd, 407 | 1000 / portTICK_RATE_MS ); 408 | i2c_cmd_link_delete(cmd); 409 | return ret; 410 | } 411 | 412 | esp_err_t max30102_set_mode(max30102_config_t* this, max30102_mode_t mode) { 413 | uint8_t current_mode_reg; 414 | //Tratar erros 415 | esp_err_t ret = max30102_read_register( this, 416 | MAX30102_MODE_CONF, 417 | ¤t_mode_reg ); 418 | if(ret != ESP_OK) return ret; 419 | printf("Setting the mode..."); 420 | printf("%x\n", (current_mode_reg & 0xF8) | mode ); 421 | return max30102_write_register( this, 422 | MAX30102_MODE_CONF, 423 | (current_mode_reg & 0xF8) | mode ); 424 | } 425 | 426 | /* Lets Keep this just in case we break something 427 | 428 | esp_err_t max30102_set_high_res(max30102_config_t* this, bool enabled) { 429 | uint8_t previous; 430 | 431 | //Tratar erros 432 | esp_err_t ret = max30102_read_register(this, MAX30102_SPO2_CONF, &previous); 433 | if(ret != ESP_OK) return ret; 434 | if(enabled) { 435 | return max30102_write_register( this, 436 | MAX30102_SPO2_CONF, 437 | previous | MAX30102_SPO2_HI_RES_EN ); 438 | } else { 439 | return max30102_write_register( this, 440 | MAX30102_SPO2_CONF, 441 | previous & ~MAX30102_SPO2_HI_RES_EN ); 442 | } 443 | } 444 | */ 445 | 446 | esp_err_t max30102_set_high_res(max30102_config_t* this, max30102_adc_range_t adc_range) { 447 | uint8_t current_spO2_reg; 448 | 449 | //Tratar erros 450 | esp_err_t ret = max30102_read_register(this, MAX30102_SPO2_CONF, ¤t_spO2_reg); 451 | if(ret != ESP_OK) return ret; 452 | printf("Setting the ADC range..."); 453 | printf("%x\n", (current_spO2_reg & 0x9F) | (adc_range<<5) ); 454 | return max30102_write_register( this, 455 | MAX30102_SPO2_CONF, 456 | (current_spO2_reg & 0x9F) | (adc_range<<5) ); 457 | 458 | } 459 | 460 | esp_err_t max30102_set_sampling_rate( max30102_config_t* this, 461 | max30102_sampling_rate_t rate ) 462 | { 463 | uint8_t current_spO2_reg; 464 | 465 | //Tratar erros 466 | esp_err_t ret = max30102_read_register( this, 467 | MAX30102_SPO2_CONF, 468 | ¤t_spO2_reg ); 469 | if(ret != ESP_OK) return ret; 470 | printf("Setting the sampling rate..."); 471 | printf("%x\n", (current_spO2_reg & 0xE3) | (rate<<2) ); 472 | return max30102_write_register( this, 473 | MAX30102_SPO2_CONF, 474 | (current_spO2_reg & 0xE3) | (rate<<2) ); 475 | } 476 | 477 | esp_err_t max30102_set_pulse_width( max30102_config_t* this, 478 | max30102_pulse_width_t pw ) 479 | { 480 | uint8_t current_spO2_reg; 481 | 482 | //Tratar erros 483 | esp_err_t ret = max30102_read_register( this, 484 | MAX30102_SPO2_CONF, 485 | ¤t_spO2_reg ); 486 | if(ret != ESP_OK) return ret; 487 | printf("Setting the pulse width..."); 488 | printf("%x\n", (current_spO2_reg & 0xFC) | pw ); 489 | return max30102_write_register( this, 490 | MAX30102_SPO2_CONF, 491 | (current_spO2_reg & 0xFC) | pw ); 492 | } 493 | 494 | esp_err_t max30102_set_led_current( max30102_config_t* this, 495 | max30102_current_t red_current, 496 | max30102_current_t ir_current ) 497 | { 498 | //Tratar erros 499 | /*return max30102_write_register( this, 500 | MAX30102_LED_CONF, 501 | (red_current << 4) | ir_current ); 502 | */ 503 | esp_err_t ret = max30102_write_register(this, MAX30102_LED_CONF, red_current); 504 | if(ret != ESP_OK) return ret; 505 | return max30102_write_register(this, MAX30102_LED_CONF_2, ir_current); 506 | } 507 | 508 | esp_err_t max30102_set_sample_averaging(max30102_config_t* this, 509 | max30102_sample_averaging_t sample_averaging) 510 | { 511 | uint8_t current_fifo_conf_reg; 512 | 513 | //Tratar erros 514 | esp_err_t ret = max30102_read_register( this, 515 | MAX30102_FIFO_CONF, 516 | ¤t_fifo_conf_reg ); 517 | if(ret != ESP_OK) return ret; 518 | printf("Setting the sample averaging..."); 519 | printf("%x\n", (current_fifo_conf_reg & 0x1F) | (sample_averaging<<5) ); 520 | return max30102_write_register( this, 521 | MAX30102_FIFO_CONF, 522 | (current_fifo_conf_reg & 0x1F) | (sample_averaging<<5) ); 523 | 524 | } 525 | 526 | 527 | esp_err_t max30102_set_roll_over(max30102_config_t* this, bool enabled) { 528 | uint8_t previous; 529 | 530 | //Tratar erros 531 | esp_err_t ret = max30102_read_register(this, MAX30102_FIFO_CONF, &previous); 532 | if(ret != ESP_OK) return ret; 533 | if(enabled) { 534 | return max30102_write_register( this, 535 | MAX30102_FIFO_CONF, 536 | previous | MAX30102_FIFO_ROLL_OVER_EN ); 537 | } else { 538 | return max30102_write_register( this, 539 | MAX30102_FIFO_CONF, 540 | previous & ~MAX30102_FIFO_ROLL_OVER_EN ); 541 | } 542 | } 543 | 544 | esp_err_t max30102_set_almost_full(max30102_config_t* this, 545 | max30102_almost_full_t almost_full) 546 | { 547 | uint8_t current_fifo_conf_reg; 548 | 549 | //Tratar erros 550 | esp_err_t ret = max30102_read_register( this, 551 | MAX30102_FIFO_CONF, 552 | ¤t_fifo_conf_reg ); 553 | if(ret != ESP_OK) return ret; 554 | printf("Setting the almost full value..."); 555 | printf("%x\n", (current_fifo_conf_reg & 0xF0) | almost_full ); 556 | return max30102_write_register( this, 557 | MAX30102_FIFO_CONF, 558 | (current_fifo_conf_reg & 0xF0) | almost_full ); 559 | 560 | } 561 | 562 | esp_err_t max30102_set_acceptable_intense_difff( max30102_config_t* this, 563 | uint32_t acceptable_intense_diff ) 564 | { 565 | //Add possible error check 566 | this->acceptable_intense_diff = acceptable_intense_diff; 567 | return ESP_OK; 568 | } 569 | esp_err_t max30102_set_red_current_adj_ms(max30102_config_t* this, uint32_t red_current_adj_ms) { 570 | //Add possible error check 571 | this->red_current_adj_ms = red_current_adj_ms; 572 | return ESP_OK; 573 | } 574 | 575 | esp_err_t max30102_set_reset_spo2_pulse_n(max30102_config_t* this, uint8_t reset_spo2_pulse_n) { 576 | //Add possible error check 577 | this->reset_spo2_pulse_n = reset_spo2_pulse_n; 578 | return ESP_OK; 579 | } 580 | 581 | esp_err_t max30102_set_dc_alpha(max30102_config_t* this, float dc_alpha) { 582 | //Add possible error check 583 | this->dc_alpha = dc_alpha; 584 | return ESP_OK; 585 | } 586 | 587 | esp_err_t max30102_set_pulse_min_threshold(max30102_config_t* this, uint16_t pulse_min_threshold) { 588 | //Add possible error check 589 | this->pulse_min_threshold = pulse_min_threshold; 590 | return ESP_OK; 591 | } 592 | 593 | esp_err_t max30102_set_pulse_max_threshold(max30102_config_t* this, uint16_t pulse_max_threshold) { 594 | //Add possible error check 595 | this->pulse_max_threshold = pulse_max_threshold; 596 | return ESP_OK; 597 | } 598 | 599 | esp_err_t max330100_read_temperature(max30102_config_t* this, float* temperature) { 600 | uint8_t current_mode_reg; 601 | //Tratar erros 602 | esp_err_t ret = max30102_read_register( this, 603 | MAX30102_INT_STATUS_2, 604 | ¤t_mode_reg ); 605 | if(ret != ESP_OK) return ret; 606 | ret = max30102_write_register( this, 607 | MAX30102_INT_ENABLE_2, 608 | current_mode_reg | MAX30102_MODE_TEMP_EN ); 609 | if(ret != ESP_OK) return ret; 610 | //This can be changed to a while loop, (with interrupt flag!) 611 | //there is an interrupt flag for when temperature has been read. 612 | vTaskDelay(100/portTICK_PERIOD_MS); 613 | 614 | int8_t temp; 615 | //Tratar erros 616 | ret = max30102_read_register(this, MAX30102_TEMP_INT, (uint8_t*)&temp); 617 | if(ret != ESP_OK) return ret; 618 | 619 | float temp_fraction; 620 | ret = max30102_read_register( this, 621 | MAX30102_TEMP_FRACTION, 622 | (uint8_t*)&temp_fraction); 623 | if(ret != ESP_OK) return ret; 624 | temp_fraction *= 0.0625; 625 | *temperature = (float)temp+temp_fraction; 626 | return ESP_OK; 627 | } 628 | 629 | /* Lets also keep this even though its practically useless for 30102 630 | esp_err_t max30102_read_fifo(max30102_config_t* this, max30102_fifo_t* fifo) { 631 | uint8_t buffer[4]; 632 | //Testar erros 633 | esp_err_t ret = max30102_read_from(this, MAX30102_FIFO_DATA, buffer, 4); 634 | if(ret != ESP_OK) return ret; 635 | fifo->raw_ir = ((uint16_t)buffer[0] << 8) | buffer[1]; 636 | fifo->raw_red = ((uint16_t)buffer[2] << 8) | buffer[3]; 637 | 638 | return ESP_OK; 639 | } 640 | */ 641 | 642 | esp_err_t max30102_read_fifo(max30102_config_t* this, max30102_fifo_t* fifo) { 643 | uint8_t buffer[6]; 644 | //Testar erros 645 | esp_err_t ret = max30102_read_from(this, MAX30102_FIFO_DATA, buffer, 6); 646 | if(ret != ESP_OK) return ret; 647 | fifo->raw_red = ((uint32_t)buffer[0] << 16) | (buffer[1] << 8) | buffer[2]; 648 | fifo->raw_ir = ((uint32_t)buffer[3] << 16) | (buffer[4] << 8) | buffer[5]; 649 | 650 | return ESP_OK; 651 | } 652 | 653 | max30102_dc_filter_t max30102_dc_removal( float x, 654 | float prev_w, 655 | float alpha ) 656 | { 657 | max30102_dc_filter_t filtered = {}; 658 | filtered.w = x + alpha * prev_w; 659 | filtered.result = filtered.w - prev_w; 660 | 661 | return filtered; 662 | } 663 | 664 | void max30102_lpb_filter( max30102_config_t* this, float x ) 665 | { 666 | this->lpb_filter_ir.v[0] = this->lpb_filter_ir.v[1]; 667 | 668 | //Fs = 100Hz and Fc = 10Hz 669 | this->lpb_filter_ir.v[1] = (2.452372752527856026e-1 * x) + 670 | ( 0.50952544949442879485 * this->lpb_filter_ir.v[0] ); 671 | 672 | //Fs = 100Hz and Fc = 4Hz 673 | /*this->lpb_filter_ir.v[1] = (1.367287359973195227e-1 * x) 674 | + (0.72654252800536101020 * this->lpb_filter_ir.v[0]); 675 | //Very precise butterworth filter*/ 676 | 677 | this->lpb_filter_ir.result = this->lpb_filter_ir.v[0] + 678 | this->lpb_filter_ir.v[1]; 679 | } 680 | 681 | float max30102_mean_diff( max30102_config_t* this, float M ) 682 | { 683 | float avg = 0; 684 | 685 | this->mean_diff_ir.sum -= this->mean_diff_ir.values[this->mean_diff_ir.index]; 686 | this->mean_diff_ir.values[this->mean_diff_ir.index] = M; 687 | this->mean_diff_ir.sum += this->mean_diff_ir.values[this->mean_diff_ir.index++]; 688 | 689 | this->mean_diff_ir.index = this->mean_diff_ir.index % this->mean_filter_size; 690 | 691 | if(this->mean_diff_ir.count < this->mean_filter_size) 692 | this->mean_diff_ir.count++; 693 | 694 | avg = this->mean_diff_ir.sum / this->mean_diff_ir.count; 695 | return avg - M; 696 | } 697 | 698 | esp_err_t max30102_print_registers(max30102_config_t* this) 699 | { 700 | uint8_t int_status, int_enable, fifo_write, fifo_ovf_cnt, fifo_read; 701 | uint8_t fifo_data, mode_conf, sp02_conf, led_conf, temp_int, temp_frac; 702 | uint8_t rev_id, part_id; 703 | esp_err_t ret; 704 | 705 | ret = max30102_read_register(this, MAX30102_INT_STATUS, &int_status); 706 | if(ret != ESP_OK) return ret; 707 | ret = max30102_read_register(this, MAX30102_INT_ENABLE, &int_enable); 708 | if(ret != ESP_OK) return ret; 709 | ret = max30102_read_register(this, MAX30102_FIFO_WRITE, &fifo_write); 710 | if(ret != ESP_OK) return ret; 711 | ret = max30102_read_register( this, 712 | MAX30102_FIFO_OVERFLOW_COUNTER, 713 | &fifo_ovf_cnt ); 714 | if(ret != ESP_OK) return ret; 715 | ret = max30102_read_register(this, MAX30102_FIFO_READ, &fifo_read); 716 | if(ret != ESP_OK) return ret; 717 | ret = max30102_read_register(this, MAX30102_FIFO_DATA, &fifo_data); 718 | if(ret != ESP_OK) return ret; 719 | ret = max30102_read_register(this, MAX30102_MODE_CONF, &mode_conf); 720 | if(ret != ESP_OK) return ret; 721 | ret = max30102_read_register(this, MAX30102_SPO2_CONF, &sp02_conf); 722 | if(ret != ESP_OK) return ret; 723 | ret = max30102_read_register(this, MAX30102_LED_CONF, &led_conf); 724 | if(ret != ESP_OK) return ret; 725 | ret = max30102_read_register(this, MAX30102_TEMP_INT, &temp_int); 726 | if(ret != ESP_OK) return ret; 727 | ret = max30102_read_register(this, MAX30102_TEMP_FRACTION, &temp_frac); 728 | if(ret != ESP_OK) return ret; 729 | ret = max30102_read_register(this, MAX30102_REV_ID, &rev_id); 730 | if(ret != ESP_OK) return ret; 731 | ret = max30102_read_register(this, MAX30102_PART_ID, &part_id); 732 | if(ret != ESP_OK) return ret; 733 | 734 | printf("%x\n", int_status); 735 | printf("%x\n", int_enable); 736 | printf("%x\n", fifo_write); 737 | printf("%x\n", fifo_ovf_cnt); 738 | printf("%x\n", fifo_read); 739 | printf("%x\n", fifo_data); 740 | printf("%x\n", mode_conf); 741 | printf("%x\n", sp02_conf); 742 | printf("%x\n", led_conf); 743 | printf("%x\n", temp_int); 744 | printf("%x\n", temp_frac); 745 | printf("%x\n", rev_id); 746 | printf("%x\n", part_id); 747 | 748 | return ESP_OK; 749 | } 750 | 751 | /** 752 | * MIT License 753 | * 754 | * Permission is hereby granted, free of charge, to any person obtaining a copy 755 | * of this software and associated documentation files (the "Software"), to deal 756 | * in the Software without restriction, including without limitation the rights 757 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 758 | * copies of the Software, and to permit persons to whom the Software is 759 | * furnished to do so, subject to the following conditions: 760 | 761 | * The above copyright notice and this permission notice shall be included in all 762 | * copies or substantial portions of the Software. 763 | 764 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 765 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 766 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 767 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 768 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 769 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 770 | * SOFTWARE. 771 | */ -------------------------------------------------------------------------------- /Old_C_Version/main/max30102.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file max30102.h 3 | * 4 | * @author 5 | * Joshua D John 6 | * email: joshdumo@live.com 7 | * 8 | * 9 | * @brief 10 | * This is a library for interfacing the MAX30102 Heart Rate/Pulse Oxymetry sensor 11 | * with the ESP32 processor. This part contains 12 | * - Initialization parameters and functions, 13 | * - Processing and update functions which then read the sensor, do some DSP and give the 14 | * heart rate bmp and the oxymetry SpO2 15 | * This work is adapted from Angelo Elias Dalzotto's library for the MAX30100 16 | */ 17 | #ifndef MAX30102_H 18 | #define MAX30102_H 19 | 20 | #include 21 | #include "driver/i2c.h" 22 | #include "freertos/task.h" 23 | #include "esp_err.h" 24 | 25 | /** 26 | * Default parameters for initialization. 27 | */ 28 | #define MAX30102_DEFAULT_OPERATING_MODE MAX30102_MODE_SPO2 29 | #define MAX30102_DEFAULT_IR_LED_CURRENT MAX30102_LED_CURRENT_50MA 30 | #define MAX30102_DEFAULT_START_RED_LED_CURRENT MAX30102_LED_CURRENT_40_2MA 31 | #define MAX30102_DEFAULT_SAMPLING_RATE MAX30102_SAMPLING_RATE_100HZ 32 | #define MAX30102_DEFAULT_LED_PULSE_WIDTH MAX30102_PULSE_WIDTH_1600US_ADC_16 33 | #define MAX30102_DEFAULT_ADC_RANGE MAX30102_ADC_RANGE_16384 34 | #define MAX30102_DEFAULT_SAMPLE_AVERAGING MAX30102_SAMPLE_AVERAGING_1 35 | #define MAX30102_DEFAULT_ROLL_OVER false 36 | #define MAX30102_DEFAULT_ALMOST_FULL MAX30102_ALMOST_FULL_0 37 | #define MAX30102_DEFAULT_ACCEPTABLE_INTENSITY_DIFF 65000 38 | #define MAX30102_DEFAULT_RED_LED_CURRENT_ADJUSTMENT_MS 500 39 | #define MAX30102_DEFAULT_RESET_SPO2_EVERY_N_PULSES 6 40 | #define MAX30102_DEFAULT_ALPHA 0.975 41 | #define MAX30102_DEFAULT_MEAN_FILTER_SIZE 17 42 | #define MAX30102_DEFAULT_PULSE_MIN_THRESHOLD 100 43 | #define MAX30102_DEFAULT_PULSE_MAX_THRESHOLD 2000 44 | #define MAX30102_DEFAULT_PULSE_BPM_SAMPLE_SIZE 10 45 | 46 | /** 47 | * Operation Mode Enum. 48 | * Heart rate only or Oxigen saturation + Heart rate. 49 | */ 50 | typedef enum _max30102_mode_t { 51 | MAX30102_MODE_HR_ONLY = 0x02, 52 | MAX30102_MODE_SPO2 = 0x03, 53 | MAX30102_MODE_SPO2_HR = 0x07 54 | } max30102_mode_t; 55 | 56 | 57 | /** 58 | * ADC range control enum 59 | * ADC ranges from 2048 nA to 16384 nA 60 | */ 61 | typedef enum ADCRange{ 62 | MAX30102_ADC_RANGE_2048 = 0x00, 63 | MAX30102_ADC_RANGE_4096 = 0x01, 64 | MAX30102_ADC_RANGE_8192 = 0x02, 65 | MAX30102_ADC_RANGE_16384 = 0x03 66 | }max30102_adc_range_t; 67 | 68 | /** 69 | * Sampling rate enum. 70 | * Internal sampling rates from 50Hz up to 1KHz. 71 | */ 72 | typedef enum SamplingRate { 73 | MAX30102_SAMPLING_RATE_50HZ = 0x00, 74 | MAX30102_SAMPLING_RATE_100HZ = 0x01, 75 | MAX30102_SAMPLING_RATE_200HZ = 0x02, 76 | MAX30102_SAMPLING_RATE_400HZ = 0x03, 77 | MAX30102_SAMPLING_RATE_800HZ = 0x04, 78 | MAX30102_SAMPLING_RATE_1000HZ = 0x05, 79 | MAX30102_SAMPLING_RATE_1600HZ = 0x06, 80 | MAX30102_SAMPLING_RATE_3200HZ = 0x07 81 | } max30102_sampling_rate_t; 82 | 83 | /** 84 | * Led pulse width enum. 85 | * Sets from 200us to 1600us. 86 | * fix this later to MAX30102 values 87 | */ 88 | typedef enum LEDPulseWidth { 89 | MAX30102_PULSE_WIDTH_200US_ADC_13 = 0x00, 90 | MAX30102_PULSE_WIDTH_400US_ADC_14 = 0x01, 91 | MAX30102_PULSE_WIDTH_800US_ADC_15 = 0x02, 92 | MAX30102_PULSE_WIDTH_1600US_ADC_16 = 0x03, 93 | } max30102_pulse_width_t; 94 | 95 | /** 96 | * Led current enum. 97 | * Sets the current for any of the leds. From 0mA to 50mA. 98 | */ 99 | typedef enum LEDCurrent { 100 | MAX30102_LED_CURRENT_0MA = 0x00, 101 | MAX30102_LED_CURRENT_4_4MA = 0x16, 102 | MAX30102_LED_CURRENT_7_6MA = 0x26, 103 | MAX30102_LED_CURRENT_11MA = 0x37, 104 | MAX30102_LED_CURRENT_14_2MA = 0x47, 105 | MAX30102_LED_CURRENT_17_4MA = 0x57, 106 | MAX30102_LED_CURRENT_20_8MA = 0x68, 107 | MAX30102_LED_CURRENT_24MA = 0x78, 108 | MAX30102_LED_CURRENT_27_1MA = 0x88, 109 | MAX30102_LED_CURRENT_30_6MA = 0x99, 110 | MAX30102_LED_CURRENT_33_8MA = 0xA9, 111 | MAX30102_LED_CURRENT_37MA = 0xB9, 112 | MAX30102_LED_CURRENT_40_2MA = 0xC9, 113 | MAX30102_LED_CURRENT_43_6MA = 0xDA, 114 | MAX30102_LED_CURRENT_46_8MA = 0xEA, 115 | MAX30102_LED_CURRENT_50MA = 0xFA, 116 | MAX30102_LED_CURRENT_51MA = 0xFF 117 | } max30102_current_t; 118 | 119 | /** 120 | * FIFO configuration structure. 121 | * Do NOT declare. 122 | */ 123 | typedef struct _max30102_fifo_t { 124 | uint16_t raw_ir; 125 | uint16_t raw_red; 126 | } max30102_fifo_t; 127 | 128 | /** 129 | * FIFO Configuration structure for 30102 130 | * Sample Averaging 131 | */ 132 | typedef enum SampleAveraging { 133 | MAX30102_SAMPLE_AVERAGING_1 = 0x00, 134 | MAX30102_SAMPLE_AVERAGING_2 = 0x01, 135 | MAX30102_SAMPLE_AVERAGING_4 = 0x02, 136 | MAX30102_SAMPLE_AVERAGING_8 = 0x03, 137 | MAX30102_SAMPLE_AVERAGING_16 = 0x04, 138 | MAX30102_SAMPLE_AVERAGING_32 = 0x05, 139 | MAX30102_SAMPLE_AVERAGING_32_2 = 0x06, 140 | MAX30102_SAMPLE_AVERAGING_32_3 = 0x07 141 | }max30102_sample_averaging_t; 142 | 143 | 144 | /** 145 | * FIFO Configuration structure for 30102 146 | * Roll on Full 147 | * ** one bit enable, just use code 148 | */ 149 | 150 | /** 151 | * FIFO Configuration structure for 30102 152 | * Almost Full Value 153 | */ 154 | 155 | typedef enum AlmostFull { 156 | MAX30102_ALMOST_FULL_0 = 0x00, 157 | MAX30102_ALMOST_FULL_1 = 0x01, 158 | MAX30102_ALMOST_FULL_2 = 0x02, 159 | MAX30102_ALMOST_FULL_3 = 0x03, 160 | MAX30102_ALMOST_FULL_4 = 0x04, 161 | MAX30102_ALMOST_FULL_5 = 0x05, 162 | MAX30102_ALMOST_FULL_6 = 0x06, 163 | MAX30102_ALMOST_FULL_7 = 0x07, 164 | MAX30102_ALMOST_FULL_8 = 0x08, 165 | MAX30102_ALMOST_FULL_9 = 0x09, 166 | MAX30102_ALMOST_FULL_10 = 0x0A, 167 | MAX30102_ALMOST_FULL_11 = 0x0B, 168 | MAX30102_ALMOST_FULL_12 = 0x0C, 169 | MAX30102_ALMOST_FULL_13 = 0x0D, 170 | MAX30102_ALMOST_FULL_14 = 0x0E, 171 | MAX30102_ALMOST_FULL_15 = 0x0F 172 | }max30102_almost_full_t; 173 | 174 | /** 175 | * DC filter structure. 176 | * Do NOT declare. 177 | */ 178 | typedef struct _max30102_dc_filter_t { 179 | float w; 180 | float result; 181 | } max30102_dc_filter_t; 182 | 183 | /** 184 | * Butterworth filter structure. 185 | * Do NOT declare. 186 | */ 187 | typedef struct _max30102_butterworth_filter_t 188 | { 189 | float v[2]; 190 | float result; 191 | } max30102_butterworth_filter_t; 192 | 193 | /** 194 | * Mean diff filter structure. 195 | * Do NOT declare. 196 | */ 197 | typedef struct _max30102_mean_diff_filter_t 198 | { 199 | float* values; 200 | uint8_t index; 201 | float sum; 202 | uint8_t count; 203 | } max30102_mean_diff_filter_t; 204 | 205 | /** 206 | * Data structure. 207 | * You need this to keep track of the values. 208 | */ 209 | typedef struct _max30102_data_t { 210 | bool pulse_detected; 211 | float heart_bpm; 212 | 213 | float ir_cardiogram; 214 | 215 | float ir_dc_value; 216 | float red_dc_value; 217 | 218 | float spO2; 219 | 220 | uint32_t last_beat_threshold; 221 | 222 | float dc_filtered_red; 223 | float dc_filtered_ir; 224 | } max30102_data_t; 225 | 226 | /** 227 | * The sensor "object" structure. 228 | * Use it to maintain all the configuration information. 229 | * Don't change it directly, use the functions to do so. 230 | */ 231 | typedef struct _max30102_config_t { 232 | 233 | i2c_port_t i2c_num; 234 | 235 | bool debug; 236 | 237 | uint8_t red_current; 238 | uint32_t last_red_current_check; 239 | 240 | uint8_t current_pulse_detector_state; 241 | float current_bpm; 242 | float* values_bpm; 243 | float values_bpm_sum; 244 | uint8_t values_bpm_count; 245 | uint8_t bpm_index; 246 | uint32_t last_beat_threshold; 247 | 248 | uint32_t acceptable_intense_diff; 249 | uint32_t red_current_adj_ms; 250 | uint8_t reset_spo2_pulse_n; 251 | float dc_alpha; 252 | uint16_t pulse_min_threshold; 253 | uint16_t pulse_max_threshold; 254 | 255 | uint8_t mean_filter_size; 256 | uint8_t pulse_bpm_sample_size; 257 | 258 | max30102_fifo_t prev_fifo; 259 | 260 | max30102_dc_filter_t dc_filter_ir; 261 | max30102_dc_filter_t dc_filter_red; 262 | max30102_butterworth_filter_t lpb_filter_ir; 263 | max30102_mean_diff_filter_t mean_diff_ir; 264 | 265 | float ir_ac_sq_sum; 266 | float red_ac_sq_sum; 267 | uint16_t samples_recorded; 268 | uint16_t pulses_detected; 269 | float current_spO2; 270 | 271 | max30102_current_t ir_current; 272 | 273 | } max30102_config_t; 274 | 275 | /** 276 | * @brief Initialize the sensor with the desired I2C and given parameters. 277 | * You can also set the debug mode. 278 | * 279 | * @details This functions include some parameters inside that initialize 280 | * with default values. You can change them later with max30102_set_* functions. 281 | * 282 | * @param this is the address of the configuration structure. 283 | * @param i2c_num is the I2C port of the ESP32 with the MAX30102 is attached to. 284 | * @param mode is the working mode of the sensor (HR only or HR+SPO2) 285 | * @param sampling_rate is the frequency which samples are taken internally. 286 | * @param pulse_width is the led pulse width. 287 | * @param ir_current is the current to the IR Led. 288 | * @param start_red_current is the starting value to Red Led current. 289 | * @param mean_filter_size is the sampling vector size to the filter. 290 | * @param pulse_bpm_sample_size is the heart rate sampling vector size. 291 | * @param high_res_mode is to set if high resolution mode or not. 292 | * @param debug is to set if registers output will be done to serial monitoring. 293 | * 294 | * @returns status of execution. 295 | */ 296 | esp_err_t max30102_init( max30102_config_t* this, i2c_port_t i2c_num, 297 | max30102_mode_t mode, max30102_sampling_rate_t sampling_rate, 298 | max30102_pulse_width_t pulse_width, max30102_current_t ir_current, 299 | max30102_current_t start_red_current, uint8_t mean_filter_size, 300 | uint8_t pulse_bpm_sample_size, max30102_adc_range_t high_res_mode, max30102_sample_averaging_t sample_averaging, 301 | bool overflow_enable, 302 | max30102_almost_full_t almost_full, bool debug ); 303 | 304 | /** 305 | * @brief Reads from MAX30102 internal registers and updates internal structures. 306 | * The results are written to a data structure. 307 | * 308 | * @details This functions must be called on a optimal rate of 100Hz. 309 | * 310 | * @param this is the address of the configuration structure. 311 | * @param data is the address of the data structure to save the results. 312 | * 313 | * @returns status of execution. 314 | */ 315 | esp_err_t max30102_update(max30102_config_t* this, max30102_data_t* data); 316 | 317 | /** 318 | * @brief Reads the internal temperature of the MAX30102. 319 | * 320 | * @param this is the address of the configuration structure. 321 | * @param temperature is the address of the variable to save the temperature. 322 | * 323 | * @returns status of execution. 324 | */ 325 | esp_err_t max30102_read_temperature(max30102_config_t* this, float* temperature); 326 | 327 | /** 328 | * @brief Prints the registers for debugging purposes. 329 | * 330 | * @param this is the address of the configuration structure. 331 | * 332 | * @returns status of execution. 333 | */ 334 | esp_err_t max30102_print_registers(max30102_config_t* this); 335 | 336 | /** 337 | * @brief Sets the operation mode. HR Only or HR+SPO2. 338 | * 339 | * @details This is automatically called by the initializer function. 340 | * 341 | * @param this is the address of the configuration structure. 342 | * @param mode is the mode of operation desired. 343 | * 344 | * @returns status of execution. 345 | */ 346 | esp_err_t max30102_set_mode(max30102_config_t* this, max30102_mode_t mode); 347 | 348 | /** 349 | * @brief Sets the resolution. High or standard. 350 | * 351 | * @details This is automatically called by the initializer function. 352 | * 353 | * @param this is the address of the configuration structure. 354 | * @param enable is if high resolution will be enabled. 355 | * 356 | * @returns status of execution. 357 | */ 358 | esp_err_t max30102_set_high_res(max30102_config_t* this, max30102_adc_range_t adc_range); 359 | 360 | /** 361 | * @brief Sets the led currents. 362 | * 363 | * @details This is automatically called by the initializer function 364 | * or automatically when needed by the library. 365 | * 366 | * @param this is the address of the configuration structure. 367 | * @param red_current is the current value of the Red Led. 368 | * @param ir_current is the current value of the IR Led. 369 | * 370 | * @returns status of execution. 371 | */ 372 | esp_err_t max30102_set_led_current( max30102_config_t* this, 373 | max30102_current_t red_current, 374 | max30102_current_t ir_current ); 375 | 376 | /** 377 | * @brief Sets the pulse width. 378 | * 379 | * @details This is automatically called by the initializer function. 380 | * 381 | * @param this is the address of the configuration structure. 382 | * @param pw is the pulse width desired. 383 | * 384 | * @returns status of execution. 385 | */ 386 | 387 | esp_err_t max30102_set_sample_averaging(max30102_config_t* this, 388 | max30102_sample_averaging_t sample_averaging); 389 | /** 390 | * @brief Sets the number of samples averaged 391 | * 392 | * @details This is automatically called by the initializer function. 393 | * 394 | * @param this is the address of the configuration structure. 395 | * @param sample_averaging is the desired sample averaging. 396 | * 397 | * @returns status of execution. 398 | */ 399 | 400 | esp_err_t max30102_set_roll_over(max30102_config_t* this, bool enabled); 401 | /** 402 | * @brief Sets the bit to enable or disable fifo roll over 403 | * 404 | * @details This is automatically called by the initializer function. 405 | * 406 | * @param this is the address of the configuration structure. 407 | * @param enables is the desired bit to enable or disable roll over. 408 | * 409 | * @returns status of execution. 410 | */ 411 | 412 | esp_err_t max30102_set_almost_full(max30102_config_t* this, 413 | max30102_almost_full_t almost_full); 414 | /** 415 | * @brief Sets the number of samples left to trigger almost full 416 | * 417 | * @details This is automatically called by the initializer function. 418 | * 419 | * @param this is the address of the configuration structure. 420 | * @param almost_full is the desired sample averaging. 421 | * 422 | * @returns status of execution. 423 | */ 424 | 425 | 426 | esp_err_t max30102_set_pulse_width(max30102_config_t* this, max30102_pulse_width_t pw); 427 | 428 | /** 429 | * @brief Set the internal sampling rate. 430 | * 431 | * @details This is automatically called by the initializer function. 432 | * 433 | * @param this is the address of the configuration structure. 434 | * @param rate is the sampling rate desired. 435 | * 436 | * @returns status of execution. 437 | */ 438 | esp_err_t max30102_set_sampling_rate( max30102_config_t* this, 439 | max30102_sampling_rate_t rate ); 440 | 441 | /** 442 | * @brief Sets the acceptable intense diff. 443 | * 444 | * @details This is set to a default (recommended) value in the initializer. 445 | * The only way to change is by calling this function. 446 | * 447 | * @param this is the address of the configuration structure. 448 | * @param acceptable_intense_diff is the value of the diff accepted. 449 | * 450 | * @returns status of execution. 451 | */ 452 | esp_err_t max30102_set_acceptable_intense_diff( max30102_config_t* this, 453 | uint32_t acceptable_intense_diff ); 454 | 455 | /** 456 | * @brief Sets the rate which the red led current can be adjusted. 457 | * 458 | * @details This is set to a default (recommended) value in the initializer. 459 | * The only way to change is by calling this function. 460 | * 461 | * @param this is the address of the configuration structure. 462 | * @param red_current_adj_ms is the value in milliseconds to be set. 463 | * 464 | * @returns status of execution. 465 | */ 466 | esp_err_t max30102_set_red_current_adj_ms( max30102_config_t* this, 467 | uint32_t red_current_adj_ms ); 468 | 469 | /** 470 | * @brief Sets the number of pulses which the spo2 values are reset. 471 | * 472 | * @details This is set to a default (recommended) value in the initializer. 473 | * The only way to change is by calling this function. 474 | * 475 | * @param this is the address of the configuration structure. 476 | * @param reset_spo2_pulse_n is the number of pulses. 477 | * 478 | * @returns status of execution. 479 | */ 480 | esp_err_t max30102_set_reset_spo2_pulse_n( max30102_config_t* this, 481 | uint8_t reset_spo2_pulse_n); 482 | 483 | /** 484 | * @brief Setts the DC filter alpha value. 485 | * 486 | * @details This is set to a default (recommended) value in the initializer. 487 | * The only way to change is by calling this function. 488 | * 489 | * @param this is the address of the configuration structure. 490 | * @param dc_alpha is the alpha value of the filter. 491 | * 492 | * @returns status of execution. 493 | */ 494 | esp_err_t max30102_set_dc_alpha(max30102_config_t* this, float dc_alpha ); 495 | 496 | /** 497 | * @brief Sets the minimum threshold to bpm. 498 | * 499 | * @details This is set to a default value in the initializer. 500 | * The only way to change is by calling this function. 501 | * You can't just throw these two values at random. 502 | * Check Check table 8 in datasheet on page 19. 503 | * You can set 300 for finger or 20 for wrist (with a lot of noise). 504 | * 505 | * @param this is the address of the configuration structure. 506 | * @param pulse_min_threshold is the value. 507 | * 508 | * @returns status of execution. 509 | */ 510 | esp_err_t max30102_set_pulse_min_threshold( max30102_config_t* this, 511 | uint16_t pulse_min_threshold ); 512 | 513 | /** 514 | * @brief Sets the maximum threshold to bpm. 515 | * 516 | * @details This is set to a default value in the initializer. 517 | * The only way to change is by calling this function. 518 | * You can't just throw these two values at random. 519 | * Check Check table 8 in datasheet on page 19. 520 | * 521 | * @param this is the address of the configuration structure. 522 | * @param pulse_max_threshold is the value. 523 | * 524 | * @returns status of execution. 525 | */ 526 | esp_err_t max30102_set_pulse_max_threshold( max30102_config_t* this, 527 | uint16_t pulse_max_threshold ); 528 | 529 | #endif 530 | 531 | /** 532 | * MIT License 533 | * 534 | * Permission is hereby granted, free of charge, to any person obtaining a copy 535 | * of this software and associated documentation files (the "Software"), to deal 536 | * in the Software without restriction, including without limitation the rights 537 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 538 | * copies of the Software, and to permit persons to whom the Software is 539 | * furnished to do so, subject to the following conditions: 540 | 541 | * The above copyright notice and this permission notice shall be included in all 542 | * copies or substantial portions of the Software. 543 | 544 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 545 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 546 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 547 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 548 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 549 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 550 | * SOFTWARE. 551 | */ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MAX30102A Project 2 | Author: Joshua D JOHN 3 | 4 | This project develops a library in C++ for the MAX30102 on ESP32. This documents the preparation and development of the project. 5 | 6 | ## Outline 7 | ### Setting up Dev Environment on Windows 8 | 9 | 10 | 11 | ## Setting up Dev Environment on Windows 12 | Please follow the instructions at 13 | https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/windows-setup.html 14 | 15 | The required software packages are downloaded and stored in Projects/Storage. 16 | 17 | --------------------------------------------------------------------------------