I’m using the below code to click 10 pictures and save them. The code works fine if machine.lightsleep() is not used but however pictures are corrupted when used.
# Import libraries
import pyb, machine, sensor, image, time, uos, gc
#RTC object for datetime
rtc = pyb.RTC()
# Format datetime for better usage
def dt_format():
return "_".join(str(i) for i in rtc.datetime()[0:3]+rtc.datetime()[4:7])
# Setup camera
def camera_init():
sensor.reset()
sensor.set_pixformat(sensor.GRAYSCALE)
sensor.set_framesize(sensor.VGA)
sensor.skip_frames(time = 1000)
# Click pics
def click(n):
pyb.LED(2).on()
for i in range(n):
img = sensor.snapshot()
img.save(f"{dt_format()}_{i}.jpeg")
pyb.delay(1000)
pyb.LED(2).off()
# Init sensor
camera_init()
# Buffer time to connect to IDE (if)
pyb.delay(5*1000)
# Main
while(True):
try:
# Lightsleep for 5s
pyb.LED(1).on()
machine.lightsleep(5*1000)
pyb.LED(1).off()
# camera_init()
# Click and save pics
img = click(10)
del img
gc.collect()
except Exception as e:
pyb.LED(1).on()
pyb.LED(2).on()
pyb.LED(3).on()
I have found a temporary solution to this by resetting camera setting after every wakeup (uncomment line camera_init() in main loop), but this is causing a few images to be fully damaged.
I tested this for a while and didn’t see any corrupted images. Are you saving the images to an SD card or internal file system? Also, I’ve noticed you’re always running some old firmware version in your issues. Please always use the latest.
I ran the script you posted, on the latest stable and HM01B0 and the latest dev with HM0360 . Saved images to internal flash and to SD card about 80 images in each case. They all look good.
Updating the firmware seems to have solved the issue my end also. I got these 3 anomalies upon the first run but all the other images are fine. Thanks!
Yeah, I also see the first frame get corrupted sometimes. Could you try to add a small 100ms delay after wakeup from lightsleep? Let me know if it fixes the issue.
Thanks for the suggestion; this seems to have solved the issue. I thought sensor.skip_frames() would have taken care of it but no harm is using an additional delay (for me).
I am calling camera_init() after every wakeup 'cause if I don’t the original problem comes up again. I am fine with this hack for now.
This is the working code:
# Import libraries
import pyb, machine, sensor, image, time, uos, gc
#RTC object for datetime
rtc = pyb.RTC()
# Format datetime for better usage
def dt_format():
return "_".join(str(i) for i in rtc.datetime()[0:3]+rtc.datetime()[4:7])
# Setup camera
def camera_init():
sensor.reset()
sensor.set_pixformat(sensor.GRAYSCALE)
sensor.set_framesize(sensor.VGA)
sensor.skip_frames(time = 1000)
pyb.delay(100)
# Click pics
def click(n):
pyb.LED(2).on()
for i in range(n):
img = sensor.snapshot()
img.save(f"{dt_format()}_{i}.jpeg")
pyb.delay(1000)
pyb.LED(2).off()
# Init sensor
camera_init()
# Buffer time to connect to IDE (if)
pyb.delay(5*1000)
# Main
while(True):
try:
# Lightsleep for 5s
pyb.LED(1).on()
machine.lightsleep(5*1000)
pyb.LED(1).off()
# Reset sensor
camera_init()
# Click and save pics
img = click(10)
del img
gc.collect()
except Exception as e:
pyb.LED(1).on()
pyb.LED(2).on()
pyb.LED(3).on()
I think there are 2 different issues here, that can be tested in isolation:
ULPI fails to suspend if you exit/enter suspend repeatedly in a tight loop. You can test this on its own without any sensor/image/sdcard functions.
Some frames might get corrupted around low-power entry/exit.
For the first issue, adding a small delay in the script (at least 100ms) after wakeup from low-power and before reentry (to ensure it doesn’t suspend too soon after wakeup) seems to fix the issue. We could also not suspend the ULPI in STOP mode at all, but it’s probably worth keeping it.
For the second issue, I think we need to flush the FIFO after waking up from low-power somehow. The frame capture runs continuously in the background, so if the board goes into low-power mode around a readout it could corrupt some frames. Note that calling any of those sensor functions has the side effect of flushing the FIFO or skipping frames. @kwagyeman might wanna take a look, does this make sense?
@prithulc For now I think what you have is okay, though you don’t need to reinitialize/reset the camera every time, perhaps it’s best to just skip frames after wakeup from low-power. This will introduce the delay needed for ULPI and will skip any corrupted frames at the same time. Also you don’t need to gc.collect explicitly every time, GC will run when it needs to.
The sensor driver doesn’t really know about lightsleep(). After calling that method it will leave the sensor driver in an unknown state.
Changing the resolution to something else and then back again will reset the DMA pipeline and etc.
@iabdalkader Seems like the cleanest way to deal with this issue would be to add some sort of wrapper around lightsleep() that executes a reset of all autonomous interrupt drivers. I guess the drivers could register themselves on a shared list when started and then lightsleep() when run would shut things down before executing.
We could use the enter/leave low-power hooks, call an extern function from there. Here’s the low-power function for Portenta:
But we need to reproduce the issue consistently to know exactly what we need to fix. Note we only need to handle STOP mode, as standby never wakes up anyway.
Alternatively, we could make sleep not fail for all sensors and require calling it before entering low-power. sleep already aborts the transfer. I prefer this, less custom changes to maintain. However, if we need to disable/abort anything else, sensor.sleep will not be enough. Do we need to abort anything else?
@prithulc Could you try calling sensor.sleep before lightsleep? The code in the inner loop should look something like this:
Note since these Himax sensors don’t actually implement the actual sleep function, sensor.sleep will raise an exception, which we catch and ignore, but before it does it aborts any ongoing transfer. This is just a quick test, do not use this code in you application. Note you should not need to reset/reinit the sensor either.
With the latest firmware, you should not need the try/catch around sensor.sleep. Note, we’ll update our examples later to call this before/after low-power mode.