Skip to content

Alarm pool sleep changes #16454

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 52 additions & 14 deletions ports/rp2/modmachine.c
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,12 @@ static void mp_machine_idle(void) {
MICROPY_INTERNAL_WFE(1);
}

static void alarm_sleep_callback(uint alarm_id) {
}

// Set this to 1 to enable some debug of the interrupt that woke the device
#define DEBUG_LIGHTSLEEP 0

static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) {
mp_int_t delay_ms = 0;
bool use_timer_alarm = false;
Expand Down Expand Up @@ -190,6 +196,15 @@ static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) {
// Disable ROSC.
rosc_hw->ctrl = ROSC_CTRL_ENABLE_VALUE_DISABLE << ROSC_CTRL_ENABLE_LSB;

#if DEBUG_LIGHTSLEEP
#if PICO_RP2040
uint32_t pending_intr = 0;
#else
uint32_t pending_intr[2] = { 0 };
#endif
#endif

bool alarm_armed = false;
if (n_args == 0) {
#if MICROPY_PY_NETWORK_CYW43
gpio_set_dormant_irq_enabled(CYW43_PIN_WL_HOST_WAKE, GPIO_IRQ_LEVEL_HIGH, true);
Expand All @@ -198,16 +213,7 @@ static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) {
} else {
uint32_t save_sleep_en0 = clocks_hw->sleep_en0;
uint32_t save_sleep_en1 = clocks_hw->sleep_en1;
bool timer3_enabled = irq_is_enabled(3);

const uint32_t alarm_num = 3;
const uint32_t irq_num = TIMER_ALARM_IRQ_NUM(timer_hw, alarm_num);
if (use_timer_alarm) {
// Make sure ALARM3/IRQ3 is enabled on _this_ core
if (!timer3_enabled) {
irq_set_enabled(irq_num, true);
}
hw_set_bits(&timer_hw->inte, 1u << alarm_num);
// Use timer alarm to wake.
clocks_hw->sleep_en0 = 0x0;
#if PICO_RP2040
Expand All @@ -217,8 +223,11 @@ static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) {
#else
#error Unknown processor
#endif
timer_hw->intr = 1u << alarm_num; // clear any IRQ
timer_hw->alarm[alarm_num] = timer_hw->timerawl + delay_ms * 1000;
hardware_alarm_claim(MICROPY_HW_LIGHTSLEEP_ALARM_NUM);
hardware_alarm_set_callback(MICROPY_HW_LIGHTSLEEP_ALARM_NUM, alarm_sleep_callback);
if (hardware_alarm_set_target(MICROPY_HW_LIGHTSLEEP_ALARM_NUM, make_timeout_time_ms(delay_ms)) == PICO_OK) {
alarm_armed = true;
}
} else {
// TODO: Use RTC alarm to wake.
clocks_hw->sleep_en0 = 0x0;
Expand Down Expand Up @@ -248,10 +257,17 @@ static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) {
#endif

// Go into low-power mode.
__wfi();
if (alarm_armed) {
__wfi();

if (!timer3_enabled) {
irq_set_enabled(irq_num, false);
#if DEBUG_LIGHTSLEEP
#if PICO_RP2040
pending_intr = nvic_hw->ispr;
#else
pending_intr[0] = nvic_hw->ispr[0];
pending_intr[1] = nvic_hw->ispr[1];
#endif
#endif
}
clocks_hw->sleep_en0 = save_sleep_en0;
clocks_hw->sleep_en1 = save_sleep_en1;
Expand All @@ -266,6 +282,28 @@ static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) {

// Re-sync mp_hal_time_ns() counter with aon timer.
mp_hal_time_ns_set_from_rtc();

// Note: This must be done after MICROPY_END_ATOMIC_SECTION
if (use_timer_alarm) {
if (alarm_armed) {
hardware_alarm_cancel(MICROPY_HW_LIGHTSLEEP_ALARM_NUM);
}
hardware_alarm_set_callback(MICROPY_HW_LIGHTSLEEP_ALARM_NUM, NULL);
hardware_alarm_unclaim(MICROPY_HW_LIGHTSLEEP_ALARM_NUM);

#if DEBUG_LIGHTSLEEP
// Check irq.h for the list of IRQ's
// for rp2040 00000042: TIMER_IRQ_1 woke the device as expected
// 00000020: USBCTRL_IRQ woke the device (probably early)
// For rp2350 00000000:00000002: TIMER0_IRQ_1 woke the device as expected
// 00000000:00004000: USBCTRL_IRQ woke the device (probably early)
#if PICO_RP2040
mp_printf(MP_PYTHON_PRINTER, "lightsleep: pending_intr=%08lx\n", pending_intr);
#else
mp_printf(MP_PYTHON_PRINTER, "lightsleep: pending_intr=%08lx:%08lx\n", pending_intr[1], pending_intr[0]);
#endif
#endif
}
}

NORETURN static void mp_machine_deepsleep(size_t n_args, const mp_obj_t *args) {
Expand Down
3 changes: 2 additions & 1 deletion ports/rp2/mpconfigport.h
Original file line number Diff line number Diff line change
Expand Up @@ -186,8 +186,9 @@
#define MICROPY_PY_LWIP_SOCK_RAW (MICROPY_PY_LWIP)

// Hardware timer alarm index. Available range 0-3.
// Number 3 is currently used by pico-sdk (PICO_TIME_DEFAULT_ALARM_POOL_HARDWARE_ALARM_NUM)
// Number 3 is currently used by pico-sdk alarm pool (PICO_TIME_DEFAULT_ALARM_POOL_HARDWARE_ALARM_NUM)
#define MICROPY_HW_SOFT_TIMER_ALARM_NUM (2)
#define MICROPY_HW_LIGHTSLEEP_ALARM_NUM (1)

// fatfs configuration
#define MICROPY_FATFS_ENABLE_LFN (2)
Expand Down
12 changes: 1 addition & 11 deletions ports/rp2/mphalport.c
Original file line number Diff line number Diff line change
Expand Up @@ -266,17 +266,7 @@ void soft_timer_init(void) {
}

void mp_wfe_or_timeout(uint32_t timeout_ms) {
soft_timer_entry_t timer;

// Note the timer doesn't have an associated callback, it just exists to create a
// hardware interrupt to wake the CPU
soft_timer_static_init(&timer, SOFT_TIMER_MODE_ONE_SHOT, 0, NULL);
soft_timer_insert(&timer, timeout_ms);

__wfe();

// Clean up the timer node if it's not already
soft_timer_remove(&timer);
best_effort_wfe_or_timeout(delayed_by_ms(get_absolute_time(), timeout_ms));
}

int mp_hal_is_pin_reserved(int n) {
Expand Down
60 changes: 60 additions & 0 deletions tests/ports/rp2/rp2_lightsleep_thread.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Verify that a thread running on CPU1 can go to lightsleep
# and wake up in the expected timeframe
import _thread
import time
import unittest
from machine import lightsleep, Pin

try:
led = Pin(Pin.board.LED, Pin.OUT)
except AttributeError:
led = None

N_SLEEPS = 5
SLEEP_MS = 250

IDEAL_RUNTIME = N_SLEEPS * SLEEP_MS
MAX_RUNTIME = (N_SLEEPS + 1) * SLEEP_MS


class LightSleepInThread(unittest.TestCase):
def thread_entry(self, is_thread=True):
led.toggle()
for _ in range(N_SLEEPS):
lightsleep(SLEEP_MS)
led.toggle()
if is_thread:
self.thread_done = True

def elapsed_ms(self):
return time.ticks_diff(time.ticks_ms(), self.t0)

def setUp(self):
self.thread_done = False
self.t0 = time.ticks_ms()

def test_cpu0_busy(self):
_thread.start_new_thread(self.thread_entry, ())
# CPU0 is busy-waiting not asleep itself
while not self.thread_done:
self.assertLessEqual(self.elapsed_ms(), MAX_RUNTIME)
self.assertAlmostEqual(self.elapsed_ms(), IDEAL_RUNTIME, delta=IDEAL_RUNTIME / 2)

def test_cpu0_sleeping(self):
_thread.start_new_thread(self.thread_entry, ())
time.sleep_ms(MAX_RUNTIME)
self.assertTrue(self.thread_done)
self.assertAlmostEqual(self.elapsed_ms(), IDEAL_RUNTIME, delta=IDEAL_RUNTIME / 2)

def test_cpu0_also_lightsleep(self):
_thread.start_new_thread(self.thread_entry, ())
time.sleep(0.050) # account for any delay in starting the thread
self.thread_entry(False) # does the same lightsleep loop, doesn't set the done flag
self.assertTrue(self.thread_done)
# only one thread can actually be in lightsleep at a time to avoid races, so the total
# runtime is doubled by doing it on both CPUs
self.assertAlmostEqual(self.elapsed_ms(), IDEAL_RUNTIME * 2, delta=IDEAL_RUNTIME)


if __name__ == "__main__":
unittest.main()
6 changes: 0 additions & 6 deletions tests/ports/rp2/rp2_machine_idle.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import sys
import machine
import time

Expand All @@ -18,11 +17,6 @@
# Verification uses the average idle time, as individual iterations will always
# have outliers due to interrupts, scheduler, etc.

# RP2350 currently fails this test because machine.idle() resumes immediately.
if "RP2350" in sys.implementation._machine:
print("SKIP")
raise SystemExit

ITERATIONS = 500
total = 0

Expand Down
Loading