Add DORMANT_CLOCK_SOURCE_RTC option on RP2040 (#3048)
* Add DORMANT_CLOCK_SOURCE_RTC option on RP2040
This runs RTC from XOSC, which allows going dormant without an external timer, at the expense of higher power consumption
* Switch DORMANT_CLOCK_SOURCE_DEFAULT on RP2040 to DORMANT_CLOCK_SOURCE_RTC
Also change power consumption figures and re-jig tests to match this
diff --git a/src/rp2_common/pico_low_power/include/pico/low_power.h b/src/rp2_common/pico_low_power/include/pico/low_power.h
index 3855a6b..18de824 100644
--- a/src/rp2_common/pico_low_power/include/pico/low_power.h
+++ b/src/rp2_common/pico_low_power/include/pico/low_power.h
@@ -60,13 +60,13 @@
* Mode | Pico (VSYS) | Pico 2 (VSYS) | Pico (3V3) | Pico 2 (3V3)
* ----------------------|----------------|-----------------|----------------|----------------
* Sleep | 7.3mA (37.9mW) | 5.9mA (30.7mW) | 8.7mA (28.5mW) | 6.9mA (22.7mW)
- * Dormant | 0.76mA (4.0mW) | 3.3mA (17.0mW) | 0.75mA (2.5mW) | 3.7mA (12.0mW)
+ * Dormant | 0.95mA (5.0mW) | 3.3mA (17.0mW) | 1.2mA (4.0mW) | 3.7mA (12.0mW)
* Pstate (SRAM0 On) | N/A | 0.25mA (1.32mW) | N/A | 0.14mA (0.47mW)
* Pstate (XIP SRAM On) | N/A | 0.22mA (1.21mW) | N/A | 0.10mA (0.44mW)
* Pstate (All SRAM Off) | N/A | 0.18mA (1.10mW) | N/A | 0.08mA (0.40mW)
*
* NOTE: The RP2350 dormant values are higher than the RP2040 ones because RP2350 continues running clk_ref from the LPOSC to run the timer,
- * whereas RP2040 requires an external clock input.
+ * whereas RP2040 only runs clk_rtc from the XOSC.
*/
// PICO_CONFIG: PARAM_ASSERTIONS_ENABLED_PICO_LOW_POWER, Enable/disable assertions in the pico_low_power module, type=bool, default=0, group=pico_low_power
@@ -108,14 +108,16 @@
typedef enum {
DORMANT_CLOCK_SOURCE_XOSC,
DORMANT_CLOCK_SOURCE_ROSC,
-#if !PICO_RP2040
+#if PICO_RP2040
+ DORMANT_CLOCK_SOURCE_RTC,
+#else
DORMANT_CLOCK_SOURCE_LPOSC,
#endif
NUM_DORMANT_CLOCK_SOURCES
} dormant_clock_source_t;
#if PICO_RP2040
-#define DORMANT_CLOCK_SOURCE_DEFAULT DORMANT_CLOCK_SOURCE_XOSC
+#define DORMANT_CLOCK_SOURCE_DEFAULT DORMANT_CLOCK_SOURCE_RTC
#else
#define DORMANT_CLOCK_SOURCE_DEFAULT DORMANT_CLOCK_SOURCE_LPOSC
#endif
@@ -222,13 +224,18 @@
* The clocks specified in keep_enabled will be kept enabled during dormant, but XOSC and ROSC will be stopped.
*
* \if rp2040_specific
- * This requires an external clock source to be set using \ref low_power_set_external_clock_source before calling this function.
- * If the external clock source is not set, or it is not running, this will return PICO_ERROR_PRECONDITION_NOT_MET.
+ * If the clock source is set to DORMANT_CLOCK_SOURCE_RTC, all clocks will be switched to the ROSC while dormant so
+ * they can be stopped, except clk_rtc which will be run from the XOSC so that it continues running for the timer.
+ * In this case the XOSC will not be stopped.
+ *
+ * Otherwise, this requires an external clock source to be set using \ref low_power_set_external_clock_source before
+ * calling this function. If the external clock source is not set, or it is not running, this will return
+ * PICO_ERROR_PRECONDITION_NOT_MET.
* \endif
*
* \if (!rp2040_specific || combined_docs)
- * If the clock source is set to DORMANT_CLOCK_SOURCE_LPOSC, clk_sys will be switched to the ROSC while dormant so
- * it can be stopped, while clk_ref will be run from the LPOSC so that it continues running for the timer.
+ * The clock source must be set to DORMANT_CLOCK_SOURCE_LPOSC, which means clk_sys will be switched to the ROSC while
+ * dormant so it can be stopped, while clk_ref will be run from the LPOSC so that it continues running for the timer.
* \endif
*
* \param until The time to go dormant until.
@@ -243,6 +250,13 @@
*
* Go dormant until the given GPIO pin changes state.
* The clocks specified in keep_enabled will be kept enabled during dormant, but XOSC and ROSC will be stopped.
+ *
+ * \if rp2040_specific
+ * If the clock source is set to DORMANT_CLOCK_SOURCE_RTC, all clocks will be switched to the ROSC while dormant so
+ * they can be stopped, except clk_rtc which will be run from the XOSC. In this case the XOSC will not be stopped.
+ * For the lowest power consumption, you should use DORMANT_CLOCK_SOURCE_ROSC instead, as the GPIO interrupt does
+ * not require a clock.
+ * \endif
*
* \if (!rp2040_specific || combined_docs)
* If the clock source is set to DORMANT_CLOCK_SOURCE_LPOSC, clk_sys will be run from the ROSC while dormant so
diff --git a/src/rp2_common/pico_low_power/low_power.c b/src/rp2_common/pico_low_power/low_power.c
index 2559e56..5b4d67e 100644
--- a/src/rp2_common/pico_low_power/low_power.c
+++ b/src/rp2_common/pico_low_power/low_power.c
@@ -424,7 +424,15 @@
clk_sys_src = CLOCKS_CLK_SYS_CTRL_SRC_VALUE_CLK_REF;
clk_sys_aux_src = 0;
break;
-#if !PICO_RP2040
+#if PICO_RP2040
+ case DORMANT_CLOCK_SOURCE_RTC:
+ clk_ref_src_hz = rosc_measure_freq_khz() * KHZ;
+ clk_ref_src = CLOCKS_CLK_REF_CTRL_SRC_VALUE_ROSC_CLKSRC_PH;
+ clk_sys_src_hz = clk_ref_src_hz;
+ clk_sys_src = CLOCKS_CLK_SYS_CTRL_SRC_VALUE_CLK_REF;
+ clk_sys_aux_src = 0;
+ break;
+#else
case DORMANT_CLOCK_SOURCE_LPOSC:
clk_ref_src_hz = 32 * KHZ;
clk_ref_src = CLOCKS_CLK_REF_CTRL_SRC_VALUE_LPOSC_CLKSRC;
@@ -475,6 +483,26 @@
if (dormant_source == DORMANT_CLOCK_SOURCE_XOSC) {
// Safe to disable rosc
rosc_disable();
+#if PICO_RP2040
+ } else if (dormant_source == DORMANT_CLOCK_SOURCE_RTC) {
+ // Run RTC directly from XOSC
+ #if (XOSC_HZ % RTC_CLOCK_FREQ_HZ == 0)
+ // this doesn't pull in 64 bit arithmetic
+ clock_configure_int_divider(clk_rtc,
+ 0, // No GLMUX
+ CLOCKS_CLK_RTC_CTRL_AUXSRC_VALUE_XOSC_CLKSRC,
+ XOSC_HZ,
+ XOSC_HZ / RTC_CLOCK_FREQ_HZ);
+
+ #else
+ clock_configure(clk_rtc,
+ 0, // No GLMUX
+ CLOCKS_CLK_RTC_CTRL_AUXSRC_VALUE_XOSC_CLKSRC,
+ XOSC_HZ,
+ RTC_CLOCK_FREQ_HZ);
+
+ #endif
+#endif
} else {
// Safe to disable xosc
xosc_disable();
@@ -496,7 +524,9 @@
static void low_power_go_dormant(dormant_clock_source_t dormant_clock_source) {
valid_params_if(PICO_LOW_POWER,
dormant_clock_source == DORMANT_CLOCK_SOURCE_XOSC || dormant_clock_source == DORMANT_CLOCK_SOURCE_ROSC
- #if !PICO_RP2040
+ #if PICO_RP2040
+ || dormant_clock_source == DORMANT_CLOCK_SOURCE_RTC
+ #else
|| dormant_clock_source == DORMANT_CLOCK_SOURCE_LPOSC
#endif
);
@@ -529,12 +559,14 @@
replace_null_enable_values(keep_enabled, &local_keep_enabled);
#if PICO_RP2040
- if (dormant_rtc_src_hz == 0) {
- return PICO_ERROR_PRECONDITION_NOT_MET;
- }
- // The RTC must be run from an external source, since the dormant source will be inactive
- if (!rtc_run_from_external_source(dormant_rtc_src_hz, dormant_rtc_gpio_pin)) {
- return PICO_ERROR_PRECONDITION_NOT_MET;
+ if (dormant_clock_source != DORMANT_CLOCK_SOURCE_RTC) {
+ if (dormant_rtc_src_hz == 0) {
+ return PICO_ERROR_PRECONDITION_NOT_MET;
+ }
+ // The RTC must be run from an external source, since the dormant source will be inactive
+ if (!rtc_run_from_external_source(dormant_rtc_src_hz, dormant_rtc_gpio_pin)) {
+ return PICO_ERROR_PRECONDITION_NOT_MET;
+ }
}
clock_dest_bitset_add(&local_keep_enabled, CLK_DEST_RTC_RTC);
#elif PICO_RP2350
diff --git a/test/pico_low_power_test/low_power_test_simple.c b/test/pico_low_power_test/low_power_test_simple.c
index d830163..1769cbd 100644
--- a/test/pico_low_power_test/low_power_test_simple.c
+++ b/test/pico_low_power_test/low_power_test_simple.c
@@ -65,6 +65,16 @@
} else {
printf("ERROR: Woken up from Pstate\n");
}
+#elif PICO_RP2040
+ printf("Going dormant from the XOSC for %dms\n", SLEEP_TIME_MS);
+ gpio_put(SLEEP_MONITOR_PIN, 0);
+ ret = low_power_dormant_for_ms(SLEEP_TIME_MS, DORMANT_CLOCK_SOURCE_XOSC, NULL);
+ gpio_put(SLEEP_MONITOR_PIN, 1);
+ if (ret != PICO_OK) {
+ printf("ERROR: low_power_dormant_for_ms returned %d\n", ret);
+ } else {
+ printf("Woken up\n");
+ }
#endif
}
diff --git a/test/pico_low_power_test/low_power_test_timers.c b/test/pico_low_power_test/low_power_test_timers.c
index b828043..c53504f 100644
--- a/test/pico_low_power_test/low_power_test_timers.c
+++ b/test/pico_low_power_test/low_power_test_timers.c
@@ -129,6 +129,7 @@
absolute_time_t start_time;
static absolute_time_t __persistent_data(wakeup_time);
+ absolute_time_t system_time_before;
int64_t diff;
int ret;
@@ -243,21 +244,16 @@
- // dormant
+ // dormant using default clock source
printf("Going DORMANT for %d seconds via AON TIMER\n", SLEEP_TIME_S);
gpio_put(SLEEP_MONITOR_PIN, 0);
start_time = aon_timer_get_absolute_time();
- absolute_time_t system_time_before = get_absolute_time();
+ system_time_before = get_absolute_time();
wakeup_time = delayed_by_ms(start_time, SLEEP_TIME_MS);
ret = low_power_dormant_until_aon_timer(wakeup_time, DORMANT_CLOCK_SOURCE_DEFAULT, NULL);
if (ret != PICO_OK) {
printf("ERROR: %d returned by low_power_dormant_until_aon_timer\n", ret);
- #if PICO_RP2040
- if (ret == PICO_ERROR_PRECONDITION_NOT_MET) {
- printf("ERROR: RTC clock source is not running - connect a device running external_sleep_timer to GPIO %d\n", RTC_GPIO_IN);
- }
- #endif
EXIT_TEST;
}
gpio_put(SLEEP_MONITOR_PIN, 1);
@@ -390,6 +386,42 @@
printf("ERROR: number of POWMAN reboots was %d not 2\n", powman_hw->scratch[3]);
EXIT_TEST;
}
+#else
+ // dormant using XOSC clock source
+ printf("Going DORMANT from the XOSC for %d seconds via AON TIMER\n", SLEEP_TIME_S);
+
+ gpio_put(SLEEP_MONITOR_PIN, 0);
+ start_time = aon_timer_get_absolute_time();
+ system_time_before = get_absolute_time();
+ wakeup_time = delayed_by_ms(start_time, SLEEP_TIME_MS);
+ ret = low_power_dormant_until_aon_timer(wakeup_time, DORMANT_CLOCK_SOURCE_XOSC, NULL);
+ if (ret != PICO_OK) {
+ printf("ERROR: %d returned by low_power_dormant_until_aon_timer\n", ret);
+ #if PICO_RP2040
+ if (ret == PICO_ERROR_PRECONDITION_NOT_MET) {
+ printf("ERROR: RTC clock source is not running - connect a device running external_sleep_timer to GPIO %d\n", RTC_GPIO_IN);
+ }
+ #endif
+ EXIT_TEST;
+ }
+ gpio_put(SLEEP_MONITOR_PIN, 1);
+ // check the system timer was stopped while dormant
+ diff = absolute_time_diff_us(system_time_before, get_absolute_time());
+ if (diff > 200 * 1000 // 200ms
+ #ifdef PICO_STDIO_USB_CONNECT_WAIT_TIMEOUT_MS
+ + (PICO_STDIO_USB_CONNECT_WAIT_TIMEOUT_MS * 1000)
+ #endif
+ ) {
+ printf("ERROR: doesn't seem like timer was stopped: diff %lldus\n", diff);
+ return - 1;
+ }
+ diff = absolute_time_diff_us(wakeup_time, aon_timer_get_absolute_time());
+ printf("Woken up now @%dus since target\n", (int)diff);
+ if (diff < 0) {
+ printf("WARNING: Woke up too soon - is this within the resolution of the aon timer?\n");
+ }
+ printf("Doing %d second pause to prove timer running\n", SLEEP_TIME_S);
+ busy_wait_ms(SLEEP_TIME_MS);
#endif
printf("PASSED\n");