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:
Th3maz1ng 2023-05-12 15:24:59 +02:00
parent 880dd9cb0e
commit 2f50aa50c6
3 changed files with 272 additions and 11 deletions

View File

@ -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);
}

View File

@ -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

View File

@ -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)
{