With the current driver state, it is possible to detect heart beats and thus compute the current heart rate. More things to come.
This commit is contained in:
parent
880dd9cb0e
commit
2f50aa50c6
@ -1,6 +1,7 @@
|
||||
#include "i2c.h"
|
||||
#include "MAX3010X.h"
|
||||
#include "app_utils.h"
|
||||
#include <string.h>
|
||||
#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);
|
||||
}
|
||||
|
@ -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
|
@ -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)
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user