diff --git a/docs/images/max30100.png b/docs/images/max30100.png new file mode 100644 index 00000000..9604fe69 Binary files /dev/null and b/docs/images/max30100.png differ diff --git a/examples/c++/CMakeLists.txt b/examples/c++/CMakeLists.txt index 8cb6a171..ff86c4eb 100644 --- a/examples/c++/CMakeLists.txt +++ b/examples/c++/CMakeLists.txt @@ -328,6 +328,7 @@ add_example (mb704x) add_example (rf22-server) add_example (rf22-client) add_example (mcp2515) +add_example (max30100) # These are special cases where you specify example binary, source file and module(s) include_directories (${PROJECT_SOURCE_DIR}/src) diff --git a/examples/c++/max30100.cxx b/examples/c++/max30100.cxx new file mode 100644 index 00000000..42760349 --- /dev/null +++ b/examples/c++/max30100.cxx @@ -0,0 +1,99 @@ +/* + * Author: Noel Eck + * Copyright (c) 2016 Intel Corporation. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include + +#include "max30100.hpp" + +using namespace upm; + +int shouldRun = true; + +void sig_handler(int signo) +{ + if (signo == SIGINT) + shouldRun = false; +} + +// Example Callback handler +class mycallback : public Callback +{ + public: + virtual void run(max30100_value samp) + { + std::cout << "My callback sample IR: " + << samp.IR << " R: " << samp.R << std::endl; + } +}; + +int main(int argc, char **argv) +{ + signal(SIGINT, sig_handler); + //! [Interesting] + + // Instantiate a MAX30100 instance using i2c bus 0 + upm::MAX30100 sensor(0); + + // Create an instance of the Callback class + mycallback cb; + + // Read the temperature and version + std::cout << "Temperature: " << sensor.temperature() << " C" << std::endl; + std::cout << "Version: " << sensor.version() << std::endl; + + // Set high-res (50 Hz, 16-bit) + sensor.high_res_enable(true); + + // Set to sample SpO2 + sensor.mode(MAX30100_MODE_SPO2_EN); + + // Read continuously, stepping up the LED current every second, + // us GPIO 0 as the interrupt pin + sensor.sample_continuous(0, false, &cb); + for (int i = MAX30100_LED_CURRENT_0_0_MA; + i <= MAX30100_LED_CURRENT_50_0_MA && shouldRun; i++) + { + // Toggle the LED current + std::cout << "Setting LED current = " << i << std::endl; + + sensor.current((MAX30100_LED_CURRENT)i, (MAX30100_LED_CURRENT)i); + + upm_delay(1); + } + + // Read individual samples + for (int i = 0; i < 10; i++) + { + max30100_value val = sensor.sample(); + std::cout << "Single value IR: " << val.IR << " R: " << val.R << std::endl; + } + + //! [Interesting] + + std::cout << "Exiting..." << std::endl; + + return 0; +} diff --git a/examples/c/CMakeLists.txt b/examples/c/CMakeLists.txt index 8eb8e86b..2f8bfed5 100644 --- a/examples/c/CMakeLists.txt +++ b/examples/c/CMakeLists.txt @@ -142,6 +142,7 @@ add_example (ims) add_example (ecezo) add_example (mb704x) add_example (mcp2515) +add_example (max30100) # Custom examples add_custom_example (nmea_gps_i2c-example-c nmea_gps_i2c.c nmea_gps) diff --git a/examples/c/max30100.c b/examples/c/max30100.c new file mode 100644 index 00000000..d4898941 --- /dev/null +++ b/examples/c/max30100.c @@ -0,0 +1,130 @@ +/* + * Author: Noel Eck + * Copyright (c) 2016 Intel Corporation. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include + +#include "max30100.h" +#include "upm_utilities.h" + +bool shouldRun = true; + +void sig_handler(int signo) +{ + if (signo == SIGINT) + shouldRun = false; +} + +void my_sample_handler(max30100_value sample, void* arg) +{ + printf("My callback sample IR: %d R: %d\n", + sample.IR, sample.R); +} + +int main() +{ + signal(SIGINT, sig_handler); + + //! [Interesting] + max30100_context* sensor = max30100_init(0); + + if (!sensor) + { + printf("max30100_init() failed\n"); + return -1; + } + + /* Get the temperature */ + float temp; + if (max30100_get_temperature(sensor, &temp) != UPM_SUCCESS) + { + printf("max30100_get_temperature failed\n"); + goto max30100_exit; + } + + /* Get the version */ + uint16_t version; + if (max30100_get_version(sensor, &version) != UPM_SUCCESS) + { + printf("max30100_get_version failed\n"); + goto max30100_exit; + } + + printf("Temperature: %f C\n", temp); + printf("Version: 0x%04x\n", version); + + /* Set high-res (50 Hz, 16-bit) */ + if (max30100_set_high_res(sensor, true) != UPM_SUCCESS) + { + printf("max30100_set_high_res failed\n"); + goto max30100_exit; + } + + /* Set to sample SpO2 */ + if (max30100_set_mode(sensor, MAX30100_MODE_SPO2_EN) != UPM_SUCCESS) + { + printf("max30100_set_mode failed\n"); + goto max30100_exit; + } + + /* Read continuously, stepping up the LED current every second, + * us GPIO 0 as the interrupt pin */ + max30100_sample_continuous(sensor, 0, false, &my_sample_handler, sensor); + for (int i = MAX30100_LED_CURRENT_0_0_MA; + i <= MAX30100_LED_CURRENT_50_0_MA && shouldRun; i++) + { + /* Toggle the LED current */ + printf("Setting LED current = %d\n", i); + + if ( max30100_set_current(sensor, (MAX30100_LED_CURRENT)i, + (MAX30100_LED_CURRENT)i) != UPM_SUCCESS ) + { + printf("max30100_set_current failed\n"); + goto max30100_exit; + } + + upm_delay(1); + } + + /* Read individual samples */ + for (int i = 0; i < 10; i++) + { + max30100_value samp; + if (max30100_sample(sensor, &samp) != UPM_SUCCESS) + { + printf("max30100_sample failed\n"); + goto max30100_exit; + } + + printf("Single value IR: %d R: %d\n", samp.IR, samp.R); + } + +max30100_exit: + //! [Interesting] + printf("Exiting\n"); + max30100_close(sensor); + + return 0; +} diff --git a/examples/java/CMakeLists.txt b/examples/java/CMakeLists.txt index 50d53ad4..dbceccf8 100644 --- a/examples/java/CMakeLists.txt +++ b/examples/java/CMakeLists.txt @@ -162,6 +162,7 @@ add_example(IMS_Example ims) add_example(MB704X_Example mb704x) add_example(MCP2515_Example mcp2515) add_example(Ads1015Sample ads1x15) +add_example(MAX30100_Example max30100) add_example_with_path(Jhd1313m1_lcdSample lcd i2clcd) add_example_with_path(Jhd1313m1Sample lcd i2clcd) diff --git a/examples/java/MAX30100_Example.java b/examples/java/MAX30100_Example.java new file mode 100644 index 00000000..5d5319e8 --- /dev/null +++ b/examples/java/MAX30100_Example.java @@ -0,0 +1,88 @@ +/* + * Author: Noel Eck + * Copyright (c) 2016 Intel Corporation. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import upm_max30100.*; + +public class MAX30100_Example +{ + public static void main(String[] args) throws InterruptedException + { + // ! [Interesting] + + // Instantiate a MAX30100 instance using bus 0 + MAX30100 sensor = new MAX30100((short)0); + + System.out.println("Oximeter sensor example..."); + + // Read the temperature and version + System.out.format("Temperature: %f C\n", sensor.temperature()); + System.out.format("Version: 0x%04x\n", sensor.version()); + + // Set high-res (50 Hz, 16-bit) + sensor.high_res_enable(true); + + // Set to sample SpO2 + sensor.mode(MAX30100_MODE.MAX30100_MODE_SPO2_EN); + + Callback cb = new JavaCallback(); + + // Read continuously, stepping up the LED current every second, + // us GPIO 0 as the interrupt pin + sensor.sample_continuous(0, false, cb); + for (int i = 0; i <= 15; i++) + { + // Toggle the LED current + System.out.format("Setting LED current = %d\n", i); + + sensor.current(MAX30100_LED_CURRENT.swigToEnum(i), + MAX30100_LED_CURRENT.swigToEnum(i)); + + Thread.sleep(1000); + } + + sensor.sample_stop(); + + // Read individual samples + for (int i = 0; i < 10; i++) { + max30100_value val = sensor.sample(); + System.out.format("Single value IR: %d R: %d\n", val.getIR(), val.getR()); + } + + // ! [Interesting] + } +} + +class JavaCallback extends Callback +{ + public JavaCallback() + { + super(); + } + + public void run(max30100_value val) + { + System.out.format("My callback sample IR: %d R: %d\n", val.getIR(), val.getR()); + } +} + diff --git a/examples/javascript/max30100.js b/examples/javascript/max30100.js new file mode 100644 index 00000000..3eac912a --- /dev/null +++ b/examples/javascript/max30100.js @@ -0,0 +1,57 @@ +/* + * Author: Noel Eck + * Copyright (c) 2016 Intel Corporation. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +var sensorObj = require('jsupm_max30100'); + +// Instantiate a MAX30100 instance using bus 0 +var sensor = new sensorObj.MAX30100(0); + +console.log('Oximeter sensor example...'); + +// Read the temperature and version +console.log ('Temperature: %d C', sensor.temperature()); +console.log ('Version: 0x%s', sensor.version().toString(16)); + +// Set high-res (50 Hz, 16-bit) +sensor.high_res_enable(true); + +// Set to sample SpO2 +sensor.mode(sensorObj.MAX30100_MODE_SPO2_EN); + +// Read individual samples +for (var i = 0; i < 10; i++) +{ + var val = sensor.sample(); + console.log('Single value IR: %d R: %d ', val.IR, val.R); +} + +// exit on ^C +process.on('SIGINT', function() +{ + sensor = null; + sensorObj.cleanUp(); + sensorObj = null; + console.log('Exiting.'); + process.exit(0); +}); diff --git a/examples/python/max30100.py b/examples/python/max30100.py new file mode 100755 index 00000000..2a681999 --- /dev/null +++ b/examples/python/max30100.py @@ -0,0 +1,71 @@ +#!/usr/bin/python +# Author: Noel Eck +# Copyright (c) 2016 Intel Corporation. +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +from __future__ import print_function +import time, sys, signal, atexit +from upm import pyupm_max30100 + +# Callback class derived from CXX Callback +class mycallback(pyupm_max30100.Callback): + def __init__(self): + self.count = 0 + pyupm_max30100.Callback.__init__(self) + + def run(self, samp): + print("My callback sample IR: %d R: %d" % (samp.IR, samp.R)) + +def main(): + # Create an instance of the oximiter + # I2C bus 0 + x = pyupm_max30100.MAX30100(0) + + print ('Oximeter sensor example...') + + # Create an instance of the mycallback class + cb = mycallback().__disown__() + + # Read the temperature and version + print ("Temperature: %d C" % x.temperature()) + print ("Version: 0x%04x" % x.version()) + + # Set high-res (50 Hz, 16-bit) + x.high_res_enable(True) + + # Set to sample SpO2 + x.mode(pyupm_max30100.MAX30100_MODE_SPO2_EN); + + # Read continuously, stepping up the LED current every second, + # us GPIO 0 as the interrupt pin + x.sample_continuous(0, False, cb) + for i in range(16): + print("Setting LED current = %d" % i) + x.current(i, i) + time.sleep(1) + + # Read individual samples + for i in range(10): + val = x.sample(); + print("Single value IR: %d R: %d " % (val.IR, val.R)) + +if __name__ == '__main__': + main() diff --git a/src/max30100/CMakeLists.txt b/src/max30100/CMakeLists.txt new file mode 100644 index 00000000..7eb76482 --- /dev/null +++ b/src/max30100/CMakeLists.txt @@ -0,0 +1,9 @@ +upm_mixed_module_init (NAME max30100 + DESCRIPTION "Pulse oximeter and heart-rate sensor" + C_HDR max30100.h + C_SRC max30100.c + CPP_HDR max30100.hpp + CPP_SRC max30100.cxx + FTI_SRC max30100_fti.c + CPP_WRAPS_C + REQUIRES mraa) diff --git a/src/max30100/javaupm_max30100.i b/src/max30100/javaupm_max30100.i new file mode 100644 index 00000000..c370b689 --- /dev/null +++ b/src/max30100/javaupm_max30100.i @@ -0,0 +1,21 @@ +%module(directors="1", threads="1") javaupm_max30100 +%include "../upm.i" + +%{ + #include "max30100.hpp" +%} + +%feature("director") upm::Callback; +%include "max30100_regs.h" +%include "max30100.hpp" + +%pragma(java) jniclasscode=%{ + static { + try { + System.loadLibrary("javaupm_max30100"); + } catch (UnsatisfiedLinkError e) { + System.err.println("Native code library failed to load. \n" + e); + System.exit(1); + } + } +%} diff --git a/src/max30100/jsupm_max30100.i b/src/max30100/jsupm_max30100.i new file mode 100644 index 00000000..edb35335 --- /dev/null +++ b/src/max30100/jsupm_max30100.i @@ -0,0 +1,9 @@ +%module jsupm_max30100 +%include "../upm.i" + +%{ + #include "max30100.hpp" +%} + +%include "max30100_regs.h" +%include "max30100.hpp" diff --git a/src/max30100/max30100.c b/src/max30100/max30100.c new file mode 100644 index 00000000..c4d13ff7 --- /dev/null +++ b/src/max30100/max30100.c @@ -0,0 +1,529 @@ +/* + * Author: Noel Eck + * Copyright (c) 2015 Intel Corporation. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include +#include + +#include "max30100.h" + +max30100_context* max30100_init(int16_t i2c_bus) +{ + /* Allocate space for the sensor structure */ + max30100_context* dev = (max30100_context*) malloc(sizeof(max30100_context)); + if(dev == NULL) + { + syslog(LOG_CRIT, "%s: malloc() failed\n", __FUNCTION__); + goto max30100_init_fail; + } + + /* Initilize mraa */ + mraa_result_t result = mraa_init(); + if (result != MRAA_SUCCESS) + { + syslog(LOG_ERR, "%s: mraa_init() failed (%d)\n", __FUNCTION__, result); + goto max30100_init_fail; + } + + /* Initialize I2C */ + dev->_i2c_context = mraa_i2c_init(i2c_bus); + if(dev->_i2c_context == NULL) + { + syslog(LOG_ERR, "%s: mraa_i2c_init() failed\n", __FUNCTION__); + goto max30100_init_fail; + } + + /* Set the I2C slave address for this device */ + if (mraa_i2c_address(dev->_i2c_context, MAX30100_I2C_ADDRESS) != MRAA_SUCCESS) + { + syslog(LOG_ERR, "%s: mraa_i2c_address() failed\n", __FUNCTION__); + goto max30100_init_fail; + } + + /* Attempt to run the device at 100kHz */ + if (mraa_i2c_frequency(dev->_i2c_context, MRAA_I2C_STD)) + syslog(LOG_ERR, "%s: mraa_i2c_frequency() failed, device may not function correctly\n", __FUNCTION__); + + /* Start without GPIO */ + dev->_gpio_context = NULL; + + return dev; + + /* Handle all failing cases here */ +max30100_init_fail: + /* Free structure memory if allocated */ + if (dev != NULL) + free(dev); + + return NULL; +} + +void max30100_close(max30100_context* dev) +{ + assert(dev != NULL && "max30100_close: Context cannot be NULL"); + + /* Cleanup the I2C context */ + mraa_i2c_stop(dev->_i2c_context); + free(dev); +} + +static void internal_uninstall_isr(max30100_context* dev) +{ + assert(dev != NULL && "internal_uninstall_isr: Context cannot be NULL"); + + /* If no GPIO context exists, return */ + if (dev->_gpio_context == NULL) return; + + mraa_gpio_isr_exit(dev->_gpio_context); + mraa_gpio_close(dev->_gpio_context); + dev->_gpio_context = NULL; +} + +static upm_result_t _internal_install_isr(max30100_context* dev, int gpio_pin, + void (*isr)(void *), void *arg) +{ + /* Only allow one ISR */ + internal_uninstall_isr(dev); + + if (!(dev->_gpio_context = mraa_gpio_init(gpio_pin))) + return UPM_ERROR_OPERATION_FAILED; + + /* Set the GPIO to input */ + if (mraa_gpio_dir(dev->_gpio_context, MRAA_GPIO_IN) != MRAA_SUCCESS) + return UPM_ERROR_OPERATION_FAILED; + + /* MAX30100 interrupts are active low, pull GPIO high */ + if (mraa_gpio_mode(dev->_gpio_context, MRAA_GPIO_PULLUP) != MRAA_SUCCESS) + return UPM_ERROR_OPERATION_FAILED; + + /* Install the interrupt handler */ + if (mraa_gpio_isr(dev->_gpio_context, MRAA_GPIO_EDGE_FALLING, isr, arg) != MRAA_SUCCESS) + return UPM_ERROR_OPERATION_FAILED; + + return UPM_SUCCESS; +} + +upm_result_t max30100_read(const max30100_context* dev, MAX30100_REG reg, uint8_t* rd_data) +{ + assert(dev != NULL && "max30100_read: Context cannot be NULL"); + /* Read the register */ + int tmp_val = mraa_i2c_read_byte_data(dev->_i2c_context, reg); + if (tmp_val < 0) return UPM_ERROR_OPERATION_FAILED; + + *rd_data = (uint8_t)tmp_val; + + return UPM_SUCCESS; +} + +upm_result_t max30100_write(const max30100_context* dev, MAX30100_REG reg, uint8_t wr_data) +{ + assert(dev != NULL && "max30100_write: Context cannot be NULL"); + + if (mraa_i2c_write_byte_data(dev->_i2c_context, (uint8_t)wr_data, reg) != MRAA_SUCCESS) + return UPM_ERROR_OPERATION_FAILED; + + return UPM_SUCCESS; +} + +upm_result_t max30100_rd_mod_wr(const max30100_context* dev, + MAX30100_REG reg, uint8_t value, uint8_t mask) +{ + uint8_t tmp_val = 0; + + /* Read the register */ + upm_result_t result = max30100_read(dev, reg, &tmp_val); + + if (result != UPM_SUCCESS) return result; + + /* Modify the value, firt clear the bits from mask */ + tmp_val &= (~mask); + /* Make sure the new value doesn't have anything set outside the mask */ + value &= mask; + /* OR in the new value */ + tmp_val |= value; + + /* Write the value back */ + return max30100_write(dev, reg, tmp_val); +} + +upm_result_t max30100_get_version(const max30100_context* dev, uint16_t* version) +{ + assert(dev != NULL && "max30100_get_version: Context cannot be NULL"); + + /* Read the revision ID */ + uint8_t tmp_val = 0; + upm_result_t result = max30100_read(dev, MAX30100_REG_REV_ID, &tmp_val); + if (result != UPM_SUCCESS) return result; + + *version = (uint8_t)tmp_val; + + result = max30100_read(dev, MAX30100_REG_PART_ID, &tmp_val); + if (result != UPM_SUCCESS) return result; + + /* Move the PART ID to upper byte */ + *version += ((uint16_t)tmp_val << 8); + + return UPM_SUCCESS; +} + +upm_result_t max30100_get_temperature(const max30100_context* dev, float* temperature) +{ + assert(dev != NULL && "max30100_get_temperature: Context cannot be NULL"); + int8_t tmp_val = 0; + + /* First, set TEMP_EN to initiate a temperature read */ + upm_result_t result = max30100_rd_mod_wr(dev, MAX30100_REG_MODE_CONFIG, + MAX30100_TEMP_EN, MAX30100_TEMP_EN); + if (result != UPM_SUCCESS) return result; + + /* Note, the docs for reading a temperature value states: + * This is a self-clearing bit which, when set, initiates a single + * temperature reading from the temperature sensor. This bit is + * cleared automatically back to zero at the conclusion of the + * temperature reading when the bit is set to one in heart rate + * or SpO2 mode. + * + * However, the next read of the MODE CONFIG register *always* seems + * to have TEMP_EN cleared w/o values in TINT/TFRAC until a short + * while later. To account for this, a delay has been added - sorry */ + + upm_delay_ms(100); + + /* Read the integer portion of the temperature */ + result = max30100_read(dev, MAX30100_REG_TEMP_INTEGER, (uint8_t*)&tmp_val); + if (result != UPM_SUCCESS) return result; + + /* cast the signed integer portion to float */ + *temperature = (float)tmp_val; + + /* This register stores the fractional temperature data in increments of + * 0.0625C (1/16th of a degree C). If this fractional temperature is + * paired with a negative integer, it still adds as a positive fractional + * value (e.g., -128°C + 0.5°C = -127.5°C). */ + result = max30100_read(dev, MAX30100_REG_TEMP_FRACTION, (uint8_t*)&tmp_val); + if (result != UPM_SUCCESS) return result; + + /* Add the fraction */ + *temperature += ((float)tmp_val)/16.0; + + return UPM_SUCCESS; +} + +upm_result_t max30100_set_mode(const max30100_context* dev, MAX30100_MODE mode) +{ + assert(dev != NULL && "max30100_set_mode: Context cannot be NULL"); + return max30100_rd_mod_wr(dev, MAX30100_REG_MODE_CONFIG, (uint8_t)mode, 0x03); +} + +upm_result_t max30100_get_mode(const max30100_context* dev, MAX30100_MODE* mode) +{ + assert(dev != NULL && "max30100_get_mode: Context cannot be NULL"); + + /* Read the mode configuration register */ + uint8_t data = 0; + upm_result_t result = max30100_read(dev, MAX30100_REG_MODE_CONFIG, &data); + if (result != UPM_SUCCESS) return result; + + *mode = (MAX30100_MODE) data & 0x7; + + return UPM_SUCCESS; +} + +upm_result_t max30100_set_high_res(const max30100_context* dev, bool high_res) +{ + assert(dev != NULL && "MAX30100_set_high_res: Context cannot be NULL"); + uint8_t wr_val = high_res ? MAX30100_SPO2_HI_RES_EN : ~MAX30100_SPO2_HI_RES_EN; + return max30100_rd_mod_wr(dev, MAX30100_REG_SPO2_CONFIG, wr_val, 0x40); +} + +upm_result_t max30100_get_high_res(const max30100_context* dev, bool* high_res) +{ + assert(dev != NULL && "MAX30100_get_high_res: Context cannot be NULL"); + + /* Read the SpO2 configuration register */ + uint8_t data = 0; + upm_result_t result = max30100_read(dev, MAX30100_REG_SPO2_CONFIG, &data); + if (result != UPM_SUCCESS) return result; + + *high_res = data & 0x40; + + return UPM_SUCCESS; +} + +upm_result_t max30100_set_sample_rate(const max30100_context* dev, MAX30100_SR sample_rate) +{ + assert(dev != NULL && "MAX30100_set_sample_rate: Context cannot be NULL"); + return max30100_rd_mod_wr(dev, MAX30100_REG_SPO2_CONFIG, (uint8_t)sample_rate << 2, 0x1c); +} + +upm_result_t max30100_get_sample_rate(const max30100_context* dev, MAX30100_SR* sample_rate) +{ + assert(dev != NULL && "MAX30100_get_high_res: Context cannot be NULL"); + + /* Read the SpO2 configuration register */ + uint8_t data = 0; + upm_result_t result = max30100_read(dev, MAX30100_REG_SPO2_CONFIG, &data); + if (result != UPM_SUCCESS) return result; + + *sample_rate = (MAX30100_SR)((data >> 2) & 0x7); + + return UPM_SUCCESS; +} + +upm_result_t max30100_set_pulse_width(const max30100_context* dev, MAX30100_LED_PW pulse_width) +{ + assert(dev != NULL && "MAX30100_set_pulse_width: Context cannot be NULL"); + return max30100_rd_mod_wr(dev, MAX30100_REG_SPO2_CONFIG, (uint8_t)pulse_width, 0x03); +} + +upm_result_t max30100_get_pulse_width(const max30100_context* dev, MAX30100_LED_PW* pulse_width) +{ + assert(dev != NULL && "MAX30100_get_high_res: Context cannot be NULL"); + + /* Read the SpO2 configuration register */ + uint8_t data = 0; + upm_result_t result = max30100_read(dev, MAX30100_REG_SPO2_CONFIG, &data); + if (result != UPM_SUCCESS) return result; + + *pulse_width = (MAX30100_LED_PW)(data & 0x3); + + return UPM_SUCCESS; +} + +upm_result_t max30100_set_current(const max30100_context* dev, + MAX30100_LED_CURRENT ir, MAX30100_LED_CURRENT r) +{ + assert(dev != NULL && "max30100_set_current: Context cannot be NULL"); + + return max30100_write(dev, MAX30100_REG_LED_CONFIG, + (uint8_t)((r << 4) | r)); +} + +upm_result_t max30100_get_current(const max30100_context* dev, + MAX30100_LED_CURRENT* ir, MAX30100_LED_CURRENT* r) +{ + assert(dev != NULL && "max30100_get_current: Context cannot be NULL"); + + /* Read the LED configuration register */ + uint8_t data = 0; + upm_result_t result = max30100_read(dev, MAX30100_REG_LED_CONFIG, &data); + if (result != UPM_SUCCESS) return result; + + *ir = (MAX30100_LED_CURRENT)(data & 0x0f); + *r = (MAX30100_LED_CURRENT)((data >> 4) & 0x0f); + + return UPM_SUCCESS; +} + +upm_result_t max30100_reset(const max30100_context* dev) +{ + assert(dev != NULL && "max30100_reset: Context cannot be NULL"); + + /* Set the RESET bit, don't worry about read/mod/write */ + return max30100_write(dev, MAX30100_REG_MODE_CONFIG, (uint8_t)0x40); +} + +upm_result_t max30100_sleep(const max30100_context* dev, bool sleep) +{ + assert(dev != NULL && "max30100_sleep: Context cannot be NULL"); + + /* Read/mod/write to set the SHDN bit */ + uint8_t wr_val = sleep ? MAX30100_SHDN : (uint8_t)~MAX30100_SHDN; + return max30100_rd_mod_wr(dev, MAX30100_REG_MODE_CONFIG, wr_val, MAX30100_SHDN); +} + +static upm_result_t _read_single_sample(const max30100_context* dev, max30100_value *samp) +{ + uint8_t data[4]; + if (mraa_i2c_read_bytes_data(dev->_i2c_context, MAX30100_REG_FIFO_DATA, data, 4) != 4) + return UPM_ERROR_OPERATION_FAILED; + + samp->IR = ((uint16_t)data[0] << 8) | data[1]; + samp->R = ((uint16_t)data[2] << 8) | data[3]; + + return UPM_SUCCESS; +} + +static void _internal_sample_rdy(void *arg) +{ + max30100_context* dev = arg; + + if (dev->sample_state == MAX30100_SAMPLE_STATE_IDLE) return; + + int i = 15; + max30100_value samp = {0, 0}; + /* If state is BUFFERED, read 16 samples, else read 1 sample */ + do + { + if (_read_single_sample(dev, &samp) != UPM_SUCCESS) + goto max30100_sample_rdy_fail; + + // Call handler + dev->func_sample_ready(samp, dev->arg); + + } while ((i-- > 0) && (dev->sample_state == MAX30100_SAMPLE_STATE_CONTINUOUS_BUFFERED)); + + /* If a FIFO full interrupt generated this, clear it by reading sts */ + uint8_t tmp; + if (dev->sample_state == MAX30100_SAMPLE_STATE_CONTINUOUS_BUFFERED) + if(max30100_read(dev, MAX30100_REG_INTERRUPT_STATUS, &tmp) != UPM_SUCCESS) + goto max30100_sample_rdy_fail; + + return; + + /* If a failure occurs in this method (which running on a seperate thread, + * log an error in syslog and attempt to stop sampling + * Handle all failing cases here */ + max30100_sample_rdy_fail: + syslog(LOG_CRIT, "%s: _internal_sample_rdy() failed, attempting to stop sampling...\n", + __FUNCTION__); + max30100_sample_stop(dev); + return; +} + +upm_result_t max30100_sample(max30100_context* dev, max30100_value *samp) +{ + assert(dev != NULL && "max30100_sample: Context cannot be NULL"); + + upm_result_t result = UPM_SUCCESS; + + // Disable interrupts + result = max30100_write(dev, MAX30100_REG_INTERRUPT_ENABLE, 0x00); + if (result != UPM_SUCCESS) return result; + + /* Set the state to one-shot */ + dev->sample_state = MAX30100_SAMPLE_STATE_ONE_SHOT; + + /* Clear wr/rd pointers */ + result = max30100_write(dev, MAX30100_REG_FIFO_WR_PTR, 0x00); + if (result != UPM_SUCCESS) return result; + result = max30100_write(dev, MAX30100_REG_FIFO_RD_PTR, 0x00); + if (result != UPM_SUCCESS) return result; + + /* Wait for a sample */ + uint8_t wr_ptr = 0; + int retry = 50; + while ((wr_ptr == 0) && (--retry > 0)) + { + result = max30100_read(dev, MAX30100_REG_FIFO_WR_PTR, &wr_ptr); + if (result != UPM_SUCCESS) return result; + } + + /* Return timeout if retry count is zero */ + if (retry == 0) return UPM_ERROR_TIMED_OUT; + + /* Set the rd ptr to wr ptr to ensure reading the most current sample */ + result = max30100_write(dev, MAX30100_REG_FIFO_RD_PTR, wr_ptr - 1); + if (result != UPM_SUCCESS) return result; + + /* Read the sample */ + if (_read_single_sample(dev, samp) != UPM_SUCCESS) + return UPM_ERROR_OPERATION_FAILED; + + return UPM_SUCCESS; +} + +upm_result_t max30100_sample_continuous(max30100_context* dev, int gpio_pin, + bool buffered, func_sample_ready_handler isr, void* arg) +{ + assert(dev != NULL && "max30100_sample_continuous: Context cannot be NULL"); + uint8_t tmp; + + upm_result_t result = UPM_SUCCESS; + + // Set state to IDLE + dev->sample_state = MAX30100_SAMPLE_STATE_IDLE; + + // Disable interrupts + result = max30100_write(dev, MAX30100_REG_INTERRUPT_ENABLE, 0x00); + if (result != UPM_SUCCESS) return result; + + /* Setup the external callback info */ + dev->func_sample_ready = isr; + dev->arg = arg; + // Register internal callback handler + result = _internal_install_isr(dev, gpio_pin, _internal_sample_rdy, dev); + if (result != UPM_SUCCESS) return result; + + uint8_t tmp_int_en = 0; + if (buffered) + { + dev->sample_state = MAX30100_SAMPLE_STATE_CONTINUOUS_BUFFERED; + + // Set value of interrupt for FIFO_FULL + tmp_int_en = MAX30100_EN_A_FULL; + } + else + { + dev->sample_state = MAX30100_SAMPLE_STATE_CONTINUOUS; + + // Read the mode field from the mode configuration register, + // decide which interrupt to set + result = max30100_read(dev, MAX30100_REG_MODE_CONFIG, &tmp); + if (result != UPM_SUCCESS) return result; + MAX30100_MODE mode = (MAX30100_MODE)(tmp & 0x3); + + // Set value of interrupt for HR or SpO2 + tmp_int_en = mode == MAX30100_MODE_HR_EN ? MAX30100_EN_HR_RDY : + (mode == MAX30100_MODE_SPO2_EN ? MAX30100_EN_SPO2_RDY : 0x00); + } + + /* Clear wr/rd pointers */ + result = max30100_write(dev, MAX30100_REG_FIFO_WR_PTR, 0x00); + if (result != UPM_SUCCESS) return result; + result = max30100_write(dev, MAX30100_REG_FIFO_RD_PTR, 0x00); + if (result != UPM_SUCCESS) return result; + + /* Enable interrupt, either FIFO full, HR only, or SpO2 */ + result = max30100_write(dev, MAX30100_REG_INTERRUPT_ENABLE, tmp_int_en); + if (result != UPM_SUCCESS) return result; + + /* Read the STATUS register to get things moving */ + result = max30100_read(dev, MAX30100_REG_INTERRUPT_STATUS, &tmp); + if (result != UPM_SUCCESS) return result; + + return UPM_SUCCESS; +} + +upm_result_t max30100_sample_stop(max30100_context* dev) +{ + assert(dev != NULL && "max30100_sample_stop: Context cannot be NULL"); + + dev->sample_state = MAX30100_SAMPLE_STATE_IDLE; + + /* Uninstall sampling ISR */ + internal_uninstall_isr(dev); + +// // Disable sampling +// upm_result_t result = max30100_write(dev, MAX30100_REG_MODE_CONFIG, MAX30100_MODE_DISABLED); +// if (result != UPM_SUCCESS) return result; + + // Disable interrupts + upm_result_t result = max30100_write(dev, MAX30100_REG_INTERRUPT_ENABLE, 0); + if (result != UPM_SUCCESS) return result; + + return UPM_SUCCESS; +} + diff --git a/src/max30100/max30100.cxx b/src/max30100/max30100.cxx new file mode 100644 index 00000000..c0507d0a --- /dev/null +++ b/src/max30100/max30100.cxx @@ -0,0 +1,211 @@ +/* + * Author: Noel Eck + * Copyright (c) 2015 Intel Corporation. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include +#include "max30100.hpp" + +using namespace upm; + +void max30100_throw(std::string func, std::string cmd, upm_result_t result) +{ + throw std::runtime_error(func + ": " + cmd + " failed, " + + "upm_result_t: " + std::to_string(result)); +} + +MAX30100::MAX30100(int16_t i2c_bus) : _dev(max30100_init(i2c_bus)) +{ + if (_dev == NULL) + throw std::runtime_error(std::string(__FUNCTION__) + + ": failed to initialize sensor, check syslog"); +} + +void _read_sample_proxy(max30100_value sample, void* _max30100) +{ + if ((_max30100 != NULL) && ((MAX30100*)_max30100)->_callback != NULL) + ((MAX30100*)_max30100)->_callback->run(sample); +} + +max30100_value MAX30100::sample() +{ + max30100_value retval; + upm_result_t result = max30100_sample(_dev, &retval); + if (result != UPM_SUCCESS) + max30100_throw(__FUNCTION__, "max30100_sample", result); + return retval; +} + + +void MAX30100::sample_continuous(int gpio_pin, bool buffered, Callback *cb) +{ + // Use a default callback if one is NOT provided + if (cb == NULL) + _callback = (Callback *)&_default_callback; + else + _callback = cb; + max30100_sample_continuous(_dev, gpio_pin, buffered, &_read_sample_proxy, this); +} + +void MAX30100::sample_stop() +{ + upm_result_t result = max30100_sample_stop(_dev); + if (result != UPM_SUCCESS) + max30100_throw(__FUNCTION__, "max30100_sample_stop" , result); +} + +uint16_t MAX30100::version() +{ + uint16_t retval; + upm_result_t result = max30100_get_version(_dev, &retval); + if (result != UPM_SUCCESS) + max30100_throw(__FUNCTION__, "max30100_get_version" , result); + return retval; +} + +float MAX30100::temperature() +{ + float retval; + upm_result_t result = max30100_get_temperature(_dev, &retval); + if (result != UPM_SUCCESS) + max30100_throw(__FUNCTION__, "max30100_get_temperature", result); + return retval; +} + +void MAX30100::mode(MAX30100_MODE mode) +{ + upm_result_t result = max30100_set_mode(_dev, mode); + if (result != UPM_SUCCESS) + max30100_throw(__FUNCTION__, "max30100_set_mode", result); +} + +MAX30100_MODE MAX30100::mode() +{ + MAX30100_MODE mode; + upm_result_t result = max30100_get_mode(_dev, &mode); + if (result != UPM_SUCCESS) + max30100_throw(__FUNCTION__, "max30100_get_mode", result); + return mode; +} + +void MAX30100::high_res_enable(bool enable) +{ + upm_result_t result = max30100_set_high_res(_dev, enable); + if (result != UPM_SUCCESS) + max30100_throw(__FUNCTION__, "max30100_set_high_res", result); +} + +bool MAX30100::high_res_enable() +{ + bool enabled; + upm_result_t result = max30100_get_high_res(_dev, &enabled); + if (result != UPM_SUCCESS) + max30100_throw(__FUNCTION__, "max30100_get_high_res", result); + return enabled; +} + +void MAX30100::sample_rate(MAX30100_SR sample_rate) +{ + upm_result_t result = max30100_set_sample_rate(_dev, sample_rate); + if (result != UPM_SUCCESS) + max30100_throw(__FUNCTION__, "max30100_set_sample_rate", result); +} + +MAX30100_SR MAX30100::sample_rate() +{ + MAX30100_SR sample_rate; + upm_result_t result = max30100_get_sample_rate(_dev, &sample_rate); + if (result != UPM_SUCCESS) + max30100_throw(__FUNCTION__, "max30100_get_sample_rate", result); + return sample_rate; +} + +void MAX30100::pulse_width(MAX30100_LED_PW pulse_width) +{ + upm_result_t result = max30100_set_pulse_width(_dev, pulse_width); + if (result != UPM_SUCCESS) + max30100_throw(__FUNCTION__, "max30100_set_pulse_width", result); +} + +MAX30100_LED_PW MAX30100::pulse_width() +{ + MAX30100_LED_PW pulse_width; + upm_result_t result = max30100_get_pulse_width(_dev, &pulse_width); + if (result != UPM_SUCCESS) + max30100_throw(__FUNCTION__, "max30100_get_pulse_width", result); + return pulse_width; +} + +void MAX30100::current(MAX30100_LED_CURRENT ir, MAX30100_LED_CURRENT r) +{ + upm_result_t result = max30100_set_current(_dev, ir, r); + if (result != UPM_SUCCESS) + max30100_throw(__FUNCTION__, "set_current", result); +} + +MAX30100_LED_CURRENT MAX30100::current_ir() +{ + MAX30100_LED_CURRENT ir, r; + upm_result_t result = max30100_get_current(_dev, &ir, &r); + if (result != UPM_SUCCESS) + max30100_throw(__FUNCTION__, "get_current_ir", result); + return ir; +} + +MAX30100_LED_CURRENT MAX30100::current_r() +{ + MAX30100_LED_CURRENT ir, r; + upm_result_t result = max30100_get_current(_dev, &ir, &r); + if (result != UPM_SUCCESS) + max30100_throw(__FUNCTION__, "get_current_r", result); + return r; +} + +void MAX30100::reset() +{ + upm_result_t result = max30100_reset(_dev); + if (result != UPM_SUCCESS) + max30100_throw(__FUNCTION__, "max30100_reset", result); +} + +uint8_t MAX30100::read(MAX30100_REG reg) +{ + uint8_t retval; + upm_result_t result = max30100_read(_dev, reg, &retval); + if (result != UPM_SUCCESS) + max30100_throw(__FUNCTION__, "max30100_read", result); + return retval; +} + +void MAX30100::write(MAX30100_REG reg, uint8_t value) +{ + upm_result_t result = max30100_write(_dev, reg, value); + if (result != UPM_SUCCESS) + max30100_throw(__FUNCTION__, "max30100_write", result); +} + +void MAX30100::sleep(bool sleep) +{ + upm_result_t result = max30100_sleep(_dev, sleep); + if (result != UPM_SUCCESS) + max30100_throw(__FUNCTION__, "max30100_sleep", result); +} diff --git a/src/max30100/max30100.h b/src/max30100/max30100.h new file mode 100644 index 00000000..8f5fad29 --- /dev/null +++ b/src/max30100/max30100.h @@ -0,0 +1,318 @@ +/* + * Author: Noel Eck + * Copyright (c) 2015 Intel Corporation. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#pragma once + +#include +#include + +#include "max30100_regs.h" + +#include "mraa/gpio.h" +#include "mraa/i2c.h" +#include "upm.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @file max30100.h + * @library max30100 + * @brief C API for the Pulse oximeter and heart-rate sensor. + * + * @include max30100.c + */ + +/** + * device context + */ +typedef struct { + /* mraa I2C context */ + mraa_i2c_context _i2c_context; + + /* mraa gpio context (for interrupt) */ + mraa_gpio_context _gpio_context; + + /* Sensor acquire mode */ + MAX30100_SAMPLE_STATE sample_state; + + /* Continuous sampling function ptr */ + func_sample_ready_handler func_sample_ready; + + /* Optional void ptr arg returned from callback */ + void* arg; +} max30100_context; + +/** + * Initialize sensor. Note, the MAX30100 I2C address is set to 0x57. + * + * @param i2c_bus Target I2C bus + * @return sensor context pointer + */ +max30100_context* max30100_init(int16_t i2c_bus); + +/** + * Sensor close method. + * + * Cleans up any memory held by this device + * @param dev Sensor context pointer + */ +void max30100_close(max30100_context* dev); + +/** + * Sample a single set of infrared/red values + * + * Note, all setup (sample rate, LED current, and pulse width must be done + * prior to calling this sample method. + * + * @param dev Sensor context pointer + * @param samp IR/R values are returned in this structure + * @return Function result code + */ +upm_result_t max30100_sample(max30100_context* dev, max30100_value *samp); + +/** + * Continuously sample Infrared/Red values. + * + * This method requires a GPIO pin which is used to signal + * sample/samples ready. The INT * pin is open-drain and requires a + * pullup resistor. The interrupt pin is not designed to sink large + * currents, so the pull-up resistor value should be large, such as + * 4.7k ohm. The RCWL-0530 PCB which this library was designed with + * had the I2C lines and INT pin pulled up to 1.8v. + * + * Note, all setup (sample rate, mode, LED current, and pulse width + * must be done prior to calling this sample method. + * + * @param dev Sensor context pointer + * @param gpio_pin GPIO pin used for interrupt (input from sensor INT pin) + * @param buffered Enable buffered sampling. In buffered sampling mode, the + * device reads 16 samples at a time. This can help with I2C read timing. + * buffered == true, enable buffered sampling + * buffered == false, single-sample mode + * @param isr Function pointer which handles 1 IR/R sample and a void ptr arg + * @param arg Void * passed back with ISR call + * @return Function result code + */ +upm_result_t max30100_sample_continuous(max30100_context* dev, + int gpio_pin, + bool buffered, + func_sample_ready_handler isr, + void* arg); + +/** + * Stop continuous sampling. Disable interrupts. + * + * @param dev Sensor context pointer + * @return Function result code + */ +upm_result_t max30100_sample_stop(max30100_context* dev); + +/** + * Read Oximeter and heart-rate sensor register + * + * @param dev Sensor context pointer + * @param reg Target register + * @param rd_data Data from sensor + * @return Function result code + */ +upm_result_t max30100_read(const max30100_context* dev, MAX30100_REG reg, uint8_t* rd_data); + +/** + * Write Oximeter and heart-rate sensor register + * + * @param dev Sensor context pointer + * @param reg Target register to write + * @param wr_data Target data to write + * @return Function result code + */ +upm_result_t max30100_write(const max30100_context* dev, MAX30100_REG reg, uint8_t wr_data); + +/** + * Read modify write Oximeter and heart-rate sensor register + * + * @param dev Sensor context pointer + * @param reg Target register + * @param value Target bits to set/clear + * @param mask Specify the bits to set/clear + * If mask = 0xf0, read full byte, modify only the upper 4 bits + * If mask = 0xaa, read full byte, modify every other bit + * @return Function result code + */ +upm_result_t max30100_rd_mod_wr(const max30100_context* dev, + MAX30100_REG reg, uint8_t value, uint8_t mask); + +/** + * Get sensor version + * Sensor version is a 2 byte value: + * upper byte = PART ID + * lower byte = REVISION ID + * + * example: + * version() return 0x1105 + * 0x11 = PART ID + * 0x05 = REVISION + * + * @param dev Sensor context pointer + * @param rd_data Sensor version + * @return Function result code + */ +upm_result_t max30100_get_version(const max30100_context* dev, uint16_t* version); + +/** + * Get temperature reading from device + * @param dev Sensor context pointer + * @param rd_data Temperature in degrees Celsius + * @return Function result code + */ +upm_result_t max30100_get_temperature(const max30100_context* dev, float* temperature); + +/** + * Set the sampling mode (none vs red only vs SpO2) + * + * @param dev Sensor context pointer + * @param mode Sensor mode value to write into the mode configuration register + * @return Function result code + */ +upm_result_t max30100_set_mode(const max30100_context* dev, MAX30100_MODE mode); + +/** + * Get the mode field from the mode configuration register + * + * @param dev Sensor context pointer + * @param mode Sensor mode value read from mode configuration register + * @return Function result code + */ +upm_result_t max30100_get_mode(const max30100_context* dev, MAX30100_MODE* mode); + +/** + * Set the high-res field in the SpO2 configuration register + * + * @param dev Sensor context pointer + * @param high_res Sensor high-res value to write into the SpO2 configuration register + * @return Function result code + */ +upm_result_t max30100_set_high_res(const max30100_context* dev, bool high_res); + +/** + * Get the high-res field from the SpO2 configuration register + * + * @param dev Sensor context pointer + * @param high_res Sensor high_res value read from the SpO2 configuration register + * @return Function result code + */ +upm_result_t max30100_get_high_res(const max30100_context* dev, bool* high_res); + +/** + * Set the sample rate field in the SpO2 configuration register + * + * @param dev Sensor context pointer + * @param sample_rate Sensor sample rate value to write into the SpO2 configuration register + * @return Function result code + */ +upm_result_t max30100_set_sample_rate(const max30100_context* dev, MAX30100_SR sample_rate); + +/** + * Get the sample rate field from the SpO2 configuration register + * + * @param dev Sensor context pointer + * @param sample_rate Sensor sample rate value read from the SpO2 configuration register + * @return Function result code + */ +upm_result_t max30100_get_sample_rate(const max30100_context* dev, MAX30100_SR* sample_rate); + +/** + * Set the pulse width field in the SpO2 configuration register + * + * @param dev Sensor context pointer + * @param pulse_width Sensor pulse width value to write into the SpO2 configuration register + * @return Function result code + */ +upm_result_t max30100_set_pulse_width(const max30100_context* dev, MAX30100_LED_PW pulse_width); + +/** + * Get the pulse width field from the SpO2 configuration register + * + * @param dev Sensor context pointer + * @param pulse_width Sensor pulse width value read from the SpO2 configuration register + * @return Function result code + */ +upm_result_t max30100_get_pulse_width(const max30100_context* dev, MAX30100_LED_PW* pulse_width); + +/** + * Set the LED current + * + * @param dev Sensor context pointer + * @param ir Infrared LED current enum + * @param r Red LED current enum + * @return Function result code + */ +upm_result_t max30100_set_current(const max30100_context* dev, + MAX30100_LED_CURRENT ir, + MAX30100_LED_CURRENT r); + +/** + * Get the LED current + * + * @param dev Sensor context pointer + * @param ir Infrared LED current read from the LED configuration register + * @param r Red LED current read from the LED configuration register + * @return Function result code + */ +upm_result_t max30100_get_current(const max30100_context* dev, + MAX30100_LED_CURRENT* ir, + MAX30100_LED_CURRENT* r); + +/** + * Reset sensor + * + * When the RESET bit is set to one, all configuration, threshold, + * and data registers are reset to their power-on-state. The only + * exception is writing both RESET and TEMP_EN bits to one at the + * same time since temperature data registers 0x16 and 0x17 are not + * cleared. The RESET bit is cleared automatically back to zero after + * the reset sequence is completed. + * + * @param dev Sensor context pointer + * @return Function result code + */ +upm_result_t max30100_reset(const max30100_context* dev); + +/** + * Put device into power-save mode. While in power-save mode, all + * registers retain their values, and write/read operations function + * as normal. All interrupts are cleared to zero in this mode. + * + * @param dev Sensor context pointer + * @param sleep Enter/exit power-save mode + * true = Enter power-save mode + * false = Exit power-save mode + * @return Function result code + */ +upm_result_t max30100_sleep(const max30100_context* dev, bool sleep); + +#ifdef __cplusplus +} +#endif diff --git a/src/max30100/max30100.hpp b/src/max30100/max30100.hpp new file mode 100644 index 00000000..806f67d6 --- /dev/null +++ b/src/max30100/max30100.hpp @@ -0,0 +1,295 @@ +/* + * Author: Noel Eck + * Copyright (c) 2015 Intel Corporation. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#pragma once + +#include + +#include "mraa/i2c.h" +#include "max30100.h" + +namespace upm { + +/* Callback class for continuously reading samples */ +class Callback { + public: + virtual ~Callback() { } + /* Default run method, called for each new sample in continous + * sampling mode. + * Override this method */ + virtual void run(max30100_value samp) + { std::cout << "Base sample IR: " << samp.IR << " R: " << samp.R << std::endl; } +}; + +/** + * @brief Pulse oximeter and heart-rate sensor + * @defgroup max30100 libupm-max30100 + * @ingroup maxim i2c medical + */ + +/** + * @library max30100 + * @sensor max30100 + * @comname Pulse Oximeter and Heart-rate Sensor + * @type medical + * @man maxim + * @web https://www.maximintegrated.com/en/products/analog/sensors-and-sensor-interface/MAX30100.html + * @con i2c gpio + * + * @brief API for the Pulse oximeter and heart-rate sensor + * + * The MAX30100 is an integrated pulse oximetry and heartrate monitor sensor + * solution. It combines two LEDs, a photodetector, optimized optics, and + * low-noise analog signal processing to detect pulse oximetry and heart-rate + * signals. + * + * I2C sensor which can be used to read: + * Heart-rate + * Peripheral capillary oxygen saturation + * temperature + * + * @image html max30100.png + * @snippet max30100.cxx Interesting + */ + +class MAX30100 { + public: + /** + * Oximeter and heart-rate sensor constructor + * + * Initialize Oximeter and heart-rate sensor. Note, the I2C address + * is 0x57. + * @param i2c_bus Target I2C bus + * @return sensor context pointer + * @throws std::runtime_error if sensor initializate fails + */ + MAX30100(int16_t i2c_bus); + + /** + * MAX30100 destructor + */ + virtual ~MAX30100() {}; + + /** + * Sample a single set of infrared/red values + * + * Note, all setup (sample rate, LED current, and pulse width must be done + * prior to calling this sample method. + * + * @return One IR/R sample + * @throws std::runtime_error on I2C command failure + */ + max30100_value sample(); + + /** + * Continuously sample Infrared/Red values. + * + * This method requires a GPIO pin which is used to signal + * sample/samples ready. The INT * pin is open-drain and requires a + * pullup resistor. The interrupt pin is not designed to sink large + * currents, so the pull-up resistor value should be large, such as + * 4.7k ohm. The RCWL-0530 PCB which this library was designed with + * had the I2C lines and INT pin pulled up to 1.8v. + * + * Note, all setup (sample rate, mode, LED current, and pulse width + * must be done prior to calling this sample method. + * + * @param gpio_pin GPIO pin for interrupt (input from sensor INT pin) + * @param buffered Enable buffered sampling. In buffered sampling mode, + * the device reads 16 samples at a time. This can help with I2C read + * timing. + * buffered == true, enable buffered sampling + * buffered == false, single-sample mode + * @param cb Pointer to instance of Callback class. If parameter is left + * NULL, a default instance of the Callback class will be used which + * prints out the IR/R values. + * @throws std::runtime_error on I2C command failure + */ + void sample_continuous(int gpio_pin, bool buffered, Callback *cb = NULL); + + /** + * Stop continuous sampling. Disable interrupts. + */ + void sample_stop(); + + /** + * Read Oximeter and heart-rate sensor registers + * @param reg Target register to read + * @return Data returned from sensor + * @throws std::runtime_error if I2C read command fails + */ + uint8_t read(MAX30100_REG reg); + + /** + * Write Oximeter and heart-rate sensor registers + * @param reg Target register to write + * @param wr_data Target data to write + * @throws std::runtime_error if I2C write command fails + */ + void write(MAX30100_REG reg, uint8_t wr_data); + + /** + * Get sensor version + * Sensor version is a 2 byte value: + * upper byte = PART ID + * lower byte = REVISION ID + * + * example: + * version() return 0x1105 + * 0x11 = PART ID + * 0x05 = REVISION + * @return Sensor version + * @throws std::runtime_error on I2C command failure + */ + uint16_t version(); + + /** + * Get temperature reading from device + * @return rd_data Temperature in degrees Celsius + * @throws std::runtime_error on I2C command failure + */ + float temperature(); + + /** + * Set the sampling mode (none vs red only vs SpO2) + * + * @param mode Target sampling mode + * @throws std::runtime_error on I2C command failure + */ + void mode(MAX30100_MODE mode); + + /** + * Get the sampling mode + * + * @return Current sampling mode + * @throws std::runtime_error on I2C command failure + */ + MAX30100_MODE mode(); + + /** + * Enable or disable high-resolution mode + * + * @param enable High-resolution enable + * true == SpO2 ADC resolution of 16 bit with 1.6ms LED pw + * @throws std::runtime_error on I2C command failure + */ + void high_res_enable(bool enable); + + /** + * Get the high-resolution enable bit + * + * @return Current high-resolution bit value + * @throws std::runtime_error on I2C command failure + */ + bool high_res_enable(); + + /** + * Set the sample rate + * + * @param sample_rate Target sample rate + * @throws std::runtime_error on I2C command failure + */ + void sample_rate(MAX30100_SR sample_rate); + + /** + * Get the sample rate + * + * @return Current sample rate + * @throws std::runtime_error on I2C command failure + */ + MAX30100_SR sample_rate(); + + /** + * Set the LED pulse width + * + * @param pulse_width Target LED pulse width + * @throws std::runtime_error on I2C command failure + */ + void pulse_width(MAX30100_LED_PW pulse_width); + + /** + * Get the LED pulse width + * + * @return Current LED pulse width + * @throws std::runtime_error on I2C command failure + */ + MAX30100_LED_PW pulse_width(); + + /** + * Set the current for the infrared and red LEDs + * + * @param ir LED current enum + * @param r LED current enum + * @throws std::runtime_error on I2C command failure + */ + void current(MAX30100_LED_CURRENT ir, MAX30100_LED_CURRENT r); + + /** + * Get the infrared LED current + * + * @throws std::runtime_error on I2C command failure + */ + MAX30100_LED_CURRENT current_ir(); + + /** + * Get the red LED current + * + * @throws std::runtime_error on I2C command failure + */ + MAX30100_LED_CURRENT current_r(); + + /** + * Reset sensor + * + * When the RESET bit is set to one, all configuration, threshold, + * and data registers are reset to their power-on-state. The only + * exception is writing both RESET and TEMP_EN bits to one at the + * same time since temperature data registers 0x16 and 0x17 are not + * cleared. The RESET bit is cleared automatically back to zero after + * the reset sequence is completed. + * + * @throws std::runtime_error on I2C command failure + */ + void reset(); + + /** + * Put device into power-save mode. While in power-save mode, all + * registers retain their values, and write/read operations function + * as normal. All interrupts are cleared to zero in this mode. + * + * @param sleep Enter/exit power-save mode + * @throws std::runtime_error on I2C command failure + */ + void sleep(bool sleep); + + /* Callback pointer available for a user-specified callback */ + Callback *_callback; + private: + /* base Callback instance to use if none provided */ + Callback _default_callback; + + /* device context struct */ + max30100_context* _dev; +}; +} diff --git a/src/max30100/max30100_fti.c b/src/max30100/max30100_fti.c new file mode 100644 index 00000000..4bdf4edd --- /dev/null +++ b/src/max30100/max30100_fti.c @@ -0,0 +1,109 @@ +/* + * Author: Noel Eck + * Copyright (c) 2015 Intel Corporation. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include +#include + +#include "max30100.h" +#include "upm_fti.h" +#include "fti/upm_sensor.h" + +/** + * This file implements the Function Table Interface (FTI) for this sensor + */ + +const char upm_max30100_name[] = "MAX30100"; +const char upm_max30100_description[] = "Pulse oximeter and heart-rate sensor"; +const upm_protocol_t upm_max30100_protocol[] = {UPM_I2C}; +/* TODO: Add/implement heart rate and SpO2 categories */ +const upm_sensor_t upm_max30100_category[] = {UPM_TEMPERATURE}; + +// forward declarations +const void* upm_max30100_get_ft(upm_sensor_t sensor_type); +void* upm_max30100_init_str(const char* protocol, const char* params); +void upm_max30100_close(void* dev); +const upm_sensor_descriptor_t upm_max30100_get_descriptor(); +upm_result_t upm_max30100_get_temperature(void* dev, float *value, upm_temperature_u unit); + +/* This sensor implementes 2 function tables */ +/* 1. Generic base function table */ +static const upm_sensor_ft ft_gen = +{ + .upm_sensor_init_name = &upm_max30100_init_str, + .upm_sensor_close = &upm_max30100_close, + .upm_sensor_get_descriptor = &upm_max30100_get_descriptor +}; + +/* 2. Temperatur function table */ +static const upm_temperature_ft ft_temperature = +{ + .upm_temperature_set_offset = NULL, + .upm_temperature_set_scale = NULL, + .upm_temperature_get_value = &upm_max30100_get_temperature +}; + +const void* upm_max30100_get_ft(upm_sensor_t sensor_type) +{ + switch(sensor_type) + { + case UPM_SENSOR: + return &ft_gen; + case UPM_TEMPERATURE: + return &ft_temperature; + default: + return NULL; + } +} + +void* upm_max30100_init_str(const char* protocol, const char* params) +{ + fprintf(stderr, + "String initialization - not implemented, using i2c bus 0: %s\n", __FILENAME__); + return max30100_init(0); +} + +void upm_max30100_close(void* dev) +{ + max30100_close(dev); +} + +const upm_sensor_descriptor_t upm_max30100_get_descriptor() +{ + /* Fill in the descriptor */ + upm_sensor_descriptor_t usd; + usd.name = upm_max30100_name; + usd.description = upm_max30100_description; + usd.protocol_size = 1; + usd.protocol = upm_max30100_protocol; + usd.category_size = 2; + usd.category = upm_max30100_category; + + return usd; +} + +upm_result_t upm_max30100_get_temperature(void* dev, float *value, upm_temperature_u unit) +{ + upm_result_t result = max30100_get_temperature((max30100_context*)dev, value); + return result; +} diff --git a/src/max30100/max30100_regs.h b/src/max30100/max30100_regs.h new file mode 100644 index 00000000..6bf35f23 --- /dev/null +++ b/src/max30100/max30100_regs.h @@ -0,0 +1,170 @@ +/* + * Author: Noel Eck + * Copyright (c) 2015 Intel Corporation. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define MAX30100_I2C_ADDRESS 0x57 + +/* Single IR/R sample */ +typedef struct { + /* Raw IR (pulse) read value */ + uint16_t IR; + /* Raw R (O2) read value */ + uint16_t R; +} max30100_value; + +/* Function pointer for returning 1 IR/R sample */ +typedef void (*func_sample_ready_handler)(max30100_value sample, void* arg); + +/* Sample state */ +typedef enum { + /* NOT sampling */ + MAX30100_SAMPLE_STATE_IDLE, + /* Take one sample/currently taking one sample */ + MAX30100_SAMPLE_STATE_ONE_SHOT, + /* Sample continuously/currently sampling continuously */ + MAX30100_SAMPLE_STATE_CONTINUOUS, + /* Sample continuously using buffer/currently sampling continuously using buffer*/ + MAX30100_SAMPLE_STATE_CONTINUOUS_BUFFERED +} MAX30100_SAMPLE_STATE; + +/* Pulse oximeter and heart-rate sensor I2C registers */ +typedef enum { + /* Interrupt status (RO) */ + MAX30100_REG_INTERRUPT_STATUS = 0x00, + /* Interrupt enable */ + MAX30100_REG_INTERRUPT_ENABLE = 0x01, + /* FIFO write pointer */ + MAX30100_REG_FIFO_WR_PTR = 0x02, + /* FIFO overflow counter */ + MAX30100_REG_FIFO_OVF_COUNTER = 0x03, + /* FIFO read pointer */ + MAX30100_REG_FIFO_RD_PTR = 0x04, + /* FIFO data */ + MAX30100_REG_FIFO_DATA = 0x05, + /* Mode configuration */ + MAX30100_REG_MODE_CONFIG = 0x06, + /* SPO2 configuration */ + MAX30100_REG_SPO2_CONFIG = 0x07, + /* LED configuration */ + MAX30100_REG_LED_CONFIG = 0x09, + /* Temperature integer (2's compliment) */ + MAX30100_REG_TEMP_INTEGER = 0x16, + /* Temperature fraction) */ + MAX30100_REG_TEMP_FRACTION = 0x17, + /* Revision ID (RO)*/ + MAX30100_REG_REV_ID = 0xFE, + /* Part ID */ + MAX30100_REG_PART_ID = 0xFF +} MAX30100_REG; + +/* MAX30100_REG_INTERRUPT_STATUS register fields */ +/* FIFO almost full, set to 1 when WR_PTR == RD_PTR - 1 */ +#define MAX30100_A_FULL (1 << 7) +/* Temperature date ready flag */ +#define MAX30100_TEMP_RDY (1 << 6) +/* Heartrate data ready flag */ +#define MAX30100_HR_RDY (1 << 5) +/* HR and O2 data ready flag */ +#define MAX30100_SPO2_RDY (1 << 4) +/* Power ready after brownout flag */ +#define MAX30100_PWR_RDY (1 << 0) + +/* MAX30100_REG_INTERRUPT_ENABLE register fields */ +/* Enable interrupt on FIFO almost full */ +#define MAX30100_EN_A_FULL (1 << 7) +/* Enable interrupt on temperature date ready */ +#define MAX30100_EN_TEMP_RDY (1 << 6) +/* Enable interrupt on HR data ready */ +#define MAX30100_EN_HR_RDY (1 << 5) +/* Enable interrupt on HR and O2 data ready */ +#define MAX30100_EN_SPO2_RDY (1 << 4) + +/* MAX30100_REG_MODE_CONFIG register fields */ +/* Enable power-save mode */ +#define MAX30100_SHDN (1 << 7) +/* Reset device */ +#define MAX30100_RESET (1 << 6) +/* Initiate temperature reading */ +#define MAX30100_TEMP_EN (1 << 3) +/* Device sample mode (HR, vs SpO2) */ +typedef enum _MAX30100_MODE { +/* Turn off sampling */ + MAX30100_MODE_DISABLED = 0x00, +/* Enable heartrate ONLY sampling */ + MAX30100_MODE_HR_EN = 0x02, +/* Enable SpO2 sampling */ + MAX30100_MODE_SPO2_EN = 0x03 +} MAX30100_MODE; + +/* MAX30100_REG_SPO2_CONFIG register fields */ +#define MAX30100_SPO2_HI_RES_EN (1 << 6) +typedef enum _MAX30100_SR { + MAX30100_SR_50_HZ = 0x00, + MAX30100_SR_100_HZ = 0x01, + MAX30100_SR_167_HZ = 0x02, + MAX30100_SR_200_HZ = 0x03, + MAX30100_SR_400_HZ = 0x04, + MAX30100_SR_600_HZ = 0x05, + MAX30100_SR_900_HZ = 0x06, + MAX30100_SR_1000_HZ = 0x07 +} MAX30100_SR; +/* LED pulse width (microseconds) */ +typedef enum _MAX30100_LED_PW { + MAX30100_LED_PW_200_US_13_BITS = 0x00, + MAX30100_LED_PW_400_US_14_BITS = 0x01, + MAX30100_LED_PW_800_US_15_BITS = 0x02, + MAX30100_LED_PW_1600_US_16_BITS = 0x03 +} MAX30100_LED_PW; + +/* MAX30100_REG_LED_CONFIG register fields */ +/* LED (IR and R) current (milliamps) */ +typedef enum _MAX30100_LED_CURRENT { + MAX30100_LED_CURRENT_0_0_MA = 0x00, + MAX30100_LED_CURRENT_4_4_MA = 0x01, + MAX30100_LED_CURRENT_7_6_MA = 0x02, + MAX30100_LED_CURRENT_11_0_MA = 0x03, + MAX30100_LED_CURRENT_14_2_MA = 0x04, + MAX30100_LED_CURRENT_17_4_MA = 0x05, + MAX30100_LED_CURRENT_20_8_MA = 0x06, + MAX30100_LED_CURRENT_24_0_MA = 0x07, + MAX30100_LED_CURRENT_27_1_MA = 0x08, + MAX30100_LED_CURRENT_30_6_MA = 0x09, + MAX30100_LED_CURRENT_33_8_MA = 0x0a, + MAX30100_LED_CURRENT_37_0_MA = 0x0b, + MAX30100_LED_CURRENT_40_2_MA = 0x0c, + MAX30100_LED_CURRENT_43_6_MA = 0x0d, + MAX30100_LED_CURRENT_46_8_MA = 0x0e, + MAX30100_LED_CURRENT_50_0_MA = 0x0f +} MAX30100_LED_CURRENT; + +#ifdef __cplusplus +} +#endif diff --git a/src/max30100/pyupm_max30100.i b/src/max30100/pyupm_max30100.i new file mode 100644 index 00000000..8d5bef55 --- /dev/null +++ b/src/max30100/pyupm_max30100.i @@ -0,0 +1,14 @@ +// Include doxygen-generated documentation +%module(directors="1", threads="1") pyupm_max30100 +%include "pyupm_doxy2swig.i" +%include "../upm.i" + +%feature("autodoc", "3"); + +%{ + #include "max30100.hpp" +%} + +%feature("director") upm::Callback; +%include "max30100_regs.h" +%include "max30100.hpp"