Skip to content

alarm.sleep_memory corruption when pin_alarm (but not time_alarm) triggered during simulated and deep sleep #11167

Description

@jmangum

CircuitPython version and board name

Adafruit CircuitPython 10.3.0-alpha.4 on 2026-07-23; Adafruit Feather ESP32S3 4MB Flash 2MB PSRAM with ESP32S3

Code/REPL

#
# Test deep sleep and alarm.sleep_memory interaction
# CP 10.3.0-alpha4
# 2026-08-02

import board
import busio
import time
import digitalio
import neopixel
from adafruit_datetime import datetime
from adafruit_onewire.bus import OneWireBus
#import adafruit_register
import keypad # Use for rain gauge and wind bird
import math
from analogio import AnalogIn
import struct
import alarm
#
# wifi setup libraries
import os
import ipaddress
import ssl
import wifi


print("\nESP32-S3 pin_alarm with alarm.sleep_memory and deep sleep test")

# Enable i2c_power pin to turn-on i2c power after sleep
i2c_power = digitalio.DigitalInOut(board.I2C_POWER)
i2c_power.direction = digitalio.Direction.OUTPUT
i2c_power.value = True

print("Setup wifi")

print(f"My MAC address: {[hex(i) for i in wifi.radio.mac_address]}")

print("Available WiFi networks:")
for network in wifi.radio.start_scanning_networks():
    print("\t%s\t\tRSSI: %d\tChannel: %d" % (str(network.ssid, "utf-8"),
                                             network.rssi, network.channel))
wifi.radio.stop_scanning_networks()

print(f"Connecting to {os.getenv('CIRCUITPY_WIFI_SSID')}")
wifi.radio.connect(os.getenv("CIRCUITPY_WIFI_SSID"), os.getenv("CIRCUITPY_WIFI_PASSWORD"))
print(f"Connected to {os.getenv('CIRCUITPY_WIFI_SSID')}")
print(f"My IP address: {wifi.radio.ipv4_address}")

ping_ip = ipaddress.IPv4Address("8.8.8.8")
ping = wifi.radio.ping(ip=ping_ip)

# retry once if timed out
if ping is None:
    ping = wifi.radio.ping(ip=ping_ip)

if ping is None:
    print("Couldn't ping 'google.com' successfully")
else:
    # convert s to ms
    print(f"Pinging 'google.com' took: {ping * 1000} ms")

print("Done with wifi setup")

def rain(rain_count, keys):
    # Listen for rain...
    #
    # Rain bucket size
    bucket_size = 0.2794
    pixel.fill((127, 255, 0))
    event = keys.events.get()
    # TODO: Get number of triggers rather an assuming each event is just one trigger (for ENS161 sleep rain meas)
    if event and event.key_number == 0 and event.pressed:
        rain_count += 1
        #print("Detected Rain: ", common.rain_count * bucket_size)
        print(event)
        print("Rain Total: ",rain_count * bucket_size," from ",rain_count," measurements")
        # Write to measurements dictionary
        #common.measurements['RainTot'] = common.rain_count * bucket_size
        #
    pixel.fill((0, 0, 0))
        #await asyncio.sleep(0) # NOTE: Rain and wind measurements must run continuously

#
# Start by (re)-enabling i2c_power after deep sleep with i2c_power=False (I2C power off)
i2c_power.value = True
#async def main():
# Enable i2c
#i2c = board.I2C()  # uses board.SCL and board.SDA
#i2c = board.STEMMA_I2C()  # For using the built-in STEMMA QT connector on a microcontroller
                           # Allows one to use all digital pins (D5 and D6 tested)
i2c = busio.I2C(board.SCL, board.SDA, frequency=100000) # Must use 100kHz frequency for i2c bus
                                                        # when PM2.5 sensor included

# Set up the LED
led = digitalio.DigitalInOut(board.LED)
led.direction = digitalio.Direction.OUTPUT
led.value = False

# Set up the neopixel
pixel = neopixel.NeoPixel(board.NEOPIXEL, 1)
pixel.brightness = 0.3

# Rain gauge pin assignment using keypad function
# Rain gauge: Detects bucket tip
rain_gauge_pin = board.A4
pins = (rain_gauge_pin, # Rain Gauge
        )
keys = keypad.Keys(pins, value_when_pressed=False, pull=True)

# Start with a clean-slate for rainfall measurements...
rain_count = 0

measurement_time = 120 # Send measurement to Gamera every measurement_time

# Check to see if pin or time alarm has triggered wake and act appropriately...
keys.deinit() # De-initialize keys to use rain pin as alarm

if isinstance(alarm.wake_alarm, alarm.pin.PinAlarm):
    for i in range(0,5): print("alarm.sleep_memory[",i,"]: ", alarm.sleep_memory[i])
    byte_string = bytes(alarm.sleep_memory[1:5])
    struct.unpack('f', byte_string)
    alarm_time = struct.unpack('f', byte_string)[0]
    print("Alarm Time After Pin Alarm Reset: ",alarm_time)
else:
    alarm_time = time.monotonic() + measurement_time
    print(alarm_time)
    # Store alarm_time in alarm.sleep_memory for recall if pin_alarm triggered
    print(struct.pack('f', alarm_time))
    byte_list = [byte for byte in struct.pack('f', alarm_time)]
    print(byte_list)
    for i in range(1,len(byte_list)+1): alarm.sleep_memory[i] = byte_list[i-1]
    for i in range(len(byte_list)+1): print("alarm.sleep_memory[",i,"]: ", alarm.sleep_memory[i])

time_alarm = alarm.time.TimeAlarm(monotonic_time=alarm_time)
pin_alarm = alarm.pin.PinAlarm(pin=rain_gauge_pin, value=False, edge=False, pull=True)

# HACK: Have to re-define keys here after deinit (even though defined above)...
keys = keypad.Keys(pins, value_when_pressed=False, pull=True)

print("alarm.wake_alarm: ",alarm.wake_alarm)

if isinstance(alarm.wake_alarm, alarm.pin.PinAlarm):
#if alarm.wake_alarm == pin_alarm:
    print('Woke from ',alarm.wake_alarm,'...rain...add one to alarm.sleep_memory[0]...')
    #my_common.rain_count += 1
    # Use sleep memory byte 0 to store rain count
    alarm.sleep_memory[0] = (alarm.sleep_memory[0] + 1) % 256 # Add one rain count to sleep memory
elif isinstance(alarm.wake_alarm, alarm.time.TimeAlarm):
    print('Woke from ',alarm.wake_alarm)
    rain_count = alarm.sleep_memory[0] # Read stored rain count to common rain_count variable for send
    rain(rain_count,keys)
    alarm.sleep_memory[0] = 0 # Reset rain count storage after send

keys.deinit()

print('Alarm Time: ',alarm_time)
runsleep = 60 # Allow access to REPL before deep sleep
print('Sleep with microcontroller power on and i2c power on for '+str(runsleep)+' seconds to allow access to REPL')
time.sleep(runsleep)
# Turn-off power to i2c devices before going to sleep
i2c_power.value = False # Turn-off i2c power during sleep
#time.sleep(5) # TEST...to see if green LEDs on i2c devices goes off...
#
print('Going to deep sleep for ',measurement_time,' seconds')
alarm.exit_and_deep_sleep_until_alarms(time_alarm,pin_alarm,preserve_dios=[i2c_power])
#alarm.exit_and_deep_sleep_until_alarms(time_alarm,pin_alarm)

Behavior

ESP32-S3 pin_alarm with alarm.sleep_memory and deep sleep test
Setup wifi
My MAC address: [REDACTED]
Available WiFi networks:
	REDACTED		RSSI: -43	Channel: 9
Connecting to REDACTED
Connected to REDACTED
My IP address: REDACTED
Pinging 'google.com' took: 11.0 ms
Done with wifi setup
353.287
b'\xbf\xa4\xb0C'
[191, 164, 176, 67]
alarm.sleep_memory[ 0 ]:  0
alarm.sleep_memory[ 1 ]:  191
alarm.sleep_memory[ 2 ]:  164
alarm.sleep_memory[ 3 ]:  176
alarm.sleep_memory[ 4 ]:  67
alarm.wake_alarm:  None
Alarm Time:  353.287
Sleep with microcontroller power on and i2c power on for 60 seconds to allow access to REPL
Going to deep sleep for  120  seconds
�]0;�192.168.0.82 | BLE:Off | Done | 10.3.0-alpha.4\
Code done running.uto-reload is on. Simply save files over USB to run them or enter REPL to disable.

Press any key to enter the REPL. Use CTRL-D to reload.
Pretending to deep sleep until alarm, CTRL-C or file write.
Woken up by alarm.

==> Triggered rain sensor (pin_alarm) at this point, which woke-up microcontroller from deep sleep

| BLE:Off | code.py | 10.3.0-alpha.4\
ESP32-S3 pin_alarm with alarm.sleep_memory and deep sleep test
Setup wifi
My MAC address: [REDACTED]
Available WiFi networks:
	REDACTED		RSSI: -48	Channel: 9
Connecting to REDACTED
Connected to REDACTED
My IP address: REDACTED
Pinging 'google.com' took: 13.0 ms
Done with wifi setup
alarm.sleep_memory[ 0 ]:  0
alarm.sleep_memory[ 1 ]:  0
alarm.sleep_memory[ 2 ]:  0
alarm.sleep_memory[ 3 ]:  0
alarm.sleep_memory[ 4 ]:  0
Alarm Time After Pin Alarm Reset:  0.0
Traceback (most recent call last):
  File "code.py", line 134, in <module>
ValueError: Time is in the past.
�]0;�192.168.0.82 | BLE:Off | Done | 10.3.0-alpha.4\
uto-reload is on. Simply save files over USB to run them or enter REPL to disable.

Press any key to enter the REPL. Use CTRL-D to reload.

### Description

Tested with both 10.3.0-alpha4 and 10.2.1 and USB-computer and USB-power-only connection:

10.2.1 with USB-computer and USB-power-only connection:
- No issues with pin_alarm and sleep_memory
- Note that `preserve_dios = [i2c_power]` option in code above does not work with 10.2.1 due to #10790 

10.3.0-alpha4 with USB-computer connection:
- `alarm.sleep_memory` setting is preserved when time_alarm triggered after deep sleep
- `alarm.sleep_memory` setting is set to 0 for all entries used (indices 0 through 4) when pin_alarm triggered
- Triggering pin_alarm during time.sleep (not deep sleep) does not affect `alarm.sleep_memory` settings

10.3.0-alpha4 with USB-power-only connection:
- pin_alarm getting triggered randomly when microcontroller goes into deep sleep
- No REPL so no way to see what `alarm.sleep_memory` values are set to
- Behaviour independent of `preseverve_dios` setting in deep sleep

### Additional information

- Rain trigger (pin_alarm) confirmed during USB-power-only tests (no REPL) by using neopixel trigger (see code).
- Maybe unrelated by when using 10.3.0-alpha4 microcontroller does not eject USB from computer cleanly (while it does eject cleanly with 10.2.1).  Always get an error "Failed to eject...".  Can post as separate issue if needed.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions