Package: micropython-max30102
Maxim MAX30102 MicroPython driverA port of the SparkFun driver for Maxim MAX30102 sensor to MicroPython.Itshouldwork for MAX30105, too. If you have the chance to test this library with a MAX30105, please leave your
feedback in the Discussions section.Table of contentsDisclaimerUsage1 - Including this library into your project1a -network-enabled MicroPython ports1b -manual way(no Internet access required)2 - I2C setup and sensor configurationI2C connectionSensor setup3 - Data acquisitionRead data from sensorNotes on data acquisition rateDie temperature readingChangelogAcknowledgementsTested platforms and known issuesOther useful thingsRealtime plot over SerialSensor clonesHeartrate and SPO2 estimationDisclaimerThis work is not intended to be used in professional environments, and there are no guarantees on its functionalities.
Please do not rely on it for medical purposes or professional usage.UsageDriver usage is quite straightforward. You just need to import the library, and to set up aSoftI2Cinstance.A full example is provided in/exampledirectory.1 - Including this library into your project1a -network-enabled MicroPython portsWarning: in latest MicroPython releasesupiphas been deprecated in favor ofmip. This module is compatible with both of them. Please use the package manager included into your MicroPython version.If your MicroPython version supportsmippackage manager, put these linesafterthe setup of an Internet connection:importmipmip.install("github:n-elia/MAX30102-MicroPython-driver")If your MicroPython version supportsupippackage manager, put these linesafterthe setup of an Internet connection:importupipupip.install("micropython-max30102")To run the example in./examplefolder, please set your WiFi credentials inboot.pyand then upload./examplecontent into your microcontroller. If you prefer, you can perform a manual install as explained below.1b -manual way(no Internet access required)To directly include the library into a MicroPython project, it's sufficient to copymax30102/circular_buffer.pyandmax30102/__init__.pynext to yourmain.pyfile, into alibdirectory.The folder tree should look as follows:.
┣ 📜 boot.py
┣ 📜 main.py
┗ 📂 lib
  ┣ 📜 __init__.py
  ┗ 📜 circular_buffer.pyThen, import the constructor as follows:frommax30102importMAX30102To run the example in./examplefolder, copymax30102/circular_buffer.pyandmax30102/__init__.pyinto
the./example/libdirectory. Then, upload the./exampledirectory content into your microcontroller. After the
upload, press the reset button of your board are you're good to go.2 - I2C setup and sensor configurationI2C connectionCreate aSoftI2Cinstance as in the following example:frommachineimportSoftI2C,Pinmy_SDA_pin=21# I2C SDA pin number here!my_SCL_pin=22# I2C SCL pin number here!my_i2c_freq=400000# I2C frequency (Hz) here!i2c=SoftI2C(sda=Pin(my_SDA_pin),scl=Pin(my_SCL_pin),freq=my_i2c_freq)sensor=MAX30102(i2c=i2c)The I2C pin numbers depend on the board that you are using, and how you wired the sensor to it.Sensor setupThe library provides a method to setup the sensor at once. Leaving the arguments empty, makes the library load the
default values.Default configuration values:Led mode: 2 (RED + IR)ADC range: 16384Sample rate: 400 HzLed power: maximum (50.0mA - Presence detection of ~12 inch)Averaged samples: 8Pulse width: 411# Setup with default valuessensor.setup_sensor()# Alternative example:setup_sensor(self,led_mode=2,adc_range=16384,sample_rate=400)The library provides the methods to change the configuration parameters one by one, too. Remember that
thesetup_sensor()method has still to be called before modifying the single parameters.# Set the number of samples to be averaged by the chipSAMPLE_AVG=8# Options: 1, 2, 4, 8, 16, 32sensor.set_fifo_average(SAMPLE_AVG)# Set the ADC rangeADC_RANGE=4096# Options: 2048, 4096, 8192, 16384sensor.set_adc_range(ADC_RANGE)# Set the sample rateSAMPLE_RATE=400# Options: 50, 100, 200, 400, 800, 1000, 1600, 3200sensor.set_sample_rate(SAMPLE_RATE)# Set the Pulse WidthPULSE_WIDTH=118# Options: 69, 118, 215, 411sensor.set_pulse_width(PULSE_WIDTH)# Set the LED modeLED_MODE=2# Options: 1 (red), 2 (red + IR), 3 (red + IR + g - MAX30105 only)sensor.set_led_mode(LED_MODE)# Set the LED brightness of each LEDLED_POWER=MAX30105_PULSEAMP_MEDIUM# Options:# MAX30105_PULSE_AMP_LOWEST =  0x02 # 0.4mA  - Presence detection of ~4 inch# MAX30105_PULSE_AMP_LOW =     0x1F # 6.4mA  - Presence detection of ~8 inch# MAX30105_PULSE_AMP_MEDIUM =  0x7F # 25.4mA - Presence detection of ~8 inch# MAX30105_PULSE_AMP_HIGH =    0xFF # 50.0mA - Presence detection of ~12 inchsensor.set_pulse_amplitude_red(LED_POWER)sensor.set_pulse_amplitude_it(LED_POWER)sensor.set_pulse_amplitude_green(LED_POWER)# Set the LED brightness of all the active LEDsLED_POWER=MAX30105_PULSEAMP_MEDIUM# Options:# MAX30105_PULSE_AMP_LOWEST =  0x02 # 0.4mA  - Presence detection of ~4 inch# MAX30105_PULSE_AMP_LOW =     0x1F # 6.4mA  - Presence detection of ~8 inch# MAX30105_PULSE_AMP_MEDIUM =  0x7F # 25.4mA - Presence detection of ~8 inch# MAX30105_PULSE_AMP_HIGH =    0xFF # 50.0mA - Presence detection of ~12 inchsensor.set_active_leds_amplitude(LED_POWER)3 - Data acquisitionThe sensor will store all the readings into a FIFO register (FIFO_DATA). Based on the number of active LEDs and other
configuration parameters, the sensor instance will read data from that register, putting it into the_storage_.
The_storage_ is a circular buffer, that can be read using the provided methods.Thecheck()method polls the sensor to check if new samples are available in the FIFO queue. If data is available, it
will be read and put into thestorage. We can access those samples using the provided methods such
aspopRedFromStorage().Read data from sensorAs a consequence, this is an example on how the library can be used to read data from the sensor:while(True):# The check() method has to be continuously polled, to check if# there are new readings into the sensor's FIFO queue. When new# readings are available, this function will put them into the storage.sensor.check()# Check if the storage contains available samplesif(sensor.available()):# Access the storage FIFO and gather the readings (integers)red_sample=sensor.pop_red_from_storage()ir_sample=sensor.pop_ir_from_storage()# Print the acquired data (can be plot with Arduino Serial Plotter)print(red_sample,",",ir_sample)Notes on data acquisition rateConsidering the sensor configuration, two main parameters will affect the data throughput of the sensor itself:Thesample rate, which is the number of RAW readings per second made by the sensorTheaveraged samples, which is the number of RAW readings averaged together for composing a single sampleTherefore, the FIFO_DATA register will contain averaged RAW readings. The rate at which that register is fed depends on
the two parameters:real rate = sample rate / averaged samples.The library computes this value, that can be accessed with:# Get the estimated acquisition rateacquisition_rate=sensor.get_acquisition_frequency()However, there are some limitations on sensor side and on micropocessor side that may affect the acquisition rate (see
issue #6 for more details about it). Is is possible to measure the real throughput as
inthisexample sketch by SparkFun, using the following snippet:# (Assuming that the sensor instance has been already set-up)fromutimeimportticks_diff,ticks_mst_start=ticks_us()# Starting time of the acquisitionsamples_n=0# Number of samples that have been collectedwhileTrue:sensor.check()ifsensor.available():red_reading=sensor.pop_red_from_storage()ir_reading=sensor.pop_ir_from_storage()# Print the acquired data (so that it can be plotted with a Serial Plotter)print(red_reading,",",ir_reading)# Compute the real frequency at which we receive data (with microsecond precision)ifcompute_frequency:ifticks_diff(ticks_us(),t_start)>=999999:f_HZ=samples_nsamples_n=0print("acquisition frequency = ",f_HZ)t_start=ticks_us()else:samples_n=samples_n+1Die temperature readingTheread_temperature()method allows to read the internal die temperature. An example is proposed below.# Read the die temperature in Celsius degreetemperature_C=sensor.read_temperature()print("Die temperature: ",temperature_C,"°C")Note: as stated in thedatasheet, the internal die
temperature sensor is intended for calibrating the temperature dependence of the SpO2 subsystem. It has an inherent
resolution of 0.0625°C, but be aware that the accuracy is ±1°C.Changelogv0.4.1Changed the module files organization.Added support tomippackage manager.v0.4.0According to some best practices discussedhere, some
changes have been made.Removed the I2C scan at instantiation time, so that the sensor object could be instantiated even if the sensor
is not available at the moment.Removed the part ID checks at instantiation time, so that the user may skip them saving time.The example has been updated.v0.3.6The library now performs a I2C scan to check if the sensor is connected. This prevents unexpected I2C errors with
some boards (such as Raspberry Pi Pico).v0.3.5ASoftI2Cinstance is now required by the constructor.The constructor now raisesRuntimeErrorwhen the sensor is not found on I2C bus.The example has been updated to intercept the errors thrown by the constructor.The example has been updated to estimate real acquisition frequency with a precision of 1 microsecond.The readme has been re-organized to improve readability.v0.3.4The package has been refactored to be compliant to PEP standards.v0.3.3Made a PyPi package. Now you can install this package with upip.Tested with Raspberry Pi Pico and non-genuine sensors.v0.3Tested with TinyPico board (based on ESP32-D4) and genuine Maxim MAX30102 sensor.AcknowledgementsThis work is a lot based on:SparkFun MAX3010x Sensor LibraryWritten byPeter JansenandNathan Seidle(SparkFun)
This is a library written for the Maxim MAX30105 Optical Smoke Detector
It should also work with the MAX30102. However, the MAX30102 does not have a Green LED.
These sensors use I2C to communicate, as well as a single (optional)
interrupt line that is not currently supported in this driver.
Written by Peter Jansen and Nathan Seidle (SparkFun)
BSD license, all text above must be included in any redistribution.esp32-micropythonA port of the library to MicroPython bykandizzyTested platforms and known issuesThe library has been tested onTinyPico(board based onESP32-D4) running 'tinypico-20210418-v1.15.bin' MicroPython
firmware, connected to a genuine Maxim 30102 breakout
board (MAXREFDES117#).Tested (thanks to ebolisa) and working onRaspberry Pi
Pico+ non-Maxim breakout board.Tested and working onESP32-S3(Unexpected Maker TinyS3) + non-Maxim breakout board.I2C read issue: as discussed in theMicroPython forumand
in theGitHub Discussions section,
some board/sensor combinations lead to an issue that makes the first I2C read fail. This issue can be mitigated by
running an I2C scan before actually using the sensor, as shown in the provided example.Other useful thingsRealtime plot over SerialThe example proposed in this repository (main.py) contains a print statement in a CSV-like
format:print(red_reading, ",", IR_reading). If you open Arduino IDE and connect your board, then you will be able to
open theserial plotter(Ctrl+Maiusc+L) and see a real-time plot of your readings (need some help? take a
lookhere).For instance, this is an example of my heartbeat taken on the index finger:Sensor clonesThere is an issue involving chinese clones of the Maxim MAX30102: some of them appear to have the red and IR registers
inverted (or maybe the LEDs swapped) (seehere). You can easily
check if your sensor is inverted by putting it in LED mode 1: only the red LED should work. If you see the IR LED (use
your phone camera to check), then you have to collect IR samples as red ones and viceversa.Heartrate and SPO2 estimationIf you're looking for algorithms for extracting heartrate and SPO2 from your RAW data, take a
lookhereandhere