Hardware components | ||||||
![]() |
| × | 1 | |||
![]() |
| × | 1 | |||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
Software apps and online services | ||||||
| ||||||
Hand tools and fabrication machines | ||||||
![]() |
| |||||
|
The project main intention is to offer the convenient method for measuring and monitoring of vital COVID19 patients parameters together with the environment parameters. In the situation of overload of medical services with significant amount of heavily ill patients in presence of very active virus damaging human breathing ability it can be difficult even to identify the patient.
So we propose the solution which will allow to identify patient using fingerprints, measure their oxygenation level and report these parameters to the medical staff. This is important for patients who is not yet provided with the individual means of oxygenation monitoring, for patients with breathing complications, for patients who just entering the medical centers or emergency transport.
To check and report the oxygenation level patient first apply finger to get the finger print and apply it another time to measure the pulse and oxygenation. Other parameters including environment humidity, temperature and air quality to be measured and reported periodically because air quality is also important for patients with breathing complications.
After each measurement the Quadcorder will be blocked for some time and will disinfect its scanners using UV-C LEDs built-in into its cover:
Credit for the cover image: Bobbie Johnson Flickr (CC BY-SA 2.0)
#include "lcd1602.h"
#include "nrf_gpio.h"
#include "nrf_delay.h"
static void pulse_enable(){
nrf_gpio_pin_clear(E_PIN);
nrf_delay_ms(1);
nrf_gpio_pin_set(E_PIN);
nrf_delay_ms(1);
nrf_gpio_pin_clear(E_PIN);
nrf_delay_ms(1);
}
static void send4bits(uint8_t data){
nrf_gpio_pin_write(D7_PIN, ((data>>3)&0x01));
nrf_gpio_pin_write(D6_PIN, ((data>>2)&0x01));
nrf_gpio_pin_write(D5_PIN, ((data>>1)&0x01));
nrf_gpio_pin_write(D4_PIN, ((data>>0)&0x01));
pulse_enable();
}
void lcd_send(uint8_t data, uint8_t rd){
nrf_gpio_pin_write(RS_PIN, rd);
nrf_gpio_pin_clear(RW_PIN);
send4bits(data>>4);
send4bits(data);
}
void lcd_init(){
// Init GPIO
nrf_gpio_cfg_output(RS_PIN);
nrf_gpio_pin_clear(RS_PIN);
nrf_gpio_cfg_output(RW_PIN);
nrf_gpio_pin_clear(RW_PIN);
nrf_gpio_cfg_output(E_PIN);
nrf_gpio_pin_clear(E_PIN);
nrf_gpio_cfg_output(D4_PIN);
nrf_gpio_pin_clear(D4_PIN);
nrf_gpio_cfg_output(D5_PIN);
nrf_gpio_pin_clear(D5_PIN);
nrf_gpio_cfg_output(D6_PIN);
nrf_gpio_pin_clear(D6_PIN);
nrf_gpio_cfg_output(D7_PIN);
nrf_gpio_pin_clear(D7_PIN);
// Init LCD
nrf_gpio_pin_clear(RS_PIN);
nrf_gpio_pin_clear(RW_PIN);
nrf_delay_ms(100);
send4bits(0x03);
nrf_delay_ms(5);
send4bits(0x03);
nrf_delay_ms(5);
send4bits(0x03);
nrf_delay_ms(5);
send4bits(0x02);
nrf_delay_ms(1);
lcd_send(0x08|0x20, 0);
nrf_delay_ms(5);
lcd_send(0x08|0x20, 0);
nrf_delay_ms(1);
lcd_send(0x08|0x20, 0);
nrf_delay_ms(1);
lcd_send(0x08, 0);
nrf_delay_ms(5);
lcd_send(0x01, 0);
nrf_delay_ms(5);
lcd_send(0x06, 0);
nrf_delay_ms(5);
lcd_send(0x0c, 0);
nrf_delay_ms(5);
}
void lcd_set_cursor(uint8_t row, uint8_t column){
lcd_send(column | ( row ? 0xC0 : 0x80 ), 0);
}
void lcd_write(char *s){
while(*s) lcd_send(*s++, 1);
}
void lcd_clear(){
lcd_send(0x01, 0);
}
#include <stdint.h>
#ifndef LCD1602_H
#define LCD1602_H
#define RS_PIN NRF_GPIO_PIN_MAP(0, 27)
#define RW_PIN NRF_GPIO_PIN_MAP(0, 26)
#define E_PIN NRF_GPIO_PIN_MAP(0, 2)
#define D4_PIN NRF_GPIO_PIN_MAP(1, 15)
#define D5_PIN NRF_GPIO_PIN_MAP(1, 14)
#define D6_PIN NRF_GPIO_PIN_MAP(1, 13)
#define D7_PIN NRF_GPIO_PIN_MAP(1, 12)
void lcd_init();
void lcd_send(uint8_t c, uint8_t rd);
void lcd_set_cursor(uint8_t row, uint8_t column);
void lcd_write(char *s);
void lcd_clear();
#endif //LCD1602_H
#include <stddef.h>
#include <stdio.h>
#include "app_uart.h"
#include "nrf_delay.h"
#include "FPScanner.h"
int fp_scanner_request(command_t command, command_t response, uint8_t *data){
char status[64] = "";
uint8_t fp_parity = command[0];
fp_parity ^= command[1];
fp_parity ^= command[2];
fp_parity ^= command[3];
//fp_parity ^= 0;
while(app_uart_flush() != NRF_SUCCESS);
/* Send request */
while(app_uart_put(FPS_DELIMITER) != NRF_SUCCESS);
while(app_uart_put(command[0]) != NRF_SUCCESS);
while(app_uart_put(command[1]) != NRF_SUCCESS);
while(app_uart_put(command[2]) != NRF_SUCCESS);
while(app_uart_put(command[3]) != NRF_SUCCESS);
while(app_uart_put((uint8_t)0) != NRF_SUCCESS);
while(app_uart_put(fp_parity) != NRF_SUCCESS);
while(app_uart_put(FPS_DELIMITER) != NRF_SUCCESS);
/* Wait */
//nrf_delay_ms(10);
/* Read repsonse */
uint16_t data_length;
uint8_t byte = 0;
while(app_uart_get(&byte) != NRF_SUCCESS);
if(byte != FPS_DELIMITER)
return FPS_FAIL;
while(app_uart_get(&byte) != NRF_SUCCESS);
response[0] = byte;
while(app_uart_get(&byte) != NRF_SUCCESS);
response[1] = byte;
while(app_uart_get(&byte) != NRF_SUCCESS);
response[2] = byte;
while(app_uart_get(&byte) != NRF_SUCCESS);
response[3] = byte;
while(app_uart_get(&byte) != NRF_SUCCESS);
while(app_uart_get(&byte) != NRF_SUCCESS);
while(app_uart_get(&byte) != NRF_SUCCESS);
if(data != NULL) {
while (app_uart_get(&byte) != NRF_SUCCESS);
while (app_uart_get(&byte) != NRF_SUCCESS);
while (app_uart_get(&byte) != NRF_SUCCESS);
data_length = ((uint16_t)response[1] << 8) + (uint16_t)response[2] - 3;
for(uint8_t i = 0; i < data_length; i++){
while (app_uart_get(&byte) != NRF_SUCCESS);
data[i] = byte;
}
while (app_uart_get(&byte) != NRF_SUCCESS);
while (app_uart_get(&byte) != NRF_SUCCESS);
while (app_uart_get(&byte) != NRF_SUCCESS);
sprintf(status, "data was red");
}
sprintf(status, "result = %i", response[3]);
return response[3];
}
int fp_scanner_flush(){
char status[64] = "";
command_t request, response;
int result_code = 0;
request[0] = FPS_DELETE_ALL_USERS;
request[1] = 0x00;
request[2] = 0x00;
request[3] = 0x00;
result_code = fp_scanner_request(request, response, NULL);
sprintf(status, "result = %i", result_code);
return result_code;
}
int fp_scanner_get_user_count(){
char status[64] = "";
command_t request, response;
int result_code = 0;
request[0] = FPS_GET_USER_COUNT;
request[1] = 0x00;
request[2] = 0x00;
request[3] = 0x00;
result_code = fp_scanner_request(request, response, NULL);
uint16_t nou = response[2] + (response[1]<<8);
sprintf(status, "users = %i", nou);
return nou;
}
int fp_scanner_get_ev(uint8_t *ev){
char status[64];
command_t request, response;
int result_code;
request[0] = FPS_SCAN_AND_LOOKUP;
request[1] = 0x00;
request[2] = 0x00;
request[3] = 0x00;
result_code = fp_scanner_request(request, response, NULL);
if(result_code == FPS_TIMEOUT){
sprintf(status, "result = %i", result_code);
return result_code;
}
else if(result_code == 1 || result_code == 2 || result_code == 3) {
request[0] = FPS_GET_USER_EV;
request[1] = response[1];
request[2] = response[2];
sprintf(status, "Fingerprint found");
uint16_t uid = response[2] + (response[1]<<8);
sprintf(status, "result = %i", uid);
result_code = fp_scanner_request(request, response, ev);
return result_code;
}
else if(result_code == FPS_NO_USER) {
request[0] = FPS_ADD_FINGERPRINT_1;
fps_user_id++;
request[1] = ((uint8_t *)&fps_user_id)[1];
request[2] = ((uint8_t *)&fps_user_id)[0];
request[3] = 0x01;
result_code = fp_scanner_request(request, response, NULL);
if(result_code != FPS_SUCCESS)
return result_code;
request[0] = FPS_ADD_FINGERPRINT_2;
result_code = fp_scanner_request(request, response, NULL);
if(result_code != FPS_SUCCESS)
return result_code;
request[0] = FPS_ADD_FINGERPRINT_3;
result_code = fp_scanner_request(request, response, NULL);
if(result_code != FPS_SUCCESS)
return result_code;
request[0] = FPS_GET_USER_EV;
request[3] = 0x00;
result_code = fp_scanner_request(request, response, ev);
sprintf(status, "New fingerprint");
sprintf(status, "result = %i", result_code);
return result_code;
}
else
return FPS_FAIL;
}
#ifndef FPSCANNER_H
#define FPSCANNER_H
#include <stdint.h>
#include "nrf.h"
#define FPS_SUCCESS 0x00
#define FPS_FAIL 0x01
#define FPS_FULL 0x04
#define FPS_NO_USER 0x05
#define FPS_USER_FOUND 0x07
#define FPS_TIMEOUT 0x08
#define FPS_ADD_FINGERPRINT_1 0x01
#define FPS_ADD_FINGERPRINT_2 0x02
#define FPS_ADD_FINGERPRINT_3 0x03
#define FPS_DELETE_USER 0x04
#define FPS_DELETE_ALL_USERS 0x05
#define FPS_GET_USER_COUNT 0x09
#define FPS_GET_USER_PRIVILEGE 0x0A
#define FPS_SCAN_AND_COMPARE 0x0B
#define FPS_SCAN_AND_LOOKUP 0x0C
#define FPS_SCAN_AND_GET_EV 0x23
#define FPS_SCAN_AND_GET_IMAGE 0x24
#define FPS_STRICTNESS 0x28
#define FPS_GET_ALL_USERS 0x2B
#define FPS_SLEEP 0x2C
#define FPS_SET_MODE 0x2D
#define FPS_SCAN_TIMEOUT 0x2E
#define FPS_GET_USER_EV 0x31
#define FPS_PUT_USER_EV 0x41
#define FPS_PUT_EV_ID_AND_COMPARE 0x42
#define FPS_PUT_EV_AND_LOOKUP 0x43
#define FPS_PUT_EV_SCAN_AND_COMPARE 0x44
#define TERM1602_RIGHT 0
#define TERM1602_UP 1
#define TERM1602_DOWN 2
#define TERM1602_LEFT 3
#define TERM1602_SELECT 4
#define FPS_READY_TIMEOUT 10
#define FPS_BAUDRATE 115200
#define FPS_DELIMITER 0xF5
#define FPS_DEBUG 1
typedef uint8_t command_t[4];
typedef uint8_t data_t[197];
static uint16_t fps_user_id = 0;
//void fp_scanner_send_command(SoftwareSerial *s, command_t command);
//void fp_scanner_read_response(SoftwareSerial *s, command_t response);
int fp_scanner_request(command_t command, command_t response, uint8_t *data);
int fp_scanner_get_user_count();
int fp_scanner_flush();
int fp_scanner_get_ev(uint8_t *ev);
/*
int fp_scanner_sleep(SoftwareSerial *s);
int fp_scanner_get_mode(SoftwareSerial *s, uint8_t *mode);
int fp_scanner_set_mode(SoftwareSerial *s, uint8_t mode);
int fp_scanner_add_user(SoftwareSerial *s, uint16_t id, uint8_t priv);
int fp_scanner_delete_user(SoftwareSerial *s, uint16_t id, uint8_t priv);
int fp_scanner_allowOverwrite(SoftwareSerial *s, char b)
int fp_scanner_add(SoftwareSerial *s, int userId, char us
int fp_scanner_deleteUser(SoftwareSerial *s, int userId)
int fp_scanner_countUsers(SoftwareSerial *s)
int fp_scanner_scan(SoftwareSerial *s, int userId, int *uid)
int fp_scanner_lookupUserPrivlage(SoftwareSerial *s, int userId)
int fp_scanner_setTimeout(SoftwareSerial *s, int timeout)
int fp_scanner_setStrictness(SoftwareSerial *s, char s_level)
*/
#endif /* FPSCANNER_H */
Ported library for Maxim 310102 sensor
C/C++/*******************************************************************************
* This is max30102.cpp from MAXREFDES117# ported for nrf52840 DK
* Please refer to
* https://www.maximintegrated.com/en/design/reference-design-center/system-board/6300.html/tb_tab2
*
*******************************************************************************/
/*******************************************************************************
* Project: MAXREFDES117#
* Filename: max30102.cpp
* Description: This module is an embedded controller driver for the MAX30102
*******************************************************************************/
/*******************************************************************************
* Copyright (C) 2016 Maxim Integrated Products, Inc., All Rights Reserved.
*
* 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 MAXIM INTEGRATED 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.
*
* Except as contained in this notice, the name of Maxim Integrated
* Products, Inc. shall not be used except as stated in the Maxim Integrated
* Products, Inc. Branding Policy.
*
* The mere transfer of this software does not imply any licenses
* of trade secrets, proprietary technology, copyrights, patents,
* trademarks, maskwork rights, or any other form of intellectual
* property whatsoever. Maxim Integrated Products, Inc. retains all
* ownership rights.
*******************************************************************************/
#include "MAX30102.h"
#include "nrf_delay.h"
ret_code_t maxim_max30102_write_reg(nrf_drv_twi_t const * instance, uint8_t addr, uint8_t data){
uint8_t param[2] = {addr, data};
nrf_delay_ms(I2C_WRITE_DELAY);
return nrf_drv_twi_tx(instance, I2C_WRITE_ADDR, param, 2, false);
nrf_delay_ms(I2C_PAUSE);
}
ret_code_t maxim_max30102_read_reg(nrf_drv_twi_t const * instance, uint8_t addr, uint8_t *data){
uint32_t result_code;
nrf_delay_ms(I2C_WRITE_DELAY);
result_code = nrf_drv_twi_tx(instance, I2C_WRITE_ADDR, &addr, 1, true);
if(result_code != NRF_SUCCESS)
return result_code;
nrf_delay_ms(I2C_READ_DELAY);
result_code = nrf_drv_twi_rx(instance, I2C_READ_ADDR, data, 1);
return result_code;
}
ret_code_t maxim_max30102_init(nrf_drv_twi_t const * instance){
ret_code_t result_code;
uint8_t dummy;
result_code = maxim_max30102_read_reg(instance, REG_INTR_STATUS_1, &dummy);
if(result_code != NRF_SUCCESS) // Clear INT
return result_code;
result_code = maxim_max30102_write_reg(instance, REG_MODE_CONFIG, REG_MODE_RESET_VALUE);
if(result_code != NRF_SUCCESS) // Reset
return result_code;
result_code = maxim_max30102_write_reg(instance, REG_INTR_ENABLE_1, 0x00);
if(result_code != NRF_SUCCESS) // INTR setting
return result_code;
result_code = maxim_max30102_write_reg(instance, REG_INTR_ENABLE_2, 0x00);
if(result_code != NRF_SUCCESS)
return result_code;
result_code = maxim_max30102_write_reg(instance, REG_FIFO_WR_PTR, 0x00);
if(result_code != NRF_SUCCESS) //FIFO_WR_PTR[4:0]
return result_code;
result_code = maxim_max30102_write_reg(instance, REG_OVF_COUNTER, 0x00);
if(result_code != NRF_SUCCESS) //OVF_COUNTER[4:0]
return result_code;
result_code = maxim_max30102_write_reg(instance, REG_FIFO_RD_PTR, 0x00);
if(result_code != NRF_SUCCESS) //FIFO_RD_PTR[4:0]
return result_code;
result_code = maxim_max30102_write_reg(instance, REG_FIFO_CONFIG, 0x0f);
if(result_code != NRF_SUCCESS) //sample avg = 1, fifo rollover=false,
return result_code; //fifo almost full = 17
result_code = maxim_max30102_write_reg(instance, REG_MODE_CONFIG, 0x03);
if(result_code != NRF_SUCCESS) //0x02 for Red only,
return result_code; //0x03 for SpO2 mode 0x07 multimode LED
result_code = maxim_max30102_write_reg(instance, REG_SPO2_CONFIG, 0x27);
if(result_code != NRF_SUCCESS) //SPO2_ADC range = 4096nA,
return result_code;//SPO2 sample rate (100 Hz), LED pulseWidth (400uS)
result_code = maxim_max30102_write_reg(instance, REG_LED1_PA, 0x24);
if(result_code != NRF_SUCCESS) //Choose value for ~ 7mA for LED1
return result_code;
result_code = maxim_max30102_write_reg(instance, REG_LED2_PA, 0x24);
if(result_code != NRF_SUCCESS) //Choose value for ~ 7mA for LED2
return result_code;
result_code = maxim_max30102_write_reg(instance, REG_PILOT_PA, 0xff/*7f*/);
if(result_code != NRF_SUCCESS) //Choose value for ~ 25mA for Pilot LED
return result_code;
return NRF_SUCCESS;
}
ret_code_t maxim_max30102_read_fifo(nrf_drv_twi_t const * instance, uint32_t *red_sequence, uint32_t *ir_sequence) {
//read and clear status register
uint8_t tmp8;
maxim_max30102_read_reg(instance, REG_INTR_STATUS_1, &tmp8);
maxim_max30102_read_reg(instance, REG_INTR_STATUS_2, &tmp8);
// read to the buffer
uint8_t buffer[6];
ret_code_t result_code;
result_code = maxim_max30102_read_reg(instance, REG_FIFO_DATA, buffer);
if( result_code != NRF_SUCCESS)
return result_code;
// unpack
*red_sequence = buffer[2];
*red_sequence += buffer[1]<<8;
*red_sequence += buffer[0]<<16;
*ir_sequence = buffer[5];
*ir_sequence += buffer[4]<<8;
*ir_sequence += buffer[3]<<16;
*red_sequence &= 0x03FFFF; //Mask MSB [23:18]
*ir_sequence &= 0x03FFFF; //Mask MSB [23:18]
return NRF_SUCCESS;
}
Ported library for Maxim 310102 sensor
C/C++/*******************************************************************************
* This is max30102.h from MAXREFDES117# ported for nrf52840 DK
* Please refer to
* https://www.maximintegrated.com/en/design/reference-design-center/system-board/6300.html/tb_tab2
*
*******************************************************************************/
/*******************************************************************************
* Project: MAXREFDES117#
* Filename: max30102.h
* Description: This module is an embedded controller driver for the MAX30102
*******************************************************************************/
/*******************************************************************************
* Copyright (C) 2016 Maxim Integrated Products, Inc., All Rights Reserved.
*
* 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 MAXIM INTEGRATED 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.
*
* Except as contained in this notice, the name of Maxim Integrated
* Products, Inc. shall not be used except as stated in the Maxim Integrated
* Products, Inc. Branding Policy.
*
* The mere transfer of this software does not imply any licenses
* of trade secrets, proprietary technology, copyrights, patents,
* trademarks, maskwork rights, or any other form of intellectual
* property whatsoever. Maxim Integrated Products, Inc. retains all
* ownership rights.
*******************************************************************************
*/
#ifndef MAX30102_H
#define MAX30102_H
#include "nrf_drv_twi.h"
#define I2C_WRITE_ADDR 0xAE
#define I2C_READ_ADDR 0xAF
#define I2C_DELAY 20
#define I2C_WRITE_DELAY I2C_DELAY
#define I2C_READ_DELAY I2C_DELAY
#define I2C_PAUSE I2C_DELAY
//register addresses
#define REG_INTR_STATUS_1 0x00
#define REG_INTR_STATUS_2 0x01
#define REG_INTR_ENABLE_1 0x02
#define REG_INTR_ENABLE_2 0x03
#define REG_FIFO_WR_PTR 0x04
#define REG_OVF_COUNTER 0x05
#define REG_FIFO_RD_PTR 0x06
#define REG_FIFO_DATA 0x07
#define REG_FIFO_CONFIG 0x08
#define REG_MODE_CONFIG 0x09
#define REG_SPO2_CONFIG 0x0A
#define REG_LED1_PA 0x0C
#define REG_LED2_PA 0x0D
#define REG_PILOT_PA 0x10
#define REG_MULTI_LED_CTRL1 0x11
#define REG_MULTI_LED_CTRL2 0x12
#define REG_TEMP_INTR 0x1F
#define REG_TEMP_FRAC 0x20
#define REG_TEMP_CONFIG 0x21
#define REG_PROX_INT_THRESH 0x30
#define REG_REV_ID 0xFE
#define REG_PART_ID 0xFF
#define REG_MODE_RESET_VALUE 0x40
ret_code_t maxim_max30102_init(nrf_drv_twi_t const * instance);
ret_code_t maxim_max30102_read_fifo(nrf_drv_twi_t const * instance, uint32_t *red_led, uint32_t *ir_led);
ret_code_t maxim_max30102_write_reg(nrf_drv_twi_t const * instance, uint8_t addr, uint8_t data);
ret_code_t maxim_max30102_read_reg(nrf_drv_twi_t const * instance, uint8_t addr, uint8_t *data);
#endif /* MAX30102_H */
Ported library for Maxim 310102 sensor
C/C++/*******************************************************************************
* This is algorithm.cpp from MAXREFDES117# ported for nrf52840 DK
* Please refer to
* https://www.maximintegrated.com/en/design/reference-design-center/system-board/6300.html/tb_tab2
*
*******************************************************************************/
/*******************************************************************************
* Project: MAXREFDES117#
* Filename: algorithm.cpp
* Description: This module is an embedded controller driver for the MAX30102
*******************************************************************************/
/*******************************************************************************
* Copyright (C) 2015 Maxim Integrated Products, Inc., All Rights Reserved.
*
* 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 MAXIM INTEGRATED 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.
*
* Except as contained in this notice, the name of Maxim Integrated
* Products, Inc. shall not be used except as stated in the Maxim Integrated
* Products, Inc. Branding Policy.
*
* The mere transfer of this software does not imply any licenses
* of trade secrets, proprietary technology, copyrights, patents,
* trademarks, maskwork rights, or any other form of intellectual
* property whatsoever. Maxim Integrated Products, Inc. retains all
* ownership rights.
*******************************************************************************/
#include "algorithm.h"
//#include "mbed.h"
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)
/**
* \brief Calculate the heart rate and SpO2 level
* \par Details
* By detecting peaks of PPG cycle and corresponding AC/DC of red/infra-red signal, the ratio for the SPO2 is computed.
* Since this algorithm is aiming for Arm M0/M3. formaula for SPO2 did not achieve the accuracy due to register overflow.
* Thus, accurate SPO2 is precalculated and save longo uch_spo2_table[] per each ratio.
*
* \param[in] *pun_ir_buffer - IR sensor data buffer
* \param[in] n_ir_buffer_length - IR sensor data buffer length
* \param[in] *pun_red_buffer - Red sensor data buffer
* \param[out] *pn_spo2 - Calculated SpO2 value
* \param[out] *pch_spo2_valid - 1 if the calculated SpO2 value is valid
* \param[out] *pn_heart_rate - Calculated heart rate value
* \param[out] *pch_hr_valid - 1 if the calculated heart rate value is valid
*
* \retval None
*/
{
uint32_t un_ir_mean ,un_only_once ;
int32_t k ,n_i_ratio_count;
int32_t i, s, m, n_exact_ir_valley_locs_count ,n_middle_idx;
int32_t n_th1, n_npks,n_c_min;
int32_t an_ir_valley_locs[15] ;
int32_t an_exact_ir_valley_locs[15] ;
int32_t an_dx_peak_locs[15] ;
int32_t n_peak_interval_sum;
int32_t n_y_ac, n_x_ac;
int32_t n_spo2_calc;
int32_t n_y_dc_max, n_x_dc_max;
int32_t n_y_dc_max_idx, n_x_dc_max_idx;
int32_t an_ratio[5],n_ratio_average;
int32_t n_nume, n_denom ;
// remove DC of ir signal
un_ir_mean =0;
for (k=0 ; k<n_ir_buffer_length ; k++ ) un_ir_mean += pun_ir_buffer[k] ;
un_ir_mean =un_ir_mean/n_ir_buffer_length ;
for (k=0 ; k<n_ir_buffer_length ; k++ ) an_x[k] = pun_ir_buffer[k] - un_ir_mean ;
// 4 pt Moving Average
for(k=0; k< BUFFER_SIZE-MA4_SIZE; k++){
n_denom= ( an_x[k]+an_x[k+1]+ an_x[k+2]+ an_x[k+3]);
an_x[k]= n_denom/(int32_t)4;
}
// get difference of smoothed IR signal
for( k=0; k<BUFFER_SIZE-MA4_SIZE-1; k++)
an_dx[k]= (an_x[k+1]- an_x[k]);
// 2-pt Moving Average to an_dx
for(k=0; k< BUFFER_SIZE-MA4_SIZE-2; k++){
an_dx[k] = ( an_dx[k]+an_dx[k+1])/2 ;
}
// hamming window
// flip wave form so that we can detect valley with peak detector
for ( i=0 ; i<BUFFER_SIZE-HAMMING_SIZE-MA4_SIZE-2 ;i++){
s= 0;
for( k=i; k<i+ HAMMING_SIZE ;k++){
s -= an_dx[k] *auw_hamm[k-i] ;
}
an_dx[i]= s/ (int32_t)1146; // divide by sum of auw_hamm
}
n_th1=0; // threshold calculation
for ( k=0 ; k<BUFFER_SIZE-HAMMING_SIZE ;k++){
n_th1 += ((an_dx[k]>0)? an_dx[k] : ((int32_t)0-an_dx[k])) ;
}
n_th1= n_th1/ ( BUFFER_SIZE-HAMMING_SIZE);
// peak location is acutally index for sharpest location of raw signal since we flipped the signal
maxim_find_peaks( an_dx_peak_locs, &n_npks, an_dx, BUFFER_SIZE-HAMMING_SIZE, n_th1, 8, 5 );//peak_height, peak_distance, max_num_peaks
n_peak_interval_sum =0;
if (n_npks>=2){
for (k=1; k<n_npks; k++)
n_peak_interval_sum += (an_dx_peak_locs[k]-an_dx_peak_locs[k -1]);
n_peak_interval_sum=n_peak_interval_sum/(n_npks-1);
*pn_heart_rate=(int32_t)(6000/n_peak_interval_sum);// beats per minutes
*pch_hr_valid = 1;
}
else {
*pn_heart_rate = -999;
*pch_hr_valid = 0;
}
for ( k=0 ; k<n_npks ;k++)
an_ir_valley_locs[k]=an_dx_peak_locs[k]+HAMMING_SIZE/2;
// raw value : RED(=y) and IR(=X)
// we need to assess DC and AC value of ir and red PPG.
for (k=0 ; k<n_ir_buffer_length ; k++ ) {
an_x[k] = pun_ir_buffer[k] ;
an_y[k] = pun_red_buffer[k] ;
}
// find precise min near an_ir_valley_locs
n_exact_ir_valley_locs_count =0;
for(k=0 ; k<n_npks ;k++){
un_only_once =1;
m=an_ir_valley_locs[k];
n_c_min= 16777216;//2^24;
if (m+5 < BUFFER_SIZE-HAMMING_SIZE && m-5 >0){
for(i= m-5;i<m+5; i++)
if (an_x[i]<n_c_min){
if (un_only_once >0){
un_only_once =0;
}
n_c_min= an_x[i] ;
an_exact_ir_valley_locs[k]=i;
}
if (un_only_once ==0)
n_exact_ir_valley_locs_count ++ ;
}
}
if (n_exact_ir_valley_locs_count <2 ){
*pn_spo2 = -999 ; // do not use SPO2 since signal ratio is out of range
*pch_spo2_valid = 0;
return;
}
// 4 pt MA
for(k=0; k< BUFFER_SIZE-MA4_SIZE; k++){
an_x[k]=( an_x[k]+an_x[k+1]+ an_x[k+2]+ an_x[k+3])/(int32_t)4;
an_y[k]=( an_y[k]+an_y[k+1]+ an_y[k+2]+ an_y[k+3])/(int32_t)4;
}
//using an_exact_ir_valley_locs , find ir-red DC andir-red AC for SPO2 calibration ratio
//finding AC/DC maximum of raw ir * red between two valley locations
n_ratio_average =0;
n_i_ratio_count =0;
for(k=0; k< 5; k++) an_ratio[k]=0;
for (k=0; k< n_exact_ir_valley_locs_count; k++){
if (an_exact_ir_valley_locs[k] > BUFFER_SIZE ){
*pn_spo2 = -999 ; // do not use SPO2 since valley loc is out of range
*pch_spo2_valid = 0;
return;
}
}
// find max between two valley locations
// and use ratio betwen AC compoent of Ir & Red and DC compoent of Ir & Red for SPO2
for (k=0; k< n_exact_ir_valley_locs_count-1; k++){
n_y_dc_max= -16777216 ;
n_x_dc_max= - 16777216;
if (an_exact_ir_valley_locs[k+1]-an_exact_ir_valley_locs[k] >10){
for (i=an_exact_ir_valley_locs[k]; i< an_exact_ir_valley_locs[k+1]; i++){
if (an_x[i]> n_x_dc_max) {n_x_dc_max =an_x[i];n_x_dc_max_idx =i; }
if (an_y[i]> n_y_dc_max) {n_y_dc_max =an_y[i];n_y_dc_max_idx=i;}
}
n_y_ac= (an_y[an_exact_ir_valley_locs[k+1]] - an_y[an_exact_ir_valley_locs[k] ] )*(n_y_dc_max_idx -an_exact_ir_valley_locs[k]); //red
n_y_ac= an_y[an_exact_ir_valley_locs[k]] + n_y_ac/ (an_exact_ir_valley_locs[k+1] - an_exact_ir_valley_locs[k]) ;
n_y_ac= an_y[n_y_dc_max_idx] - n_y_ac; // subracting linear DC compoenents from raw
n_x_ac= (an_x[an_exact_ir_valley_locs[k+1]] - an_x[an_exact_ir_valley_locs[k] ] )*(n_x_dc_max_idx -an_exact_ir_valley_locs[k]); // ir
n_x_ac= an_x[an_exact_ir_valley_locs[k]] + n_x_ac/ (an_exact_ir_valley_locs[k+1] - an_exact_ir_valley_locs[k]);
n_x_ac= an_x[n_y_dc_max_idx] - n_x_ac; // subracting linear DC compoenents from raw
n_nume=( n_y_ac *n_x_dc_max)>>7 ; //prepare X100 to preserve floating value
n_denom= ( n_x_ac *n_y_dc_max)>>7;
if (n_denom>0 && n_i_ratio_count <5 && n_nume != 0)
{
an_ratio[n_i_ratio_count]= (n_nume*100)/n_denom ; //formular is ( n_y_ac *n_x_dc_max) / ( n_x_ac *n_y_dc_max) ;
n_i_ratio_count++;
}
}
}
maxim_sort_ascend(an_ratio, n_i_ratio_count);
n_middle_idx= n_i_ratio_count/2;
if (n_middle_idx >1)
n_ratio_average =( an_ratio[n_middle_idx-1] +an_ratio[n_middle_idx])/2; // use median
else
n_ratio_average = an_ratio[n_middle_idx ];
if( n_ratio_average>2 && n_ratio_average <184){
n_spo2_calc= uch_spo2_table[n_ratio_average] ;
*pn_spo2 = n_spo2_calc ;
*pch_spo2_valid = 1;// float_SPO2 = -45.060*n_ratio_average* n_ratio_average/10000 + 30.354 *n_ratio_average/100 + 94.845 ; // for comparison with table
}
else{
*pn_spo2 = -999 ; // do not use SPO2 since signal ratio is out of range
*pch_spo2_valid = 0;
}
}
void maxim_find_peaks(int32_t *pn_locs, int32_t *pn_npks, int32_t *pn_x, int32_t n_size, int32_t n_min_height, int32_t n_min_distance, int32_t n_max_num)
/**
* \brief Find peaks
* \par Details
* Find at most MAX_NUM peaks above MIN_HEIGHT separated by at least MIN_DISTANCE
*
* \retval None
*/
{
maxim_peaks_above_min_height( pn_locs, pn_npks, pn_x, n_size, n_min_height );
maxim_remove_close_peaks( pn_locs, pn_npks, pn_x, n_min_distance );
*pn_npks = min( *pn_npks, n_max_num );
}
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++;
if (pn_x[i] > pn_x[i+n_width] && (*pn_npks) < 15 ){ // find right edge of peaks
pn_locs[(*pn_npks)++] = i;
// for flat peaks, peak location is left edge
i += n_width+1;
}
else
i += n_width;
}
else
i++;
}
}
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_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;
}
}
Ported library for Maxim 310102 sensor
C/C++/*******************************************************************************
* This is algorithm.h from MAXREFDES117# ported for nrf52840 DK
* Please refer to
* https://www.maximintegrated.com/en/design/reference-design-center/system-board/6300.html/tb_tab2
*
*******************************************************************************/
/*******************************************************************************
* Project: MAXREFDES117#
* Filename: algorithm.h
* Description: This module is an embedded controller driver for the MAX30102
*******************************************************************************/
/*******************************************************************************
* Copyright (C) 2015 Maxim Integrated Products, Inc., All Rights Reserved.
*
* 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 MAXIM INTEGRATED 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.
*
* Except as contained in this notice, the name of Maxim Integrated
* Products, Inc. shall not be used except as stated in the Maxim Integrated
* Products, Inc. Branding Policy.
*
* The mere transfer of this software does not imply any licenses
* of trade secrets, proprietary technology, copyrights, patents,
* trademarks, maskwork rights, or any other form of intellectual
* property whatsoever. Maxim Integrated Products, Inc. retains all
* ownership rights.
*******************************************************************************/
#ifndef ALGORITHM_H
#define ALGORITHM_H
//#include "mbed.h"
#include <stdint.h>
//#define true 1
//#define false 0
#define FS 100
#define BUFFER_SIZE (FS* 5)
#define HR_FIFO_SIZE 7
#define MA4_SIZE 4 // DO NOT CHANGE
#define HAMMING_SIZE 5// DO NOT CHANGE
#define min(x,y) ((x) < (y) ? (x) : (y))
static 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 ;
static 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);
void maxim_find_peaks(int32_t *pn_locs, int32_t *pn_npks, int32_t *pn_x, int32_t n_size, int32_t n_min_height, int32_t n_min_distance, int32_t n_max_num);
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);
void maxim_remove_close_peaks(int32_t *pn_locs, int32_t *pn_npks, int32_t *pn_x, int32_t n_min_distance);
void maxim_sort_ascend(int32_t *pn_x, int32_t n_size);
void maxim_sort_indices_descend(int32_t *pn_x, int32_t *pn_indx, int32_t n_size);
#endif /* ALGORITHM_H */
Comments