The Heart Rate click from MikroElektronika is a pulse-oximeter featuring one MAX30100 sensor, plus all the required additional circuits, including the 1.8V power supply implemented with an AP7331 low dropout regulator. The click boards requires only a 3.3V power supply (sorry Arduino users, there’s no 5V version of this click board). Communication with the host is done via I2C interface, plus one INT pin that can be configured in software.
The MAX30100 is an optical, reflective sensor that combines one IR and one red LED, a photodetector, plus a low noise analog processing stage, which among other things performs ambient light cancellation. The analog signal is fed into an A/D converter with programmable resolution and integration time. A digital filter follows, then the signal is sent to the host microcontroller via I2C. A temperature sensor is also provided, as temperature compensation is needed in precise SpO2 determination.
With this being said, at the first glance things should be nice — just let the sensor do the hard work. In practice things are not that bright — the sensor does return only the IR and Red reflectance, leaving the tasks to determine the heart rate and SpO2 to the user. Moreover, there’s a complicated procedure to read the acquired data, involving FIFO register as circular buffer, with read and write pointers stored in separate registers. Another register stores the number of FIFO overflows (the number of lost samples). This overflow register tops sat 0x0F, after that we don’t know how many samples we have lost.
I have spent over one week to try to understand how this sensor works, and all I could do so far is to get a steady data flow, and to show the red and IR reflectance data on an LCD. The development board used in this blog post is an EasyPIC v7, with PIC18F45K22 microcontroller, at a clock frequency of 8MHz. The reason for using this clock rate is that the LCD refuses to work on higher frequencies when powered from 3.3V.
Only I2C communication is used. The interrupt pin of the MAX30100 is not used, as the INT pin of the click board is shared with the LCD data lines, and is set as output by the LCD routine.
I’ve split the code in three parts, so one can reuse the MAX30100 functions with ease. So, we start with the max30100.h file, which contains definitions of MAX30100 registers and register values, and declarations of functions in max30100.c.
Themax30100.c file contains all the required functions to set the MAX30100 and to read and write data to its registers.
The MAX3100 features 8-bit registers for most functions, with the exception of the FIFO register which stores four bytes (two for IR reflectance and two for red reflectance). Even when working in heart rate mode, without data from the red LED, we still have to read four bytes from the FIFO register, with the first two bytes containing red reflectance and the last two bytes being zero. My approach in HR mode is to use *data1 to update heart rate (IR reflectance), and I assign *data2 to a dummy variable which is left unused. In SpO2 mode I use *data1 to update IR reflectance and *data2 to update red reflectance. The code is as follows:
In the above code, I first check if the MAX30100 is present by reading from register 0xFF. If the returned data is 0x11 then I know that MAX30100 is connected correctly. Then I read from register 0xFE which stores the revision version and I print that data on LCD.
Then I set the IR current to 8mA and the Red LED current to 11mA. The currents can be the same, this is just to show how to set them. In practice, one should adjust the LED currents to get the best readings (make the most of the A/D resolution).
Sampling rate is set at 50 samples per second. Anything more than this and the PIC won’t be able to handle the data flow.
Pulse width is set to 1600μs, thus A/D resolution is 16 bit. A short comment here: the A/D data is left-justified, so if you use a lower A/D resolution you’ll have to perform some byte-shifting to get rid of the extra zeroes.
Now I can wake up the sensor and initialize the FIFO by setting the read, write and overflow registers to 0x00.
Finally, writing the bits MODE[2:0] of the CONFIG register starts the data acquisition process. If you have chosen SpO2 mode you’ll notice the Red LED going on. In heart rate mode only the IR led works.
Now we go to the main loop. We first read the value in the OVF (overflow) register. If the value is 0x0F this means we have lost too much data — how much is unknown, as the OVF tops at 0x0F — and the program stops.
If the value in the OVF register is below 0x0F, even if we have some data lost, at least we can compensate for this. All OK, we can read from FIFO.
To do this we fist have to determine how much data is in the FIFO — we can have from 0 (no samples) up to 16 samples, each sample having four bytes. This is done by the MAX30100_getNumSamp function. If the function returns 0 we have no data, so we wait for 20ms — that is, we take 50 samples per second, so in the next 20ms we will have a sample acquired.
If the number of samples is greater than 0 we enter a loop in which we read the samples, and we perform data processing — in this code example we display the Red and IR reflectances on LCD. One observation here: the whole process of reading one single sample takes a little below 1.3ms, leaving only a bit over 14ms to process the data until the next sample is ready. Not much considering and 8bit PIC and the low clock frequency.
Of course, one can take full advantage of the FIFO architecture and leave the data to gather in FIFO until some more complicated computations are performed, followed by a burst read of everything gathered in the FIFO. One should take care not to lose — the OVF register is your friend.
What I get from this sensor is a steady stream of IR and Red reflectances. That stream of data has to be converted to heart rate and SpO2 information. What I have found so far is that both IR and red data have a strong DC component with a small AC component that varies with the heartbeat. To make things worse, even the slightest movement of the finger results in a large variation of the DC component.
The 8bit PIC is a bit underpowered for this task, and I think I will have to move to a much powerful processor — perhaps a PIC32 programmed in MPIDE, or even an AT91SAM3X8E (think Flip & Click).
What algorithms should I use? I don’t have any idea. I hope I’ll find out something that works…
Originally published at https://electronza.com on April 13, 2016. Moved to Medium on May 3, 2020.