Mario SorannoJamez_007Mia KRoberto Basili
Published © MIT

Ear-ly Detection of Respiratory Viral Illnesses

A hearable that can detect asymptomatic and presymptomatic viral illness to prevent transmission of disease

AdvancedProtip837
Ear-ly Detection of Respiratory Viral Illnesses

Things used in this project

Hardware components

nRF5340 Development Kit
Nordic Semiconductor nRF5340 Development Kit
×1
MAXREFDES117# Heart-Rate and Pulse-Oximetry Monitor Development Platform
Maxim Integrated MAXREFDES117# Heart-Rate and Pulse-Oximetry Monitor Development Platform
×1
MLX90632 sensor module
Medical accuracy | 1.8V I2C level
×1
Level Shifter Board
SparkFun Level Shifter Board
×1
nRF52840 Dongle
Nordic Semiconductor nRF52840 Dongle
×1
Nordic Semiconductor Power Profiler Kit II
×1

Software apps and online services

SAM Labs SEGGER Embedded Studio
nRF Connect SDK
Nordic Semiconductor nRF Connect SDK
Zephyr RTOS
Zephyr Project Zephyr RTOS

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)
Solder Wire, Lead Free
Solder Wire, Lead Free
PCB Holder, Soldering Iron
PCB Holder, Soldering Iron
3D Printer (generic)
3D Printer (generic)
Drill / Driver, Cordless
Drill / Driver, Cordless
Solder Paste, Silver Bearing
Solder Paste, Silver Bearing
Hot Air Station, Industrial
Hot Air Station, Industrial

Story

Read more

Custom parts and enclosures

PCB Gerber - micro IR Sensor

PCB Gerber - micro IR Sensor

micro IR Sensor

micro IR Sensor

Schematics

Development kit Connections

Development kit Connections

Electric Schematic

Electric Schematic

Code

main

C/C++
main.c
/*
 * Ear-ly Detection of Respiratory Viral Illnesses
 * Advanced Wearables
 */

#include <zephyr.h>
#include <device.h>
#include <drivers/gpio.h>
#include <sys/util.h>
#include <sys/printk.h>
#include <inttypes.h>
#include <drivers/i2c.h>
#include <math.h>

#define MLX90632_ADDRESS  0x3A

#define MLX90632_PRODUCT_CODE 0x2409
#define MLX90632_EE_P_R 0x240C
#define MLX90632_EE_P_G 0x240E
#define MLX90632_EE_P_T 0x2410
#define MLX90632_EE_P_O 0x2412
#define MLX90632_EE_Ea 0x2424
#define MLX90632_EE_Bb 0x2426
#define MLX90632_EE_Fa 0x2428
#define MLX90632_EE_Fb 0x242A
#define MLX90632_EE_Ga 0x242C
#define MLX90632_EE_Ka 0x242F
#define MLX90632_EE_Ha 0x2481
#define MLX90632_EE_Hb 0x2482
#define MLX90632_EE_Gb 0x242E
#define MLX90632_REG_CONTROL 0x3001
#define MLX90632_REG_STATUS 0x3FFF
#define MLX90632_RAM_4 0x4003
#define MLX90632_RAM_5 0x4004
#define MLX90632_RAM_6 0x4005
#define MLX90632_RAM_7 0x4006
#define MLX90632_RAM_8 0x4007
#define MLX90632_RAM_9 0x4008

#define MLX90632_MODE_SLEEP 0x01
#define MLX90632_MODE_CONTINUOUS 0x03

double MLX90632_P_R = 0;
double MLX90632_P_G = 0;
double MLX90632_P_T = 0;
double MLX90632_P_O = 0;
double MLX90632_Ea = 0;
double MLX90632_Eb = 0;
double MLX90632_Fa = 0;
double MLX90632_Fb = 0;
double MLX90632_Ga = 0;
double MLX90632_Gb = 0;
double MLX90632_Ka = 0;
double MLX90632_Ha = 0;
double MLX90632_Hb = 0;

double TOdut = 25.0;  //Assume 25C for first iteration
double TO0 = 25.0;    //object temp from previous calculation
double TA0 = 25.0;    //ambient temp from previous calculation
double objectT = 0;

#define MAX30102_ADDRESS  0x57

#define MAX30102_INT_STATUS_1 0x00
#define MAX30102_INT_STATUS_2 0x01
#define MAX30102_INT_ENABLE_1 0x02
#define MAX30102_INT_ENABLE_2 0x03
#define MAX30102_FIFO_WRITE_PTR 0x04
#define MAX30102_OVERFLOW_COUNT 0x05
#define MAX30102_FIFO_READ_PTR 0x06
#define MAX30102_REG_FIFO_DATA 0x07
#define MAX30102_FIFO_CONFIG 0x08
#define MAX30102_MODE_CONFIG 0x09
#define MAX30102_SPO2_CONFIG 0x0A
#define MAX30102_LED1_PULSE_AMP 0x0C
#define MAX30102_LED2_PULSE_AMP 0x0D

#define MAX30102_PARTIDR 0xFF
#define MAX30102_RESET 0x40

#define MAX_BRIGHTNESS 255
int32_t n_ir_buffer_length;         //data length
uint32_t aun_ir_buffer[500];        //IR LED sensor data
uint32_t aun_red_buffer[500];       //Red LED sensor data
int32_t n_sp02;                     //SPO2 value
int8_t ch_spo2_valid;               //indicator to show if the SP02 calculation is valid
int32_t n_heart_rate;               //heart rate value
int8_t  ch_hr_valid;                //indicator to show if the heart rate calculation is valid
#define FS 100
#define BUFFER_SIZE  (FS* 5)
#define MA4_SIZE  4                 // DO NOT CHANGE
#define HAMMING_SIZE  5             // DO NOT CHANGE
#define min(x,y) ((x) < (y) ? (x) : (y))
const uint16_t auw_hamm[31]={ 41,    276,    512,    276,     41 }; //Hamm=  long16(512* hamming(5)');
//uch_spo2_table is computed as  -45.060*ratioAverage* ratioAverage + 30.354 *ratioAverage + 94.845 ;
const uint8_t uch_spo2_table[184]={ 95, 95, 95, 96, 96, 96, 97, 97, 97, 97, 97, 98, 98, 98, 98, 98, 99, 99, 99, 99, 
                            99, 99, 99, 99, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 
                            100, 100, 100, 100, 99, 99, 99, 99, 99, 99, 99, 99, 98, 98, 98, 98, 98, 98, 97, 97, 
                            97, 97, 96, 96, 96, 96, 95, 95, 95, 94, 94, 94, 93, 93, 93, 92, 92, 92, 91, 91, 
                            90, 90, 89, 89, 89, 88, 88, 87, 87, 86, 86, 85, 85, 84, 84, 83, 82, 82, 81, 81, 
                            80, 80, 79, 78, 78, 77, 76, 76, 75, 74, 74, 73, 72, 72, 71, 70, 69, 69, 68, 67, 
                            66, 66, 65, 64, 63, 62, 62, 61, 60, 59, 58, 57, 56, 56, 55, 54, 53, 52, 51, 50, 
                            49, 48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 31, 30, 29, 
                            28, 27, 26, 25, 23, 22, 21, 20, 19, 17, 16, 15, 14, 12, 11, 10, 9, 7, 6, 5, 
                            3, 2, 1 } ;
static  int32_t an_dx[ BUFFER_SIZE - MA4_SIZE]; // delta
static  int32_t an_x[ BUFFER_SIZE]; //ir
static  int32_t an_y[ BUFFER_SIZE]; //red
void maxim_heart_rate_and_oxygen_saturation(uint32_t *pun_ir_buffer,  int32_t n_ir_buffer_length, uint32_t *pun_red_buffer, int32_t *pn_spo2, int8_t *pch_spo2_valid, int32_t *pn_heart_rate, int8_t  *pch_hr_valid);

uint16_t cyclePosition;
uint16_t sixRAM;
uint16_t nineRAM;
uint16_t lowerRAM = 0;
uint16_t upperRAM = 0;
double VRta = 0;
double AMB = 0;
double sensorTemp = 0;
float S = 0;
double VRto = 0;
double Sto = 0;
double TAdut = 0;
double ambientTempK = 0;
double objectTempCalc = 0;
double objectTemp = 0;

#define SLEEP_TIME_MS	2000

/*
 * Get button configuration from the devicetree sw0 alias.
 *
 * At least a GPIO device and pin number must be provided. The 'flags'
 * cell is optional.
 */

#define SW0_NODE	DT_ALIAS(sw0)

#if DT_NODE_HAS_STATUS(SW0_NODE, okay)
#define SW0_GPIO_LABEL	DT_GPIO_LABEL(SW0_NODE, gpios)
#define SW0_GPIO_PIN	DT_GPIO_PIN(SW0_NODE, gpios)
#define SW0_GPIO_FLAGS	(GPIO_INPUT | DT_GPIO_FLAGS(SW0_NODE, gpios))
#else
#error "Unsupported board: sw0 devicetree alias is not defined"
#define SW0_GPIO_LABEL	""
#define SW0_GPIO_PIN	0
#define SW0_GPIO_FLAGS	0
#endif

#define SW3_NODE	DT_ALIAS(sw3)
#define PORT3		DT_GPIO_LABEL(SW3_NODE, gpios)

#if DT_NODE_HAS_STATUS(SW3_NODE, okay)
#define SW3_GPIO_LABEL	DT_GPIO_LABEL(SW3_NODE, gpios)
#define SW3_GPIO_PIN	DT_GPIO_PIN(SW3_NODE, gpios)
#define SW3_GPIO_FLAGS	(GPIO_INPUT | DT_GPIO_FLAGS(SW3_NODE, gpios))
#else
#error "Unsupported board: sw0 devicetree alias is not defined"
#define SW3_GPIO_LABEL	""
#define SW3_GPIO_PIN	0
#define SW3_GPIO_FLAGS	0
#endif

/* LED helpers, which use the led0 devicetree alias if it's available. */
static const struct device *initialize_led(void);
static void match_led_to_button(const struct device *button,
				const struct device *led);

static struct gpio_callback button_cb_data;

static int init_I2C(const struct device *i2c_dev)
{
    uint32_t dev_config = I2C_SPEED_SET(I2C_SPEED_STANDARD) | I2C_MODE_MASTER;
    int ret;

    ret = i2c_configure(i2c_dev, dev_config);
    if(ret) printk("I2C: Error (%d)\n", ret);
    else printk("I2C: Ok (%d)\n", ret);
    return ret;
}

static int write_bytes(const struct device *i2c_dev, uint8_t *buf, uint32_t num_bytes, uint16_t addrDevice)
{
  struct i2c_msg msg;

  msg.buf = buf;
  msg.len = num_bytes;
  msg.flags = I2C_MSG_WRITE | I2C_MSG_STOP;

  return i2c_transfer(i2c_dev, &msg, 1, addrDevice);
}

static int read_bytes(const struct device *i2c_dev, uint16_t addr, uint8_t *data, uint32_t num_bytes)
{
  uint8_t wr_addr[2];
  struct i2c_msg msgs[2];

  wr_addr[0] = (addr >> 8) & 0xFF;
  wr_addr[1] = addr & 0xFF;

  /* Setup I2C messages */

  /* Send the address to read from */
  msgs[0].buf = wr_addr;
  msgs[0].len = 2U;
  msgs[0].flags = I2C_MSG_WRITE;

  /* Read from device. STOP after this. */
  msgs[1].buf = data;
  msgs[1].len = num_bytes;
  msgs[1].flags = I2C_MSG_READ | I2C_MSG_STOP;

  return i2c_transfer(i2c_dev, &msgs[0], 2, MLX90632_ADDRESS);
}

static int check_MLX90632(const struct device *i2c_dev) // Read Product Code
{
    uint8_t ProductCode[2] = {0x0, 0x0};
    int ret;

    ret = read_bytes(i2c_dev, MLX90632_PRODUCT_CODE, ProductCode, 2); 
    if(ret) printk("MLX90632: Error - not present!\n");
    else
      {
      if ((ProductCode[0] == 0x00) && (ProductCode[1] == 0x21)) printk("MLX90632: present! PRODUCT CODE: (%d) (%d)\n", ProductCode[0], ProductCode[1]);   // MSByte ProductCode[0] - LSByte: ProductCode[1]
      else
        { 
        printk("MLX90632: Error! PRODUCT CODE: (%d) (%d)\n", ProductCode[0], ProductCode[1]);
        ret = -99;
        }
      }
    return ret;
}

static int init_MLX90632(const struct device *i2c_dev)     // This function resets the MAX30102
{
  int32_t tempValue32;
  int16_t tempValue16;
  uint16_t lowerByte;
  uint16_t upperByte;
  uint8_t buf4[4] = {0x0, 0x0, 0x0, 0x0};
  int ret;
  uint8_t mode = 0x03;
  uint8_t buf[2] = {0x0, 0x0};
  uint16_t reg = 0;

  ret = read_bytes(i2c_dev, MLX90632_REG_CONTROL, buf, 2);
  reg = ((buf[0] << 8) | buf[1]);
  reg &= ~(0x0003 << 1);    
  reg &= ~(0x06);          
  reg |= (mode << 1);       

  buf4[0] = 0x30;
  buf4[1] = 0x01;
  buf4[2] = (reg >> 8) & 0xFF;  
  buf4[3] = reg & 0xFF;         
  ret = write_bytes(i2c_dev, buf4, 4, MLX90632_ADDRESS);

  // MLX90632_EE_P_R
  ret = read_bytes(i2c_dev, MLX90632_EE_P_R, buf4, 4); 
  lowerByte = ((buf4[0] << 8) | buf4[1]);
  upperByte = ((buf4[2] << 8) | buf4[3]);
  tempValue32 = upperByte << 16 | lowerByte;
  MLX90632_P_R = (double)tempValue32 * pow(2, -8);
  
  // MLX90632_EE_P_G
  ret = read_bytes(i2c_dev, MLX90632_EE_P_G, buf4, 4); 
  lowerByte = ((buf4[0] << 8) | buf4[1]);
  upperByte = ((buf4[2] << 8) | buf4[3]);
  tempValue32 = upperByte << 16 | lowerByte;
  MLX90632_P_G = (double)tempValue32 * pow(2, -20);
 
  // MLX90632_EE_P_T
  ret = read_bytes(i2c_dev, MLX90632_EE_P_T, buf4, 4); 
  lowerByte = ((buf4[0] << 8) | buf4[1]);
  upperByte = ((buf4[2] << 8) | buf4[3]);
  tempValue32 = upperByte << 16 | lowerByte;
  MLX90632_P_T = (double)tempValue32 * pow(2, -44);
  
  // MLX90632_EE_P_O
  ret = read_bytes(i2c_dev, MLX90632_EE_P_O, buf4, 4); 
  lowerByte = ((buf4[0] << 8) | buf4[1]);
  upperByte = ((buf4[2] << 8) | buf4[3]);
  tempValue32 = upperByte << 16 | lowerByte;
  MLX90632_P_O = (double)tempValue32 * pow(2, -8);
  
  // MLX90632_EE_Ea
  ret = read_bytes(i2c_dev, MLX90632_EE_Ea, buf4, 4); 
  lowerByte = ((buf4[0] << 8) | buf4[1]);
  upperByte = ((buf4[2] << 8) | buf4[3]);
  tempValue32 = upperByte << 16 | lowerByte;
  MLX90632_Ea = (double)tempValue32 * pow(2, -16);
  
  // MLX90632_EE_Bb
  ret = read_bytes(i2c_dev, MLX90632_EE_Bb, buf4, 4); 
  lowerByte = ((buf4[0] << 8) | buf4[1]);
  upperByte = ((buf4[2] << 8) | buf4[3]);
  tempValue32 = upperByte << 16 | lowerByte;
  MLX90632_Eb = (double)tempValue32 * pow(2, -8);
  
  // MLX90632_EE_Fa
  ret = read_bytes(i2c_dev, MLX90632_EE_Fa, buf4, 4); 
  lowerByte = ((buf4[0] << 8) | buf4[1]);
  upperByte = ((buf4[2] << 8) | buf4[3]);
  tempValue32 = upperByte << 16 | lowerByte;
  MLX90632_Fa = (double)tempValue32 * pow(2, -46);

  // MLX90632_EE_Fb
  ret = read_bytes(i2c_dev, MLX90632_EE_Fb, buf4, 4); 
  lowerByte = ((buf4[0] << 8) | buf4[1]);
  upperByte = ((buf4[2] << 8) | buf4[3]);
  tempValue32 = upperByte << 16 | lowerByte;
  MLX90632_Fb = (double)tempValue32 * pow(2, -36);

  // MLX90632_EE_Ga
  ret = read_bytes(i2c_dev, MLX90632_EE_Ga, buf4, 4); 
  lowerByte = ((buf4[0] << 8) | buf4[1]);
  upperByte = ((buf4[2] << 8) | buf4[3]);
  tempValue32 = upperByte << 16 | lowerByte;
  MLX90632_Ga = (double)tempValue32 * pow(2, -36);

  // MLX90632_EE_Gb
  ret = read_bytes(i2c_dev, MLX90632_EE_Gb, buf4, 2); 
  tempValue16 = (((uint16_t)buf4[0] << 8) | buf4[1]);
  MLX90632_Gb = (double)tempValue16 * pow(2, -10);

  // MLX90632_EE_Ka
  ret = read_bytes(i2c_dev, MLX90632_EE_Ka, buf4, 2); 
  tempValue16 = (((uint16_t)buf4[0] << 8) | buf4[1]);
  MLX90632_Ka = (double)tempValue16 * pow(2, -10);

  // MLX90632_EE_Ha
  ret = read_bytes(i2c_dev, MLX90632_EE_Ha, buf4, 2); 
  tempValue16 = (((uint16_t)buf4[0] << 8) | buf4[1]);
  MLX90632_Ha = (double)tempValue16 * pow(2, -14);

  // MLX90632_EE_Hb
  ret = read_bytes(i2c_dev, MLX90632_EE_Hb, buf4, 2); 
  tempValue16 = (((uint16_t)buf4[0] << 8) | buf4[1]);
  MLX90632_Hb = (double)tempValue16 * pow(2, -14);

  return 0;
}

bool dataAvailable_MLX90632(const struct device *i2c_dev) //Returns true if new data is available
{
  int ret;
  uint8_t buf[2] = {0x00, 0x00};
  uint16_t deviceStatus;

  ret = read_bytes(i2c_dev, MLX90632_REG_STATUS, buf, 2); 
  deviceStatus = ((buf[0] << 8) | buf[1]);
  if (deviceStatus & 0xFFFF) return(true);
  return (false);
}


double getObjectTemp(const struct device *i2c_dev)
{
  uint16_t mode;
  uint8_t buf[2] = {0x0, 0x0};
  uint8_t buf4[4] = {0x0, 0x0, 0x0, 0x0};
  uint16_t reg = 0;
  int ret;

  ret = read_bytes(i2c_dev, MLX90632_REG_CONTROL, buf, 2);
  mode = ((buf[0] << 8) | buf[1]);
  mode = (mode >> 1) & 0x0003; 
  if(mode != MLX90632_MODE_CONTINUOUS)
  {
    printk("Errore - Sets the Start of Conversion (SOC) bit\n");

    ret = read_bytes(i2c_dev, MLX90632_REG_CONTROL, buf, 2); 
    if(ret) printk("MLX90632_REG_CONTROL: R Error!\n");
    else printk("MLX90632_REG_CONTROL: R (%d) (%d)\n", buf[0], buf[1]);  
    reg = ((buf[0] << 8) | buf[1]);
    reg |= (1 << 3); //Set the bit

    buf4[0] = 0x30;
    buf4[1] = 0x01;
    buf4[2] = (reg >> 8) & 0xFF;   
    buf4[3] = reg & 0xFF;          
    ret = write_bytes(i2c_dev, buf4, 4, MLX90632_ADDRESS);
    if(ret) printk("MLX90632_REG_CONTROL: W Error! %d\n",ret);
    else printk("MLX90632_REG_CONTROL: W (%d) (%d)\n", buf[0], buf[1]);   
  }

  ret = read_bytes(i2c_dev, MLX90632_REG_STATUS, buf, 2); 
  reg = ((buf[0] << 8) | buf[1]);
  reg &= 0xFFFE; 

  buf4[0] = 0x3F;
  buf4[1] = 0xFF;
  buf4[2] = (reg >> 8) & 0xFF;   
  buf4[3] = reg & 0xFF;          
  ret = write_bytes(i2c_dev, buf4, 4, MLX90632_ADDRESS);

  //Check when new_data = 1
  uint16_t counter = 0;
  while (dataAvailable_MLX90632(i2c_dev) == false)
  {
    k_msleep(1);
    counter++;
    if (counter == 750)
    {
      printk("Data available timeout\n"); 
      return 0;
    }
  }

  //Get MLX90632_RAM_6 and RAM_9
  ret = read_bytes(i2c_dev, MLX90632_RAM_6, buf, 2);
  sixRAM = ((buf[0] << 8) | buf[1]);
  ret = read_bytes(i2c_dev, MLX90632_RAM_9, buf, 2);
  nineRAM = ((buf[0] << 8) | buf[1]);

  VRta = nineRAM + MLX90632_Gb * (sixRAM / 12.0);
  AMB = (sixRAM / 12.0) / VRta * pow(2, 19);
  sensorTemp = MLX90632_P_O + (AMB - MLX90632_P_R) / MLX90632_P_G + MLX90632_P_T * pow((AMB - MLX90632_P_R), 2);

  //Read cycle_pos to get measurement pointer
  ret = read_bytes(i2c_dev, MLX90632_REG_STATUS, buf, 2); 
  reg = ((buf[0] << 8) | buf[1]);
  cyclePosition = (reg >> 2) & 0x1F;

  //If cycle_pos = 1
  //Calculate TA and TO based on RAM_4, RAM_5, RAM_6, RAM_9
  if (cyclePosition == 1)
  {
    ret = read_bytes(i2c_dev, MLX90632_RAM_4, buf, 2);
    lowerRAM = ((buf[0] << 8) | buf[1]);

    ret = read_bytes(i2c_dev, MLX90632_RAM_5, buf, 2);
    upperRAM = ((buf[0] << 8) | buf[1]);
  }
  //If cycle_pos = 2
  //Calculate TA and TO based on RAM_7, RAM_8, RAM_6, RAM_9
  else if (cyclePosition == 2)
  {
    ret = read_bytes(i2c_dev, MLX90632_RAM_7, buf, 2);
    lowerRAM = ((buf[0] << 8) | buf[1]);

    ret = read_bytes(i2c_dev, MLX90632_RAM_8, buf, 2);
    upperRAM = ((buf[0] << 8) | buf[1]);
  }
  else
  {
    ret = read_bytes(i2c_dev, MLX90632_RAM_4, buf, 2);
    lowerRAM = ((buf[0] << 8) | buf[1]);

    ret = read_bytes(i2c_dev, MLX90632_RAM_5, buf, 2);
    upperRAM = ((buf[0] << 8) | buf[1]);
  }


  //Object temp requires 3 iterations
  for (uint8_t i = 0 ; i < 3 ; i++)
  {
    VRta = nineRAM + MLX90632_Gb * (sixRAM / 12.0);

    AMB = (sixRAM / 12.0) / VRta * pow(2, 19);
    
    sensorTemp = MLX90632_P_O + (AMB - MLX90632_P_R) / MLX90632_P_G + MLX90632_P_T * pow((AMB - MLX90632_P_R), 2);

    S = (float)(lowerRAM + upperRAM) / 2.0;
    VRto = nineRAM + MLX90632_Ka * (sixRAM / 12.0);
    Sto = (S / 12.0) / (VRto * (double)pow(2, 19));

    TAdut = ((AMB - MLX90632_Eb) / MLX90632_Ea) + 25.0;

    ambientTempK = TAdut + 273.15;

    objectTempCalc = Sto / (1 * MLX90632_Fa * MLX90632_Ha * (1 + MLX90632_Ga * (TOdut - TO0) + MLX90632_Fb * (TAdut - TA0)));

    objectTemp = objectTempCalc + pow(ambientTempK, 4);
    objectTemp = pow(objectTemp, 0.25); //Take 4th root
    objectTemp = objectTemp - 273.15 - MLX90632_Hb;

    TO0 = objectTemp;
  }
  return TO0;
}

// ---------- MAX30102 -----------
static int check_MAX30102(const struct device *i2c_dev)
{
    uint8_t partid[1] = {0x0};
    int ret;

    ret = i2c_burst_read(i2c_dev, MAX30102_ADDRESS, MAX30102_PARTIDR, partid, 1);    // Read part ID 
    if(ret) printk("MAX30102: Error - not present!\n");
    else
      {
      if (partid[0] == 0x15) printk("MAX30102: present! Part ID: %dd\n", partid[0]);
      else
        { 
        printk("MAX30102: Error! Part ID: %dd\n", partid[0]);
        ret = -99;
        }
      }
    return ret;
}

static int reset_MAX30102(const struct device *i2c_dev)     // This function resets the MAX30102
{
    uint8_t sendbuf[1] = {MAX30102_RESET};
    int ret;

    ret = i2c_reg_write_byte(i2c_dev, MAX30102_ADDRESS, MAX30102_MODE_CONFIG, sendbuf[0]);
    if(ret) printk("MAX30102: Reset Error!\n");
    else printk("MAX30102: Reset ok!\n");
    return ret;
}

static int clearInt_MAX30102(const struct device *i2c_dev)     // Reads/clears the interrupt status register
{
    uint8_t IntStatus1[1] = {0x0};
    int ret;

    ret = i2c_burst_read(i2c_dev, MAX30102_ADDRESS, MAX30102_INT_STATUS_1, IntStatus1, 1);    
    if(ret) printk("MAX30102: Interrupt Status 1: Error!\n");
    else printk("MAX30102: Interrupt Status 1: Ok (%d)\n", IntStatus1[0]);
    return ret;
}

static int init_MAX30102(const struct device *i2c_dev)     // This function resets the MAX30102
{
    uint8_t buf[1] = {0x00};
    int ret, ret1 = 0;

    buf[0] = 0xC0;
    ret =  i2c_reg_write_byte(i2c_dev, MAX30102_ADDRESS, MAX30102_INT_ENABLE_1, buf[0]); // INTR setting
    if(ret)
      {
      ret1 = -99;
      printk("MAX30102: INTR setting: Error!\n");
      }
    else printk("MAX30102: INTR setting: ok!\n");

    buf[0] = 0x00;
    ret =  i2c_reg_write_byte(i2c_dev, MAX30102_ADDRESS, MAX30102_INT_ENABLE_2, buf[0]); // INTR setting
    if(ret) 
      {
      ret1 = -99;
      printk("MAX30102: INTR setting: Error!\n");
      }
    else printk("MAX30102: INTR setting: ok!\n");

    buf[0] = 0x00;
     ret =  i2c_reg_write_byte(i2c_dev, MAX30102_ADDRESS, MAX30102_FIFO_WRITE_PTR, buf[0]); // FIFO_WR_PTR[4:0]
    if(ret)
      {
      ret1 = -99;      
      printk("MAX30102: FIFO_WR_PTR: Error!\n");
      }
    else printk("MAX30102: FIFO_WR_PTR: ok!\n");

    buf[0] = 0x00;
    ret =  i2c_reg_write_byte(i2c_dev, MAX30102_ADDRESS, MAX30102_OVERFLOW_COUNT, buf[0]);  // OVF_COUNTER[4:0]
    if(ret)
      {
      ret1 = -99;    
      printk("MAX30102: FIFO_WR_PTR: Error!\n");
      }
    else printk("MAX30102: FIFO_WR_PTR: ok!\n");

    buf[0] = 0x00;
    ret =  i2c_reg_write_byte(i2c_dev, MAX30102_ADDRESS, MAX30102_FIFO_READ_PTR, buf[0]); // FIFO_RD_PTR[4:0]
    if(ret)
      {
      ret1 = -99;
      printk("MAX30102: FIFO_RD_PTR: Error!\n");
      }
    else printk("MAX30102: FIFO_RD_PTR: ok!\n");

    buf[0] = 0x0F;
    ret =  i2c_reg_write_byte(i2c_dev, MAX30102_ADDRESS, MAX30102_FIFO_CONFIG, buf[0]); // sample avg = 1, fifo rollover=false, fifo almost full = 17
    
    if(ret)
      {
      ret1 = -99;
      printk("MAX30102: FIFO CONFIG: Error!\n");
      }
    else printk("MAX30102: FIFO CONFIG: ok!\n");

    buf[0] = 0x03;
    ret =  i2c_reg_write_byte(i2c_dev, MAX30102_ADDRESS, MAX30102_MODE_CONFIG, buf[0]);// 0x02 for Red only, 0x03 for SpO2 mode 0x07 multimode LED
    if(ret)
      {
      ret1 = -99;    
      printk("MAX30102: MODE CONFIG: Error!\n");
      }
    else printk("MAX30102: MODE CONFIG: ok!\n");

    buf[0] = 0x27;
    ret =  i2c_reg_write_byte(i2c_dev, MAX30102_ADDRESS, MAX30102_SPO2_CONFIG, buf[0]);// SPO2_ADC range = 4096nA, SPO2 sample rate (100 Hz), LED pulseWidth (400uS)
    if(ret)
      {
      ret1 = -99;    
      printk("MAX30102: SPO2 ADC: Error!\n");
      }
    else printk("MAX30102: SPO2 ADC: ok!\n");

    buf[0] = 0x24;
    ret =  i2c_reg_write_byte(i2c_dev, MAX30102_ADDRESS, MAX30102_LED1_PULSE_AMP, buf[0]);  // Choose value for ~ 7mA for LED1
    if(ret)
      {
      ret1 = -99;
      printk("MAX30102: LED1: Error!\n");
      }
    else printk("MAX30102: LED1: ok!\n");

    buf[0] = 0x24;
    ret =  i2c_reg_write_byte(i2c_dev, MAX30102_ADDRESS, MAX30102_LED2_PULSE_AMP, buf[0]);  // Choose value for ~ 7mA for LED2
    if(ret)
      {
      ret1 = -99;   
      printk("MAX30102: LED2: Error!\n");
      }
    else printk("MAX30102: LED2: ok!\n");

    return ret1;
}

bool maxim_max30102_read_reg(const struct device *i2c_dev, uint8_t uch_addr, uint8_t *puch_data)
{
  int ret;
  uint8_t buf[1] = {0x00};

  ret = i2c_burst_read(i2c_dev, MAX30102_ADDRESS, uch_addr, buf, 1);  
  if(ret)
  {  
    printk("MAX30102: Error!\n");
    return false;
  }
  else 
  {
    *puch_data = buf[0];
    return true;
  }
}

bool maxim_max30102_read_fifo(const struct device *i2c_dev, uint32_t *pun_red_led, uint32_t *pun_ir_led)
{
  int ret;
  uint32_t un_temp;
  unsigned char uch_temp;
  *pun_red_led=0;
  *pun_ir_led=0;
  char ach_i2c_data[6];
  
  //read and clear status register
  maxim_max30102_read_reg(i2c_dev, MAX30102_INT_STATUS_1, &uch_temp);
  maxim_max30102_read_reg(i2c_dev, MAX30102_INT_STATUS_2, &uch_temp);

  ret = i2c_burst_read(i2c_dev, MAX30102_ADDRESS, MAX30102_REG_FIFO_DATA, ach_i2c_data, 6);
  if(ret) printk("MAX30102: maxim_max30102_read_fifo Error!\n");

  un_temp=(unsigned char) ach_i2c_data[0];
  un_temp<<=16;
  *pun_red_led+=un_temp;
  un_temp=(unsigned char) ach_i2c_data[1];
  un_temp<<=8;
  *pun_red_led+=un_temp;
  un_temp=(unsigned char) ach_i2c_data[2];
  *pun_red_led+=un_temp;
  
  un_temp=(unsigned char) ach_i2c_data[3];
  un_temp<<=16;
  *pun_ir_led+=un_temp;
  un_temp=(unsigned char) ach_i2c_data[4];
  un_temp<<=8;
  *pun_ir_led+=un_temp;
  un_temp=(unsigned char) ach_i2c_data[5];
  *pun_ir_led+=un_temp;
  *pun_red_led&=0x03FFFF;
  *pun_ir_led&=0x03FFFF;

  return true;
}


void button_pressed(const struct device *dev, struct gpio_callback *cb,
		    uint32_t pins)
{
	printk("Button pressed at %" PRIu32 "\n", k_cycle_get_32());
}



void main(void)
{
  const struct device *button;
  const struct device *led;
  int ret;
  uint8_t data[16];

  // Button and LED Setup
  button = device_get_binding(SW0_GPIO_LABEL);
  if (button == NULL) {
		printk("Error: didn't find %s device\n", SW0_GPIO_LABEL);
		return;
  }

  ret = gpio_pin_configure(button, SW0_GPIO_PIN, SW0_GPIO_FLAGS);
  if (ret != 0) {
		printk("Error %d: failed to configure %s pin %d\n",
			   ret, SW0_GPIO_LABEL, SW0_GPIO_PIN);
		return;
  }

  ret = gpio_pin_interrupt_configure(button,
					   SW0_GPIO_PIN,
					   GPIO_INT_EDGE_TO_ACTIVE);
  if (ret != 0) {
		printk("Error %d: failed to configure interrupt on %s pin %d\n",
			ret, SW0_GPIO_LABEL, SW0_GPIO_PIN);
		return;
  }

  gpio_init_callback(&button_cb_data, button_pressed, BIT(SW0_GPIO_PIN));
  gpio_add_callback(button, &button_cb_data);
  printk("Set up button at %s pin %d\n", SW0_GPIO_LABEL, SW0_GPIO_PIN);

  led = initialize_led();
		
  printk("Press the button\n");

 
  // -------------------------------------
  uint32_t un_min, un_max, un_prev_data;  //variables to calculate the on-board LED brightness that reflects the heartbeats
  int i;
  int32_t n_brightness;
  float f_temp;
  const struct device *IntMAX30102; 

  IntMAX30102 = device_get_binding(SW3_GPIO_LABEL);
  if (IntMAX30102 == NULL)
  {
    printk("IntMAX30102 Error: didn't find %s device\n", SW3_GPIO_LABEL);
    return;
  }

  ret = gpio_pin_configure(IntMAX30102, SW3_GPIO_PIN, SW3_GPIO_FLAGS);
  if (ret != 0)
  {
    printk("Error %d: failed to configure %s pin %d\n", ret, SW3_GPIO_LABEL, SW3_GPIO_PIN);
    return;
  }

  k_msleep(1000);
  const struct device *i2c_dev;
  i2c_dev = device_get_binding("I2C_1");
  if (i2c_dev == NULL)
  {
    printk("Error: didn't find I2C_1 device\n");
    return;
  }
  init_I2C(i2c_dev);            // I2C initialize
  
  // *************** MLX90632 *******************
  check_MLX90632(i2c_dev);          // Read Product Code - MLX90632
  init_MLX90632(i2c_dev);           // Initialize the MLX90632
  objectT = getObjectTemp(i2c_dev); // get Object Temp

  // *************** MAX30102 *******************
  // Initialize MAX30102 Settings
  check_MAX30102(i2c_dev);      // Read part ID
  reset_MAX30102(i2c_dev);      // This function resets the MAX30102
  clearInt_MAX30102(i2c_dev);   // Reads/clears the interrupt status register
  init_MAX30102(i2c_dev);       // Initialize the MAX30102

  n_brightness=0;
  un_min=0x3FFFF;
  un_max=0;

  n_ir_buffer_length=500;

  // MAX30102 - read the first 500 samples, and determine the signal range
  for(i=0; i<n_ir_buffer_length; i++)
  {
    while(gpio_pin_get(device_get_binding(PORT3), SW3_GPIO_PIN) == 0);  // wait until the interrupt pin asserts
    maxim_max30102_read_fifo(i2c_dev, (aun_red_buffer+i), (aun_ir_buffer+i));    // read from MAX30102 FIFO

    if(un_min>aun_red_buffer[i]) un_min=aun_red_buffer[i];    //update signal min
    if(un_max<aun_red_buffer[i]) un_max=aun_red_buffer[i];    //update signal max
    
    printk("red=%i, ir=%i\n", aun_red_buffer[i], aun_ir_buffer[i]);
  }
  un_prev_data=aun_red_buffer[i];

  //calculate heart rate and SpO2 after first 500 samples (first 5 seconds of samples)
  maxim_heart_rate_and_oxygen_saturation(aun_ir_buffer, n_ir_buffer_length, aun_red_buffer, &n_sp02, &ch_spo2_valid, &n_heart_rate, &ch_hr_valid); 
  
  //Continuously taking samples from MAX30102.  Heart rate and SpO2 are calculated every 1 second
  while(1)
  {
      i=0;
      un_min=0x3FFFF;
      un_max=0;
      
      //dumping the first 100 sets of samples in the memory and shift the last 400 sets of samples to the top
      for(i=100;i<500;i++)
      {
          aun_red_buffer[i-100]=aun_red_buffer[i];
          aun_ir_buffer[i-100]=aun_ir_buffer[i];
          
          //update the signal min and max
          if(un_min>aun_red_buffer[i])
          un_min=aun_red_buffer[i];
          if(un_max<aun_red_buffer[i])
          un_max=aun_red_buffer[i];
      }
      
      //take 100 sets of samples before calculating the heart rate.
      for(i=400;i<500;i++)
      {
          un_prev_data=aun_red_buffer[i-1];
          while(gpio_pin_get(device_get_binding(PORT3), SW3_GPIO_PIN) == 0);  // wait until the interrupt pin asserts
          maxim_max30102_read_fifo(i2c_dev, (aun_red_buffer+i), (aun_ir_buffer+i));
      
          if(aun_red_buffer[i]>un_prev_data)
          {
              f_temp=aun_red_buffer[i]-un_prev_data;
              f_temp/=(un_max-un_min);
              f_temp*=MAX_BRIGHTNESS;
              n_brightness-=(int)f_temp;
              if(n_brightness<0)
                  n_brightness=0;
          }
          else
          {
              f_temp=un_prev_data-aun_red_buffer[i];
              f_temp/=(un_max-un_min);
              f_temp*=MAX_BRIGHTNESS;
              n_brightness+=(int)f_temp;
              if(n_brightness>MAX_BRIGHTNESS)
                  n_brightness=MAX_BRIGHTNESS;
          }
          //send samples and calculation result to terminal program through UART
        printk("red=%i, ir=%i, HR=%i, HRvalid=%i, SpO2=%i, SPO2Valid=%i \n", aun_red_buffer[i], aun_ir_buffer[i], n_heart_rate, ch_hr_valid, n_sp02, ch_spo2_valid);
        k_msleep(10);
      }
      maxim_heart_rate_and_oxygen_saturation(aun_ir_buffer, n_ir_buffer_length, aun_red_buffer, &n_sp02, &ch_spo2_valid, &n_heart_rate, &ch_hr_valid); 
  

      objectT = getObjectTemp(i2c_dev); // get Object Temp
      uint16_t printobjectT = (int)(objectT*100);
      printk("Temp_ear: %d (value/100 = [degrees Celsius])\n", printobjectT);
      

	  match_led_to_button(button, led);
  }
}

/*
 * The led0 devicetree alias is optional. If present, we'll use it
 * to turn on the LED whenever the button is pressed.
 */

#define LED0_NODE	DT_ALIAS(led0)

#if DT_NODE_HAS_STATUS(LED0_NODE, okay) && DT_NODE_HAS_PROP(LED0_NODE, gpios)
#define LED0_GPIO_LABEL	DT_GPIO_LABEL(LED0_NODE, gpios)
#define LED0_GPIO_PIN	DT_GPIO_PIN(LED0_NODE, gpios)
#define LED0_GPIO_FLAGS	(GPIO_OUTPUT | DT_GPIO_FLAGS(LED0_NODE, gpios))
#endif

#ifdef LED0_GPIO_LABEL
static const struct device *initialize_led(void)
{
	const struct device *led;
	int ret;

	led = device_get_binding(LED0_GPIO_LABEL);
	if (led == NULL) {
		printk("Didn't find LED device %s\n", LED0_GPIO_LABEL);
		return NULL;
	}

	ret = gpio_pin_configure(led, LED0_GPIO_PIN, LED0_GPIO_FLAGS);
	if (ret != 0) {
		printk("Error %d: failed to configure LED device %s pin %d\n",
		       ret, LED0_GPIO_LABEL, LED0_GPIO_PIN);
		return NULL;
	}

	printk("Set up LED at %s pin %d\n", LED0_GPIO_LABEL, LED0_GPIO_PIN);

	return led;
}

static void match_led_to_button(const struct device *button,
				const struct device *led)
{
	bool val;

	val = gpio_pin_get(button, SW0_GPIO_PIN);
	gpio_pin_set(led, LED0_GPIO_PIN, val);

        if(val == 1) k_msleep(SLEEP_TIME_MS);
}

#else  /* !defined(LED0_GPIO_LABEL) */
static const struct device *initialize_led(void)
{
	printk("No LED device was defined\n");
	return NULL;
}

static void match_led_to_button(const struct device *button,
				const struct device *led)
{
	return;
}
#endif	/* LED0_GPIO_LABEL */


// ********** Algorithm - maxim_heart_rate_and_oxygen_saturation **********************
void maxim_sort_ascend(int32_t *pn_x,int32_t n_size) 
/**
* \brief        Sort array
* \par          Details
*               Sort array in ascending order (insertion sort algorithm)
*
* \retval       None
*/
{
    int32_t i, j, n_temp;
    for (i = 1; i < n_size; i++) {
        n_temp = pn_x[i];
        for (j = i; j > 0 && n_temp < pn_x[j-1]; j--)
            pn_x[j] = pn_x[j-1];
        pn_x[j] = n_temp;
    }
}

void maxim_sort_indices_descend(int32_t *pn_x, int32_t *pn_indx, int32_t n_size)
/**
* \brief        Sort indices
* \par          Details
*               Sort indices according to descending order (insertion sort algorithm)
*
* \retval       None
*/ 
{
    int32_t i, j, n_temp;
    for (i = 1; i < n_size; i++) {
        n_temp = pn_indx[i];
        for (j = i; j > 0 && pn_x[n_temp] > pn_x[pn_indx[j-1]]; j--)
            pn_indx[j] = pn_indx[j-1];
        pn_indx[j] = n_temp;
    }
}

void maxim_remove_close_peaks(int32_t *pn_locs, int32_t *pn_npks, int32_t *pn_x, int32_t n_min_distance)
/**
* \brief        Remove peaks
* \par          Details
*               Remove peaks separated by less than MIN_DISTANCE
*
* \retval       None
*/
{
    
    int32_t i, j, n_old_npks, n_dist;
    
    /* Order peaks from large to small */
    maxim_sort_indices_descend( pn_x, pn_locs, *pn_npks );

    for ( i = -1; i < *pn_npks; i++ ){
        n_old_npks = *pn_npks;
        *pn_npks = i+1;
        for ( j = i+1; j < n_old_npks; j++ ){
            n_dist =  pn_locs[j] - ( i == -1 ? -1 : pn_locs[i] ); // lag-zero peak of autocorr is at index -1
            if ( n_dist > n_min_distance || n_dist < -n_min_distance )
                pn_locs[(*pn_npks)++] = pn_locs[j];
        }
    }

    // Resort indices longo ascending order
    maxim_sort_ascend( pn_locs, *pn_npks );
}

void maxim_peaks_above_min_height(int32_t *pn_locs, int32_t *pn_npks, int32_t  *pn_x, int32_t n_size, int32_t n_min_height)
/**
* \brief        Find peaks above n_min_height
* \par          Details
*               Find all peaks above MIN_HEIGHT
*
* \retval       None
*/
{
    int32_t i = 1, n_width;
    *pn_npks = 0;
    
    while (i < n_size-1){
        if (pn_x[i] > n_min_height && pn_x[i] > pn_x[i-1]){            // find left edge of potential peaks
            n_width = 1;
            while (i+n_width < n_size && pn_x[i] == pn_x[i+n_width])    // find flat peaks
                n_width++;
...

This file has been truncated, please download it to see its full contents.

Credits

Mario Soranno

Mario Soranno

9 projects • 24 followers
I have a Bachelor's Degree in Industrial Engineering - Electronics. I am an expert in hardware design, I can program in C/C++,Python and ROS
Jamez_007

Jamez_007

3 projects • 2 followers
Resident Physician in Internal Medicine and Preventative Medicine in San Francisco.
Mia K

Mia K

1 project • 1 follower
I'm a resident physician in Internal Medicine/Preventive Medicine with a Master's degree in Bioinformatics
Roberto Basili

Roberto Basili

1 project • 1 follower
I have a master's degree in architecture (Polytechnic of Bari). I can design the appearance of objects and their functional structure.

Comments