Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
protocentral-healthypi-v3/firmware/healthypi3_streaming/healthypi3_streaming.ino
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
1560 lines (1307 sloc)
46.3 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| ////////////////////////////////////////////////////////////////////////////////////////// | |
| // | |
| // HealthyPi HAT v3 Arduino Firmware | |
| // | |
| // Copyright (c) 2016 ProtoCentral | |
| // | |
| // Heartrate and respiration computation based on original code from Texas Instruments | |
| // | |
| // SpO2 computation based on original code from Maxim Integrated | |
| // | |
| // This software is licensed under the MIT License(http://opensource.org/licenses/MIT). | |
| // | |
| // 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 THE AUTHORS OR COPYRIGHT HOLDERS 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. | |
| // | |
| // For information on how to use the HealthyPi, visit https://github.com/protocentral/protocentral-healthypi-v3 | |
| ///////////////////////////////////////////////////////////////////////////////////////// | |
| #include <SPI.h> | |
| #include <Wire.h> | |
| #include "wiring_private.h" // pinPeripheral() function | |
| #include "ads1292r.h" | |
| #include "max30205.h" | |
| #define TEMPERATURE 0 | |
| #define FILTERORDER 161 | |
| /* DC Removal Numerator Coeff*/ | |
| //#define NRCOEFF (0.992) | |
| #define WAVE_SIZE 1 | |
| #define NRCOEFF (0.992) | |
| #define IOPORT_PIN_LEVEL_HIGH 1 | |
| #define IOPORT_PIN_LEVEL_LOW 0 | |
| #define ADS1292R_CS_PIN 3 | |
| #define ADS1292R_START_PIN 10 | |
| #define ADS1292R_PWDN_PIN 12 | |
| #define ADS1292R_DRDY_PIN A4 | |
| #define AFE4490_START_PIN 16//A2 | |
| #define AFE4490_CS_PIN 15//A1 | |
| #define AFE4490_DRDY_PIN A0 | |
| //******* ecg filter ********* | |
| #define MAX_PEAK_TO_SEARCH 5 | |
| #define MAXIMA_SEARCH_WINDOW 40 | |
| #define MINIMUM_SKIP_WINDOW 50 | |
| #define SAMPLING_RATE 125 | |
| #define TWO_SEC_SAMPLES 2 * SAMPLING_RATE | |
| #define QRS_THRESHOLD_FRACTION 0.4 | |
| #define TRUE 1 | |
| #define FALSE 0 | |
| //SPI class(MISO | |
| SPIClass ads1292rSPI (&sercom2, 4, 0, 1, SPI_PAD_2_SCK_3, SERCOM_RX_PAD_0); | |
| //SPI class(MISO | |
| SPIClass afe4490SPI (&sercom0, 17, 9 , 8, SPI_PAD_2_SCK_3, SERCOM_RX_PAD_0); | |
| //SPI class(MISO | |
| Uart RpiSerial(&sercom4, 38, 22 , SERCOM_RX_PAD_1, UART_TX_PAD_0); | |
| TwoWire max30205(&sercom1, 11, 13); | |
| int8_t NewDataAvailable; | |
| uint8_t data_len = 20; | |
| volatile byte MSB; | |
| volatile byte data; | |
| volatile byte LSB; | |
| volatile byte *SPI_RX_Buff_Ptr; | |
| unsigned long resultTemp = 0; | |
| signed long sresultTemp,sresultTempResp ; | |
| volatile float fresultTemp = 0; | |
| volatile long iresultTemp = 0; | |
| volatile long voltageTemp = 0; | |
| volatile bool ads1292DataReceived = false; | |
| volatile uint8_t SPI_Dummy_Buff[10]; | |
| volatile long count=0; | |
| uint8_t AFE4490_SPI_TX_Buff[10]; | |
| volatile uint8_t AFE4490_SPI_RX_Buff[3]={0,0,0}; | |
| volatile uint32_t AFE4490resultTemp = 0; | |
| volatile int32_t AFE4490sresultTempIR=0,AFE4490sresultTempRED=0; | |
| volatile float AFE4490fresultTemp = 0; | |
| volatile int32_t AFE4490iresultTemp = 0; | |
| volatile int32_t AFE4490voltageTemp = 0; | |
| volatile bool afe4490DataReceived = false; | |
| long v[9]; | |
| long sresultTempECG; | |
| uint8_t heartRate_ads1292=0,heartRate_BP=0,spo2=0; | |
| //************ filter *************** | |
| uint8_t Respiration_Rate = 0 ; | |
| int RESP_Second_Prev_Sample = 0 ; | |
| int RESP_Prev_Sample = 0 ; | |
| int RESP_Current_Sample = 0 ; | |
| int RESP_Next_Sample = 0 ; | |
| int RESP_Second_Next_Sample = 0 ; | |
| uint16_t iprocess=0; | |
| int16_t RESP_WorkingBuff[2 * FILTERORDER]; | |
| int16_t Pvev_DC_Sample=0, Pvev_Sample=0; | |
| int16_t ECG_Pvev_DC_Sample, ECG_Pvev_Sample; | |
| int16_t RespCoeffBuf[FILTERORDER] = | |
| { | |
| /* Coeff for lowpass Fc=2Hz @ 125 SPS*/ | |
| 120, 124, 126, 127, 127, 125, 122, 118, 113, | |
| 106, 97, 88, 77, 65, 52, 38, 24, 8, | |
| -8, -25, -42, -59, -76, -93, -110, -126, -142, | |
| -156, -170, -183, -194, -203, -211, -217, -221, -223, | |
| -223, -220, -215, -208, -198, -185, -170, -152, -132, | |
| -108, -83, -55, -24, 8, 43, 80, 119, 159, | |
| 201, 244, 288, 333, 378, 424, 470, 516, 561, | |
| 606, 650, 693, 734, 773, 811, 847, 880, 911, | |
| 939, 964, 986, 1005, 1020, 1033, 1041, 1047, 1049, | |
| 1047, 1041, 1033, 1020, 1005, 986, 964, 939, 911, | |
| 880, 847, 811, 773, 734, 693, 650, 606, 561, | |
| 516, 470, 424, 378, 333, 288, 244, 201, 159, | |
| 119, 80, 43, 8, -24, -55, -83, -108, -132, | |
| -152, -170, -185, -198, -208, -215, -220, -223, -223, | |
| -221, -217, -211, -203, -194, -183, -170, -156, -142, | |
| -126, -110, -93, -76, -59, -42, -25, -8, 8, | |
| 24, 38, 52, 65, 77, 88, 97, 106, 113, | |
| 118, 122, 125, 127, 127, 126, 124, 120 | |
| }; | |
| int16_t CoeffBuf_40Hz_LowPass[FILTERORDER] = | |
| { | |
| -72, 122, -31, -99, 117, 0, -121, 105, 34, | |
| -137, 84, 70, -146, 55, 104, -147, 20, 135, | |
| -137, -21, 160, -117, -64, 177, -87, -108, 185, | |
| -48, -151, 181, 0, -188, 164, 54, -218, 134, | |
| 112, -238, 90, 171, -244, 33, 229, -235, -36, | |
| 280, -208, -115, 322, -161, -203, 350, -92, -296, | |
| 361, 0, -391, 348, 117, -486, 305, 264, -577, | |
| 225, 445, -660, 93, 676, -733, -119, 991, -793, | |
| -480, 1486, -837, -1226, 2561, -865, -4018, 9438, 20972, | |
| 9438, -4018, -865, 2561, -1226, -837, 1486, -480, -793, | |
| 991, -119, -733, 676, 93, -660, 445, 225, -577, | |
| 264, 305, -486, 117, 348, -391, 0, 361, -296, | |
| -92, 350, -203, -161, 322, -115, -208, 280, -36, | |
| -235, 229, 33, -244, 171, 90, -238, 112, 134, | |
| -218, 54, 164, -188, 0, 181, -151, -48, 185, | |
| -108, -87, 177, -64, -117, 160, -21, -137, 135, | |
| 20, -147, 104, 55, -146, 70, 84, -137, 34, | |
| 105, -121, 0, 117, -99, -31, 122, -72 | |
| }; | |
| volatile int16_t i16respbuff; | |
| uint16_t resp_buff_indx=0; | |
| int16_t res_wave_buff; | |
| int16_t resp_filterout; | |
| uint8_t DataPacket[100]; | |
| uint16_t k=0; | |
| uint8_t i = 0; | |
| #define FS 25 //sampling frequency | |
| #define BUFFER_SIZE (FS*4) | |
| #define MA4_SIZE 4 // DONOT CHANGE | |
| #define min(x,y) ((x) < (y) ? (x) : (y)) | |
| 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_x[ BUFFER_SIZE]; | |
| static int32_t an_y[ BUFFER_SIZE]; | |
| int16_t ECG_WorkingBuff[2 * FILTERORDER]; | |
| unsigned char Start_Sample_Count_Flag = 0; | |
| unsigned char first_peak_detect = FALSE ; | |
| unsigned int sample_index[MAX_PEAK_TO_SEARCH+2] ; | |
| uint16_t scaled_result_display[150]; | |
| uint8_t indx =0; | |
| int cnt =0; | |
| volatile uint8_t flag =0; | |
| int QRS_Second_Prev_Sample = 0 ; | |
| int QRS_Prev_Sample = 0 ; | |
| int QRS_Current_Sample = 0 ; | |
| int QRS_Next_Sample = 0 ; | |
| int QRS_Second_Next_Sample = 0 ; | |
| static uint16_t QRS_B4_Buffer_ptr = 0 ; | |
| int16_t QRS_Threshold_Old = 0; | |
| int16_t QRS_Threshold_New = 0; | |
| unsigned int sample_count = 0 ; | |
| uint16_t QRS_Heart_Rate = 0 ; | |
| int16_t ecg_wave_buff,ecg_filterout; | |
| volatile uint8_t global_HeartRate=0; | |
| volatile uint8_t global_RespirationRate=0; | |
| uint16_t aun_ir_buffer[600]; //infrared LED sensor data | |
| uint16_t aun_red_buffer[600]; //red LED sensor data | |
| int32_t n_buffer_count; //data length | |
| int32_t n_spo2; //SPO2 value | |
| int8_t ch_spo2_valid; //indicator to show if the SPO2 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 | |
| long status_byte=0; | |
| uint8_t LeadStatus=0; | |
| boolean leadoff_deteted = true; | |
| uint8_t spo2_probe_open = false; | |
| long time_elapsed =0; | |
| void ads1292_detection_callback(void) | |
| { | |
| digitalWrite(ADS1292R_CS_PIN,LOW); | |
| for (int x = 0; x < 9 ; x++) | |
| { | |
| SPI_Dummy_Buff[x] = ads1292rSPI.transfer(0xff); | |
| // SerialUSB.print(SPI_Dummy_Buff[x],HEX); | |
| } | |
| status_byte = (long)((long)SPI_Dummy_Buff[2] | ((long) SPI_Dummy_Buff[1]) <<8 | ((long) SPI_Dummy_Buff[0])<<16); | |
| status_byte = (status_byte & 0x0f8000) >> 15; | |
| LeadStatus = (unsigned char ) status_byte ; | |
| if(!((LeadStatus & 0x1f) == 0 )) | |
| leadoff_deteted = TRUE; | |
| else leadoff_deteted = FALSE; | |
| digitalWrite(ADS1292R_CS_PIN,HIGH); | |
| resultTemp = (unsigned long)((0x00 << 24) | (SPI_Dummy_Buff[6] << 16)| SPI_Dummy_Buff[7] << 8 | SPI_Dummy_Buff[8]);//6,7,8 | |
| resultTemp = (unsigned long)(resultTemp << 8); | |
| sresultTemp = (signed long)(resultTemp); | |
| ecg_wave_buff = (int16_t) (sresultTemp>>16); // store for TI's algorithm | |
| resultTemp = (uint32_t)((0 << 24) | (SPI_Dummy_Buff[3] << 16)| SPI_Dummy_Buff[4] << 8 | SPI_Dummy_Buff[5]);//6,7,8 | |
| resultTemp = (uint32_t)(resultTemp << 8); | |
| sresultTempResp = (long)(resultTemp); | |
| res_wave_buff = (int16_t)(sresultTempResp>>8) ; | |
| ads1292DataReceived = true; | |
| } | |
| void afe4490_detection_callback(void) | |
| { | |
| afe4490DataReceived = true; | |
| count++; | |
| } | |
| void setup() | |
| { | |
| int cnt; | |
| delay(2000); | |
| SerialUSB.begin(115200); | |
| RpiSerial.begin(57600); | |
| max30205begin(); | |
| ads1292Rbegin(); | |
| delay(100); | |
| afe4490begin(); | |
| delay(500); | |
| AFE4490Write(CONTROL0,0x000001); | |
| n_buffer_count=0; | |
| for (cnt =0 ; cnt < FILTERORDER; cnt++) | |
| { | |
| RESP_WorkingBuff[cnt] = 0; | |
| ECG_WorkingBuff[cnt] = 0; | |
| } | |
| Pvev_DC_Sample = 0; | |
| Pvev_Sample = 0; | |
| ECG_Pvev_DC_Sample = 0; | |
| ECG_Pvev_Sample = 0; | |
| } | |
| int dec=0; | |
| void loop() | |
| { | |
| if(ads1292DataReceived == true) | |
| { | |
| if(leadoff_deteted == true) | |
| { | |
| if(millis() > time_elapsed ) | |
| leadoff_deteted = true; | |
| else leadoff_deteted = false; | |
| } | |
| else | |
| { | |
| time_elapsed = millis() + 2000; | |
| } | |
| float temp = getTemperature()*100; // read temperature for every 100ms | |
| int temperature = (uint16_t) temp; | |
| resp_filterout = Resp_ProcessCurrSample(res_wave_buff); | |
| //resp_filterout=res_wave_buff; | |
| RESP_Algorithm_Interface(resp_filterout); | |
| //ecg_filterout=ecg_wave_buff; | |
| ecg_filterout = ECG_ProcessCurrSample(ecg_wave_buff); | |
| QRS_Algorithm_Interface(ecg_filterout); | |
| DataPacket[0] = 0x0A; // sync0 | |
| DataPacket[1] = 0xFA; | |
| DataPacket[2] = (uint8_t) (data_len); | |
| DataPacket[3] = (uint8_t) (data_len>>8); | |
| DataPacket[4] = 0x02; | |
| if(leadoff_deteted == true) | |
| { | |
| DataPacket[5] = 0; | |
| DataPacket[6] = 0; | |
| DataPacket[7] = 0; | |
| DataPacket[8] = 0; | |
| } | |
| else | |
| { | |
| DataPacket[5] = ecg_filterout; | |
| DataPacket[6] = ecg_filterout>>8; | |
| DataPacket[7] = resp_filterout; | |
| DataPacket[8] = resp_filterout>>8; | |
| } | |
| if(1) // if(afe4490DataReceived == true) | |
| { | |
| AFE4490_SPI_TX_Buff[0] = 0x2C; | |
| digitalWrite(AFE4490_CS_PIN,LOW); | |
| afe4490SPI.transfer(AFE4490_SPI_TX_Buff[0]); | |
| AFE4490_SPI_RX_Buff[0] = afe4490SPI.transfer(0xff); | |
| AFE4490_SPI_RX_Buff[1] = afe4490SPI.transfer(0xff); | |
| AFE4490_SPI_RX_Buff[2] = afe4490SPI.transfer(0xff); | |
| AFE4490resultTemp = (uint32_t)( ((0x00 << 24) | AFE4490_SPI_RX_Buff[0] << 16)| AFE4490_SPI_RX_Buff[1] << 8 | AFE4490_SPI_RX_Buff[2]); | |
| AFE4490resultTemp = (uint32_t)(AFE4490resultTemp << 10); | |
| if(dec==5) | |
| { | |
| aun_ir_buffer[n_buffer_count]=(uint16_t) (AFE4490resultTemp>>16); | |
| } | |
| AFE4490sresultTempIR = (int32_t)(AFE4490resultTemp ); | |
| //AFE4490sresultTempIR = (AFE4490sresultTempIR);// * 100; | |
| AFE4490_SPI_TX_Buff[0] = 0x2A; | |
| afe4490SPI.transfer(AFE4490_SPI_TX_Buff[0]); | |
| AFE4490_SPI_RX_Buff[0] = afe4490SPI.transfer(0xff); | |
| AFE4490_SPI_RX_Buff[1] = afe4490SPI.transfer(0xff); | |
| AFE4490_SPI_RX_Buff[2] = afe4490SPI.transfer(0xff); | |
| AFE4490resultTemp = (uint32_t)( ((0x00 << 24) | AFE4490_SPI_RX_Buff[0] << 16)| AFE4490_SPI_RX_Buff[1] << 8 | AFE4490_SPI_RX_Buff[2]); | |
| AFE4490resultTemp = (uint32_t)(AFE4490resultTemp << 10); | |
| if(dec==5) | |
| { | |
| aun_red_buffer[n_buffer_count]=(uint16_t) (AFE4490resultTemp>>16); | |
| dec=0; | |
| n_buffer_count++; | |
| } | |
| dec++; | |
| AFE4490sresultTempRED = (int32_t)(AFE4490resultTemp ); | |
| AFE4490sresultTempRED = (AFE4490sresultTempRED >> 10);// * 100; | |
| digitalWrite(AFE4490_CS_PIN,HIGH); | |
| if(n_buffer_count>99) | |
| { | |
| estimate_spo2(aun_ir_buffer, 100, aun_red_buffer, &n_spo2, &ch_spo2_valid); | |
| if(n_spo2 == -999) | |
| spo2_probe_open = true; | |
| else | |
| spo2_probe_open = false; | |
| n_buffer_count=0; | |
| } | |
| } | |
| DataPacket[9] = AFE4490sresultTempIR; // spo2 ir | |
| DataPacket[10] = AFE4490sresultTempIR>>8; | |
| DataPacket[11] = AFE4490sresultTempIR>>16; | |
| DataPacket[12] = AFE4490sresultTempIR>>24; | |
| DataPacket[13] = AFE4490sresultTempRED; // spo2 ir | |
| DataPacket[14] = AFE4490sresultTempRED>>8; | |
| DataPacket[15] = AFE4490sresultTempRED>>16; | |
| DataPacket[16] = AFE4490sresultTempRED>>24; | |
| DataPacket[17] = (uint8_t) temperature; // temperature | |
| DataPacket[18] = (uint8_t) (temperature >> 8); | |
| DataPacket[19] = global_RespirationRate; //QRS_Heart_Rate; // calculated from ads1292r | |
| DataPacket[20] = n_spo2; | |
| DataPacket[21] = global_HeartRate; // systolic pressure | |
| DataPacket[22] = 80; //diastolic pressure | |
| DataPacket[23] = heartRate_BP; // from BP module | |
| DataPacket[24] = spo2_probe_open<< 1 |leadoff_deteted; // leadstatus | |
| DataPacket[25] = 0x00; | |
| DataPacket[26] = 0x0b; | |
| for(int i=0; i<27; i++) // transmit the data | |
| { | |
| SerialUSB.write(DataPacket[i]); | |
| RpiSerial.write(DataPacket[i]); | |
| } | |
| ads1292DataReceived = false; | |
| } | |
| } | |
| void ads1292Rbegin() | |
| { | |
| ads1292rSPI.begin(); | |
| pinMode(ADS1292R_START_PIN, OUTPUT); | |
| pinMode(ADS1292R_DRDY_PIN, INPUT); | |
| pinMode(ADS1292R_DRDY_PIN, INPUT_PULLUP); | |
| attachInterrupt(ADS1292R_DRDY_PIN, ads1292_detection_callback , FALLING); | |
| pinMode(ADS1292R_PWDN_PIN, OUTPUT); | |
| pinMode(ADS1292R_CS_PIN, OUTPUT); | |
| pinPeripheral(4, PIO_SERCOM_ALT); | |
| pinPeripheral(0, PIO_SERCOM_ALT); | |
| pinPeripheral(1, PIO_SERCOM_ALT); | |
| ads1292rSPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE1)); | |
| SPI_Reset(); | |
| delay(100); | |
| ADS1292_Disable_Start(); | |
| delay(100); | |
| ADS1292_Enable_Start(); | |
| delay(10); | |
| ADS1292_Hard_Stop(); | |
| ADS1292_Start_Data_Conv_Command(); | |
| ADS1292_Soft_Stop(); | |
| digitalWrite(ADS1292R_CS_PIN,LOW); | |
| ADS1292_Reg_Write(0x01,0x00); //Set sampling rate to 125 SPS | |
| delay(10); | |
| ADS1292_Reg_Write(0x02,0b11100000); //Lead-off comp off, test signal disabled | |
| delay(10); | |
| ADS1292_Reg_Write(0x03,0b11110000); //Lead-off defaults | |
| delay(10); | |
| ADS1292_Reg_Write(0x04,0b01000000); //Ch 1 enabled, gain 6, connected to electrode in | |
| delay(10); | |
| ADS1292_Reg_Write(0x05,0b01100000); //Ch 2 enabled, gain 6, connected to electrode in | |
| delay(10); | |
| ADS1292_Reg_Write(0x06,0b00101100); //RLD settings: fmod/16, RLD enabled, RLD inputs from Ch2 only | |
| delay(10); | |
| ADS1292_Reg_Write(0x07,0x00); //LOFF settings: all disabled | |
| delay(10); | |
| //Skip register 8, LOFF Settings default | |
| ADS1292_Reg_Write(0x09,0b11110010); //Respiration: MOD/DEMOD turned only, phase 0 | |
| delay(10); | |
| ADS1292_Reg_Write(0x0A,0b00000011); //Respiration: Calib OFF, respiration freq defaults | |
| delay(10); | |
| ADS1292_Enable_Start(); | |
| ADS1292_Start_Read_Data_Continuous(); | |
| digitalWrite(ADS1292R_CS_PIN,HIGH); //release chip, signal end transfer | |
| SPI_Start(); | |
| delay(100); | |
| } | |
| void writeRegister(uint8_t address, uint8_t value) | |
| { | |
| digitalWrite(ADS1292R_CS_PIN,LOW); | |
| delay(5); | |
| ads1292rSPI.transfer(WREG|(address<<2)); | |
| ads1292rSPI.transfer(value); | |
| delay(5); | |
| digitalWrite(ADS1292R_CS_PIN,HIGH); | |
| } | |
| uint8_t readRegister(uint8_t address) | |
| { | |
| uint8_t data; | |
| digitalWrite(ADS1292R_CS_PIN,LOW); | |
| delay(5); | |
| ads1292rSPI.transfer(RREG|(address<<2)); | |
| // data = ads1292rSPI.transfer(SPI_MASTER_DUMMY); | |
| delay(5); | |
| digitalWrite(ADS1292R_CS_PIN,HIGH); | |
| return data; | |
| } | |
| uint8_t * Read_Data() | |
| { | |
| static byte SPI_Buff[3]; | |
| if((digitalRead(ADS1292R_DRDY_PIN)) == LOW) // Wait for DRDY to transition low | |
| { | |
| digitalWrite(ADS1292R_CS_PIN,LOW); //Take CS low | |
| delayMicroseconds(100); | |
| for (int i = 0; i < 3; i++) | |
| { | |
| // SPI_Buff[i] = ads1292rSPI.transfer(SPI_MASTER_DUMMY); | |
| } | |
| delayMicroseconds(100); | |
| digitalWrite(ADS1292R_CS_PIN,HIGH); // Clear CS to high | |
| NewDataAvailable = true; | |
| } | |
| return SPI_Buff; | |
| } | |
| void SPI_Reset() | |
| { | |
| digitalWrite(ADS1292R_PWDN_PIN, IOPORT_PIN_LEVEL_HIGH); | |
| delay(20); | |
| digitalWrite(ADS1292R_PWDN_PIN, IOPORT_PIN_LEVEL_LOW); | |
| delay(20); | |
| digitalWrite(ADS1292R_PWDN_PIN, IOPORT_PIN_LEVEL_HIGH); | |
| delay(20); | |
| ADS1292_SPI_Command_Data(RESET); | |
| } | |
| void SPI_Start() | |
| { | |
| ADS1292_SPI_Command_Data(START); | |
| } | |
| void SPI_Stop() | |
| { | |
| ADS1292_SPI_Command_Data(STOP); | |
| } | |
| void ADS1292_Reg_Write (unsigned char READ_WRITE_ADDRESS, unsigned char DATA) | |
| { | |
| uint8_t SPI_TX_Buff[10]; | |
| switch (READ_WRITE_ADDRESS) | |
| { | |
| case 1: DATA = DATA & 0x87; | |
| break; | |
| case 2: DATA = DATA & 0xFB; | |
| DATA |= 0x80; | |
| break; | |
| case 3: DATA = DATA & 0xFD; | |
| DATA |= 0x10; | |
| break; | |
| case 7: DATA = DATA & 0x3F; | |
| break; | |
| case 8: DATA = DATA & 0x5F; | |
| break; | |
| case 9:DATA |= 0x02; | |
| break; | |
| case 10:DATA = DATA & 0x87; | |
| DATA |= 0x01; | |
| break; | |
| case 11:DATA = DATA & 0x0F; | |
| break; | |
| default:break; | |
| } | |
| SPI_TX_Buff[0] = READ_WRITE_ADDRESS | WREG; | |
| SPI_TX_Buff[1] = 0; // Write Single byte | |
| SPI_TX_Buff[2] = DATA; // Write Single byte // Write Single byte | |
| digitalWrite(ADS1292R_CS_PIN,LOW); | |
| delay(5); | |
| ads1292rSPI.transfer( SPI_TX_Buff[0]); | |
| ads1292rSPI.transfer(SPI_TX_Buff[1]); | |
| ads1292rSPI.transfer(SPI_TX_Buff[2]); | |
| delay(5); | |
| digitalWrite(ADS1292R_CS_PIN,HIGH); | |
| } | |
| void ADS1292_Enable_Start(void) | |
| { | |
| digitalWrite(ADS1292R_START_PIN, IOPORT_PIN_LEVEL_HIGH); | |
| delay(20); | |
| } | |
| void ADS1292_Disable_Start(void) | |
| { | |
| digitalWrite(ADS1292R_START_PIN, IOPORT_PIN_LEVEL_LOW); | |
| delay(20); | |
| } | |
| void ADS1292_Hard_Stop (void) | |
| { | |
| digitalWrite(ADS1292R_START_PIN, IOPORT_PIN_LEVEL_LOW); | |
| delay(100); | |
| } | |
| void ADS1292_Start_Data_Conv_Command (void) | |
| { | |
| ADS1292_SPI_Command_Data(START); | |
| } | |
| void ADS1292_Soft_Stop (void) | |
| { | |
| ADS1292_SPI_Command_Data(STOP); | |
| } | |
| void ADS1292_Start_Read_Data_Continuous (void) | |
| { | |
| ADS1292_SPI_Command_Data(RDATAC); // Send 0x10 to the ADS1x9x | |
| } | |
| void ADS1292_SPI_Command_Data(unsigned char data_in) | |
| { | |
| digitalWrite(ADS1292R_CS_PIN, LOW); | |
| delay(2); | |
| digitalWrite(ADS1292R_CS_PIN, HIGH); | |
| delay(2); | |
| digitalWrite(ADS1292R_CS_PIN, LOW); | |
| delay(2); | |
| ads1292rSPI.transfer(data_in); | |
| delay(2); | |
| digitalWrite(ADS1292R_CS_PIN, HIGH); | |
| } | |
| void afe4490begin() | |
| { | |
| afe4490SPI.begin(); | |
| delay(100); | |
| pinMode(AFE4490_START_PIN, OUTPUT); | |
| digitalWrite(AFE4490_START_PIN, HIGH); | |
| pinMode(AFE4490_DRDY_PIN, INPUT); | |
| pinMode(AFE4490_DRDY_PIN, INPUT_PULLUP); | |
| attachInterrupt(AFE4490_DRDY_PIN, afe4490_detection_callback , FALLING); | |
| delay(100); | |
| pinMode(AFE4490_CS_PIN, OUTPUT); | |
| pinPeripheral(17, PIO_SERCOM_ALT); | |
| pinPeripheral(9, PIO_SERCOM_ALT); | |
| pinPeripheral(8, PIO_SERCOM_ALT); | |
| delay(100); | |
| afe4490SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0)); | |
| AFE4490Write(CONTROL0,0x000000); | |
| AFE4490Write(CONTROL0,0x000008); | |
| AFE4490Write(TIAGAIN,0x000000); // CF = 5pF, RF = 500kR | |
| AFE4490Write(TIA_AMB_GAIN,0x000001); | |
| AFE4490Write(LEDCNTRL,0x001414); | |
| AFE4490Write(CONTROL2,0x000000); // LED_RANGE=100mA, LED=50mA | |
| AFE4490Write(CONTROL1,0x010707); // Timers ON, average 3 samples | |
| AFE4490Write(PRPCOUNT, 0X001F3F); | |
| AFE4490Write(LED2STC, 0X001770); //timer control | |
| AFE4490Write(LED2ENDC,0X001F3E); //timer control | |
| AFE4490Write(LED2LEDSTC,0X001770); //timer control | |
| AFE4490Write(LED2LEDENDC,0X001F3F); //timer control | |
| AFE4490Write(ALED2STC, 0X000000); //timer control | |
| AFE4490Write(ALED2ENDC, 0X0007CE); //timer control | |
| AFE4490Write(LED2CONVST,0X000002); //timer control | |
| AFE4490Write(LED2CONVEND, 0X0007CF); //timer control | |
| AFE4490Write(ALED2CONVST, 0X0007D2); //timer control | |
| AFE4490Write(ALED2CONVEND,0X000F9F); //timer control | |
| AFE4490Write(LED1STC, 0X0007D0); //timer control | |
| AFE4490Write(LED1ENDC, 0X000F9E); //timer control | |
| AFE4490Write(LED1LEDSTC, 0X0007D0); //timer control | |
| AFE4490Write(LED1LEDENDC, 0X000F9F); //timer control | |
| AFE4490Write(ALED1STC, 0X000FA0); //timer control | |
| AFE4490Write(ALED1ENDC, 0X00176E); //timer control | |
| AFE4490Write(LED1CONVST, 0X000FA2); //timer control | |
| AFE4490Write(LED1CONVEND, 0X00176F); //timer control | |
| AFE4490Write(ALED1CONVST, 0X001772); //timer control | |
| AFE4490Write(ALED1CONVEND, 0X001F3F); //timer control | |
| AFE4490Write(ADCRSTCNT0, 0X000000); //timer control | |
| AFE4490Write(ADCRSTENDCT0,0X000000); //timer control | |
| AFE4490Write(ADCRSTCNT1, 0X0007D0); //timer control | |
| AFE4490Write(ADCRSTENDCT1, 0X0007D0); //timer control | |
| AFE4490Write(ADCRSTCNT2, 0X000FA0); //timer control | |
| AFE4490Write(ADCRSTENDCT2, 0X000FA0); //timer control | |
| AFE4490Write(ADCRSTCNT3, 0X001770); //timer control | |
| AFE4490Write(ADCRSTENDCT3, 0X001770); | |
| } | |
| void AFE4490Write (uint8_t address, uint32_t data) | |
| { | |
| AFE4490_SPI_TX_Buff[0] = address; | |
| AFE4490_SPI_TX_Buff[1] = ((data >> 16) & 0xFF) ; | |
| AFE4490_SPI_TX_Buff[2] = ((data >> 8) & 0xFF); // Write Single byte | |
| AFE4490_SPI_TX_Buff[3] = (data & 0xFF); // Write Single byte // Write Single byte | |
| digitalWrite(AFE4490_CS_PIN,LOW); | |
| delay(5); | |
| afe4490SPI.transfer(AFE4490_SPI_TX_Buff[0]); | |
| afe4490SPI.transfer(AFE4490_SPI_TX_Buff[1]); | |
| afe4490SPI.transfer(AFE4490_SPI_TX_Buff[2]); | |
| afe4490SPI.transfer(AFE4490_SPI_TX_Buff[3]); | |
| delay(5); | |
| digitalWrite(AFE4490_CS_PIN,HIGH); | |
| } | |
| void max30205begin() | |
| { | |
| max30205.begin(); | |
| pinPeripheral(11, PIO_SERCOM); | |
| pinPeripheral(13, PIO_SERCOM); | |
| I2CwriteByte(MAX30205_ADDRESS, MAX30205_CONFIGURATION, 0x00); //mode config | |
| I2CwriteByte(MAX30205_ADDRESS, MAX30205_THYST , 0x00); // set threshold | |
| I2CwriteByte(MAX30205_ADDRESS, MAX30205_TOS, 0x00); // | |
| } | |
| // Wire.h read and write protocols | |
| void I2CwriteByte(uint8_t address, uint8_t subAddress, uint8_t data) | |
| { | |
| max30205.beginTransmission(address); // Initialize the Tx buffer | |
| max30205.write(subAddress); // Put slave register address in Tx buffer | |
| max30205.write(data); // Put data in Tx buffer | |
| max30205.endTransmission(); // Send the Tx buffer | |
| } | |
| float getTemperature(void){ | |
| uint8_t readRaw[2] = {0}; | |
| float temperature; | |
| I2CreadBytes(MAX30205_ADDRESS,MAX30205_TEMPERATURE, &readRaw[0] ,2); // read two bytes | |
| int16_t raw = readRaw[0] << 8 | readRaw[1]; //combine two bytes | |
| temperature = raw * 0.00390625; // convert to temperature | |
| return temperature; | |
| } | |
| void I2CreadBytes(uint8_t address, uint8_t subAddress, uint8_t * dest, uint8_t count) | |
| { | |
| max30205.beginTransmission(address); // Initialize the Tx buffer | |
| // Next send the register to be read. OR with 0x80 to indicate multi-read. | |
| max30205.write(subAddress); | |
| max30205.endTransmission(false); | |
| uint8_t i = 0; | |
| max30205.requestFrom(address, count); // Read bytes from slave register address | |
| while (max30205.available()) | |
| { | |
| dest[i++] = max30205.read(); | |
| } | |
| } | |
| void Resp_FilterProcess(int16_t * RESP_WorkingBuff, int16_t * CoeffBuf, int16_t* FilterOut) | |
| { | |
| int32_t acc=0; // accumulator for MACs | |
| int k; | |
| // perform the multiply-accumulate | |
| for ( k = 0; k < 161; k++ ) | |
| { | |
| acc += (int32_t)(*CoeffBuf++) * (int32_t)(*RESP_WorkingBuff--); | |
| } | |
| // saturate the result | |
| if ( acc > 0x3fffffff ) | |
| { | |
| acc = 0x3fffffff; | |
| } else if ( acc < -0x40000000 ) | |
| { | |
| acc = -0x40000000; | |
| } | |
| // convert from Q30 to Q15 | |
| *FilterOut = (int16_t)(acc >> 15); | |
| } | |
| int16_t Resp_ProcessCurrSample(int16_t CurrAqsSample) | |
| { | |
| static uint16_t bufStart=0, bufCur = FILTERORDER-1, FirstFlag = 1; | |
| int16_t temp1, temp2;//, RESPData; | |
| int16_t RESPData; | |
| /* Count variable*/ | |
| uint16_t Cur_Chan; | |
| int16_t FiltOut; | |
| temp1 = NRCOEFF * Pvev_DC_Sample; | |
| Pvev_DC_Sample = (CurrAqsSample - Pvev_Sample) + temp1; | |
| Pvev_Sample = CurrAqsSample; | |
| temp2 = Pvev_DC_Sample; | |
| RESPData = (int16_t) temp2; | |
| RESPData = CurrAqsSample; | |
| /* Store the DC removed value in RESP_WorkingBuff buffer in millivolts range*/ | |
| //RESP_WorkingBuff[bufCur]=(CurrAqsSample[0]); | |
| RESP_WorkingBuff[bufCur] = RESPData; | |
| //FiltOut = RESPData; | |
| Resp_FilterProcess(&RESP_WorkingBuff[bufCur],RespCoeffBuf,(int16_t*)&FiltOut); | |
| /* Store the DC removed value in Working buffer in millivolts range*/ | |
| RESP_WorkingBuff[bufStart] = RESPData; | |
| //int x = *((int*)(&arg)); | |
| /* Store the filtered out sample to the LeadInfo buffer*/ | |
| //FilteredOut = &FiltOut ;//(CurrOut); | |
| //FilteredOut[0] = CurrAqsSample[0]; | |
| //FilteredOut = CurrAqsSample; | |
| bufCur++; | |
| bufStart++; | |
| if ( bufStart >= (FILTERORDER-1)) | |
| { | |
| bufStart=0; | |
| bufCur = FILTERORDER-1; | |
| } | |
| return FiltOut; | |
| } | |
| void Respiration_Rate_Detection(int16_t Resp_wave) | |
| { | |
| static uint16_t skipCount = 0, SampleCount = 0,TimeCnt=0, SampleCountNtve=0, PtiveCnt =0,NtiveCnt=0 ; | |
| static int16_t MinThreshold = 0x7FFF, MaxThreshold = 0x8000, PrevSample = 0, PrevPrevSample = 0, PrevPrevPrevSample =0; | |
| static int16_t MinThresholdNew = 0x7FFF, MaxThresholdNew = 0x8000, AvgThreshold = 0; | |
| static unsigned char startCalc=0, PtiveEdgeDetected=0, NtiveEdgeDetected=0, peakCount = 0; | |
| static uint16_t PeakCount[8]; | |
| SampleCount++; | |
| SampleCountNtve++; | |
| TimeCnt++; | |
| if (Resp_wave < MinThresholdNew) MinThresholdNew = Resp_wave; | |
| if (Resp_wave > MaxThresholdNew) MaxThresholdNew = Resp_wave; | |
| if (SampleCount > 1000) | |
| { | |
| SampleCount =0; | |
| } | |
| if (SampleCountNtve > 1000) | |
| { | |
| SampleCountNtve =0; | |
| } | |
| if ( startCalc == 1) | |
| { | |
| if (TimeCnt >= 500) | |
| { | |
| TimeCnt =0; | |
| if ( (MaxThresholdNew - MinThresholdNew) > 400) | |
| { | |
| MaxThreshold = MaxThresholdNew; | |
| MinThreshold = MinThresholdNew; | |
| AvgThreshold = MaxThreshold + MinThreshold; | |
| AvgThreshold = AvgThreshold >> 1; | |
| } | |
| else | |
| { | |
| startCalc = 0; | |
| Respiration_Rate = 0; | |
| } | |
| } | |
| PrevPrevPrevSample = PrevPrevSample; | |
| PrevPrevSample = PrevSample; | |
| PrevSample = Resp_wave; | |
| if ( skipCount == 0) | |
| { | |
| if (PrevPrevPrevSample < AvgThreshold && Resp_wave > AvgThreshold) | |
| { | |
| if ( SampleCount > 40 && SampleCount < 700) | |
| { | |
| // Respiration_Rate = 6000/SampleCount; // 60 * 100/SampleCount; | |
| PtiveEdgeDetected = 1; | |
| PtiveCnt = SampleCount; | |
| skipCount = 4; | |
| } | |
| SampleCount = 0; | |
| } | |
| if (PrevPrevPrevSample < AvgThreshold && Resp_wave > AvgThreshold) | |
| { | |
| if ( SampleCountNtve > 40 && SampleCountNtve < 700) | |
| { | |
| NtiveEdgeDetected = 1; | |
| NtiveCnt = SampleCountNtve; | |
| skipCount = 4; | |
| } | |
| SampleCountNtve = 0; | |
| } | |
| if (PtiveEdgeDetected ==1 && NtiveEdgeDetected ==1) | |
| { | |
| PtiveEdgeDetected = 0; | |
| NtiveEdgeDetected =0; | |
| if (abs(PtiveCnt - NtiveCnt) < 5) | |
| { | |
| PeakCount[peakCount++] = PtiveCnt; | |
| PeakCount[peakCount++] = NtiveCnt; | |
| if( peakCount == 8) | |
| { | |
| peakCount = 0; | |
| PtiveCnt = PeakCount[0] + PeakCount[1] + PeakCount[2] + PeakCount[3] + | |
| PeakCount[4] + PeakCount[5] + PeakCount[6] + PeakCount[7]; | |
| PtiveCnt = PtiveCnt >> 3; | |
| Respiration_Rate = 6000/PtiveCnt; // 60 * 125/SampleCount; | |
| } | |
| } | |
| } | |
| } | |
| else | |
| { | |
| skipCount--; | |
| } | |
| } | |
| else | |
| { | |
| TimeCnt++; | |
| if (TimeCnt >= 500) | |
| { | |
| TimeCnt = 0; | |
| if ( (MaxThresholdNew - MinThresholdNew) > 400) | |
| { | |
| startCalc = 1; | |
| MaxThreshold = MaxThresholdNew; | |
| MinThreshold = MinThresholdNew; | |
| AvgThreshold = MaxThreshold + MinThreshold; | |
| AvgThreshold = AvgThreshold >> 1; | |
| PrevPrevPrevSample = Resp_wave; | |
| PrevPrevSample = Resp_wave; | |
| PrevSample = Resp_wave; | |
| } | |
| } | |
| } | |
| //Sanity Check | |
| //if((Respiration_Rate>0)&&(abs(global_RespirationRate-Respiration_Rate)<50)) | |
| //{ | |
| global_RespirationRate=(uint8_t)Respiration_Rate; | |
| //} | |
| } | |
| void RESP_Algorithm_Interface(int16_t CurrSample) | |
| { | |
| static int16_t prev_data[64] ={0}; | |
| //static unsigned char Decimeter = 0; | |
| char i; | |
| long Mac=0; | |
| prev_data[0] = CurrSample; | |
| for ( i=63; i > 0; i--) | |
| { | |
| Mac += prev_data[i]; | |
| prev_data[i] = prev_data[i-1]; | |
| } | |
| Mac += CurrSample; | |
| // Mac = Mac; | |
| CurrSample = (int16_t) Mac >> 1; | |
| RESP_Second_Prev_Sample = RESP_Prev_Sample ; | |
| RESP_Prev_Sample = RESP_Current_Sample ; | |
| RESP_Current_Sample = RESP_Next_Sample ; | |
| RESP_Next_Sample = RESP_Second_Next_Sample ; | |
| RESP_Second_Next_Sample = CurrSample;// << 3 ; | |
| // fprintf(fp,"%d\n", CurrSample); | |
| //Decimeter++; | |
| //Resp_Rr_val = RESP_Second_Next_Sample; | |
| //if ( Decimeter == 5) | |
| //{ | |
| // Decimeter = 0; | |
| // RESP_process_buffer(); | |
| Respiration_Rate_Detection(RESP_Second_Next_Sample); | |
| //} | |
| } | |
| void ECG_FilterProcess(int16_t * WorkingBuff, int16_t * CoeffBuf, int16_t* FilterOut) | |
| { | |
| int32_t acc=0; // accumulator for MACs | |
| int k; | |
| // perform the multiply-accumulate | |
| for ( k = 0; k < 161; k++ ) | |
| { | |
| acc += (int32_t)(*CoeffBuf++) * (int32_t)(*WorkingBuff--); | |
| } | |
| // saturate the result | |
| if ( acc > 0x3fffffff ) | |
| { | |
| acc = 0x3fffffff; | |
| } else if ( acc < -0x40000000 ) | |
| { | |
| acc = -0x40000000; | |
| } | |
| // convert from Q30 to Q15 | |
| *FilterOut = (int16_t)(acc >> 15); | |
| } | |
| int16_t ECG_ProcessCurrSample(int16_t CurrAqsSample) | |
| { | |
| static uint16_t ECG_bufStart=0, ECG_bufCur = FILTERORDER-1, ECGFirstFlag = 1; | |
| /* Working Buffer Used for Filtering*/ | |
| // static short ECG_WorkingBuff[2 * FILTERORDER]; | |
| int16_t *CoeffBuf; | |
| int16_t temp1, temp2, ECGData; | |
| /* Count variable*/ | |
| uint16_t Cur_Chan; | |
| int16_t FiltOut=0; | |
| // short FilterOut[2]; | |
| CoeffBuf = CoeffBuf_40Hz_LowPass; // Default filter option is 40Hz LowPass | |
| if ( ECGFirstFlag ) // First Time initialize static variables. | |
| { | |
| ECGFirstFlag = 0; | |
| } | |
| temp1 = NRCOEFF * ECG_Pvev_DC_Sample; //First order IIR | |
| ECG_Pvev_DC_Sample = (CurrAqsSample - ECG_Pvev_Sample) + temp1; | |
| ECG_Pvev_Sample = CurrAqsSample; | |
| temp2 = ECG_Pvev_DC_Sample>>2 ; | |
| ECGData = (int16_t) temp2; | |
| /* Store the DC removed value in Working buffer in millivolts range*/ | |
| ECG_WorkingBuff[ECG_bufCur] = ECGData; | |
| ECG_FilterProcess(&ECG_WorkingBuff[ECG_bufCur],CoeffBuf,(int16_t*)&FiltOut); | |
| /* Store the DC removed value in ECG_WorkingBuff buffer in millivolts range*/ | |
| ECG_WorkingBuff[ECG_bufStart] = ECGData; | |
| /* Store the filtered out sample to the LeadInfo buffer*/ | |
| //FiltOut = CurrAqsSample;//(CurrOut); | |
| // FilteredOut[0] = CurrAqsSample[0]; | |
| ECG_bufCur++; | |
| ECG_bufStart++; | |
| if ( ECG_bufStart == (FILTERORDER-1)) | |
| { | |
| ECG_bufStart=0; | |
| ECG_bufCur = FILTERORDER-1; | |
| } | |
| return FiltOut; | |
| } | |
| void QRS_Algorithm_Interface(int16_t CurrSample) | |
| { | |
| static int16_t prev_data[32] ={0}; | |
| int16_t i; | |
| long Mac=0; | |
| prev_data[0] = CurrSample; | |
| for ( i=31; i > 0; i--) | |
| { | |
| Mac += prev_data[i]; | |
| prev_data[i] = prev_data[i-1]; | |
| } | |
| Mac += CurrSample; | |
| Mac = Mac >> 2; | |
| CurrSample = (int16_t) Mac; | |
| QRS_Second_Prev_Sample = QRS_Prev_Sample ; | |
| QRS_Prev_Sample = QRS_Current_Sample ; | |
| QRS_Current_Sample = QRS_Next_Sample ; | |
| QRS_Next_Sample = QRS_Second_Next_Sample ; | |
| QRS_Second_Next_Sample = CurrSample ; | |
| QRS_process_buffer(); | |
| } | |
| static void QRS_process_buffer( void ) | |
| { | |
| int16_t first_derivative = 0 ; | |
| int16_t scaled_result = 0 ; | |
| static int16_t max = 0 ; | |
| /* calculating first derivative*/ | |
| first_derivative = QRS_Next_Sample - QRS_Prev_Sample ; | |
| /*taking the absolute value*/ | |
| if(first_derivative < 0) | |
| { | |
| first_derivative = -(first_derivative); | |
| } | |
| scaled_result = first_derivative; | |
| if( scaled_result > max ) | |
| { | |
| max = scaled_result ; | |
| } | |
| QRS_B4_Buffer_ptr++; | |
| if (QRS_B4_Buffer_ptr == TWO_SEC_SAMPLES) | |
| { | |
| QRS_Threshold_Old = ((max *7) /10 ) ; | |
| QRS_Threshold_New = QRS_Threshold_Old ; | |
| //if(max > 70) | |
| first_peak_detect = TRUE ; | |
| max = 0; | |
| QRS_B4_Buffer_ptr = 0; | |
| } | |
| if( TRUE == first_peak_detect ) | |
| { | |
| QRS_check_sample_crossing_threshold( scaled_result ) ; | |
| } | |
| //scaled_result_display[indx++] = scaled_result ; | |
| } | |
| static void QRS_check_sample_crossing_threshold( uint16_t scaled_result ) | |
| { | |
| /* array to hold the sample indexes S1,S2,S3 etc */ | |
| static uint16_t s_array_index = 0 ; | |
| static uint16_t m_array_index = 0 ; | |
| static unsigned char threshold_crossed = FALSE ; | |
| static uint16_t maxima_search = 0 ; | |
| static unsigned char peak_detected = FALSE ; | |
| static uint16_t skip_window = 0 ; | |
| static long maxima_sum = 0 ; | |
| static unsigned int peak = 0; | |
| static unsigned int sample_sum = 0; | |
| static unsigned int nopeak=0; | |
| uint16_t max = 0 ; | |
| uint16_t HRAvg; | |
| uint16_t RRinterval =0; | |
| if( TRUE == threshold_crossed ) | |
| { | |
| /* | |
| Once the sample value crosses the threshold check for the | |
| maxima value till MAXIMA_SEARCH_WINDOW samples are received | |
| */ | |
| sample_count ++ ; | |
| maxima_search ++ ; | |
| if( scaled_result > peak ) | |
| { | |
| peak = scaled_result ; | |
| } | |
| if( maxima_search >= MAXIMA_SEARCH_WINDOW ) | |
| { | |
| // Store the maxima values for each peak | |
| maxima_sum += peak ; | |
| maxima_search = 0 ; | |
| threshold_crossed = FALSE ; | |
| peak_detected = TRUE ; | |
| } | |
| } | |
| else if( TRUE == peak_detected ) | |
| { | |
| /* | |
| Once the sample value goes below the threshold | |
| skip the samples untill the SKIP WINDOW criteria is meet | |
| */ | |
| sample_count ++ ; | |
| skip_window ++ ; | |
| if( skip_window >= MINIMUM_SKIP_WINDOW ) | |
| { | |
| skip_window = 0 ; | |
| peak_detected = FALSE ; | |
| } | |
| if( m_array_index == MAX_PEAK_TO_SEARCH ) | |
| { | |
| sample_sum = sample_sum / (MAX_PEAK_TO_SEARCH - 1); | |
| HRAvg = (uint16_t) sample_sum ; | |
| // Compute HR without checking LeadOffStatus | |
| QRS_Heart_Rate = (uint16_t) 60 * SAMPLING_RATE; | |
| QRS_Heart_Rate = QRS_Heart_Rate/ HRAvg ; | |
| if(QRS_Heart_Rate > 250) | |
| QRS_Heart_Rate = 250 ; | |
| /* Setting the Current HR value in the ECG_Info structure*/ | |
| maxima_sum = maxima_sum / MAX_PEAK_TO_SEARCH; | |
| max = (int16_t) maxima_sum ; | |
| /* calculating the new QRS_Threshold based on the maxima obtained in 4 peaks */ | |
| maxima_sum = max * 7; | |
| maxima_sum = maxima_sum/10; | |
| QRS_Threshold_New = (int16_t)maxima_sum; | |
| /* Limiting the QRS Threshold to be in the permissible range*/ | |
| if(QRS_Threshold_New > (4 * QRS_Threshold_Old)) | |
| { | |
| QRS_Threshold_New = QRS_Threshold_Old; | |
| } | |
| sample_count = 0 ; | |
| s_array_index = 0 ; | |
| m_array_index = 0 ; | |
| maxima_sum = 0 ; | |
| sample_index[0] = 0 ; | |
| sample_index[1] = 0 ; | |
| sample_index[2] = 0 ; | |
| sample_index[3] = 0 ; | |
| Start_Sample_Count_Flag = 0; | |
| sample_sum = 0; | |
| } | |
| } | |
| else if( scaled_result > QRS_Threshold_New ) | |
| { | |
| /* | |
| If the sample value crosses the threshold then store the sample index | |
| */ | |
| Start_Sample_Count_Flag = 1; | |
| sample_count ++ ; | |
| m_array_index++; | |
| threshold_crossed = TRUE ; | |
| peak = scaled_result ; | |
| nopeak = 0; | |
| /* storing sample index*/ | |
| sample_index[ s_array_index ] = sample_count ; | |
| if( s_array_index >= 1 ) | |
| { | |
| sample_sum += sample_index[ s_array_index ] - sample_index[ s_array_index - 1 ] ; | |
| } | |
| s_array_index ++ ; | |
| } | |
| else if(( scaled_result < QRS_Threshold_New ) && (Start_Sample_Count_Flag == 1)) | |
| { | |
| sample_count ++ ; | |
| nopeak++; | |
| if (nopeak > (3 * SAMPLING_RATE)) | |
| { | |
| sample_count = 0 ; | |
| s_array_index = 0 ; | |
| m_array_index = 0 ; | |
| maxima_sum = 0 ; | |
| sample_index[0] = 0 ; | |
| sample_index[1] = 0 ; | |
| sample_index[2] = 0 ; | |
| sample_index[3] = 0 ; | |
| Start_Sample_Count_Flag = 0; | |
| peak_detected = FALSE ; | |
| sample_sum = 0; | |
| first_peak_detect = FALSE; | |
| nopeak=0; | |
| QRS_Heart_Rate = 0; | |
| } | |
| } | |
| else | |
| { | |
| nopeak++; | |
| if (nopeak > (3 * SAMPLING_RATE)) | |
| { | |
| /* Reset heart rate computation sate variable in case of no peak found in 3 seconds */ | |
| sample_count = 0 ; | |
| s_array_index = 0 ; | |
| m_array_index = 0 ; | |
| maxima_sum = 0 ; | |
| sample_index[0] = 0 ; | |
| sample_index[1] = 0 ; | |
| sample_index[2] = 0 ; | |
| sample_index[3] = 0 ; | |
| Start_Sample_Count_Flag = 0; | |
| peak_detected = FALSE ; | |
| sample_sum = 0; | |
| first_peak_detect = FALSE; | |
| nopeak = 0; | |
| QRS_Heart_Rate = 0; | |
| } | |
| } | |
| global_HeartRate= (uint8_t)(QRS_Heart_Rate); | |
| //SerialUSB.println(global_HeartRate); | |
| } | |
| void estimate_spo2(uint16_t *pun_ir_buffer, int32_t n_ir_buffer_length, uint16_t *pun_red_buffer, int32_t *pn_spo2, int8_t *pch_spo2_valid) | |
| { | |
| 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 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 ; | |
| // calculates DC mean and subtract DC from ir | |
| 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 ; | |
| // remove DC and invert signal so that we can use peak detector as valley detector | |
| for (k=0 ; k<n_ir_buffer_length ; k++ ) | |
| an_x[k] = -1*(pun_ir_buffer[k] - un_ir_mean) ; | |
| // 4 pt Moving Average | |
| 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])/(int)4; | |
| } | |
| // calculate threshold | |
| n_th1=0; | |
| for ( k=0 ; k<BUFFER_SIZE ;k++){ | |
| n_th1 += an_x[k]; | |
| } | |
| n_th1= n_th1/ ( BUFFER_SIZE); | |
| if( n_th1<30) n_th1=30; // min allowed | |
| if( n_th1>60) n_th1=60; // max allowed | |
| for ( k=0 ; k<15;k++) an_ir_valley_locs[k]=0; | |
| // since we flipped signal, we use peak detector as valley detector | |
| find_peak( an_ir_valley_locs, &n_npks, an_x, BUFFER_SIZE, n_th1, 4, 15 );//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_ir_valley_locs[k] -an_ir_valley_locs[k -1] ) ; | |
| n_peak_interval_sum =n_peak_interval_sum/(n_npks-1); | |
| //*pn_heart_rate =(int32_t)( (FS*60)/ n_peak_interval_sum ); | |
| //*pch_hr_valid = 1; | |
| } | |
| else { | |
| //*pn_heart_rate = -999; // unable to calculate because # of peaks are too small | |
| //*pch_hr_valid = 0; | |
| } | |
| // load raw value again for SPO2 calculation : RED(=y) and IR(=X) | |
| 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 =n_npks; | |
| //using exact_ir_valley_locs , find ir-red DC andir-red AC for SPO2 calibration an_ratio | |
| //finding AC/DC maximum of raw | |
| 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_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 an_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_ir_valley_locs[k+1]-an_ir_valley_locs[k] >3){ | |
| for (i=an_ir_valley_locs[k]; i< an_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_ir_valley_locs[k+1]] - an_y[an_ir_valley_locs[k] ] )*(n_y_dc_max_idx -an_ir_valley_locs[k]); //red | |
| n_y_ac= an_y[an_ir_valley_locs[k]] + n_y_ac/ (an_ir_valley_locs[k+1] - an_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_ir_valley_locs[k+1]] - an_x[an_ir_valley_locs[k] ] )*(n_x_dc_max_idx -an_ir_valley_locs[k]); // ir | |
| n_x_ac= an_x[an_ir_valley_locs[k]] + n_x_ac/ (an_ir_valley_locs[k+1] - an_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++; | |
| } | |
| } | |
| } | |
| // choose median value since PPG signal may varies from beat to beat | |
| 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 an_ratio is out of range | |
| *pch_spo2_valid = 0; | |
| } | |
| } | |
| void find_peak( int32_t *pn_locs, int32_t *n_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 | |
| */ | |
| { | |
| find_peak_above( pn_locs, n_npks, pn_x, n_size, n_min_height ); | |
| remove_close_peaks( pn_locs, n_npks, pn_x, n_min_distance ); | |
| *n_npks = min( *n_npks, n_max_num ); | |
| } | |
| void find_peak_above( int32_t *pn_locs, int32_t *n_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; | |
| *n_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] && (*n_npks) < 15 ){ // find right edge of peaks | |
| pn_locs[(*n_npks)++] = i; | |
| // for flat peaks, peak location is left edge | |
| i += n_width+1; | |
| } | |
| else | |
| i += n_width; | |
| } | |
| else | |
| i++; | |
| // Serial.println("beat"); | |
| } | |
| } | |
| void 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 */ | |
| 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 int32_to ascending order | |
| sort_ascend( pn_locs, *pn_npks ); | |
| } | |
| void 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 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; | |
| } | |
| } | |