Closed robcazzaro closed 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.
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.
:) 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.
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: 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: 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.
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!
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
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
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
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
so seems conversion won't start any idea why the conversion is no perfomed ?
thnaks Davide
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
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
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
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
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.
You guys are awesome. I had the same problem. Did not realize I got a clone sensor until I read this thread. Thanks!!
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.
@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
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.
@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
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.
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
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:
Here one for the chip part:
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.
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.
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!
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.
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!
@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.....!!!
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.
then I changed the interrupt pin from 10 to 19 as robcazzaro did. It's a convenient pin because just closed to gnd, and gpio 21 and 22 (sda and scl)
then, in the main.cpp, I changed the line concerning the hardware switch of the led as Robcazzaro found it 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
then, the compilation failed because the only function of the script (millis_to_hours) needed to befined at the top of the script (probably a small difference between .cpp and .ino and if one uses a .ino file, this may probably be ignored) . I thus add this line juste before the setup() function:
void millis_to_hours(uint32_t ms, char* hr_str)
Then I compiled and transfered the sketch and it works. I got 99% of spo2 and around 60 of heart rate. I performed somes tests and if i do some exercices, the heart rate increase accordinlgy. If I stopped breathing until I just can't maintain it, the spo2 decreases but with a delay and not a lot (from 99% to 97%). I would have expected a quicker response and a stronger decrease (for instance to 90%) but i'm not an expert on that stuff and it could be the physiological reality.
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
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
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