/* * 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" #include "upm_utilities.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; } /* Initialize 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 restart sampling...\n", __FUNCTION__); /* Read FIFO AND status register in a last attempt to continue * sampling, ignore result */ max30100_read(dev, MAX30100_REG_FIFO_WR_PTR, &tmp); max30100_read(dev, MAX30100_REG_INTERRUPT_STATUS, &tmp); 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; }