pimoroni / pimoroni-pico

Libraries and examples to support Pimoroni Pico add-ons in C++ and MicroPython.
https://shop.pimoroni.com/collections/pico
MIT License
1.27k stars 482 forks source link

SCD41 breakout: automatic garbage collection and gc.collect() breaks the sensor when using uasyncio #893

Closed leonahess closed 7 months ago

leonahess commented 7 months ago

I am using a SCD41 Breakout connected to a badger 2040 w. When I try to use the sensor it works until the ram is full and garbage collec tion kicks in. Then the program just stops. This also happens when manually invoking the GC with gc.collect(). A hard reset is needed for the Pico to be responsive and usable again after it locks up.

I have also sometimes seen a RuntimeError: SCD41: Reading failed. when calling breakout_scd41.ready() after garbage collection.

The following code reproduces the issue:

import breakout_scd41
import gc
import micropython
import time

import uasyncio as asyncio
from pimoroni_i2c import PimoroniI2C

PINS_BREAKOUT_GARDEN = {"sda": 4, "scl": 5}

co2 = 0
temperature = 0
humidity = 0

def start_sensor():
    # Initialize Sensor
    i2c = PimoroniI2C(**PINS_BREAKOUT_GARDEN)
    breakout_scd41.init(i2c)
    breakout_scd41.start()

def read_sensor():
    if breakout_scd41.ready():
        global co2, temperature, humidity
        try:
            co2, temperature, humidity = breakout_scd41.measure()
            print("sensor read: tmp {}°C hum {}% co2 {}ppm".format(temperature, humidity, co2))
        except RuntimeError:
            print("reading sensor failed")
    else:
        print("sensor was not ready")

print('Intializing Sensor...')
start_sensor()

time.sleep(5)

while True:
    read_sensor()

    micropython.mem_info()
    gc.collect()
    micropython.mem_info()
    time.sleep(5)

I also tested it with a BME688 Breakout. There the sensor works as normal after invoking the Garbage Collector.

leonahess commented 7 months ago

I figured something more out. I was using uasyncio when I discovered this.

When import uasyncio as asyncio is not in the script it does not lock up after gc.collect() and works flawlessly.

Even just importing uasynic without using it in any way causes the bug.

leonahess commented 7 months ago

Importing urequests also leads to the program crashing after a GC invocation.

leonahess commented 7 months ago

I figured it out. Initializing the sensor in its own function call leads to the problem. Taking the initialization out of the function fixes the problem with uasyncio and urequests.

This works:

import breakout_scd41
import gc
import micropython
import time

import uasyncio as asyncio
from pimoroni_i2c import PimoroniI2C

PINS_BREAKOUT_GARDEN = {"sda": 4, "scl": 5}

co2 = 0
temperature = 0
humidity = 0

def read_sensor():
    if breakout_scd41.ready():
        global co2, temperature, humidity
        try:
            co2, temperature, humidity = breakout_scd41.measure()
            print("sensor read: tmp {}°C hum {}% co2 {}ppm".format(temperature, humidity, co2))
        except RuntimeError:
            print("reading sensor failed")
    else:
        print("sensor was not ready")

# Initialize Sensor
i2c = PimoroniI2C(**PINS_BREAKOUT_GARDEN)
breakout_scd41.init(i2c)
breakout_scd41.start()

time.sleep(5)

while True:
    read_sensor()

    micropython.mem_info()
    gc.collect()
    micropython.mem_info()
    time.sleep(5)