diff --git a/src/W800_SDK_v1.00.10/app/app_drivers/i2c/MAX3010X.c b/src/W800_SDK_v1.00.10/app/app_drivers/i2c/MAX3010X.c index 6c25764..6379b66 100644 --- a/src/W800_SDK_v1.00.10/app/app_drivers/i2c/MAX3010X.c +++ b/src/W800_SDK_v1.00.10/app/app_drivers/i2c/MAX3010X.c @@ -1,6 +1,7 @@ #include "i2c.h" #include "MAX3010X.h" #include "app_utils.h" +#include #include "app_log.h" /* Device i2c address */ @@ -47,9 +48,28 @@ /* Other */ #define MAX3010X_PART_ID 0x15 +#define MAX3010X_MAX_FIFO_SIZE (288) + +#define STORAGE_SIZE (4) +typedef struct sense_data +{ + uint32_t red[STORAGE_SIZE]; + uint32_t IR[STORAGE_SIZE]; + uint32_t green[STORAGE_SIZE]; + uint8_t head, tail; +} sense_data_t; + +static sense_data_t _sense; +static uint8_t _active_LEDs; + + bool MAX3010X_init(void) { uint8_t readPartId = 0; + + memset(&_sense, 0, sizeof _sense); + _active_LEDs = 0; + if(!i2c_read_reg(MAX3010X_I2C_ADDR, MAX3010X_PART_ID_REG, &readPartId)) return false; return readPartId == MAX3010X_PART_ID; @@ -243,37 +263,50 @@ bool MAX3010X_configure(uint8_t powerLevel, MAX3010X_Sample_Average_e sampleAver APP_LOG_DEBUG("%d", __LINE__); return false; } - + ms_delay(1); if(!MAX3010X_set_FIFO_average(sampleAverage)) { APP_LOG_DEBUG("%d", __LINE__); return false; } - + ms_delay(1); if(!MAX3010X_enable_FIFO_rollover(true)) { APP_LOG_DEBUG("%d", __LINE__); return false; } - + ms_delay(1); if(!MAX3010X_set_LED_mode(LEDMode)) { APP_LOG_DEBUG("%d", __LINE__); return false; } - + else + { + switch(LEDMode) + { + case MAX3010X_LED_MODE_RED_ONLY: + _active_LEDs = 1; + break; + case MAX3010X_LED_MODE_RED_AND_IR: + case MAX3010X_LED_MODE_MULTI_LED: + _active_LEDs = 2; + break; + } + } + ms_delay(1); if(!MAX3010X_set_ADC_range(ADCRange)) { APP_LOG_DEBUG("%d", __LINE__); return false; } - + ms_delay(1); if(!MAX3010X_set_sample_rate(sampleRate)) { APP_LOG_DEBUG("%d", __LINE__); return false; } - + ms_delay(1); if(!MAX3010X_set_pulse_width(LEDPulseWidth)) { APP_LOG_DEBUG("%d", __LINE__); @@ -349,3 +382,217 @@ float MAX3010X_read_temperature(void) return (float) temp + ((float) data * 0.0625); } + +uint32_t MAX3010X_get_Red(void) +{ + if(MAX3010X_safe_check(250)) + return (_sense.red[_sense.head]); + return 0; +} + +uint32_t MAX3010X_get_IR(void) +{ + if(MAX3010X_safe_check(250)) + return (_sense.IR[_sense.head]); + return 0; +} + +bool MAX3010X_safe_check(uint8_t timeout) +{ + do + { + if(MAX3010X_check()) return true; + ms_delay(1); + } while (timeout --> 0); + + return false; +} + +uint16_t MAX3010X_check(void) +{ + uint8_t read_ptr = MAX3010X_get_read_ptr(), write_ptr = MAX3010X_get_write_ptr(); + + int sample_count = 0; + + if(read_ptr != write_ptr) + { + sample_count = write_ptr - read_ptr; + if(sample_count < 0) sample_count += 32; + + size_t bytes_to_read = sample_count * _active_LEDs * 3; + size_t index = 0; + + if(bytes_to_read > MAX3010X_MAX_FIFO_SIZE) + { + bytes_to_read = MAX3010X_MAX_FIFO_SIZE; + } + + uint8_t fifo_data[MAX3010X_MAX_FIFO_SIZE] = {0}; + + if(!i2c_read(MAX3010X_I2C_ADDR, MAX3010X_FIFO_DATA_REG, fifo_data, bytes_to_read)) return 0; + + while(index < bytes_to_read) + { + _sense.head++; + _sense.head %= STORAGE_SIZE; + + uint8_t bytes_word[sizeof(uint32_t)]; + + bytes_word[3] = 0; + bytes_word[2] = fifo_data[index]; + bytes_word[1] = fifo_data[index + 1]; + bytes_word[0] = fifo_data[index + 2]; + + memcpy(&_sense.red[_sense.head], bytes_word, sizeof bytes_word); + _sense.red[_sense.head] &= 0x3FFFF; + + if(_active_LEDs > 1) + { + bytes_word[3] = 0; + bytes_word[2] = fifo_data[index + 3]; + bytes_word[1] = fifo_data[index + 4]; + bytes_word[0] = fifo_data[index + 5]; + + memcpy(&_sense.IR[_sense.head], bytes_word, sizeof bytes_word); + _sense.IR[_sense.head] &= 0x3FFFF; + } + + if(_active_LEDs > 2) + { + bytes_word[3] = 0; + bytes_word[2] = fifo_data[index + 6]; + bytes_word[1] = fifo_data[index + 7]; + bytes_word[0] = fifo_data[index + 8]; + + memcpy(&_sense.green[_sense.head], bytes_word, sizeof bytes_word); + _sense.green[_sense.head] &= 0x3FFFF; + } + + index += _active_LEDs * 3; + } + } + + return (uint16_t) sample_count; +} + +uint8_t MAX3010X_get_read_ptr(void) +{ + uint8_t data; + if(!i2c_read_reg(MAX3010X_I2C_ADDR, MAX3010X_FIFO_RD_PTR_REG, &data)) return 0; + + return data; +} + +uint8_t MAX3010X_get_write_ptr(void) +{ + uint8_t data; + if(!i2c_read_reg(MAX3010X_I2C_ADDR, MAX3010X_FIFO_WR_PTR_REG, &data)) return 0; + + return data; +} + +static int16_t IR_AC_Max = 20; +static int16_t IR_AC_Min = -20; + +static int16_t IR_AC_Signal_Current = 0; +static int16_t IR_AC_Signal_Previous; +static int16_t IR_AC_Signal_min = 0; +static int16_t IR_AC_Signal_max = 0; +static int16_t IR_Average_Estimated; + +static int16_t positiveEdge = 0; +static int16_t negativeEdge = 0; +static int32_t ir_avg_reg = 0; + +static int16_t cbuf[32]; +static uint8_t offset = 0; + +static const uint16_t FIRCoeffs[12] = {172, 321, 579, 927, 1360, 1858, 2390, 2916, 3391, 3768, 4012, 4096}; + +static int16_t averageDCEstimator(int32_t *p, uint16_t x); +static int16_t lowPassFIRFilter(int16_t din); +static int32_t mul16(int16_t x, int16_t y); + +bool MAX3010X_check_for_beat(int32_t sample) +{ + bool beatDetected = false; + + // Save current state + IR_AC_Signal_Previous = IR_AC_Signal_Current; + + // Process next data sample + IR_Average_Estimated = averageDCEstimator(&ir_avg_reg, sample); + IR_AC_Signal_Current = lowPassFIRFilter(sample - IR_Average_Estimated); + + // Detect positive zero crossing (rising edge) + if((IR_AC_Signal_Previous < 0) & (IR_AC_Signal_Current >= 0)) + { + + IR_AC_Max = IR_AC_Signal_max; //Adjust our AC max and min + IR_AC_Min = IR_AC_Signal_min; + + positiveEdge = 1; + negativeEdge = 0; + IR_AC_Signal_max = 0; + + //if ((IR_AC_Max - IR_AC_Min) > 100 & (IR_AC_Max - IR_AC_Min) < 1000) + if (((IR_AC_Max - IR_AC_Min) > 20) & ((IR_AC_Max - IR_AC_Min) < 1000)) + { + //Heart beat!!! + beatDetected = true; + } + } + + // Detect negative zero crossing (falling edge) + if((IR_AC_Signal_Previous > 0) & (IR_AC_Signal_Current <= 0)) + { + positiveEdge = 0; + negativeEdge = 1; + IR_AC_Signal_min = 0; + } + + // Find Maximum value in positive cycle + if(positiveEdge & (IR_AC_Signal_Current > IR_AC_Signal_Previous)) + { + IR_AC_Signal_max = IR_AC_Signal_Current; + } + + // Find Minimum value in negative cycle + if(negativeEdge & (IR_AC_Signal_Current < IR_AC_Signal_Previous)) + { + IR_AC_Signal_min = IR_AC_Signal_Current; + } + + return beatDetected; +} + +// Average DC Estimator +static int16_t averageDCEstimator(int32_t *p, uint16_t x) +{ + *p += ((((long) x << 15) - *p) >> 4); + return (*p >> 15); +} + +// Low Pass FIR Filter +static int16_t lowPassFIRFilter(int16_t din) +{ + cbuf[offset] = din; + + int32_t z = mul16(FIRCoeffs[11], cbuf[(offset - 11) & 0x1F]); + + for (uint8_t i = 0 ; i < 11 ; i++) + { + z += mul16(FIRCoeffs[i], cbuf[(offset - i) & 0x1F] + cbuf[(offset - 22 + i) & 0x1F]); + } + + offset++; + offset %= 32; //Wrap condition + + return(z >> 15); +} + +// Integer multiplier +static int32_t mul16(int16_t x, int16_t y) +{ + return((long)x * (long)y); +} diff --git a/src/W800_SDK_v1.00.10/app/app_drivers/i2c/MAX3010X.h b/src/W800_SDK_v1.00.10/app/app_drivers/i2c/MAX3010X.h index bea3760..cbc047f 100644 --- a/src/W800_SDK_v1.00.10/app/app_drivers/i2c/MAX3010X.h +++ b/src/W800_SDK_v1.00.10/app/app_drivers/i2c/MAX3010X.h @@ -95,4 +95,18 @@ bool MAX3010X_enable_die_temperature_int(bool enable); float MAX3010X_read_temperature(void); +uint32_t MAX3010X_get_Red(void); + +uint32_t MAX3010X_get_IR(void); + +bool MAX3010X_safe_check(uint8_t timeout); + +uint16_t MAX3010X_check(void); + +uint8_t MAX3010X_get_read_ptr(void); + +uint8_t MAX3010X_get_write_ptr(void); + +bool MAX3010X_check_for_beat(int32_t sample); + #endif //MAX3010X_H \ No newline at end of file diff --git a/src/W800_SDK_v1.00.10/app/gfx/gfx_task.c b/src/W800_SDK_v1.00.10/app/gfx/gfx_task.c index e6fe413..017f0f4 100644 --- a/src/W800_SDK_v1.00.10/app/gfx/gfx_task.c +++ b/src/W800_SDK_v1.00.10/app/gfx/gfx_task.c @@ -490,6 +490,10 @@ void gfx_task(void *param) watch_peripherals_accelerometer_wrist_wakeup_enable(persistency_get_settings()->display.display_wrist_wakeup); watch_peripherals_accelerometer_step_counter_enable(true); + /*APP_LOG_DEBUG("MAX30102 init : %d", MAX3010X_init()); + APP_LOG_DEBUG("MAX30102 configure : %d", MAX3010X_configure(0x1F, MAX3010X_SAMPLE_AVERAGE_4, MAX3010X_LED_MODE_MULTI_LED, MAX3010X_SAMPLES_PER_SECONDS_400, MAX3010X_LED_PULSE_WIDTH_411, MAX3010X_ADC_RANGE_4096)); + APP_LOG_DEBUG("MAX3010X enable temp int %d", MAX3010X_enable_die_temperature_int(true));*/ + /* Make the first battery voltage reading here */ _battery_stats.battery_voltage = watch_peripherals_get_battery_voltage(battery_unit_mv); _battery_stats.battery_percentage = battery_voltage_to_percentage(_battery_stats.battery_voltage); @@ -680,13 +684,9 @@ void gfx_task(void *param) _battery_stats.battery_voltage, _battery_stats.battery_percentage); - APP_LOG_DEBUG("MAX30102 init : %d", MAX3010X_init()); - APP_LOG_DEBUG("MAX30102 configure : %d", MAX3010X_configure(0x00, MAX3010X_SAMPLE_AVERAGE_4, MAX3010X_LED_MODE_MULTI_LED, MAX3010X_SAMPLES_PER_SECONDS_400, MAX3010X_LED_PULSE_WIDTH_411, MAX3010X_ADC_RANGE_4096)); - APP_LOG_DEBUG("MAX3010X enable temp int %d", MAX3010X_enable_die_temperature_int(true)); - APP_LOG_DEBUG("MAX3010X temperature : %.2f", MAX3010X_read_temperature()); + //APP_LOG_DEBUG("MAX3010X temperature : %.2f", MAX3010X_read_temperature()); } - /* Handle any interrupts status */ if(_interrupts_statuses.battery_controller_status) {