adafruit / circuitpython

CircuitPython - a Python implementation for teaching coding with microcontrollers
https://circuitpython.org
Other
4.04k stars 1.2k forks source link

espressif: Pins preserved during deep-sleep cannot be toggled after wakeup #9622

Open Sola85 opened 5 days ago

Sola85 commented 5 days ago

CircuitPython version

Adafruit CircuitPython 9.1.1 on 2024-07-23; Waveshare ESP32-S3-Zero with ESP32S3
Adafruit CircuitPython 9.1.3 on 2024-08-30; Waveshare ESP32-S3-Zero with ESP32S3
Adafruit CircuitPython 9.1.3 on 2024-08-30; ESP32-S3-DevKitM-1-N8 with ESP32S3

Code/REPL

import alarm, board, time
from digitalio import DigitalInOut

led = DigitalInOut(board.IO1)
led.switch_to_output()
led.value = 1

time.sleep(1)

led.value = 0
time_alarm = alarm.time.TimeAlarm(monotonic_time=time.monotonic() + 10)
alarm.exit_and_deep_sleep_until_alarms(time_alarm, preserve_dios=[led])

Behavior

The led blinks once and then remains dark until the chip is reset.

Excpected behaviour: Led blinks once every 10 seconds.

Description

This happens in both fake deep sleep as well as in real deep sleep.

If preserve_dios=[led] is omitted, the led blinks brightly and glows dimly during deep sleep and becomes bright again after deep sleep (which is correct behaviour as far as I know).

Additional information

No response

Sola85 commented 5 days ago

After some tinkering I found a workaround: Calling .deinit() on the stuck pins causes them to reset to a working state, e.g.

led = DigitalInOut(board.IO1)
led.deinit()
led = DigitalInOut(board.IO1) #led works again

The reason for this issue seems to be that preserve_pin_number() calls gpio_hold_en() but the corresponding call to gpio_hold_dis() only happens in _reset_pin() and _reset_pin() is not called after wake from deep sleep, hence the gpio hold is never disabled.

The workaround works because .deinit() calls _reset_pin() and therefore also gpio_hold_dis().

Not sure what the best strategy would be to fix this. Reset every pin after wake from deepsleep? Or call gpio_hold_dis() on all pins after wake?

tannewt commented 3 days ago

Or call gpio_hold_dis() on all pins after wake?

I think the DigitalInOut constructor should call this. It is taking control back over from the hold. Want to make a PR for this? Thanks for testing!

Sola85 commented 2 days ago

I think the DigitalInOut constructor should call this.

I thought about this too but wasn't sure: Would it be only DigitalInOut, or every class that manages a pin? Like PWMOut, AnalogOut, I2C/SPI, ... Thats why I thought managing it centrally could maybe be easier

tannewt commented 1 day ago

I think DigitalInOut would be enough because that is what preserve_dios takes.

Sola85 commented 1 day ago

But DigitalInOut might not necessarily be the first object to take control of the pin after deepsleep.

tannewt commented 14 hours ago

But DigitalInOut might not necessarily be the first object to take control of the pin after deepsleep.

Ya, that's true. I wonder if we should detect that a pin is held and then make a DigitalInOut available for it. Then you would need to deinit it to use it for something else. Otherwise we have to decide when to un-hold the pin.