Skip to content

Commit da4cbd9

Browse files
committed
LiPo: Add powman and wakeup modules.
These are useful for dealing with low power situations, though powman's time keeping leaves a little to be desired.
1 parent 9ec3001 commit da4cbd9

File tree

12 files changed

+614
-0
lines changed

12 files changed

+614
-0
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
11
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/../")
2+
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/../../")
3+
4+
# Wakeup module for early GPIO latch
5+
include(modules/c/wakeup/micropython)
6+
7+
# Powman example for low power sleep
8+
include(modules/c/powman/micropython)
29

310
include(usermod-common)
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
11
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/../")
2+
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/../../")
3+
4+
# Wakeup module for early GPIO latch
5+
include(modules/c/wakeup/micropython)
6+
7+
# Powman example for low power sleep
8+
include(modules/c/powman/micropython)
29

310
include(usermod-common)

modules/c/powman/bindings.c

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
#include <time.h>
2+
#include <sys/time.h>
3+
4+
#include "pico/stdlib.h"
5+
#include "pico/util/datetime.h"
6+
#include "powman.h"
7+
8+
#include "mphalport.h"
9+
#include "py/runtime.h"
10+
#include "shared/timeutils/timeutils.h"
11+
12+
#define GPIO_I2C_POWER 2
13+
#define GPIO_WAKE 3
14+
#define GPIO_EXT_CLK 12
15+
#define GPIO_LED_A 10
16+
17+
enum {
18+
WAKE_BUTTON_A = 0x00,
19+
WAKE_BUTTON_B,
20+
WAKE_BUTTON_C,
21+
WAKE_TIMER = 0xf0,
22+
WAKE_UNKNOWN = 0xff,
23+
};
24+
25+
mp_obj_t _sleep_get_wake_reason(void) {
26+
uint8_t wake_reason = powman_get_wake_reason();
27+
if(wake_reason & POWMAN_WAKE_ALARM) {
28+
return MP_ROM_INT(WAKE_TIMER);
29+
}
30+
if(wake_reason & POWMAN_WAKE_PWRUP0) return MP_ROM_INT(WAKE_BUTTON_A);
31+
if(wake_reason & POWMAN_WAKE_PWRUP1) return MP_ROM_INT(WAKE_BUTTON_B);
32+
if(wake_reason & POWMAN_WAKE_PWRUP2) return MP_ROM_INT(WAKE_BUTTON_C);
33+
return MP_ROM_INT(WAKE_UNKNOWN);
34+
}
35+
static MP_DEFINE_CONST_FUN_OBJ_0(_sleep_get_wake_reason_obj, _sleep_get_wake_reason);
36+
37+
/*! \brief Send system to sleep until the specified GPIO changes
38+
*
39+
* \param gpio_pin The pin to provide the wake up
40+
* \param edge true for leading edge, false for trailing edge
41+
* \param high true for active high, false for active low
42+
* \param timeout wakeup after timeout milliseconds if no edge occurs
43+
*/
44+
mp_obj_t _sleep_goto_dormant_until_pin(size_t n_args, const mp_obj_t *args) {
45+
enum { ARG_pin, ARG_edge, ARG_high, ARG_timeout };
46+
47+
uint pin = UINT16_MAX;
48+
if(args[ARG_pin] != mp_const_none) {
49+
pin = mp_hal_get_pin_obj(args[ARG_pin]);
50+
}
51+
bool edge = mp_obj_is_true(args[ARG_edge]);
52+
bool high = mp_obj_is_true(args[ARG_high]);
53+
uint64_t timeout_ms = 0;
54+
55+
if (n_args == 4) {
56+
timeout_ms = (uint64_t)mp_obj_get_float(args[ARG_timeout]) * 1000;
57+
}
58+
59+
powman_init();
60+
61+
if (pin != UINT16_MAX) {
62+
powman_setup_gpio_wakeup(POWMAN_WAKE_PWRUP0_CH, pin, edge, high, 1000);
63+
} else {
64+
int err = 0;
65+
err = powman_setup_gpio_wakeup(POWMAN_WAKE_PWRUP0_CH, 12, edge, high, 1000); // Tufty Button A
66+
if (err == -1) {mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("Timeout waiting for Button A"));}
67+
err = powman_setup_gpio_wakeup(POWMAN_WAKE_PWRUP1_CH, 13, edge, high, 1000); // Tufty Button B
68+
if (err == -1) {mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("Timeout waiting for Button B"));}
69+
err = powman_setup_gpio_wakeup(POWMAN_WAKE_PWRUP2_CH, 14, edge, high, 1000); // Tufty Button C
70+
if (err == -1) {mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("Timeout waiting for Button C"));}
71+
}
72+
73+
// power off
74+
int rc = 0;
75+
if (timeout_ms > 0) {
76+
absolute_time_t timeout = make_timeout_time_ms(timeout_ms);
77+
rc = powman_off_until_time(timeout);
78+
} else {
79+
rc = powman_off();
80+
}
81+
hard_assert(rc == PICO_OK);
82+
hard_assert(false); // should never get here!
83+
84+
return mp_const_none;
85+
}
86+
static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(_sleep_goto_dormant_until_pin_obj, 3, 4, _sleep_goto_dormant_until_pin);
87+
88+
/*! \brief Send system to dormant until the specified time, note for RP2040 the RTC must be driven by an external clock
89+
*
90+
* \param ts The time to wake up
91+
* \param callback Function to call on wakeup.
92+
*/
93+
mp_obj_t _sleep_goto_dormant_until(mp_obj_t absolute_time_in) {
94+
// Borrowed from https://github.com/micropython/micropython/blob/master/ports/rp2/machine_rtc.c#L83C1-L96
95+
mp_obj_t *items;
96+
mp_obj_get_array_fixed_n(absolute_time_in, 8, &items);
97+
timeutils_struct_time_t tm = {
98+
.tm_year = mp_obj_get_int(items[0]),
99+
.tm_mon = mp_obj_get_int(items[1]),
100+
.tm_mday = mp_obj_get_int(items[2]),
101+
.tm_hour = mp_obj_get_int(items[4]),
102+
.tm_min = mp_obj_get_int(items[5]),
103+
.tm_sec = mp_obj_get_int(items[6]),
104+
};
105+
struct timespec ts = { 0, 0 };
106+
ts.tv_sec = timeutils_seconds_since_epoch(tm.tm_year, tm.tm_mon, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
107+
108+
int rc = powman_off_until_time(timespec_to_ms(&ts));
109+
hard_assert(rc == PICO_OK);
110+
hard_assert(false); // should never get here!
111+
112+
return mp_const_none;
113+
}
114+
static MP_DEFINE_CONST_FUN_OBJ_1(_sleep_goto_dormant_until_obj, _sleep_goto_dormant_until);
115+
116+
/*! \brief Send system to dormant until the specified time, note for RP2040 the RTC must be driven by an external clock
117+
*
118+
* \param ts The time to wake up
119+
* \param callback Function to call on wakeup.
120+
*/
121+
mp_obj_t _sleep_goto_dormant_for(mp_obj_t time_seconds_in) {
122+
uint64_t ms = (uint64_t)(mp_obj_get_float(time_seconds_in) * 1000);
123+
int rc = powman_off_for_ms(ms);
124+
hard_assert(rc == PICO_OK);
125+
hard_assert(false); // should never get here!
126+
return mp_const_none;
127+
}
128+
static MP_DEFINE_CONST_FUN_OBJ_1(_sleep_goto_dormant_for_obj, _sleep_goto_dormant_for);
129+
130+
static const mp_map_elem_t sleep_globals_table[] = {
131+
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_powman) },
132+
{ MP_ROM_QSTR(MP_QSTR_goto_dormant_until_pin), MP_ROM_PTR(&_sleep_goto_dormant_until_pin_obj) },
133+
{ MP_ROM_QSTR(MP_QSTR_goto_dormant_until), MP_ROM_PTR(&_sleep_goto_dormant_until_obj) },
134+
{ MP_ROM_QSTR(MP_QSTR_goto_dormant_for), MP_ROM_PTR(&_sleep_goto_dormant_for_obj) },
135+
{ MP_ROM_QSTR(MP_QSTR_get_wake_reason), MP_ROM_PTR(&_sleep_get_wake_reason_obj) },
136+
137+
{ MP_ROM_QSTR(MP_QSTR_WAKE_BUTTON_A), MP_ROM_INT(WAKE_BUTTON_A) },
138+
{ MP_ROM_QSTR(MP_QSTR_WAKE_BUTTON_B), MP_ROM_INT(WAKE_BUTTON_B) },
139+
{ MP_ROM_QSTR(MP_QSTR_WAKE_BUTTON_C), MP_ROM_INT(WAKE_BUTTON_C) },
140+
{ MP_ROM_QSTR(MP_QSTR_WAKE_TIMER), MP_ROM_INT(WAKE_TIMER) }, // TODO: Rename to ALARM?
141+
{ MP_ROM_QSTR(MP_QSTR_WAKE_UNKNOWN), MP_ROM_INT(WAKE_UNKNOWN) },
142+
};
143+
static MP_DEFINE_CONST_DICT(mp_module_sleep_globals, sleep_globals_table);
144+
145+
const mp_obj_module_t sleep_user_cmodule = {
146+
.base = { &mp_type_module },
147+
.globals = (mp_obj_dict_t*)&mp_module_sleep_globals,
148+
};
149+
150+
MP_REGISTER_MODULE(MP_QSTR_powman, sleep_user_cmodule);

modules/c/powman/micropython.cmake

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
add_library(usermod_sleep INTERFACE)
2+
3+
target_sources(usermod_sleep INTERFACE
4+
${CMAKE_CURRENT_LIST_DIR}/bindings.c
5+
${CMAKE_CURRENT_LIST_DIR}/powman.c
6+
${CMAKE_CURRENT_LIST_DIR}/rosc.c
7+
)
8+
9+
target_include_directories(usermod_sleep INTERFACE
10+
${CMAKE_CURRENT_LIST_DIR}
11+
)
12+
13+
target_link_libraries(usermod_sleep INTERFACE hardware_powman hardware_gpio)
14+
15+
target_link_libraries(usermod INTERFACE usermod_sleep)
16+
17+
#set_source_files_properties(
18+
# ${CMAKE_CURRENT_LIST_DIR}/sleep.c
19+
# PROPERTIES COMPILE_FLAGS
20+
# "-Wno-maybe-uninitialized"
21+
#)
22+
23+
set_source_files_properties(
24+
${CMAKE_CURRENT_LIST_DIR}/bindings.c
25+
PROPERTIES COMPILE_FLAGS
26+
"-Wno-discarded-qualifiers"
27+
)

modules/c/powman/powman.c

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
2+
#include "powman.h"
3+
4+
static powman_power_state off_state;
5+
static powman_power_state on_state;
6+
7+
//#define DEBUG
8+
9+
uint8_t powman_get_wake_reason(void) {
10+
// 0 = chip reset, for the source of the last reset see POWMAN_CHIP_RESET
11+
// 1 = pwrup0 (GPIO interrupt 0)
12+
// 2 = pwrup1 (GPIO interrupt 1)
13+
// 3 = pwrup2 (GPIO interrupt 2)
14+
// 4 = pwrup3 (GPIO interrupt 3)
15+
// 5 = coresight_pwrup
16+
// 6 = alarm_pwrup (timeout or alarm wakeup)
17+
return powman_hw->last_swcore_pwrup & 0x7f;
18+
}
19+
20+
void powman_init() {
21+
uint64_t abs_time_ms = 1746057600000; // 2025/05/01 - Milliseconds since epoch
22+
23+
// Run everything from pll_usb pll and stop pll_sys
24+
set_sys_clock_48mhz();
25+
26+
// Use the 32768 Hz clockout from the RTC to keep time accurately
27+
//clock_configure_gpin(clk_ref, 12, 32.768f * KHZ, 32.768f * KHZ);
28+
//clock_configure_gpin(clk_sys, 12, 32.768f * KHZ, 32.768f * KHZ);
29+
//clock_configure_undivided(clk_peri, 0, CLOCKS_CLK_PERI_CTRL_AUXSRC_VALUE_CLK_SYS, 32.768f * KHZ);
30+
//powman_timer_set_1khz_tick_source_lposc_with_hz(32768);
31+
32+
powman_timer_set_1khz_tick_source_lposc();
33+
pll_deinit(pll_sys);
34+
35+
// Set all pins to input (as far as SIO is concerned)
36+
gpio_set_dir_all_bits(0);
37+
for (int i = 0; i < NUM_BANK0_GPIOS; ++i) {
38+
gpio_set_function(i, GPIO_FUNC_SIO);
39+
if (i > NUM_BANK0_GPIOS - NUM_ADC_CHANNELS) {
40+
gpio_disable_pulls(i);
41+
gpio_set_input_enabled(i, false);
42+
}
43+
}
44+
45+
// Unlock the VREG control interface
46+
hw_set_bits(&powman_hw->vreg_ctrl, POWMAN_PASSWORD_BITS | POWMAN_VREG_CTRL_UNLOCK_BITS);
47+
48+
// Turn off USB PHY and apply pull downs on DP & DM
49+
usb_hw->phy_direct = USB_USBPHY_DIRECT_TX_PD_BITS | USB_USBPHY_DIRECT_RX_PD_BITS | USB_USBPHY_DIRECT_DM_PULLDN_EN_BITS | USB_USBPHY_DIRECT_DP_PULLDN_EN_BITS;
50+
51+
usb_hw->phy_direct_override = USB_USBPHY_DIRECT_RX_DM_BITS | USB_USBPHY_DIRECT_RX_DP_BITS | USB_USBPHY_DIRECT_RX_DD_BITS |
52+
USB_USBPHY_DIRECT_OVERRIDE_TX_DIFFMODE_OVERRIDE_EN_BITS | USB_USBPHY_DIRECT_OVERRIDE_DM_PULLUP_OVERRIDE_EN_BITS | USB_USBPHY_DIRECT_OVERRIDE_TX_FSSLEW_OVERRIDE_EN_BITS |
53+
USB_USBPHY_DIRECT_OVERRIDE_TX_PD_OVERRIDE_EN_BITS | USB_USBPHY_DIRECT_OVERRIDE_RX_PD_OVERRIDE_EN_BITS | USB_USBPHY_DIRECT_OVERRIDE_TX_DM_OVERRIDE_EN_BITS |
54+
USB_USBPHY_DIRECT_OVERRIDE_TX_DP_OVERRIDE_EN_BITS | USB_USBPHY_DIRECT_OVERRIDE_TX_DM_OE_OVERRIDE_EN_BITS | USB_USBPHY_DIRECT_OVERRIDE_TX_DP_OE_OVERRIDE_EN_BITS |
55+
USB_USBPHY_DIRECT_OVERRIDE_DM_PULLDN_EN_OVERRIDE_EN_BITS | USB_USBPHY_DIRECT_OVERRIDE_DP_PULLDN_EN_OVERRIDE_EN_BITS | USB_USBPHY_DIRECT_OVERRIDE_DP_PULLUP_EN_OVERRIDE_EN_BITS |
56+
USB_USBPHY_DIRECT_OVERRIDE_DM_PULLUP_HISEL_OVERRIDE_EN_BITS | USB_USBPHY_DIRECT_OVERRIDE_DP_PULLUP_HISEL_OVERRIDE_EN_BITS;
57+
58+
// start powman and set the time
59+
powman_timer_start();
60+
powman_timer_set_ms(abs_time_ms);
61+
62+
// Allow power down when debugger connected
63+
powman_set_debug_power_request_ignored(true);
64+
65+
// Power states
66+
powman_power_state P1_7 = POWMAN_POWER_STATE_NONE;
67+
68+
powman_power_state P0_3 = POWMAN_POWER_STATE_NONE;
69+
P0_3 = powman_power_state_with_domain_on(P0_3, POWMAN_POWER_DOMAIN_SWITCHED_CORE);
70+
P0_3 = powman_power_state_with_domain_on(P0_3, POWMAN_POWER_DOMAIN_XIP_CACHE);
71+
72+
off_state = P1_7;
73+
on_state = P0_3;
74+
}
75+
76+
// Initiate power off
77+
int __no_inline_not_in_flash_func(powman_off)(void) {
78+
// Set power states
79+
bool valid_state = powman_configure_wakeup_state(off_state, on_state);
80+
if (!valid_state) {
81+
return PICO_ERROR_INVALID_STATE;
82+
}
83+
84+
// reboot to main
85+
powman_hw->boot[0] = 0;
86+
powman_hw->boot[1] = 0;
87+
powman_hw->boot[2] = 0;
88+
powman_hw->boot[3] = 0;
89+
90+
// Switch to required power state
91+
int rc = powman_set_power_state(off_state);
92+
if (rc != PICO_OK) {
93+
return rc;
94+
}
95+
96+
// Power down
97+
while (true) __wfi();
98+
}
99+
100+
int powman_setup_gpio_wakeup(int hw_wakeup, int gpio, bool edge, bool high, uint64_t timeout_ms) {
101+
gpio_init(gpio);
102+
gpio_set_dir(gpio, false);
103+
gpio_set_input_enabled(gpio, true);
104+
105+
// Must set pulls here, or our pin may never go into its idle state
106+
gpio_set_pulls(gpio, !high, high);
107+
108+
// If the pin is currently in a triggered state, wait for idle
109+
absolute_time_t timeout = make_timeout_time_ms(timeout_ms);
110+
if (gpio_get(gpio) == high) {
111+
while(gpio_get(gpio) == high) {
112+
sleep_ms(10);
113+
if(time_reached(timeout)) return -1;
114+
}
115+
}
116+
powman_enable_gpio_wakeup(hw_wakeup, gpio, edge, high);
117+
118+
return 0;
119+
}
120+
121+
// Power off until a gpio goes high
122+
int powman_off_until_gpio_high(int gpio, bool edge, bool high, uint64_t timeout_ms) {
123+
powman_init();
124+
125+
powman_setup_gpio_wakeup(POWMAN_WAKE_PWRUP0_CH, gpio, edge, high, 1000);
126+
127+
if (timeout_ms > 0) {
128+
uint64_t ms = powman_timer_get_ms();
129+
return powman_off_until_time(ms + timeout_ms);
130+
} else {
131+
return powman_off();
132+
}
133+
}
134+
135+
// Power off until an absolute time
136+
int powman_off_until_time(uint64_t absolute_time_ms) {
137+
powman_init();
138+
139+
// Start powman timer and turn off
140+
powman_enable_alarm_wakeup_at_ms(absolute_time_ms);
141+
return powman_off();
142+
}
143+
144+
// Power off for a number of milliseconds
145+
int powman_off_for_ms(uint64_t duration_ms) {
146+
powman_init();
147+
148+
uint64_t ms = powman_timer_get_ms();
149+
return powman_off_until_time(ms + duration_ms);
150+
}

modules/c/powman/powman.h

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
2+
3+
#include <stdio.h>
4+
#include <inttypes.h>
5+
#include "pico/stdio.h"
6+
#include "pico/sync.h"
7+
#include "hardware/gpio.h"
8+
#include "hardware/powman.h"
9+
#include "hardware/clocks.h"
10+
#include "hardware/pll.h"
11+
#include "hardware/adc.h"
12+
#include "hardware/structs/usb.h"
13+
#include "hardware/structs/xosc.h"
14+
#include "hardware/vreg.h"
15+
#include "hardware/flash.h"
16+
#include "hardware/structs/qmi.h"
17+
18+
#define POWMAN_WAKE_PWRUP0_CH 0
19+
#define POWMAN_WAKE_PWRUP1_CH 1
20+
#define POWMAN_WAKE_PWRUP2_CH 2
21+
22+
#define POWMAN_WAKE_RESET 0b00000001
23+
#define POWMAN_WAKE_PWRUP0 0b00000010
24+
#define POWMAN_WAKE_PWRUP1 0b00000100
25+
#define POWMAN_WAKE_PWRUP2 0b00001000
26+
#define POWMAN_WAKE_PWRUP3 0b00010000
27+
#define POWMAN_WAKE_CORESI 0b00100000
28+
#define POWMAN_WAKE_ALARM 0b01000000
29+
30+
int powman_off_until_gpio_high(int gpio, bool edge, bool high, uint64_t timeout_ms);
31+
int powman_off_until_time(uint64_t absolute_time_ms);
32+
int powman_off_for_ms(uint64_t duration_ms);
33+
uint8_t powman_get_wake_reason(void);
34+
35+
void powman_init();
36+
int powman_setup_gpio_wakeup(int hw_wakeup, int gpio, bool edge, bool high, uint64_t timeout_ms);
37+
int powman_off(void);

0 commit comments

Comments
 (0)