Dan Allen
/
MAX86150_ECG_PPG
MAX86150 code
Fork of MAX86150_ECG_PPG by
Diff: main.cpp
- Revision:
- 1:f62247cbeac6
- Parent:
- 0:74fff521858e
- Child:
- 2:c8b7ef52c65c
--- a/main.cpp Wed Jan 04 17:00:04 2017 +0000 +++ b/main.cpp Wed Jan 04 17:00:53 2017 +0000 @@ -1,7 +1,7 @@ //********************************************** Arduino code /* This program configures and reads data from the MAX86150 ECG/PPG (BioZ to come) ASIC from Maxim Integrated. - * Originally writter for Arduino. The MAX86150r4 or MAX86150r8 boards work well (others are noisy). + * Originally writter for Arduino. The MAX86150r4 or MAX86150r8 boards work well (others are noisy). * Keep ECG electrodes as short as possible. Due to the nature of this chip low frequency RC filters cannot be used. * Note the ECG input oscillates when not loaded with human body impedance. * ECG inputs have internal ESD protection. @@ -11,12 +11,12 @@ * If PPG is not enabled (ppgOn=0), then the first data point will be ECG. If ecg is not enabled then the only data input will be ETI. * This program uses by default the A-full interrupt which alerts when the FIFO is almost full. It does not attempt to read the entire FIFO, * but rather reads a fixed number of samples. This operation is important because the ASIC can't update the FIFO while we are in the middle - * of an I2C transaction, so we have to keep I2C transactions short. - * Note the program limits the number of samples which can be read according to a specified I2C buffer size which will depend on the HW and + * of an I2C transaction, so we have to keep I2C transactions short. + * Note the program limits the number of samples which can be read according to a specified I2C buffer size which will depend on the HW and * library you use. For Arduino the default is 32 bytes. * ECG data and PPG data are different. PPG data is unipolar 18 bits and has garbage in the MSBs that has to be masked. * ECG data is bipolar (2's complement), 17 bits and has to have the MSBs masked with FFF.. if the signal is negative. - * ECG data is displayed with a short IIR noise filter and longer IIR baseline removal filter. The filter length is specified in bits + * ECG data is displayed with a short IIR noise filter and longer IIR baseline removal filter. The filter length is specified in bits * so the math can be done with integers using register shifting, rather than floats. * This chip practically requires at least 400kHZ i2c speed for ECG readout. (I have a query in to Maxim to see if it unofficially supports 1MHz.) * ETI is electrical tissue impedance, a few kHz sampling of impedance to check for presence of user's fingers. This is not a datasheet feature. @@ -25,24 +25,24 @@ * PPG data is measured at a much lower rate than ECG, but data is buffered through in the FIFO, showing the most recent sample. * To reduce data rate it would be possible to only send PPG data when it changes. Otherwise ECG can be filtered down to the PPG data rate and * filtered data sent at a the reduced bandwidth (what Maxim does on their BLE EV kit). - * + * * IMPORTANT: Maxim recommends to read the FIFO pointer after getting data to make sure it changed. I have not implemented that yet. * Basically, if you try to read the FIFO during the few clock cycles while it is being updated (a rare but eventual event), - * then the FIFO data will be garbage. You have to reread the FIFO. - * + * then the FIFO data will be garbage. You have to reread the FIFO. + * * BTW I am not convinced it is possible to mix PPG and ETI in the same row of the flex FIFO. Eg. FD2= ECG, FD1 = PPG - * - * Note: IR and R LED power should be closed loop controlled to an optimal region above 90% of full scale for best resolution. + * + * Note: IR and R LED power should be closed loop controlled to an optimal region above 90% of full scale for best resolution. * (Not yet implemented). - * + * * Note: PPG system uses sophisticated background subtraction to cancel ambient light flicker and large ambient light signals (sunlight). * This is important for finger measurements, but less important for measurements indoors under the thigh, for example, where a generic * light source and LED can be used in DC operation. - * + * * Note the PPG ADC uses feedback cancellation from the current source to reduce noise. No LED current will result in noisy ADC readings. * In other words you can't measure ADC noise without LED current. There are test registers not available to us which can disconnect the photodiode and * measure with noise with test currents on the chip. - * + * * Note ASIC has configurable on-chip PPG averaging if-desired (prior to being written to FIFO). */ #include "mbed.h" @@ -115,7 +115,7 @@ const int16_t etiChannel = etiOn*(1+ecgOn+2*ppgOn)-1; //-1, 0,1 or 3 const uint8_t numMeasEnabled = 2*ppgOn + ecgOn + etiOn; //Index # of how many measurements will be in each block of the FIFO const uint8_t bytesPerSample = 3*numMeasEnabled; //each measurement is 3bytes -const int16_t samples2Read = i2cBufferSize / bytesPerSample ; //assuming 32 byte I2C buffer default +const int16_t samples2Read = i2cBufferSize / bytesPerSample ; //assuming 32 byte I2C buffer default const uint8_t almostFullIntEn = 1; //priority to AFULL vs. PPG in code const char bytes2Read = bytesPerSample*samples2Read; char i2cWriteBuffer[10]; @@ -139,68 +139,75 @@ //declare subroutines -void writeRegister(uint8_t addr, uint8_t reg, uint8_t val) { - /*writes 1 byte to a single register*/ +void writeRegister(uint8_t addr, uint8_t reg, uint8_t val) +{ + /*writes 1 byte to a single register*/ char writeData[2]; writeData[0] = reg ; writeData[1] = val; i2c.write(addr,writeData, 2); } -void writeBlock(uint8_t addr, uint8_t startReg, uint8_t *data, uint8_t numBytes) { -/*writes data from an array beginning at the startReg*/ +void writeBlock(uint8_t addr, uint8_t startReg, uint8_t *data, uint8_t numBytes) +{ + /*writes data from an array beginning at the startReg*/ char writeData[numBytes+1]; writeData[0]=startReg; - for(int n=1;n<numBytes;n++) { + for(int n=1; n<numBytes; n++) { writeData[n]=data[n-1]; } i2c.write(addr,writeData,numBytes+1); } -void readRegisters(uint8_t addr, uint8_t startReg, char *regData, int numBytes) { +void readRegisters(uint8_t addr, uint8_t startReg, char *regData, int numBytes) +{ char writeData = startReg; i2c.write(addr,&writeData,1,true); //true is for repeated start i2c.read(addr,regData,numBytes); } //clears interrupts -void clearInterrupts(char *data) { +void clearInterrupts(char *data) +{ readRegisters(MAX86150_Addr,InterruptStatusReg1,data,1); } -void regDump(uint8_t Addr, uint8_t startByte, uint8_t endByte) { -/*print the values of up to 20 registers*/ +void regDump(uint8_t Addr, uint8_t startByte, uint8_t endByte) +{ + /*print the values of up to 20 registers*/ char regData[20]; int numBytes; if (endByte>=startByte) { numBytes = (endByte-startByte+1) < 20 ? (endByte-startByte+1) : 20; - } - else { + } else { numBytes=1; - } - + } + regData[0] = startByte; i2c.write(Addr,regData,1,true); i2c.read(Addr, regData, numBytes); - for(int n=0;n<numBytes;n++) { - pc.printf("%X, %X \r\n", startByte+n, regData[n]); + for(int n=0; n<numBytes; n++) { + pc.printf("%X, %X \r\n", startByte+n, regData[n]); } } -bool bitRead(long data, uint8_t bitNum) { - long mask = 1<<bitNum; - long masked_bit = data & mask; - return masked_bit >> bitNum; +bool bitRead(long data, uint8_t bitNum) +{ + long mask = 1<<bitNum; + long masked_bit = data & mask; + return masked_bit >> bitNum; } -void intEvent(void) { - intFlagged = 1; +void intEvent(void) +{ + intFlagged = 1; } -void initMAX86150(void) { +void initMAX86150(void) +{ pc.printf("Initializing MAX86150\r\n"); - //print configurations + //print configurations pc.printf( (ppgOn ? "PPG on" : "PPG off") ); pc.printf( (ecgOn ? ", ECG On" : ", ECG off") ); pc.printf( (etiOn ? ", ETI On\r\n" : ", ETI off\r\n") ); @@ -218,36 +225,37 @@ FIFOCode = ppgOn ? (FIFOCode<<8 | 0x0021) : FIFOCode; //insert Red(2) and IR (1) in front of ECG in FIFO writeRegister(MAX86150_Addr,FIFODataControlReg1, (char)(FIFOCode & 0x00FF) ); writeRegister(MAX86150_Addr,FIFODataControlReg2, (char)(FIFOCode >>8) ); - writeRegister(MAX86150_Addr, ppgConfigReg0,0b11010111); //D3 for 100Hz, PPG_ADC_RGE: 32768nA, PPG_SR: 100SpS, PPG_LED_PW: 400uS + writeRegister(MAX86150_Addr, ppgConfigReg0,0b11010111); //D3 for 100Hz, PPG_ADC_RGE: 32768nA, PPG_SR: 100SpS, PPG_LED_PW: 400uS writeRegister(MAX86150_Addr,LED1PulseAmpReg, ppgOn ? rCurrent : 0x00 ); writeRegister(MAX86150_Addr,LED2PulseAmpReg, ppgOn ? irCurrent : 0x00); writeRegister(MAX86150_Addr,LEDRangeReg, irHiPWR * 2 + redHiPWR ); // PPG_ADC_RGE: 32768nA //ecg configuration if (ecgOn) { //**************************************** - //ECG data rate is user configurable in theory, but you have to adjust your filter time constants and serial printEvery variables accordingly + //ECG data rate is user configurable in theory, but you have to adjust your filter time constants and serial printEvery variables accordingly writeRegister(MAX86150_Addr,EcgConfigReg1,ecgRate); //ECG sample rate 0x00 =1600, 0x01=800Hz etc down to 200Hz. add 4 to double frequency (double ADC clock) writeRegister(MAX86150_Addr,EcgConfigReg2,0x11); //hidden register at ECG config 2, per JY's settings writeRegister(MAX86150_Addr,EcgConfigReg3,0x3D); //ECG config 3 writeRegister(MAX86150_Addr,EcgConfigReg4,0x02); //ECG config 4 per JY's settings - //enter test mode + //enter test mode writeRegister(MAX86150_Addr,0xFF,0x54); //write 0x54 to register 0xFF writeRegister(MAX86150_Addr,0xFF,0x4D); //write 0x4D to register 0xFF writeRegister(MAX86150_Addr,0xCE,0x01); writeRegister(MAX86150_Addr,0xCF,0x18); //adjust hidden registers at CE and CF per JY's settings in EV kit writeRegister(MAX86150_Addr,0xD0,0x01); //adjust hidden registers D0 (probably ETI) - writeRegister(MAX86150_Addr,0xFF,0x00); //exit test mode + writeRegister(MAX86150_Addr,0xFF,0x00); //exit test mode } //setup interrupts last writeRegister(MAX86150_Addr,InterruptEnableReg1,( almostFullIntEn ? 0x80 : (ppgRdyIntEn ? 0x40 : 0x00) ) ); writeRegister(MAX86150_Addr,InterruptEnableReg2, (ecgRdyIntEn ? 0x04 : 0x00) ); writeRegister(MAX86150_Addr,SystemControlReg,0x04);//start FIFO - + pc.printf("done configuring MAX86150\r\n"); } //end initMAX86150 -bool readFIFO(int numSamples, char *fifodata) { +bool readFIFO(int numSamples, char *fifodata) +{ char stat[1]; bool dataValid = 0; uint8_t tries = 0; @@ -255,15 +263,15 @@ clearInterrupts(stat); //get FIFO position readRegisters(MAX86150_Addr, FIFOReadPointerReg, i2cReadBuffer, 1); //you can do more sophisticated stuff looking for missed samples, but I'm keeping this lean and simple - char readPointer = i2cReadBuffer[0]; - while(!dataValid) { + char readPointer = i2cReadBuffer[0]; + while(!dataValid) { tries++; //try reading FIFO readRegisters(MAX86150_Addr, FIFODataReg, fifodata, bytes2Read); //get data //see if it worked if you are not servicing interrupts faster than the sample rate //if you are servicing interrupts much faster than the sample rate, then you can get rid of the extra pointer register check. dataValid=1; - //readRegisters(MAX86150_Addr, FIFOReadPointerReg, i2cReadBuffer, 1); //check that the read pointer has moved (otherwise FIFO was being written to) + //readRegisters(MAX86150_Addr, FIFOReadPointerReg, i2cReadBuffer, 1); //check that the read pointer has moved (otherwise FIFO was being written to) // newReadPointer = i2cReadBuffer[0]; // if( (newReadPointer - readPointer == numSamples) || (newReadPointer+32 -readPointer ==numSamples ) ){ //check that we got the right number of samples (including FIFO pointer wrap around possiblity) // dataValid=1; @@ -274,72 +282,74 @@ // } // else { // wait_us(100); //try again a moment later -// } +// } } return dataValid; } //end readFIFO //for serial monitoring -void printData(uint16_t numSamples,char *fifodata) { - //cat and mask the bits from the FIFO - for (int n = 0; n < numSamples; n++) { //for every sample - char p = bytesPerSample; - for (int m=0;m<numMeasEnabled;m++) { //for every enabled measurement - data[m][n] = ( (long)(fifodata[p*n +3*m] & 0x7) << 16) | ( (long)fifodata[p*n + 1+3*m] << 8) | ( (long)fifodata[p*n + 2+3*m]); - if (bitRead(data[m][n], 17) && ( (ecgChannel==m) || (etiChannel==m) ) ) {//handle ECG and ETI differently than PPG data. data[m][n] |= 0xFFFC0000; - data[m][n] |= 0xFFFC0000; - } +void printData(uint16_t numSamples,char *fifodata) +{ + //cat and mask the bits from the FIFO + for (int n = 0; n < numSamples; n++) { //for every sample + char p = bytesPerSample; + for (int m=0; m<numMeasEnabled; m++) { //for every enabled measurement + data[m][n] = ( (long)(fifodata[p*n +3*m] & 0x7) << 16) | ( (long)fifodata[p*n + 1+3*m] << 8) | ( (long)fifodata[p*n + 2+3*m]); + if (bitRead(data[m][n], 17) && ( (ecgChannel==m) || (etiChannel==m) ) ) {//handle ECG and ETI differently than PPG data. data[m][n] |= 0xFFFC0000; + data[m][n] |= 0xFFFC0000; + } + } } - } - for (int n = 0; n < numSamples; n++) { //for every sample - ind++; - ind = ind % printEvery; - - //calc avg and baseline - for(int m=0;m<numMeasEnabled;m++) { //for every enabled measurement - dataAvg[m] += ( data[m][n] - dataAvg[m] ) >> bits2Avg[m] ; //get the running average - dataBaseline[m] += ( data[m][n] - dataBaseline[m] ) >> bits2AvgBaseline[m]; //get the long baseline - } + for (int n = 0; n < numSamples; n++) { //for every sample + ind++; + ind = ind % printEvery; + + //calc avg and baseline + for(int m=0; m<numMeasEnabled; m++) { //for every enabled measurement + dataAvg[m] += ( data[m][n] - dataAvg[m] ) >> bits2Avg[m] ; //get the running average + dataBaseline[m] += ( data[m][n] - dataBaseline[m] ) >> bits2AvgBaseline[m]; //get the long baseline + } - //print data - if (ind == 1) { //printing only every specified number of samples to reduce serial traffic to manageable level - for (int m=0;m<numMeasEnabled;m++) {//for every enabled measurement - if(bits2AvgBaseline[m]>0) { - pc.printf("%i, ",dataAvg[m]-dataBaseline[m]); //print with baseline subtraction - } - else { - pc.printf("%i, ", dataAvg[m]); //print without baseline subtraction - } - } - pc.printf("\r\n"); - } //end print loop - } //end sample loop + //print data + if (ind == 1) { //printing only every specified number of samples to reduce serial traffic to manageable level + for (int m=0; m<numMeasEnabled; m++) { //for every enabled measurement + if(bits2AvgBaseline[m]>0) { + pc.printf("%i, ",dataAvg[m]-dataBaseline[m]); //print with baseline subtraction + } else { + pc.printf("%i, ", dataAvg[m]); //print without baseline subtraction + } + } + pc.printf("\r\n"); + } //end print loop + } //end sample loop }//end printData() //for debugging -void printRawFIFO(int numSamples,char *fifodata) { - // Serial.print("FIFO bytes "); - for (int n = 0; n < numSamples; n++) {//for every sample. - for (int m=0;m<numMeasEnabled;m++) {//for every kind of measurement - pc.printf("%d: ",m); - pc.printf("%x, %x, %x, ",fifodata[bytesPerSample * n +3*m], fifodata[bytesPerSample * n + 1 +3*m], fifodata[bytesPerSample * n + 2 + 3*m] ); - } //end measurement loop - pc.printf("\r\n"); - } //end sample loop +void printRawFIFO(int numSamples,char *fifodata) +{ + // Serial.print("FIFO bytes "); + for (int n = 0; n < numSamples; n++) {//for every sample. + for (int m=0; m<numMeasEnabled; m++) { //for every kind of measurement + pc.printf("%d: ",m); + pc.printf("%x, %x, %x, ",fifodata[bytesPerSample * n +3*m], fifodata[bytesPerSample * n + 1 +3*m], fifodata[bytesPerSample * n + 2 + 3*m] ); + } //end measurement loop + pc.printf("\r\n"); + } //end sample loop } //end function -void setupASIC(void) { +void setupASIC(void) +{ pc.printf("Running Setup\r\n"); runSetup = 0; //only run once i2c.frequency(recommendedi2cFreq); //set I2C frequency to 400kHz // intPin.mode(PullUp); //pullups on the sensor board //configure MAX86150 register settings initMAX86150(); - pc.printf("register dump\r\n"); + pc.printf("register dump\r\n"); // //print register configuration // regDump(MAX86150_Addr,0x00, 0x06); @@ -352,7 +362,7 @@ // regDump(MAX86150_Addr,0xD0, 0xD0); // writeRegister(MAX86150_Addr,0xFF,0x00); clearInterrupts(i2cReadBuffer); - i2c.frequency(maxi2cFreq); + i2c.frequency(maxi2cFreq); //configure averaging if(ppgOn) { bits2Avg[rChannel]=ppgBits2Avg; @@ -360,11 +370,11 @@ bits2AvgBaseline[rChannel]=ppgBaselineBits; bits2AvgBaseline[irChannel]=ppgBaselineBits; } - if(ecgOn){ + if(ecgOn) { bits2Avg[ecgChannel]=ecgBits2Avg; bits2AvgBaseline[ecgChannel]=ecgBaselineBits; } - if(etiOn){ + if(etiOn) { bits2Avg[etiChannel]=etiBits2Avg; bits2AvgBaseline[etiChannel]=etiBaselineBits; } @@ -373,43 +383,43 @@ -int main() { +int main() +{ char FIFOData[bytesPerSample]; if(runSetup) { setupASIC(); - } - + } + intPin.fall(&intEvent); while(1) { - char stat[1]; - static int n = 0; - if (intFlagged) { + char stat[1]; + static int n = 0; + if (intFlagged) { // pc.printf("intFlagged\r\n"); if (almostFullIntEn) { - n = 0; - intFlagged = 0; - readFIFO(samples2Read,FIFOData); - printData(samples2Read,FIFOData); -// printRawFIFO(samples2Read,FIFOData); //for debugging - } //end if AFULL - else { //this is to handle end of conversion interrupts (not configured by default) - if (n < samples2Read) { - n++; - intFlagged = 0; - clearInterrupts(stat); - // Serial.println(n); - } - else { n = 0; intFlagged = 0; readFIFO(samples2Read,FIFOData); printData(samples2Read,FIFOData); - pc.printf("\r\n"); - // printRawFIFO(samples2Read); - } +// printRawFIFO(samples2Read,FIFOData); //for debugging + } //end if AFULL + else { //this is to handle end of conversion interrupts (not configured by default) + if (n < samples2Read) { + n++; + intFlagged = 0; + clearInterrupts(stat); + // Serial.println(n); + } else { + n = 0; + intFlagged = 0; + readFIFO(samples2Read,FIFOData); + printData(samples2Read,FIFOData); + pc.printf("\r\n"); + // printRawFIFO(samples2Read); + } } //end if/else AFULL - } //end if intFlagged - } //end while + } //end if intFlagged + } //end while }//end main @@ -435,4 +445,3 @@ * 200 3 3 * 200 4 1 */ - \ No newline at end of file