aromring / MAX30102_by_RF

Arduino C code for MAX30102 pulse oximetry sensor (MAXIM Integrated, Inc.)
166 stars 73 forks source link

Can't get SpO2 to work #13

Closed robcazzaro closed 4 years ago

robcazzaro commented 4 years ago

First of all, thanks so much for the work you did and for making it available.

I'm having a problem making the code work properly and I'm hoping you can point me in the right direction.

I'm using an ESP32, which is many times faster than the M0 processor you are using. And I have quite a lot of ESP32 experience, so I'm sure I2C works properly. I built a separate ESP32/MAX30102 project and that worked perfectly, so I checked the code for any potential problem and I think that the data reading from the sensor is working properly. I also have 2 different MAX30102 boards, and there is no real difference in the results between them

I'm including 2 files, one a data capture in txt format, the other I imported the same data as CSV into Excel and plotted the graph for the first 100 samples. To my eyes, that looks like an almost perfect graph, so I was expecting the code to work. The only potential issue is the the amplitude of the IR signal is much lower than the RED one

MAX30102.xlsx MAX30102.txt

You will notice from the CSV that I'm printing both the results of your algorithm and the Maxim results and, if anything, the Maxim ones are worse, so I'm struggling to understand where the problem is. Your algorithm does a great job of reporting the heart rate (double checked with a commercial pulse oximeter), but does not report any valid SpO2 result (flag is 0, result is -999.00, expected when the flag is 0)

I tried changing the LED intensity (registers 0x0C and 0x0D), but it doesn't make a real difference, outside of "shifting the curves up" when I do it

Thanks in advance for any idea/pointer to troubleshoot my problem

aromring commented 4 years ago

Hi Rob, Thank you so much. Yours is the perfect model of a proper bug report submitted with enough information and data. Others should learn from you! Usually, I am getting no more than 1-3 sentences, like "It doesn't work. Help me." Some people expect me to have psychic powers. ;) Since I am dedicating part of this weekend to looking at MAX30102_by_RF issues your submission will get the first priority. Stay tuned.

aromring commented 4 years ago

Hmm, at the first glance the raw signals produced by your sensor are very good. I can't imagine why there would be no SpO2 output. Hence, debugging time... Stay tuned; I should have something by Sunday.

robcazzaro commented 4 years ago

:) thanks for the nice words. I used to work in support for a long time, and I know perfectly well what you mean.

You might notice from the file capture that I slightly changed the print() format for the algorithm results, but that should not make any difference. As you saw, the data capture seems good, but I didn't dig too deep into the algorithm yet.

I wonder if it's possible that some of the ESP32 math libraries produce different results due to rounding errors or such, but I'm not aware of any problem in that area currently and by now the ESP32 toolchain is really mature, so it would be a surprise if that's the case.

The only other thing I can think about is that I had some issue reading the MAX30102 in my other project (in that case I was reading 2 MAX30102 at the maximum speed of 400Hz and sending data via BLE, so any small glitch caused problems with the FIFO reading and my data could get out of sync. I'm wondering if it's possible that the RED and IR data are shifted by 1 frame due to I2C problems with the ESP32. I have a STM32F104 board, and I can try on that (M3 @72MHz, so closer to the board you used), if the ESP32 is a dead end

Anyway: I truly do appreciate your time, and I'd be happy to try anything to make this work and see if your code can work on the ESP32, which is a pretty common and cheap board. The value of the ESP32, is that it's then easy to use the built-in BLE to create a truly wireless solution and act as a heart rate sensor for other apps

Needless to say, there's no rush.

aromring commented 4 years ago

Well, no line debugging was necessary. I've figured what happened using an Excel spreadsheet. The answer to your problem is the applicability domain of the Maxim's calibration equation:

SpO2 = (-45.06*Z + 30.354)*Z + 94.845

where Z is defined in my Instructable (roughly, it's a ratio of normalized variabilities of RED to IR signals). The above equation is an inverted parabola with one negative root and one positive root at ~1.826. Z values above the latter would result in nonsensical negative SpO2 values. Here is an example of expected raw signals in the first graph and centered/leveled ones in the second: image As you can see, normalized AC/DC ratio for RED should be lower than IR. The Z value in the case is 0.4589 and SpO2 = 99.28% - reasonable. And here are analogous graphs plotted from your signal: image In contrast to previous example, RED's AC/DC far exceeds IR's. The Z value in your case is 1.958 which would produce SpO2 = -18.52%. The following line of code:

if(xy_ratio>0.02 && xy_ratio<1.84) { // Check boundaries of applicability

captures this case and sets *pch_spo2_valid flag to 0. BUT, if you switch your RED and IR signals, then Z = 0.51 and SpO2 = 98.59%. Therefore, I am suspecting that in your setup you've mixed RED with IR and vice versa.

robcazzaro commented 4 years ago

That's interesting... I don't think I switched anything like that in the code, but I wonder if the I2C FIFO reading loop is doing something weird.

I'll do some more tests and get back to you with the results. For now, thanks for the great analysis!

arzaman commented 4 years ago

I'm using an ESP32, which is many times faster than the M0 processor you are using. And I have quite a lot of ESP32 experience, so I'm sure I2C works properly. I built a separate ESP32/MAX30102 project and that worked perfectly, so I checked the code for any potential problem and I think that the data reading from the sensor is working properly. I also have 2 different MAX30102 boards, and there is no real difference in the results between them

I'm doing same attempt to run the amazing @aromring code running on top of the ESP32 did you fork the project and have a repository for that ? any direction / suggestions to adapt to ESP32 ? I had some compilation error

_

In file included from C:\Users\arzaman\Documents\Arduino\RD117_ARDUINO\RD117_ARDUINO.ino:35:0:

algorithm_by_RF.h:45:12: error: expected identifier before numeric constant

define FS 25 // Sampling frequency in Hz. WARNING: if you change FS, then you MUST recalcuate the sum_X2 parameter below!

C:\Users\arzaman\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.4\libraries\FS\src/FS.h:86:7: note: in expansion of macro 'FS'

class FS _

If I comment the define USE_ADALOGGER the compilation is OK

will follow also the thread for calibration issue thank Davide

robcazzaro commented 4 years ago

I did more tests, and the only possible conclusion is that indeed the IR and RED LEDs are swapped. But the weird thing is that they are not swapped in my code, but on the chip itself (!). For what is worth, I'm using a Chinese MH-ET LIVE MAX30102 module. But I really doubt that Chinese cloners reverse engineered the Maxim part. More likely it's a different silicon revision (I have a contact in Maxim, will try to see if they know anything about it)

According to the MAX30102 datasheet, LED1 is RED, LED2 is IR. And when reading the FIFO, the first 3 bytes are the RED channel values, the following 3 are IR, then repeats for all the samples

But not on my 2 devices. The actual LEDs on the chip are swapped. It's pretty easy to see if that's the case:

In file max30102.cpp, change as follows

if(!maxim_max30102_write_reg(REG_LED1_PA,0x00))   //Choose value for ~ 7mA for LED1
    return false;
if(!maxim_max30102_write_reg(REG_LED2_PA,0x24))   // Choose value for ~ 7mA for LED2
    return false;

According to the datasheet, only the IR LED should be active. But in my case, only the RED one is.

Then I tried

if(!maxim_max30102_write_reg(REG_LED1_PA,0x24))   //Choose value for ~ 7mA for LED1
    return false;
if(!maxim_max30102_write_reg(REG_LED2_PA,0x00))   // Choose value for ~ 7mA for LED2
    return false;

And in this case only the IR LED is active: use a phone camera to look at the LED in a dark room, you will see a faint purple dot, since phone cameras are sensitive to IR (if you use 0x80 instead of 0x24, the IR is even more visible)

So, unsurprisingly, when reading the FIFO, the values are also swapped.

It's trivial to fix, by simply changing one line in the main loop() from"

maxim_max30102_read_fifo((aun_red_buffer + i), (aun_ir_buffer + i)); //read from MAX30102 FIFO

to

maxim_max30102_read_fifo((aun_ir_buffer + i), (aun_red_buffer + i)); //read from MAX30102 FIFO

Just in case, I checked the device RevID and PartID , and I got 0x03 and 0x15

  uint8_t revID;
  uint8_t partID;

  maxim_max30102_read_reg(0xFE, &revID);
  maxim_max30102_read_reg(0xFF, &partID);
  Serial.print("Rev ");
  Serial.print(revID), HEX;
  Serial.print(" Part ");
  Serial.println(partID, HEX);

Would be interesting to know what your MAX30102 reports

As for the ESP32 port, there is really nothing to do. Simply use the right SDA (D21) and SCL (D22) pins and an interrupt pin (I usedD19, changing the oxiInt value to 19), commented out #define USE_ADALOGGER, compiled and everything worked just fine, happily printing values (when valid) in the serial monitor output. The ESP32 has definitely more than enough computing power and memory to handle something like this

Thanks again for the code and the troubleshooting help. Hopefully knowing that some modules might have RED and IR swapped could help other folks. By zeroing out one of the two REG_LEDx_PA registers it's pretty easy to see if the module follows the datasheet or if LED1 and LED2 are reversed

I would suggest adding part of the above to your troubleshooting steps, to avoid more people pinging you about this if they also use Chinese modules

arzaman commented 4 years ago

As for the ESP32 port, there is really nothing to do. Simply use the right SDA (D21) and SCL (D22) pins and an interrupt pin (I usedD19, changing the oxiInt value to 19), commented out #define USE_ADALOGGER, compiled and everything worked just fine, happily printing values (when valid) in the serial monitor output. The ESP32 has definitely more than enough computing power and memory to handle something like this

I'm still a step back compared to you :-) stil struggling to get some output I use EP32 pico m5stick board and connected SDA 32 and SCL 33 (if I run ESP32 port scanner I can detect the MAX3012 on the right addres) and oxInt pin to 26 commented out #define USE_ADALOGGER (this still gives me compliation error)

but when I run the code no coversion happens on serial monitor I just get Press any key to start conversion Time[s] SpO2 HR Clock Ratio Corr

but no data

if I run the DEBUG mode I can see the row data of R amd IR led and seems OK

MAX30102.txt MAX30102.xlsx

r-ir

so seems conversion won't start any idea why the conversion is no perfomed ?

thnaks Davide

robcazzaro commented 4 years ago

Your module, like mine, has the IR and RED channels swapped. So you also need to change the same line I changed into

maxim_max30102_read_fifo((aun_ir_buffer + i), (aun_red_buffer + i)); //read from MAX30102 FIFO

Keep in mind that if the SpO2 reading is not valid, the code doesn't print anything. It only prints when both the heart rate and SpO2 are valid. So when IR and RED are swapped, there is no output unless you use DEBUG, then you will see that it prints -999.0 when there is no valid reading

As I said, I think that the majority of the Chinese modules on sale have swapped IR and RED. It would be possible to check in the main algorithm if the values are swapped and automatically fix it, but IMHO would be a waste of time: after all, it only takes a minute to figure it out and once figured it out, that will never change for the lifetime of the device

Re-read my previous entry, and you will see how to check if your LEDs are reversed

arzaman commented 4 years ago

update I swap RED and IR reading of the fifo as suggested by @robcazzaro and conversion now works fine !! I have same Chinese clone module thanks so much I will never realize such a problem without your help

Davide

arzaman commented 4 years ago

One more question/doubt I see that in @aromring implementation the INT signal of MAX30102 is is controlled by the SW From data sheet there are several status when the interrupt is triggered (FIFO data ready etc..), what is the case in this sketch ? I see other examples that don't use the INT pin so I expect application does continuos polling of the FIFO register, is there any way to avoid INT usage ?...reason is simple save on more wire to the breakout sensor and just rely on I2C bus and simply the connection and wiring What happens if I force the INT with pull down resistor on the breakout board ?

thanks again for any suggestions Davide

robcazzaro commented 4 years ago

Davide, it's considered bad Github etiquette to hijack an existing issue to ask unrelated questions. ESP32 port questions might have been on topic, but changes to the code are not

The MAX30102 datasheet is easily available, as are multiple open source MAX30102 libraries that can be used with and without interrupt. The original code is easy to read and well structured, so you can simply use any method to read an array of 100 IR and RED values and invoke the rf_heart_rate_and_oxygen_saturation(() function

Please refrain from adding additional comments on this issue

aromring commented 4 years ago

Rob, Thank you very much for keeping this forum clean. Since it was determined the SpO2 problems were related to cloned hardware, I am closing this issue.

janusf commented 4 years ago

You guys are awesome. I had the same problem. Did not realize I got a clone sensor until I read this thread. Thanks!!

theophyk commented 3 years ago

I did more tests, and the only possible conclusion is that indeed the IR and RED LEDs are swapped. But the weird thing is that they are not swapped in my code, but on the chip itself (!). For what is worth, I'm using a Chinese MH-ET LIVE MAX30102 module. But I really doubt that Chinese cloners reverse engineered the Maxim part. More likely it's a different silicon revision (I have a contact in Maxim, will try to see if they know anything about it)

@robcazzaro Have you followed up on this issue to see whether this behavior is specific to a certain silicon revision of MAX30102 or the chips used on MH-ET LIVE MAX30102 are indeed clones expressing this kind of behavior? I tried to contact the guys at Maxim about this but didn't have any luck...

P.S. I also noticed that in addition to IR and red led channels being swapped, both PPG RDY and ALC OVF interrupts don't work either on this particular board.

robcazzaro commented 3 years ago

@alireza9900 I tried to get answers from Maxim, but never got a reply. If you get one, please share. The MAX30102 is a pretty old and simple chip and mostly used by hobbyists. So it wouldn't be that hard to clone and it wouldn't surprise me if many of the cheap eBay modules use cloned chips. After all, the STM32F103 series chips have been cloned by many Chinese makers, and that's a much harder chip to clone

aromring commented 3 years ago

Alireza and Rob, Both of you may save yourself some energy and stop "trying to get answers from Maxim". Just calm down for a moment and think: how could Maxim be responsible for what cloners do? Especially the ones who stole their design? Imagine you are a farmer that participates, e.g., in a pumpkin growing contest. Your competing neighbor steals your recipe and manages to grow an equally big pumpkin and thus get some share of the local food market. Now imagine customers who buy the thieving neighbor's pumpkins come to you with complaints that these don't taste good. What would be your response, hmm? Maxim is trying to be very polite by not responding at all. Otherwise, they have every right to give you an earful of you-know-what.

robcazzaro commented 3 years ago

@aromring I think you completely missed the point of the discussion. There are 2 possibilities here:

1) Maxim created a different revision of the silicon, which swapped the LEDs without documenting it. Unlikely, but not unheard of 2) Someone managed to clone the MAX30102 and made a mistake cloning it. That is also unlikely, since there aren't a lot of complex chips that get cloned, and the MAX30102 is cheap enough and not used in huge quantities, that cloning won't easily recover the cost. Also, there are no other reports on the internet of clones existing. So we have 2 unlikely scenarios

We are trying to get an answer from Maxim on "did you spin a revision with swapped LEDs?". If the answer is "no", then we will know for sure it's a clone and very likely much more than just the swapped LEDs are a problem (very likely in that case the readings are also going to be poor quality). And we can then share the knowledge with sites like Hackaday and others to warn other hobbyists. As things are now, there is only speculation. Something like this, to be clear https://hackaday.com/2020/09/15/deep-sleep-problems-lead-to-forensic-investigation-of-troublesome-chip/

If on the other hand Maxim created a different revision, then we know it's a simple fix and once again can share the knowledge. And know that once you swap the LEDs in the code, everything else works as expected

Nobody is trying to hold Maxim responsible in any way for problems created by a potential clone. Just get more info on what happened, and know more. And I strongly believe it's in Maxim's interest to let people know if a counterfeit chip exists, since it might otherwise reflect poorly on them

aromring commented 3 years ago

OK, fair enough, I stand corrected. However, if that's the case, then Maxim's silence is deafening in spite of numerous people asking this question. IMHO, it means that Maxim at least think of the scenario 2 rather than 1.

robcazzaro commented 3 years ago

Well, in my case it actually means that my contact left Maxim, so I never could get an answer :) not that Maxim refused to answer. I pinged another contact today and I will see if I get a reply. But you have to realize that, since this is not a work request, Maxim employees might simply prioritize it too low to answer (and I said as much in my request, I cannot burn my goodwill with a Maxim employee I interact thru one of my consulting projects. I'm sure Maxim people are plenty busy answering questions from actual customers who buy hundreds of chips, not hobbyists with a curiosity. If my module came from an approved distributor, I'm also sure that they would have jumped on it. But since it was a one off from eBay, well...

So, please, don't read too much into the lack of an answer. It really means nothing in my specific case

Anyeos commented 3 years ago

I don't understand really how it can be a clone because the quality of the glass and the terminals are good enought to guess it is a really working thing.

Here a picture of the leds: MAX30102-inverted-leds

Here one for the chip part: MAX30102-chip-part

Anyeos commented 3 years ago

Alireza and Rob, Both of you may save yourself some energy and stop "trying to get answers from Maxim". Just calm down for a moment and think: how could Maxim be responsible for what cloners do? Especially the ones who stole their design? Imagine you are a farmer that participates, e.g., in a pumpkin growing contest. Your competing neighbor steals your recipe and manages to grow an equally big pumpkin and thus get some share of the local food market. Now imagine customers who buy the thieving neighbor's pumpkins come to you with complaints that these don't taste good. What would be your response, hmm? Maxim is trying to be very polite by not responding at all. Otherwise, they have every right to give you an earful of you-know-what.

You are right but if I buy from other farmer with a bad taste and I go to the original farmer then he/she must tell me exactly what happened if he/she want to sell me in a future. I will buy him/her if that is the case.

But as I can guess there apperas to be a permitted cloned market. Or maybe they don't sell directly assembled chips but only the specifications. So, maybe the ones that we have are "original" but from one of the assemblers that there are lying around.

And I need an answer because this chip have the name of MAXIM. So MAXIM is permitting to have they name on something that they don't made? Really? That is something suspicious. Meanwhile the solution is just invert the channels but the doubt is still alive because I don't have the certainty over the specs of this chip. So, if this chip does not meet the specs I want to know.

Maxim must write a public letter to clarify this.

moononournation commented 3 years ago

Anyone can post some photo about original and cloned board? I want to confirm if my board is clone one and if channels are reversed.

aromring commented 3 years ago

Sorry guys, @Anyeos and @moononournation, although I sympathize with your woes this is not the right forum to ask these questions. The right forum is here: https://maximsupport.microsoftcrmportals.com/en-US/support-center/ I have no affiliation with Maxim Integrated, Inc., and simply don't know any helpful answers to your questions. Other than that, have Happy Holidays!

Anyeos commented 3 years ago

Sorry guys, @Anyeos and @moononournation, although I sympathize with your woes this is not the right forum to ask these questions. The right forum is here: https://maximsupport.microsoftcrmportals.com/en-US/support-center/ I have no affiliation with Maxim Integrated, Inc., and simply don't know any helpful answers to your questions. Other than that, have Happy Holidays!

I agree and I will not put future messages about it. But please dont remove the actual ones because it is not out of scope. We are using a library designed for Maxim chips on the first place and there are a relation with how the chip is working. Except the portion about the company decisions but the rest is related.

TheBluePhoenix10 commented 3 years ago

I am facing the same problem. Reversed LED Channels that caused erroneous spo2 and Hr measurements in mode 2. To check the sensor I just tried to run it in mode 1 (only RED LED on) and found out that the IR LED was ON instead. Never realised it was this big a problem since there are no other mentions of this issue anywhere else. I wanted to ask a few things -

It's amazing that someone has converted the instructable post into actual working code.

Thanks a lot!

hishd commented 3 years ago

@robcazzaro kudos for your proper research and your conclusion on the LED swap on MAX30102 clones. I was facing days with figuring this issue and you just helped me with solving the issue with just changing 2 lines of code.

Really appreciate your effort on identifying the issue and suggesting the solution.....!!!

viafx24 commented 2 years ago

Thanks to Robcazzaro and Aromring for precious advises. The procedure from robcazzaro perfectly works for me on a esp32 with the MH-ET live max30102. I add some details for newbies like me.

include

include

include

include `

Thus, for me, it looks to work pretty well and i'm quite satisfied by the library and the trick of the hardware switch of red and IR led. My only concern is that I can't verify the quality of spo2 in pathologic situation (i.e when spO2 is smaller than 95%) and since i don't have a commercial apparatus, I can't compare data as well but i would bet that comparison may be quite good. Any suggestion to simulate smaller Spo2 to be confident about the data in pathologic situations ?

Thanks a lot.

Cheers.

Via_Fx_24

viafx24 commented 2 years ago

Concerning my previous comment, I made some quick tests with a commercial apparatus that i borrow. Results were very similar. 99% of spo2 for the max30102 (right hand), 97% of spo2 for the commercial one (left hand). A small difference of 2%. Stopping breathing fall to 97% for max30102 et to 95% for the commercial one. Thus the difference (2%) between the two apparatus remains the same giving good confidence in max30102 data. Concerning heart rate, both apparatus found 68 bpm +/- 2. I' m thus quite confident with the data provided by this code and the hardware max30102. This comment may be considered "out of range" concerning the initial question. However I added it to encourage folks that get the MH-ET live max30102 to try the code" MAX30102_by_RF" with the modification proposed by Robcazzaro because it works well, it's an easy modification and results are quite consistent with professional apparatus. Hope it's help. Cheers. Via_Fx_24