From 887fdfb38faaf61cb16c5293bc876c5ba112b21e Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 18 Dec 2024 15:24:11 +1100 Subject: [PATCH 1/3] tests,rp2: Measure the total runtime of the rp2 lightsleep test. This is a bit hand-wavey as USB data wakes up lightsleep() pretty often, but this modified test passes on PICO builds and fails consistently on PICO_W builds where the 64ms LWIP tick timer always wakes up lightsleep. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- tests/ports/rp2/rp2_lightsleep.py | 22 ++++++++++++++++++++-- tests/ports/rp2/rp2_lightsleep.py.exp | 1 + 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/tests/ports/rp2/rp2_lightsleep.py b/tests/ports/rp2/rp2_lightsleep.py index 5ce5696e0e18c..18e5adb00f832 100644 --- a/tests/ports/rp2/rp2_lightsleep.py +++ b/tests/ports/rp2/rp2_lightsleep.py @@ -9,7 +9,7 @@ # A range of sleep periods (1 to 512ms) are tested. The total nominal sleep time # is 10.23 seconds, but on most ports this will finish much earlier as interrupts # happen before each timeout expires. -import sys +import sys, time try: from machine import lightsleep, Pin @@ -27,10 +27,28 @@ except AttributeError: led = None +t0 = time.ticks_ms() + for n in range(100): if led: led.toggle() sys.stdout.write(chr(ord("a") + (n % 26))) lightsleep(2 ** (n % 10)) -print("\nDONE") +print() # end of line + +delta = time.ticks_diff(time.ticks_ms(), t0) + +if delta < 2500: + # As per note above, we don't expect the full 10.23 seconds + # but a very short total runtime may indicate a bug with + # lightsleep + print("Unexpected short test time", delta, "ms") +elif delta > 11000: + # Similarly, the test shouldn't take much longer than the max lightsleep + # time + print("Unexpected long test time", delta, "ms") +else: + print("Test time OK") + +print("DONE") diff --git a/tests/ports/rp2/rp2_lightsleep.py.exp b/tests/ports/rp2/rp2_lightsleep.py.exp index 81d1c231c9a5e..df0b6be2305cb 100644 --- a/tests/ports/rp2/rp2_lightsleep.py.exp +++ b/tests/ports/rp2/rp2_lightsleep.py.exp @@ -1,2 +1,3 @@ abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuv +Test time OK DONE From 46b7ec723a9eb61eda925b919499de738db5c163 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 17 Dec 2024 10:57:37 +1100 Subject: [PATCH 2/3] rp2: Use the soft timer hardware alarm to wake from lightsleep. Fixes issue where PICO-W build won't lightsleep() for more than 64ms. This is a regression introduced in 74fb42a when we switched away from the pico-sdk alarm pool for soft timers and accidentally made soft timer expiry a wakeup source. Before 74fb42a, both the pico-sdk alarm pool and the lightsleep wakeup timer uses alarm 3. The lightsleep wakeup would quietly clobber alarm 3's timeout, meaning softtimer wouldn't wake the chip from lightsleep. After 74fb42a, soft timer wakeup happens on timer alarm 2 so this interrupt wakes the chip from light sleep. On PICO-W builds this happens every lwIP tick (64ms). The change is to go back to using the same timer alarm for both lightsleep wakeup and soft timer, but now being explicit about lightsleep wakeup clobbering any soft timer wakeup. Also adds a "catch up" call to soft timer handler as it's currently possible to miss soft timer events after waking up. This also reworks the changes added in 19844b to enable the timer IRQ on CPU1. This is still needed, but have changed the implementation to be more explicit about when it's needed. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/rp2/modmachine.c | 41 +++++++++++++++++++++++++---------------- ports/rp2/mphalport.c | 10 +++++++--- 2 files changed, 32 insertions(+), 19 deletions(-) diff --git a/ports/rp2/modmachine.c b/ports/rp2/modmachine.c index cd73552dd9874..02b693b8cc366 100644 --- a/ports/rp2/modmachine.c +++ b/ports/rp2/modmachine.c @@ -196,17 +196,8 @@ static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) { #endif xosc_dormant(); } else { - 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); + uint32_t timer_irq_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 clocks_hw->sleep_en1 = CLOCKS_SLEEP_EN1_CLK_SYS_TIMER_BITS; @@ -215,8 +206,18 @@ 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; + + // Clobber the soft timer hardware alarm (as soft timer doesn't wake lightsleep) + // and repurpose it for the lightsleep wakeup + // + // The timer peripheral will already be configured via softtimer_init(). + if (get_core_num() == 1) { + // On CPU0 the hardware alarm interrupt is already enabled, but IRQs won't be serviced + // due to the critical section so we also need to temporarily enable this IRQ on our CPU + timer_irq_num = hardware_alarm_get_irq_num(PICO_DEFAULT_TIMER_INSTANCE(), MICROPY_HW_SOFT_TIMER_ALARM_NUM); + irq_set_enabled(timer_irq_num, true); + } + hardware_alarm_set_target(MICROPY_HW_SOFT_TIMER_ALARM_NUM, delayed_by_ms(get_absolute_time(), delay_ms)); } else { // TODO: Use RTC alarm to wake. clocks_hw->sleep_en0 = 0x0; @@ -247,17 +248,25 @@ static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) { // Go into low-power mode. __wfi(); - - if (!timer3_enabled) { - irq_set_enabled(irq_num, false); - } clocks_hw->sleep_en0 |= ~(0u); clocks_hw->sleep_en1 |= ~(0u); + + if (use_timer_alarm && get_core_num() == 1) { + // We don't have an easy way to clear any pending IRQ on this CPU, + // so we might get a spurious soft timer interrupt handler run on + // CPU1 unfortunately - but this should be harmless + irq_set_enabled(timer_irq_num, false); + } } // Enable ROSC. rosc_hw->ctrl = ROSC_CTRL_ENABLE_VALUE_ENABLE << ROSC_CTRL_ENABLE_LSB; + // we might have missed at least one soft timer expiry that hasn't been processed + // depending on when we woke up, so force soft timer processing for when we exit + // the atomic section + hardware_alarm_force_irq(MICROPY_HW_SOFT_TIMER_ALARM_NUM); + // Bring back all clocks. runtime_init_clocks_optional_usb(disable_usb); MICROPY_END_ATOMIC_SECTION(my_interrupts); diff --git a/ports/rp2/mphalport.c b/ports/rp2/mphalport.c index 3f50151620a02..0fab6f38478de 100644 --- a/ports/rp2/mphalport.c +++ b/ports/rp2/mphalport.c @@ -250,9 +250,13 @@ static void soft_timer_hardware_callback(unsigned int alarm_num) { // a second ISR, as PendSV may be currently suspended by the other CPU. pendsv_schedule_dispatch(PENDSV_DISPATCH_SOFT_TIMER, soft_timer_handler); - // This ISR only runs on core0, but if core1 is running Python code then it - // may be blocked in WFE so wake it up as well. Unfortunately this also sets - // the event flag on core0, so a subsequent WFE on this core will not suspend + // This ISR mostly only runs on core0 but may run on core1 if core1 calls + // machine.lightsleep(). + // + // Either way, if the other core is running Python code then it may be + // blocked in WFE so wake it up as well. Unfortunately this also sets the + // event flag on this core, so a subsequent WFE on this core will not + // suspend #if MICROPY_PY_THREAD if (core1_entry != NULL) { __sev(); From e9b8a36cf8128050e614ad6f2cf277f441ba9bae Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 18 Dec 2024 16:27:48 +1100 Subject: [PATCH 3/3] tests,rp2: Add a test case for light sleeping from CPU1. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- tests/ports/rp2/rp2_lightsleep_thread.py | 60 ++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 tests/ports/rp2/rp2_lightsleep_thread.py diff --git a/tests/ports/rp2/rp2_lightsleep_thread.py b/tests/ports/rp2/rp2_lightsleep_thread.py new file mode 100644 index 0000000000000..9b9a55f7fdc32 --- /dev/null +++ b/tests/ports/rp2/rp2_lightsleep_thread.py @@ -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() pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy