Skip to content

Refactoring for PEP compliance. Bumps to v0.3.4. #9

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 84 additions & 73 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
[![Upload Python Package](https://github.com/n-elia/MAX30102-MicroPython-driver/actions/workflows/python-publish.yml/badge.svg)](https://github.com/n-elia/MAX30102-MicroPython-driver/actions/workflows/python-publish.yml) [![PyPI version](https://badge.fury.io/py/micropython-max30102.svg)](https://badge.fury.io/py/micropython-max30102)
[![PyPi Build and Upload](https://github.com/n-elia/MAX30102-MicroPython-driver/actions/workflows/python-publish.yml/badge.svg?event=release)](https://github.com/n-elia/MAX30102-MicroPython-driver/actions/workflows/python-publish.yml) [![PyPI version](https://badge.fury.io/py/micropython-max30102.svg)](https://badge.fury.io/py/micropython-max30102)
# Maxim MAX30102 MicroPython driver

A port of the SparkFun driver for Maxim MAX30102 sensor to MicroPython.
It _should_ work for MAX30105, too. Please check if it works for MAX30105 and report in the Discussions section :)

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.

## Aknowledgements

Expand Down Expand Up @@ -31,14 +32,19 @@ This work is not intended to be used in professional environments, and there are
- Driver: `./max30102`
- Example: `./example`

## Additional information

This driver has been tested with Maxim Integrated MAX30102 sensor.
However, it *should* work with MAX30105 sensor, too.
## Changelog
- 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.

### How to import the library and run the example
## How to import the library and run the example
Important note: the library will load the default TinyPico ESP32 board I2C configuration (SDA Pin 21, SCL Pin 22, 400kHz speed). If you're using a different board, follow the instructions given below, in *Setup and configuration* section.

#### Including this library into your project (network-enabled MicroPython ports)
### Including this library into your project (**network-enabled MicroPython ports**)
To include the library into a network-enabled MicroPython project, it's sufficient to install the package:

```python
Expand All @@ -50,32 +56,37 @@ Make sure that your firmware runs these lines **after** an Internet connection h

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.

#### Including this library into your project (manual way)
### Including this library into your project (**manual way**)

To directly include the library into a MicroPython project, it's sufficient to copy the `max30102` module next to your `main.py`, and then import it as follows:
To directly include the library into a MicroPython project, it's sufficient to copy `max30102/circular_buffer.py` and `max30102/max30102.py` next to your `main.py` file. Then, import the constructor as follows:

```python
from max30102 import MAX30102
```

For instance, to run the example in `./example` folder, copy the `./max30102` directory and paste it in the `./example` folder. Then, upload `./example` content into your microcontroller and run it.
To run the example in `./example` folder, copy `max30102/circular_buffer.py` and `max30102/max30102.py` into the `./example` directory. Then, upload the `./example` directory content into your microcontroller.


### Setup and configuration
#### I2C pins

#### Setup and configuration
When creating a sensor instance, if you leave the arguments empty, the library will load the default TinyPico ESP32 board I2C configuration (SDA Pin 21, SCL Pin 22, 400kHz speed).

At first, create a sensor instance. If you leave the arguments empty, the library will load the default TinyPico ESP32 board I2C configuration (SDA Pin 21, SCL Pin 22, 400kHz speed).
If you have a different board, you can set different I2C pins as shown in the following example:

```python
# Default config (ESP32):
sensor = MAX30102()

# Alternative:
from machine import SoftI2C, Pin
i2cInstance = SoftI2C(sda=Pin(my_SDA_pin),
scl=Pin(my_SCL_pin),
freq=100000)
i2c = SoftI2C(sda=Pin(my_SDA_pin),
scl=Pin(my_SCL_pin),
freq=100000)
sensor = MAX30102(i2cHexAddress = 0x57, i2c = i2cInstance)
```

#### Sensor setup
Then, the sensor has to be setup. The library provides a method to setup the sensor at once. Leaving the arguments empty, makes the library load their default values.

> Default configuration values:
Expand Down Expand Up @@ -104,44 +115,44 @@ The library provides the methods to change the configuration parameters one by o

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

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

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

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

# Set the LED mode
LED_MODE = 2 # Options: 1 (red), 2 (red + IR), 3 (red + IR + g - MAX30105 only)
self.setLEDMode(LED_MODE)
LED_MODE = 2 # Options: 1 (red), 2 (red + IR), 3 (red + IR + g - MAX30105 only)
self.set_led_mode(LED_MODE)

# Set the LED brightness of each LED
LED_POWER = MAX30105_PULSEAMP_MEDIUM
# Options:
# MAX30105_PULSEAMP_LOWEST = 0x02 # 0.4mA - Presence detection of ~4 inch
# MAX30105_PULSEAMP_LOW = 0x1F # 6.4mA - Presence detection of ~8 inch
# MAX30105_PULSEAMP_MEDIUM = 0x7F # 25.4mA - Presence detection of ~8 inch
# MAX30105_PULSEAMP_HIGH = 0xFF # 50.0mA - Presence detection of ~12 inch
self.setPulseAmplitudeRed(LED_POWER)
self.setPulseAmplitudeIR(LED_POWER)
self.setPulseAmplitudeGreen(LED_POWER)
# 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
self.set_pulse_amplitude_red(LED_POWER)
self.set_pulse_amplitude_it(LED_POWER)
self.set_pulse_amplitude_green(LED_POWER)

# Set the LED brightness of all the active LEDs
LED_POWER = MAX30105_PULSEAMP_MEDIUM
# Options:
# MAX30105_PULSEAMP_LOWEST = 0x02 # 0.4mA - Presence detection of ~4 inch
# MAX30105_PULSEAMP_LOW = 0x1F # 6.4mA - Presence detection of ~8 inch
# MAX30105_PULSEAMP_MEDIUM = 0x7F # 25.4mA - Presence detection of ~8 inch
# MAX30105_PULSEAMP_HIGH = 0xFF # 50.0mA - Presence detection of ~12 inch
sensor.setActiveLEDsAmplitude(LED_POWER)
# 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)
```

#### Data acquisition
Expand All @@ -153,20 +164,20 @@ The `check()` method polls the sensor to check if new samples are available in t
As a consequence, this is an example on how the library can be used to read data from the sensor:

```python
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.popRedFromStorage()
ir_sample = sensor.popIRFromStorage()
# Print the acquired data (can be plot with Arduino Serial Plotter)
print(red_sample, ",", ir_sample)
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)
```

#### Data acquisition rate
Expand All @@ -183,7 +194,7 @@ The library computes this value, that can be accessed with:

```python
# Get the estimated acquisition rate
acquisition_rate = sensor.getAcquisitionFrequency()
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](https://github.com/sparkfun/SparkFun_MAX3010x_Sensor_Library/blob/master/examples/Example9_RateTesting/Example9_RateTesting.ino) example sketch by SparkFun, using the following snippet:
Expand All @@ -195,33 +206,33 @@ from utime import ticks_diff, ticks_ms
# Starting time of the acquisition
t_start = ticks_ms()
# Number of samples that has been collected
samples_n = 0

while(True):
sensor.check()
if(sensor.available()):
# Access the storage FIFO and gather the readings (integers)
red_sample = sensor.popRedFromStorage()
ir_sample = sensor.popIRFromStorage()
# We can compute the real frequency at which we receive data
if (compute_frequency):
samples_n=samples_n+1
if ( ticks_diff(ticks_ms(), t_start) > 999 ):
f_HZ = samples_n/1
samples_n = 0
t_start = ticks_ms()
print("Acquisition frequency = ",f_HZ)
samples_n = 0

while (True):
sensor.check()

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()

# We can compute the real frequency at which we receive data
if (compute_frequency):
samples_n = samples_n + 1
if (ticks_diff(ticks_ms(), t_start) > 999):
f_HZ = samples_n / 1
samples_n = 0
t_start = ticks_ms()
print("Acquisition frequency = ", f_HZ)
```

#### Die temperature reading

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

```python
# Read the die temperature in Celsius degree
temperature_C = sensor.readTemperature()
temperature_C = sensor.read_temperature()
print("Die temperature: ", temperature_C, "°C")
```

Expand Down
10 changes: 5 additions & 5 deletions example/boot.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This file is executed on every boot (including wake-boot from deepsleep)
# This file is executed on every boot (including wake-boot from deep sleep)

def do_connect(ssid:str, password:str):
def do_connect(ssid: str, password: str):
import network
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
Expand All @@ -13,17 +13,17 @@ def do_connect(ssid:str, password:str):


if __name__ == '__main__':
# Put yor WiFi credentials here
# Put yor Wi-Fi credentials here
my_ssid = "my_ssid"
my_pass = "my_password"

try:
import max30102
from max30102 import MAX30102
except:
print("'max30102' not found!")
try:
import upip
do_connect(my_ssid, my_pass)
upip.install("micropython-max30102")
except:
print("Unable to get 'micropython-max30102' package!")

63 changes: 32 additions & 31 deletions example/main.py
Original file line number Diff line number Diff line change
@@ -1,69 +1,70 @@
# main.py
from machine import sleep
from max30102 import MAX30102
from utime import ticks_diff, ticks_ms

from max30102 import MAX30102

if __name__ == '__main__':
# Sensor instance. If left empty, loads default ESP32 I2C configuration
sensor = MAX30102()
# Alternatively (for other boards):
# sensor = MAX30102(i2cHexAddress = 0x57)
# sensor = MAX30102(i2cHexAddress = 0x57, i2c = i2cInstance)

# The default sensor configuration is:
# 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
# It's possible to setup the sensor at once with the setup_sensor() method.

# It's possible to set up the sensor at once with the setup_sensor() method.
# If no parameters are supplied, the default config is loaded.
print("Setting up sensor with default configuration.", '\n')
sensor.setup_sensor()

# It is also possible to tune the configuration parameters one by one.
# Set the sample rate to 800: 800 samples/s are collected by the sensor
sensor.setSampleRate(800)
sensor.set_sample_rate(800)
# Set the number of samples to be averaged per each reading
sensor.setFIFOAverage(8)
sensor.set_fifo_average(8)

sleep(1)

# The readTemperature() method allows to extract the die temperature in °C
print("Reading temperature in °C.", '\n')
print(sensor.readTemperature())
# Select wether to compute the acquisition frequency or not
compute_frequency = False
print(sensor.read_temperature())

# Select whether to compute the acquisition frequency or not
compute_frequency = True

print("Starting data acquisition from RED & IR registers...", '\n')
sleep(1)
t_start = ticks_ms() # Starting time of the acquisition
samples_n = 0 # Number of samples that has been collected
while(True):

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

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()):
if sensor.available():
# Access the storage FIFO and gather the readings (integers)
red_reading = sensor.popRedFromStorage()
IR_reading = sensor.popIRFromStorage()
# Print the acquired data (can be plot with Arduino Serial Plotter)
print(red_reading, ",", IR_reading)
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)

# We can compute the real frequency at which we receive data
if (compute_frequency):
samples_n=samples_n+1
if ( ticks_diff(ticks_ms(), t_start) > 999 ):
f_HZ = samples_n/1
if compute_frequency:
samples_n = samples_n + 1
if ticks_diff(ticks_ms(), t_start) > 999:
f_HZ = samples_n / 1
samples_n = 0
t_start = ticks_ms()
print("acquisition frequency = ",f_HZ)
print("acquisition frequency = ", f_HZ)
Loading