adafruit / circuitpython

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

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

Open Sola85 opened 2 months ago

Sola85 commented 2 months 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 2 months 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 2 months 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 months 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 2 months ago

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

Sola85 commented 2 months ago

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

tannewt commented 2 months 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.