GithubHelp home page GithubHelp logo

n-elia / max30102-micropython-driver Goto Github PK

View Code? Open in Web Editor NEW
56.0 3.0 21.0 8.66 MB

A Maxim MAX30102 driver ported to MicroPython. It should also work for MAX30105.

License: MIT License

Python 100.00%
tinypico micropython-driver max30102 heartrate micropython sensor driver esp32

max30102-micropython-driver's Introduction

Upload Python Package Pre-compile modules PyPI version PyPI - Downloads

Maxim MAX30102 MicroPython driver

A port of the SparkFun driver for Maxim MAX30102 sensor to MicroPython.

It should work 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 contents

Disclaimer

This 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.

Usage

Driver usage is quite straightforward. You just need to import the library, and to set up a SoftI2C instance.

A full example is provided in /examples/basic_usage directory.

1 - Including this library into your project

1a - network-enabled MicroPython ports

Warning: in latest MicroPython releases upip has been deprecated in favor of mip. This module is compatible with both of them. Please use the package manager included into your MicroPython version.

If your MicroPython version supports mip package manager, put these lines after the setup of an Internet connection:

import mip

mip.install("github:n-elia/MAX30102-MicroPython-driver")

If your MicroPython version supports upip package manager, put these lines after the setup of an Internet connection:

import upip

upip.install("micropython-max30102")

To run the example in ./example folder, please set your WiFi credentials in boot.py and then upload ./example content 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 copy max30102/circular_buffer.py and max30102/__init__.py, into the lib/max30102 directory.

The folder tree of your device should look as follows:

.
โ”ฃ ๐Ÿ“œ boot.py
โ”ฃ ๐Ÿ“œ main.py
โ”— ๐Ÿ“‚ lib
  โ”— ๐Ÿ“‚ max30102
    โ”ฃ ๐Ÿ“œ __init__.py
    โ”— ๐Ÿ“œ circular_buffer.py

Then, import the constructor as follows:

from max30102 import MAX30102

To run the example in ./examples/basic_usage folder, copy max30102/circular_buffer.py and max30102/__init__.py into the ./examples/basic_usage/lib/max30102 directory. Then, upload the ./examples/basic_usage directory 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 configuration

I2C connection

Create a SoftI2C instance as in the following example:

from machine import SoftI2C, Pin

my_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 setup

The 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: 16384
Sample rate: 400 Hz
Led power: maximum (50.0mA - Presence detection of ~12 inch)
Averaged samples: 8
Pulse width: 411

# Setup with default values
sensor.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 the setup_sensor() method has still to be called before modifying the single parameters.

# Set the number of samples to be averaged by the chip
SAMPLE_AVG = 8  # Options: 1, 2, 4, 8, 16, 32
sensor.set_fifo_average(SAMPLE_AVG)

# Set the ADC range
ADC_RANGE = 4096  # Options: 2048, 4096, 8192, 16384
sensor.set_adc_range(ADC_RANGE)

# Set the sample rate
SAMPLE_RATE = 400  # Options: 50, 100, 200, 400, 800, 1000, 1600, 3200
sensor.set_sample_rate(SAMPLE_RATE)

# Set the Pulse Width
PULSE_WIDTH = 118  # Options: 69, 118, 215, 411
sensor.set_pulse_width(PULSE_WIDTH)

# Set the LED mode
LED_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 LED
LED_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 inch
sensor.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 LEDs
LED_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 inch
sensor.set_active_leds_amplitude(LED_POWER)

3 - Data acquisition

The 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.

The check() 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 the storage. We can access those samples using the provided methods such as popRedFromStorage().

Read data from sensor

As 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 samples
    if (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 rate

Considering the sensor configuration, two main parameters will affect the data throughput of the sensor itself:

  • The sample rate, which is the number of RAW readings per second made by the sensor

  • The averaged samples, which is the number of RAW readings averaged together for composing a single sample

Therefore, 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 rate
acquisition_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 in this example sketch by SparkFun, using the following snippet:

# (Assuming that the sensor instance has been already set-up)
from utime import ticks_diff, ticks_ms

t_start = ticks_us()  # Starting time of the acquisition
samples_n = 0  # Number of samples that have been collected

while True:
    sensor.check()
    if sensor.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)
        if compute_frequency:
            if ticks_diff(ticks_us(), t_start) >= 999999:
                f_HZ = samples_n
                samples_n = 0
                print("acquisition frequency = ", f_HZ)
                t_start = ticks_us()
            else:
                samples_n = samples_n + 1

Die temperature reading

The read_temperature() method allows to read the internal die temperature. An example is proposed below.

# Read the die temperature in Celsius degree
temperature_C = sensor.read_temperature()
print("Die temperature: ", temperature_C, "ยฐC")

Note: as stated in the datasheet, 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.

Changelog

  • v0.4.2
    • Added an heartrate estimation example.
    • Issued a new release to update the PyPi docs.
  • v0.4.1
    • Changed the module files organization.
    • Added support to mip package manager.
  • v0.4.0
    • According to some best practices discussed here, 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.6
    • The 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.5
    • A SoftI2C instance is now required by the constructor.
    • The constructor now raises RuntimeError when 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.4
    • The package has been refactored to be compliant to PEP standards.
  • v0.3.3
    • Made a PyPi package. Now you can install this package with upip.
    • Tested with Raspberry Pi Pico and non-genuine sensors.
  • v0.3
    • Tested with TinyPico board (based on ESP32-D4) and genuine Maxim MAX30102 sensor.

Acknowledgements

This work is a lot based on:

  • SparkFun MAX3010x Sensor Library

    Written by Peter Jansen and Nathan 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-micropython

    A port of the library to MicroPython by kandizzy

Tested platforms

  • TinyPico (board based on ESP32-D4) running 'tinypico-20210418-v1.15.bin' MicroPython firmware, connected to a genuine Maxim 30102 breakout board (MAXREFDES117#).

  • Raspberry Pi Pico + non-Maxim breakout board (thanks to ebolisa)

  • ESP32-S3 (Unexpected Maker TinyS3) running MicroPython v1.18 stable and MicroPython v1.19 stable + non-Maxim breakout board.

I2C read issue: as discussed in the MicroPython forum and in the GitHub 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 things and troubleshooting

Realtime plot over Serial

The 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 the serial plotter (Ctrl+Maiusc+L) and see a real-time plot of your readings (need some help? take a look here).

For instance, this is an example of my heartbeat taken on the index finger:

Serial Plotter picture

Sensor clones

There 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) (see here). 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 estimation

If you're looking for algorithms for extracting heartrate and SPO2 from your RAW data, take a look here and here.

A basic example of heartrate detection is also available in ./examples/heart_rate.

ESP8266 module import error

If you get an error like this:

MemoryError: memory allocation failed,allocating 416 bytes

then your heap is too small to allocate the module. You can try to pre-compile the module using mpy-cross and then import it as usual. You can either use the precompiled module provided in the GitHub Action artifacts or compile ityourself.

In the first case, you just have to replace the max30102 folder of the module with the one provided in the artifact archive.

In either case, you have to choose the proper version of mpy-cross according to your Micropython version: for MicroPython v.1.18 and below, you can use mpy-cross-v5, while for MicroPython v1.19 you have to use mpy-cross-v6.

More information is provided into this pull request.

max30102-micropython-driver's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

max30102-micropython-driver's Issues

[Question] Measured Acquisition Frequency does not match with sensor acquisition frequency

Hello. First thank you for this amazing library ;)

I stumbled on a question while adjusting the sample rate and fifo average.
Say I set the sample rate to 1600 and fifo average to 32. This represents a sensor accusation frequency of 50Hz.

The measured accusation frequency starts with 40 and then drops to 13 very fast. Do you have any idea why this bevavour occurs? Is the code just too slow executed? I am showing the terminal outcome below. I removed the printing of the values.

`INFO:MAX30102:(MAX30105) MAX3010x sensor found!
Setting up sensor with default configuration.

INFO:MAX30102:(updateAcquisitionFrequency) Acq. frequency: 50.000000 Hz
INFO:MAX30102:(updateAcquisitionFrequency) Acq. period: 20.000000 ms
INFO:MAX30102:(updateAcquisitionFrequency) Acq. frequency: 200.000000 Hz
INFO:MAX30102:(updateAcquisitionFrequency) Acq. period: 5.000000 ms
INFO:MAX30102:(updateAcquisitionFrequency) Acq. frequency: 50.000000 Hz
INFO:MAX30102:(updateAcquisitionFrequency) Acq. period: 20.000000 ms
Reading temperature in ยฐC.

22.875
Starting data acquisition from RED & IR registers...

acquisition frequency = 40.0
acquisition frequency = 13.0
acquisition frequency = 13.0
acquisition frequency = 13.0
acquisition frequency = 13.0
acquisition frequency = 13.0
acquisition frequency = 13.0
acquisition frequency = 13.0`

This is of cause just an example. I tried different combinations.

sample rate, sample avg, sensor acquisition, measured acquisition
3200, 32, 100, 13
3200, 16, 200, 25
3200, 8, 400, 50
3200, 4, 800, 100
...
1600, 32, 50, 13
1600, 16, 100, 25
1600, 8, 200, 50
1600, 4, 400, 100
...
800, 32, 25, 13
800, 16, 50, 25
800, 8, 100, 50 # However here the sensor initialisation is returning a acquisition frequency of 50Hz?

Given fro the data you can clearly see that the ratio of sample rate and sample avg does not match the acquisition frequency. why is that?

thank you and
Cheers
Gruft

Calculating SPO2 and Heartrate

Hi there

I have successfully run the sample file however is it possible to compute

Heart Rate and SpO2 using this library?

Esp8266 based board has not enough RAM to load the module

When I tried from max30102 import MAX30102, always got importError: no module named 'max30102.MAX30102'
dir as below:
lib
-max30102
--circular_buffer.py
--init.py
I also tried from max30102.init import MAX30102, got another error:
Traceback(most recent call last):
File"", line1, in
MemoryError: memory allocation failed,allocating 416 bytes

Please help me.

no module named 'max30102.MAX30102'

Hi, I wanted to use the library on a pico w with version v1.19.1-724-gfb7d21153 of micropython installed.
First problem : upip is no more used in this version of micropython, but a new toool called mip
However, trying to run mip.install("micropython - max30102") returns
Installing micropython - max30102 (latest) from https://micropython.org/pi/v2 to /lib
Traceback (most recent call last):
File "", line 1, in
File "mip/init.py", line 1, in install
File "mip/init.py", line 1, in _install_package
File "mip/init.py", line 1, in _install_json
File "urequests.py", line 180, in get
File "urequests.py", line 76, in request
OSError: -6

I then copied the files into the lib folder and tried

from max30102 import MAX30102

but got the following error

Traceback (most recent call last):
File "", line 1, in
ImportError: no module named 'max30102.MAX30102'

any thoughts about how to resolve the problem ?

Code on RPi Pico

Hi,
Testing the library code (no changes made) on a Pico board and getting pretty much a linear output.
What is possible wrong?
TIA

Cap

Having issues with a raspberry pi pico

I'm using a Raspberry Pi Pico running micropython.
I have the device attached on the hardware I2C pins and I pass through the instance just fine
Sensor setup works. However I can't seem to read any actual data from it
Using the example you have given I get strange errors, most are related to strings in the check function, for example:

Traceback (most recent call last):
  File "<stdin>", line 42, in <module>
  File "max30102.py", line 714, in safe_check
  File "max30102.py", line 668, in check
TypeError: ord() expected a character, !string of length 536990608 found

Other times, I get a few readings then it just freezes.
I have tried with check() and safe_check()
Both give the same result

Also the readings I get are like this:

22665 , 30041
22663 , 30040
22661 , 30038
22660 , 30039
22660 , 30041
22661 , 30044
22663 , 30052
22668 , 30063
22672 , 30073
22673 , 30075
22669 , 30070
22667 , 30068
22665 , 30068
22666 , 30071
22667 , 30074

Are they normal for this sensor?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.