diff options
| author | Damien George <damien@micropython.org> | 2022-06-29 00:22:49 +1000 |
|---|---|---|
| committer | Damien George <damien@micropython.org> | 2022-06-30 11:40:17 +1000 |
| commit | b004e7e397577d95404fd31aec68a5c54904a48c (patch) | |
| tree | 266fa9f6938df7d28cbfb7a7a191f45c104d0eb8 | |
| parent | 932556d5fc09a196139170fe52436ea8a02020c1 (diff) | |
rp2/modmachine: Implement lightsleep() with optional sleep period.
This gets basic machine.lightsleep([n]) behaviour working on the rp2 port.
It supports:
- Calling lightsleep without a specified period, in which case it uses xosc
dormant mode. There's currently no way to wake it up from this state,
unless you write to raw registers to enable a GPIO wake up source.
- Calling lightsleep with a period n in milliseconds. This period must be
less than about 72 minutes and uses timer alarm3 to wake it up.
The RTC continues to run during lightsleep, but other peripherals have
their clock turned off during the sleep.
It doesn't yet support longer periods than 72 minutes, or waking up from
GPIO IRQ.
Measured current consumption from the USB port on a PICO board is about
1.5mA when doing machine.lightsleep(5000), and about 0.9mA when doing
machine.lightsleep().
Addresses issue #8770.
Signed-off-by: Damien George <damien@micropython.org>
| -rw-r--r-- | ports/rp2/CMakeLists.txt | 2 | ||||
| -rw-r--r-- | ports/rp2/modmachine.c | 78 |
2 files changed, 76 insertions, 4 deletions
diff --git a/ports/rp2/CMakeLists.txt b/ports/rp2/CMakeLists.txt index 0088ba2c6..9f71f6584 100644 --- a/ports/rp2/CMakeLists.txt +++ b/ports/rp2/CMakeLists.txt @@ -150,6 +150,7 @@ set(PICO_SDK_COMPONENTS hardware_i2c hardware_irq hardware_pio + hardware_pll hardware_pwm hardware_regs hardware_rtc @@ -159,6 +160,7 @@ set(PICO_SDK_COMPONENTS hardware_timer hardware_uart hardware_watchdog + hardware_xosc pico_base_headers pico_binary_info pico_bootrom diff --git a/ports/rp2/modmachine.c b/ports/rp2/modmachine.c index b988afdf3..3c8922c41 100644 --- a/ports/rp2/modmachine.c +++ b/ports/rp2/modmachine.c @@ -38,7 +38,12 @@ #include "modmachine.h" #include "uart.h" #include "hardware/clocks.h" +#include "hardware/pll.h" +#include "hardware/structs/rosc.h" +#include "hardware/structs/scb.h" +#include "hardware/structs/syscfg.h" #include "hardware/watchdog.h" +#include "hardware/xosc.h" #include "pico/bootrom.h" #include "pico/stdlib.h" #include "pico/unique_id.h" @@ -83,6 +88,7 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_0(machine_reset_cause_obj, machine_reset_cause); NORETURN mp_obj_t machine_bootloader(size_t n_args, const mp_obj_t *args) { MICROPY_BOARD_ENTER_BOOTLOADER(n_args, args); + rosc_hw->ctrl = ROSC_CTRL_ENABLE_VALUE_ENABLE << ROSC_CTRL_ENABLE_LSB; reset_usb_boot(0, 0); for (;;) { } @@ -113,13 +119,77 @@ STATIC mp_obj_t machine_idle(void) { STATIC MP_DEFINE_CONST_FUN_OBJ_0(machine_idle_obj, machine_idle); STATIC mp_obj_t machine_lightsleep(size_t n_args, const mp_obj_t *args) { - if (n_args == 0) { - for (;;) { - MICROPY_EVENT_POLL_HOOK + mp_int_t delay_ms = 0; + bool use_timer_alarm = false; + + if (n_args == 1) { + delay_ms = mp_obj_get_int(args[0]); + if (delay_ms <= 1) { + // Sleep is too small, just use standard delay. + mp_hal_delay_ms(delay_ms); + return mp_const_none; + } + use_timer_alarm = delay_ms < (1ULL << 32) / 1000; + if (use_timer_alarm) { + // Use timer alarm to wake. + } else { + // TODO: Use RTC alarm to wake. + mp_raise_ValueError(MP_ERROR_TEXT("sleep too long")); } + } + + const uint32_t xosc_hz = XOSC_MHZ * 1000000; + + // Disable USB and ADC clocks. + clock_stop(clk_usb); + clock_stop(clk_adc); + + // CLK_REF = XOSC + clock_configure(clk_ref, CLOCKS_CLK_REF_CTRL_SRC_VALUE_XOSC_CLKSRC, 0, xosc_hz, xosc_hz); + + // CLK_SYS = CLK_REF + clock_configure(clk_sys, CLOCKS_CLK_SYS_CTRL_SRC_VALUE_CLK_REF, 0, xosc_hz, xosc_hz); + + // CLK_RTC = XOSC / 256 + clock_configure(clk_rtc, 0, CLOCKS_CLK_RTC_CTRL_AUXSRC_VALUE_XOSC_CLKSRC, xosc_hz, xosc_hz / 256); + + // CLK_PERI = CLK_SYS + clock_configure(clk_peri, 0, CLOCKS_CLK_PERI_CTRL_AUXSRC_VALUE_CLK_SYS, xosc_hz, xosc_hz); + + // Disable PLLs. + pll_deinit(pll_sys); + pll_deinit(pll_usb); + + // Disable ROSC. + rosc_hw->ctrl = ROSC_CTRL_ENABLE_VALUE_DISABLE << ROSC_CTRL_ENABLE_LSB; + + if (n_args == 0) { + xosc_dormant(); } else { - mp_hal_delay_ms(mp_obj_get_int(args[0])); + uint32_t sleep_en0 = clocks_hw->sleep_en0; + uint32_t sleep_en1 = clocks_hw->sleep_en1; + clocks_hw->sleep_en0 = CLOCKS_SLEEP_EN0_CLK_RTC_RTC_BITS; + if (use_timer_alarm) { + // Use timer alarm to wake. + clocks_hw->sleep_en1 = CLOCKS_SLEEP_EN1_CLK_SYS_TIMER_BITS; + timer_hw->alarm[3] = timer_hw->timerawl + delay_ms * 1000; + } else { + // TODO: Use RTC alarm to wake. + clocks_hw->sleep_en1 = 0; + } + scb_hw->scr |= M0PLUS_SCR_SLEEPDEEP_BITS; + __wfi(); + scb_hw->scr &= ~M0PLUS_SCR_SLEEPDEEP_BITS; + clocks_hw->sleep_en0 = sleep_en0; + clocks_hw->sleep_en1 = sleep_en1; } + + // Enable ROSC. + rosc_hw->ctrl = ROSC_CTRL_ENABLE_VALUE_ENABLE << ROSC_CTRL_ENABLE_LSB; + + // Bring back all clocks. + clocks_init(); + return mp_const_none; } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_lightsleep_obj, 0, 1, machine_lightsleep); |
