diff --git a/boards/pimoroni_pico_lipo2/usermodules.cmake b/boards/pimoroni_pico_lipo2/usermodules.cmake index d3f340d..4bbf627 100644 --- a/boards/pimoroni_pico_lipo2/usermodules.cmake +++ b/boards/pimoroni_pico_lipo2/usermodules.cmake @@ -1,3 +1,10 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/../") +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/../../") + +# Wakeup module for early GPIO latch +include(modules/c/wakeup/micropython) + +# Powman example for low power sleep +include(modules/c/powman/micropython) include(usermod-common) \ No newline at end of file diff --git a/boards/pimoroni_pico_lipo2xl_w/usermodules.cmake b/boards/pimoroni_pico_lipo2xl_w/usermodules.cmake index d3f340d..4bbf627 100644 --- a/boards/pimoroni_pico_lipo2xl_w/usermodules.cmake +++ b/boards/pimoroni_pico_lipo2xl_w/usermodules.cmake @@ -1,3 +1,10 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/../") +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/../../") + +# Wakeup module for early GPIO latch +include(modules/c/wakeup/micropython) + +# Powman example for low power sleep +include(modules/c/powman/micropython) include(usermod-common) \ No newline at end of file diff --git a/modules/c/powman/bindings.c b/modules/c/powman/bindings.c new file mode 100644 index 0000000..e34abe5 --- /dev/null +++ b/modules/c/powman/bindings.c @@ -0,0 +1,150 @@ +#include +#include + +#include "pico/stdlib.h" +#include "pico/util/datetime.h" +#include "powman.h" + +#include "mphalport.h" +#include "py/runtime.h" +#include "shared/timeutils/timeutils.h" + +#define GPIO_I2C_POWER 2 +#define GPIO_WAKE 3 +#define GPIO_EXT_CLK 12 +#define GPIO_LED_A 10 + +enum { + WAKE_BUTTON_A = 0x00, + WAKE_BUTTON_B, + WAKE_BUTTON_C, + WAKE_TIMER = 0xf0, + WAKE_UNKNOWN = 0xff, +}; + +mp_obj_t _sleep_get_wake_reason(void) { + uint8_t wake_reason = powman_get_wake_reason(); + if(wake_reason & POWMAN_WAKE_ALARM) { + return MP_ROM_INT(WAKE_TIMER); + } + if(wake_reason & POWMAN_WAKE_PWRUP0) return MP_ROM_INT(WAKE_BUTTON_A); + if(wake_reason & POWMAN_WAKE_PWRUP1) return MP_ROM_INT(WAKE_BUTTON_B); + if(wake_reason & POWMAN_WAKE_PWRUP2) return MP_ROM_INT(WAKE_BUTTON_C); + return MP_ROM_INT(WAKE_UNKNOWN); +} +static MP_DEFINE_CONST_FUN_OBJ_0(_sleep_get_wake_reason_obj, _sleep_get_wake_reason); + +/*! \brief Send system to sleep until the specified GPIO changes + * + * \param gpio_pin The pin to provide the wake up + * \param edge true for leading edge, false for trailing edge + * \param high true for active high, false for active low + * \param timeout wakeup after timeout milliseconds if no edge occurs + */ +mp_obj_t _sleep_goto_dormant_until_pin(size_t n_args, const mp_obj_t *args) { + enum { ARG_pin, ARG_edge, ARG_high, ARG_timeout }; + + uint pin = UINT16_MAX; + if(args[ARG_pin] != mp_const_none) { + pin = mp_hal_get_pin_obj(args[ARG_pin]); + } + bool edge = mp_obj_is_true(args[ARG_edge]); + bool high = mp_obj_is_true(args[ARG_high]); + uint64_t timeout_ms = 0; + + if (n_args == 4) { + timeout_ms = (uint64_t)mp_obj_get_float(args[ARG_timeout]) * 1000; + } + + powman_init(); + + if (pin != UINT16_MAX) { + powman_setup_gpio_wakeup(POWMAN_WAKE_PWRUP0_CH, pin, edge, high, 1000); + } else { + int err = 0; + err = powman_setup_gpio_wakeup(POWMAN_WAKE_PWRUP0_CH, 12, edge, high, 1000); // Tufty Button A + if (err == -1) {mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("Timeout waiting for Button A"));} + err = powman_setup_gpio_wakeup(POWMAN_WAKE_PWRUP1_CH, 13, edge, high, 1000); // Tufty Button B + if (err == -1) {mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("Timeout waiting for Button B"));} + err = powman_setup_gpio_wakeup(POWMAN_WAKE_PWRUP2_CH, 14, edge, high, 1000); // Tufty Button C + if (err == -1) {mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("Timeout waiting for Button C"));} + } + + // power off + int rc = 0; + if (timeout_ms > 0) { + absolute_time_t timeout = make_timeout_time_ms(timeout_ms); + rc = powman_off_until_time(timeout); + } else { + rc = powman_off(); + } + hard_assert(rc == PICO_OK); + hard_assert(false); // should never get here! + + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(_sleep_goto_dormant_until_pin_obj, 3, 4, _sleep_goto_dormant_until_pin); + +/*! \brief Send system to dormant until the specified time, note for RP2040 the RTC must be driven by an external clock + * + * \param ts The time to wake up + * \param callback Function to call on wakeup. + */ +mp_obj_t _sleep_goto_dormant_until(mp_obj_t absolute_time_in) { + // Borrowed from https://github.com/micropython/micropython/blob/master/ports/rp2/machine_rtc.c#L83C1-L96 + mp_obj_t *items; + mp_obj_get_array_fixed_n(absolute_time_in, 8, &items); + timeutils_struct_time_t tm = { + .tm_year = mp_obj_get_int(items[0]), + .tm_mon = mp_obj_get_int(items[1]), + .tm_mday = mp_obj_get_int(items[2]), + .tm_hour = mp_obj_get_int(items[4]), + .tm_min = mp_obj_get_int(items[5]), + .tm_sec = mp_obj_get_int(items[6]), + }; + struct timespec ts = { 0, 0 }; + 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); + + int rc = powman_off_until_time(timespec_to_ms(&ts)); + hard_assert(rc == PICO_OK); + hard_assert(false); // should never get here! + + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_1(_sleep_goto_dormant_until_obj, _sleep_goto_dormant_until); + +/*! \brief Send system to dormant until the specified time, note for RP2040 the RTC must be driven by an external clock + * + * \param ts The time to wake up + * \param callback Function to call on wakeup. + */ +mp_obj_t _sleep_goto_dormant_for(mp_obj_t time_seconds_in) { + uint64_t ms = (uint64_t)(mp_obj_get_float(time_seconds_in) * 1000); + int rc = powman_off_for_ms(ms); + hard_assert(rc == PICO_OK); + hard_assert(false); // should never get here! + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_1(_sleep_goto_dormant_for_obj, _sleep_goto_dormant_for); + +static const mp_map_elem_t sleep_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_powman) }, + { MP_ROM_QSTR(MP_QSTR_goto_dormant_until_pin), MP_ROM_PTR(&_sleep_goto_dormant_until_pin_obj) }, + { MP_ROM_QSTR(MP_QSTR_goto_dormant_until), MP_ROM_PTR(&_sleep_goto_dormant_until_obj) }, + { MP_ROM_QSTR(MP_QSTR_goto_dormant_for), MP_ROM_PTR(&_sleep_goto_dormant_for_obj) }, + { MP_ROM_QSTR(MP_QSTR_get_wake_reason), MP_ROM_PTR(&_sleep_get_wake_reason_obj) }, + + { MP_ROM_QSTR(MP_QSTR_WAKE_BUTTON_A), MP_ROM_INT(WAKE_BUTTON_A) }, + { MP_ROM_QSTR(MP_QSTR_WAKE_BUTTON_B), MP_ROM_INT(WAKE_BUTTON_B) }, + { MP_ROM_QSTR(MP_QSTR_WAKE_BUTTON_C), MP_ROM_INT(WAKE_BUTTON_C) }, + { MP_ROM_QSTR(MP_QSTR_WAKE_TIMER), MP_ROM_INT(WAKE_TIMER) }, // TODO: Rename to ALARM? + { MP_ROM_QSTR(MP_QSTR_WAKE_UNKNOWN), MP_ROM_INT(WAKE_UNKNOWN) }, +}; +static MP_DEFINE_CONST_DICT(mp_module_sleep_globals, sleep_globals_table); + +const mp_obj_module_t sleep_user_cmodule = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t*)&mp_module_sleep_globals, +}; + +MP_REGISTER_MODULE(MP_QSTR_powman, sleep_user_cmodule); \ No newline at end of file diff --git a/modules/c/powman/micropython.cmake b/modules/c/powman/micropython.cmake new file mode 100644 index 0000000..2aeae0f --- /dev/null +++ b/modules/c/powman/micropython.cmake @@ -0,0 +1,27 @@ +add_library(usermod_sleep INTERFACE) + +target_sources(usermod_sleep INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/bindings.c + ${CMAKE_CURRENT_LIST_DIR}/powman.c + ${CMAKE_CURRENT_LIST_DIR}/rosc.c +) + +target_include_directories(usermod_sleep INTERFACE + ${CMAKE_CURRENT_LIST_DIR} +) + +target_link_libraries(usermod_sleep INTERFACE hardware_powman hardware_gpio) + +target_link_libraries(usermod INTERFACE usermod_sleep) + +#set_source_files_properties( +# ${CMAKE_CURRENT_LIST_DIR}/sleep.c +# PROPERTIES COMPILE_FLAGS +# "-Wno-maybe-uninitialized" +#) + +set_source_files_properties( + ${CMAKE_CURRENT_LIST_DIR}/bindings.c + PROPERTIES COMPILE_FLAGS + "-Wno-discarded-qualifiers" +) diff --git a/modules/c/powman/powman.c b/modules/c/powman/powman.c new file mode 100644 index 0000000..e4b1d80 --- /dev/null +++ b/modules/c/powman/powman.c @@ -0,0 +1,150 @@ + +#include "powman.h" + +static powman_power_state off_state; +static powman_power_state on_state; + +//#define DEBUG + +uint8_t powman_get_wake_reason(void) { + // 0 = chip reset, for the source of the last reset see POWMAN_CHIP_RESET + // 1 = pwrup0 (GPIO interrupt 0) + // 2 = pwrup1 (GPIO interrupt 1) + // 3 = pwrup2 (GPIO interrupt 2) + // 4 = pwrup3 (GPIO interrupt 3) + // 5 = coresight_pwrup + // 6 = alarm_pwrup (timeout or alarm wakeup) + return powman_hw->last_swcore_pwrup & 0x7f; +} + +void powman_init() { + uint64_t abs_time_ms = 1746057600000; // 2025/05/01 - Milliseconds since epoch + + // Run everything from pll_usb pll and stop pll_sys + set_sys_clock_48mhz(); + + // Use the 32768 Hz clockout from the RTC to keep time accurately + //clock_configure_gpin(clk_ref, 12, 32.768f * KHZ, 32.768f * KHZ); + //clock_configure_gpin(clk_sys, 12, 32.768f * KHZ, 32.768f * KHZ); + //clock_configure_undivided(clk_peri, 0, CLOCKS_CLK_PERI_CTRL_AUXSRC_VALUE_CLK_SYS, 32.768f * KHZ); + //powman_timer_set_1khz_tick_source_lposc_with_hz(32768); + + powman_timer_set_1khz_tick_source_lposc(); + pll_deinit(pll_sys); + + // Set all pins to input (as far as SIO is concerned) + gpio_set_dir_all_bits(0); + for (int i = 0; i < NUM_BANK0_GPIOS; ++i) { + gpio_set_function(i, GPIO_FUNC_SIO); + if (i > NUM_BANK0_GPIOS - NUM_ADC_CHANNELS) { + gpio_disable_pulls(i); + gpio_set_input_enabled(i, false); + } + } + + // Unlock the VREG control interface + hw_set_bits(&powman_hw->vreg_ctrl, POWMAN_PASSWORD_BITS | POWMAN_VREG_CTRL_UNLOCK_BITS); + + // Turn off USB PHY and apply pull downs on DP & DM + 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; + + usb_hw->phy_direct_override = USB_USBPHY_DIRECT_RX_DM_BITS | USB_USBPHY_DIRECT_RX_DP_BITS | USB_USBPHY_DIRECT_RX_DD_BITS | + 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 | + 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 | + 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 | + 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 | + USB_USBPHY_DIRECT_OVERRIDE_DM_PULLUP_HISEL_OVERRIDE_EN_BITS | USB_USBPHY_DIRECT_OVERRIDE_DP_PULLUP_HISEL_OVERRIDE_EN_BITS; + + // start powman and set the time + powman_timer_start(); + powman_timer_set_ms(abs_time_ms); + + // Allow power down when debugger connected + powman_set_debug_power_request_ignored(true); + + // Power states + powman_power_state P1_7 = POWMAN_POWER_STATE_NONE; + + powman_power_state P0_3 = POWMAN_POWER_STATE_NONE; + P0_3 = powman_power_state_with_domain_on(P0_3, POWMAN_POWER_DOMAIN_SWITCHED_CORE); + P0_3 = powman_power_state_with_domain_on(P0_3, POWMAN_POWER_DOMAIN_XIP_CACHE); + + off_state = P1_7; + on_state = P0_3; +} + +// Initiate power off +int __no_inline_not_in_flash_func(powman_off)(void) { + // Set power states + bool valid_state = powman_configure_wakeup_state(off_state, on_state); + if (!valid_state) { + return PICO_ERROR_INVALID_STATE; + } + + // reboot to main + powman_hw->boot[0] = 0; + powman_hw->boot[1] = 0; + powman_hw->boot[2] = 0; + powman_hw->boot[3] = 0; + + // Switch to required power state + int rc = powman_set_power_state(off_state); + if (rc != PICO_OK) { + return rc; + } + + // Power down + while (true) __wfi(); +} + +int powman_setup_gpio_wakeup(int hw_wakeup, int gpio, bool edge, bool high, uint64_t timeout_ms) { + gpio_init(gpio); + gpio_set_dir(gpio, false); + gpio_set_input_enabled(gpio, true); + + // Must set pulls here, or our pin may never go into its idle state + gpio_set_pulls(gpio, !high, high); + + // If the pin is currently in a triggered state, wait for idle + absolute_time_t timeout = make_timeout_time_ms(timeout_ms); + if (gpio_get(gpio) == high) { + while(gpio_get(gpio) == high) { + sleep_ms(10); + if(time_reached(timeout)) return -1; + } + } + powman_enable_gpio_wakeup(hw_wakeup, gpio, edge, high); + + return 0; +} + +// Power off until a gpio goes high +int powman_off_until_gpio_high(int gpio, bool edge, bool high, uint64_t timeout_ms) { + powman_init(); + + powman_setup_gpio_wakeup(POWMAN_WAKE_PWRUP0_CH, gpio, edge, high, 1000); + + if (timeout_ms > 0) { + uint64_t ms = powman_timer_get_ms(); + return powman_off_until_time(ms + timeout_ms); + } else { + return powman_off(); + } +} + +// Power off until an absolute time +int powman_off_until_time(uint64_t absolute_time_ms) { + powman_init(); + + // Start powman timer and turn off + powman_enable_alarm_wakeup_at_ms(absolute_time_ms); + return powman_off(); +} + +// Power off for a number of milliseconds +int powman_off_for_ms(uint64_t duration_ms) { + powman_init(); + + uint64_t ms = powman_timer_get_ms(); + return powman_off_until_time(ms + duration_ms); +} \ No newline at end of file diff --git a/modules/c/powman/powman.h b/modules/c/powman/powman.h new file mode 100644 index 0000000..437395f --- /dev/null +++ b/modules/c/powman/powman.h @@ -0,0 +1,37 @@ + + +#include +#include +#include "pico/stdio.h" +#include "pico/sync.h" +#include "hardware/gpio.h" +#include "hardware/powman.h" +#include "hardware/clocks.h" +#include "hardware/pll.h" +#include "hardware/adc.h" +#include "hardware/structs/usb.h" +#include "hardware/structs/xosc.h" +#include "hardware/vreg.h" +#include "hardware/flash.h" +#include "hardware/structs/qmi.h" + +#define POWMAN_WAKE_PWRUP0_CH 0 +#define POWMAN_WAKE_PWRUP1_CH 1 +#define POWMAN_WAKE_PWRUP2_CH 2 + +#define POWMAN_WAKE_RESET 0b00000001 +#define POWMAN_WAKE_PWRUP0 0b00000010 +#define POWMAN_WAKE_PWRUP1 0b00000100 +#define POWMAN_WAKE_PWRUP2 0b00001000 +#define POWMAN_WAKE_PWRUP3 0b00010000 +#define POWMAN_WAKE_CORESI 0b00100000 +#define POWMAN_WAKE_ALARM 0b01000000 + +int powman_off_until_gpio_high(int gpio, bool edge, bool high, uint64_t timeout_ms); +int powman_off_until_time(uint64_t absolute_time_ms); +int powman_off_for_ms(uint64_t duration_ms); +uint8_t powman_get_wake_reason(void); + +void powman_init(); +int powman_setup_gpio_wakeup(int hw_wakeup, int gpio, bool edge, bool high, uint64_t timeout_ms); +int powman_off(void); \ No newline at end of file diff --git a/modules/c/powman/rosc.c b/modules/c/powman/rosc.c new file mode 100644 index 0000000..39142f0 --- /dev/null +++ b/modules/c/powman/rosc.c @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +// Vendored from: https://github.com/raspberrypi/pico-extras/blob/4ccfef6fe068bbae8106d0d1f072c7f997472040/src/rp2_common/hardware_rosc/rosc.c + +#include "pico.h" + +// For MHZ definitions etc +#include "hardware/clocks.h" +#include "rosc.h" + +// Given a ROSC delay stage code, return the next-numerically-higher code. +// Top result bit is set when called on maximum ROSC code. +uint32_t next_rosc_code(uint32_t code) { + return ((code | 0x08888888u) + 1u) & 0xf7777777u; +} + +uint rosc_find_freq(uint32_t low_mhz, uint32_t high_mhz) { + // TODO: This could be a lot better + rosc_set_div(1); + for (uint32_t code = 0; code <= 0x77777777u; code = next_rosc_code(code)) { + rosc_set_freq(code); + uint rosc_mhz = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_ROSC_CLKSRC) / 1000; + if ((rosc_mhz >= low_mhz) && (rosc_mhz <= high_mhz)) { + return rosc_mhz; + } + } + return 0; +} + +void rosc_set_div(uint32_t div) { + assert(div <= 31 && div >= 1); + rosc_write(&rosc_hw->div, ROSC_DIV_VALUE_PASS + div); +} + +void rosc_set_freq(uint32_t code) { + rosc_write(&rosc_hw->freqa, (ROSC_FREQA_PASSWD_VALUE_PASS << ROSC_FREQA_PASSWD_LSB) | (code & 0xffffu)); + rosc_write(&rosc_hw->freqb, (ROSC_FREQA_PASSWD_VALUE_PASS << ROSC_FREQA_PASSWD_LSB) | (code >> 16u)); +} + +void rosc_set_range(uint range) { + // Range should use enumvals from the headers and thus have the password correct + rosc_write(&rosc_hw->ctrl, (ROSC_CTRL_ENABLE_VALUE_ENABLE << ROSC_CTRL_ENABLE_LSB) | range); +} + +void rosc_disable(void) { + uint32_t tmp = rosc_hw->ctrl; + tmp &= (~ROSC_CTRL_ENABLE_BITS); + tmp |= (ROSC_CTRL_ENABLE_VALUE_DISABLE << ROSC_CTRL_ENABLE_LSB); + rosc_write(&rosc_hw->ctrl, tmp); + // Wait for stable to go away + while(rosc_hw->status & ROSC_STATUS_STABLE_BITS); +} + +void rosc_set_dormant(void) { + // WARNING: This stops the rosc until woken up by an irq + rosc_write(&rosc_hw->dormant, ROSC_DORMANT_VALUE_DORMANT); + // Wait for it to become stable once woken up + while(!(rosc_hw->status & ROSC_STATUS_STABLE_BITS)); +} + +void rosc_enable(void) { + //Re-enable the rosc + rosc_write(&rosc_hw->ctrl, ROSC_CTRL_ENABLE_BITS); + + //Wait for it to become stable once restarted + while (!(rosc_hw->status & ROSC_STATUS_STABLE_BITS)); +} \ No newline at end of file diff --git a/modules/c/powman/rosc.h b/modules/c/powman/rosc.h new file mode 100644 index 0000000..1664c4a --- /dev/null +++ b/modules/c/powman/rosc.h @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +// Vendored from: https://github.com/raspberrypi/pico-extras/blob/4ccfef6fe068bbae8106d0d1f072c7f997472040/src/rp2_common/hardware_rosc/include/hardware/rosc.h + +#ifndef _HARDWARE_ROSC_H_ +#define _HARDWARE_ROSC_H_ + +#include "pico.h" +#include "hardware/structs/rosc.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** \file rosc.h + * \defgroup hardware_rosc hardware_rosc + * + * Ring Oscillator (ROSC) API + * + * A Ring Oscillator is an on-chip oscillator that requires no external crystal. Instead, the output is generated from a series of + * inverters that are chained together to create a feedback loop. RP2040 boots from the ring oscillator initially, meaning the + * first stages of the bootrom, including booting from SPI flash, will be clocked by the ring oscillator. If your design has a + * crystal oscillator, you’ll likely want to switch to this as your reference clock as soon as possible, because the frequency is + * more accurate than the ring oscillator. + */ + +/*! \brief Set frequency of the Ring Oscillator + * \ingroup hardware_rosc + * + * \param code The drive strengths. See the RP2040 datasheet for information on this value. + */ +void rosc_set_freq(uint32_t code); + +/*! \brief Set range of the Ring Oscillator + * \ingroup hardware_rosc + * + * Frequency range. Frequencies will vary with Process, Voltage & Temperature (PVT). + * Clock output will not glitch when changing the range up one step at a time. + * + * \param range 0x01 Low, 0x02 Medium, 0x03 High, 0x04 Too High. + */ +void rosc_set_range(uint range); + +/*! \brief Disable the Ring Oscillator + * \ingroup hardware_rosc + * + */ +void rosc_disable(void); + +/*! \brief Put Ring Oscillator in to dormant mode. + * \ingroup hardware_rosc + * + * The ROSC supports a dormant mode,which stops oscillation until woken up up by an asynchronous interrupt. + * This can either come from the RTC, being clocked by an external clock, or a GPIO pin going high or low. + * If no IRQ is configured before going into dormant mode the ROSC will never restart. + * + * PLLs should be stopped before selecting dormant mode. + */ +void rosc_set_dormant(void); + +// FIXME: Add doxygen + +uint32_t next_rosc_code(uint32_t code); + +uint rosc_find_freq(uint32_t low_mhz, uint32_t high_mhz); + +void rosc_set_div(uint32_t div); + +inline static void rosc_clear_bad_write(void) { + hw_clear_bits(&rosc_hw->status, ROSC_STATUS_BADWRITE_BITS); +} + +inline static bool rosc_write_okay(void) { + return !(rosc_hw->status & ROSC_STATUS_BADWRITE_BITS); +} + +inline static void rosc_write(io_rw_32 *addr, uint32_t value) { + rosc_clear_bad_write(); + assert(rosc_write_okay()); + *addr = value; + assert(rosc_write_okay()); +}; + +void rosc_enable(void); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/modules/c/wakeup/micropython.cmake b/modules/c/wakeup/micropython.cmake new file mode 100644 index 0000000..30ab4f7 --- /dev/null +++ b/modules/c/wakeup/micropython.cmake @@ -0,0 +1,18 @@ +add_library(usermod_wakeup INTERFACE) + +target_sources(usermod_wakeup INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/wakeup.c + ${CMAKE_CURRENT_LIST_DIR}/wakeup.cpp +) + +target_include_directories(usermod_wakeup INTERFACE + ${CMAKE_CURRENT_LIST_DIR} +) + +target_link_libraries(usermod INTERFACE usermod_wakeup) + +set_source_files_properties( + ${CMAKE_CURRENT_LIST_DIR}/wakeup.c + PROPERTIES COMPILE_FLAGS + "-Wno-discarded-qualifiers" +) diff --git a/modules/c/wakeup/wakeup.c b/modules/c/wakeup/wakeup.c new file mode 100644 index 0000000..9304f03 --- /dev/null +++ b/modules/c/wakeup/wakeup.c @@ -0,0 +1,21 @@ +#include "wakeup.h" +#include "hardware/gpio.h" +#include "pico/runtime_init.h" + + +static MP_DEFINE_CONST_FUN_OBJ_1(Wakeup_get_gpio_state_obj, Wakeup_get_gpio_state); +static MP_DEFINE_CONST_FUN_OBJ_0(Wakeup_reset_gpio_state_obj, Wakeup_reset_gpio_state); + +static const mp_map_elem_t wakeup_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_wakeup) }, + { MP_ROM_QSTR(MP_QSTR_get_gpio_state), MP_ROM_PTR(&Wakeup_get_gpio_state_obj) }, + { MP_ROM_QSTR(MP_QSTR_reset_gpio_state), MP_ROM_PTR(&Wakeup_reset_gpio_state_obj) }, +}; +static MP_DEFINE_CONST_DICT(mp_module_wakeup_globals, wakeup_globals_table); + +const mp_obj_module_t wakeup_user_cmodule = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t*)&mp_module_wakeup_globals, +}; + +MP_REGISTER_MODULE(MP_QSTR_wakeup, wakeup_user_cmodule); diff --git a/modules/c/wakeup/wakeup.cpp b/modules/c/wakeup/wakeup.cpp new file mode 100644 index 0000000..60c5d74 --- /dev/null +++ b/modules/c/wakeup/wakeup.cpp @@ -0,0 +1,27 @@ +#include "pico/stdlib.h" +#include "hardware/gpio.h" + + +uint64_t gpio_state = 0; + +static void __attribute__((constructor(101))) latch_inputs() { + gpio_set_function_masked64(0xffffffffffffffff, GPIO_FUNC_SIO); + gpio_state = gpio_get_all64(); + sleep_ms(5); + gpio_state |= gpio_get_all64(); +} + +extern "C" { +#include "wakeup.h" + +mp_obj_t Wakeup_get_gpio_state(mp_obj_t button_in) { + int button = mp_obj_get_int(button_in); + return (gpio_state & (1 << button)) ? mp_const_true : mp_const_false; +} + +mp_obj_t Wakeup_reset_gpio_state() { + gpio_state = 0; + return mp_const_none; +} + +} \ No newline at end of file diff --git a/modules/c/wakeup/wakeup.h b/modules/c/wakeup/wakeup.h new file mode 100644 index 0000000..2e6afc3 --- /dev/null +++ b/modules/c/wakeup/wakeup.h @@ -0,0 +1,5 @@ +#include "py/runtime.h" +#include "py/objstr.h" + +extern mp_obj_t Wakeup_get_gpio_state(mp_obj_t button_in); +extern mp_obj_t Wakeup_reset_gpio_state(); \ No newline at end of file