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(); 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 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()
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: