diff --git a/locale/circuitpython.pot b/locale/circuitpython.pot index 85c46aa4b5..f1f747e7e6 100644 --- a/locale/circuitpython.pot +++ b/locale/circuitpython.pot @@ -1727,6 +1727,7 @@ msgstr "" msgid "Only connectable advertisements can be directed" msgstr "" +#: ports/atmel-samd/common-hal/alarm/pin/PinAlarm.c #: ports/stm/common-hal/alarm/pin/PinAlarm.c msgid "Only edge detection is available on this hardware" msgstr "" @@ -1750,6 +1751,7 @@ msgstr "" msgid "Only one address is allowed" msgstr "" +#: ports/atmel-samd/common-hal/alarm/time/TimeAlarm.c #: ports/espressif/common-hal/alarm/time/TimeAlarm.c #: ports/nrf/common-hal/alarm/time/TimeAlarm.c #: ports/raspberrypi/common-hal/alarm/time/TimeAlarm.c @@ -2070,6 +2072,7 @@ msgstr "" msgid "Size not supported" msgstr "" +#: ports/atmel-samd/common-hal/alarm/SleepMemory.c #: ports/raspberrypi/common-hal/alarm/SleepMemory.c msgid "Sleep Memory not available" msgstr "" @@ -2224,6 +2227,7 @@ msgstr "" msgid "Total data to write is larger than %q" msgstr "" +#: ports/atmel-samd/common-hal/alarm/touch/TouchAlarm.c #: ports/raspberrypi/common-hal/alarm/touch/TouchAlarm.c #: ports/stm/common-hal/alarm/touch/TouchAlarm.c msgid "Touch alarms not available" diff --git a/ports/atmel-samd/boards/pycubed/board.c b/ports/atmel-samd/boards/pycubed/board.c index 2777b9031c..41e4655102 100644 --- a/ports/atmel-samd/boards/pycubed/board.c +++ b/ports/atmel-samd/boards/pycubed/board.c @@ -24,25 +24,12 @@ * THE SOFTWARE. */ - -#include - #include "supervisor/board.h" -#include "py/mpconfig.h" -#include "shared-bindings/nvm/ByteArray.h" +#include "mpconfigboard.h" #include "common-hal/microcontroller/Pin.h" #include "hal/include/hal_gpio.h" #include "shared-bindings/pwmio/PWMOut.h" -nvm_bytearray_obj_t bootcnt = { - .base = { - .type = &nvm_bytearray_type - }, - .len = (uint32_t)8192, - .start_address = (uint8_t *)(0x00080000 - 8192) -}; - - void board_init(void) { pwmio_pwmout_obj_t pwm; common_hal_pwmio_pwmout_construct(&pwm, &pin_PA23, 4096, 2, false); @@ -54,8 +41,7 @@ bool board_requests_safe_mode(void) { } void reset_board(void) { - uint8_t value_out = 0; - common_hal_nvm_bytearray_get_bytes(&bootcnt,0,1,&value_out); - ++value_out; - common_hal_nvm_bytearray_set_bytes(&bootcnt,0,&value_out,1); +} + +void board_deinit(void) { } diff --git a/ports/atmel-samd/boards/pycubed/mpconfigboard.h b/ports/atmel-samd/boards/pycubed/mpconfigboard.h index 3df0afd6b5..2d4d489d21 100644 --- a/ports/atmel-samd/boards/pycubed/mpconfigboard.h +++ b/ports/atmel-samd/boards/pycubed/mpconfigboard.h @@ -15,6 +15,9 @@ // External flash W25Q80DV #define EXTERNAL_FLASH_QSPI_DUAL +#define CIRCUITPY_DRIVE_LABEL "PYCUBED" +#define CIRCUITPY_BOOT_COUNTER 1 + #define BOARD_HAS_CRYSTAL 1 #define DEFAULT_I2C_BUS_SCL (&pin_PB13) diff --git a/ports/atmel-samd/boards/pycubed/mpconfigboard.mk b/ports/atmel-samd/boards/pycubed/mpconfigboard.mk index 3eda028754..347462919f 100644 --- a/ports/atmel-samd/boards/pycubed/mpconfigboard.mk +++ b/ports/atmel-samd/boards/pycubed/mpconfigboard.mk @@ -10,17 +10,25 @@ QSPI_FLASH_FILESYSTEM = 1 EXTERNAL_FLASH_DEVICES = W25Q80DV LONGINT_IMPL = MPZ -CIRCUITPY_DRIVE_LABEL = "PYCUBED" +CIRCUITPY_ULAB = 1 +CIRCUITPY_BINASCII = 1 +CIRCUITPY_SDCARDIO = 1 +CIRCUITPY_JSON = 1 +CIRCUITPY_MSGPACK = 1 +CIRCUITPY_ALARM = 1 -# Not needed. +# no SAMD51 support... yet ;) +# CIRCUITPY_DUALBANK=1 + +# Not needed CIRCUITPY_AUDIOBUSIO = 0 -CIRCUITPY_BITMAPTOOLS = 0 CIRCUITPY_DISPLAYIO = 0 CIRCUITPY_FRAMEBUFFERIO = 0 CIRCUITPY_KEYPAD = 0 CIRCUITPY_RGBMATRIX = 0 CIRCUITPY_PS2IO = 0 +CIRCUITPY_BLEIO_HCI=0 +CIRCUITPY_BLEIO=0 FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_NeoPixel FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Register -FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_SD diff --git a/ports/atmel-samd/boards/pycubed_mram/board.c b/ports/atmel-samd/boards/pycubed_mram/board.c index 2777b9031c..41e4655102 100644 --- a/ports/atmel-samd/boards/pycubed_mram/board.c +++ b/ports/atmel-samd/boards/pycubed_mram/board.c @@ -24,25 +24,12 @@ * THE SOFTWARE. */ - -#include - #include "supervisor/board.h" -#include "py/mpconfig.h" -#include "shared-bindings/nvm/ByteArray.h" +#include "mpconfigboard.h" #include "common-hal/microcontroller/Pin.h" #include "hal/include/hal_gpio.h" #include "shared-bindings/pwmio/PWMOut.h" -nvm_bytearray_obj_t bootcnt = { - .base = { - .type = &nvm_bytearray_type - }, - .len = (uint32_t)8192, - .start_address = (uint8_t *)(0x00080000 - 8192) -}; - - void board_init(void) { pwmio_pwmout_obj_t pwm; common_hal_pwmio_pwmout_construct(&pwm, &pin_PA23, 4096, 2, false); @@ -54,8 +41,7 @@ bool board_requests_safe_mode(void) { } void reset_board(void) { - uint8_t value_out = 0; - common_hal_nvm_bytearray_get_bytes(&bootcnt,0,1,&value_out); - ++value_out; - common_hal_nvm_bytearray_set_bytes(&bootcnt,0,&value_out,1); +} + +void board_deinit(void) { } diff --git a/ports/atmel-samd/boards/pycubed_mram/mpconfigboard.h b/ports/atmel-samd/boards/pycubed_mram/mpconfigboard.h index f7dfbae3a2..bd45a814fb 100644 --- a/ports/atmel-samd/boards/pycubed_mram/mpconfigboard.h +++ b/ports/atmel-samd/boards/pycubed_mram/mpconfigboard.h @@ -15,6 +15,9 @@ #define BOARD_FLASH_SIZE (FLASH_SIZE - 0x4000 - CIRCUITPY_INTERNAL_NVM_SIZE) +#define CIRCUITPY_DRIVE_LABEL "PYCUBED" +#define CIRCUITPY_BOOT_COUNTER 1 + #define BOARD_HAS_CRYSTAL 1 #define DEFAULT_I2C_BUS_SCL (&pin_PB13) diff --git a/ports/atmel-samd/boards/pycubed_mram/mpconfigboard.mk b/ports/atmel-samd/boards/pycubed_mram/mpconfigboard.mk index ccf3050d11..ac8af21dcb 100644 --- a/ports/atmel-samd/boards/pycubed_mram/mpconfigboard.mk +++ b/ports/atmel-samd/boards/pycubed_mram/mpconfigboard.mk @@ -10,17 +10,25 @@ QSPI_FLASH_FILESYSTEM = 1 EXTERNAL_FLASH_DEVICES = MR2xH40 LONGINT_IMPL = MPZ -CIRCUITPY_DRIVE_LABEL = "PYCUBED" +CIRCUITPY_ULAB = 1 +CIRCUITPY_BINASCII = 1 +CIRCUITPY_SDCARDIO = 1 +CIRCUITPY_JSON = 1 +CIRCUITPY_MSGPACK = 1 +CIRCUITPY_ALARM = 1 -# Not needed. +# no SAMD51 support... yet ;) +# CIRCUITPY_DUALBANK=1 + +# Not needed CIRCUITPY_AUDIOBUSIO = 0 -CIRCUITPY_BITMAPTOOLS = 0 CIRCUITPY_DISPLAYIO = 0 CIRCUITPY_FRAMEBUFFERIO = 0 CIRCUITPY_KEYPAD = 0 CIRCUITPY_RGBMATRIX = 0 CIRCUITPY_PS2IO = 0 +CIRCUITPY_BLEIO_HCI=0 +CIRCUITPY_BLEIO=0 FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_NeoPixel FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_Register -FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_SD diff --git a/ports/atmel-samd/boards/pycubed_mram_v05/board.c b/ports/atmel-samd/boards/pycubed_mram_v05/board.c index 032ad7b2e7..41e4655102 100644 --- a/ports/atmel-samd/boards/pycubed_mram_v05/board.c +++ b/ports/atmel-samd/boards/pycubed_mram_v05/board.c @@ -42,3 +42,6 @@ bool board_requests_safe_mode(void) { void reset_board(void) { } + +void board_deinit(void) { +} diff --git a/ports/atmel-samd/boards/pycubed_mram_v05/mpconfigboard.mk b/ports/atmel-samd/boards/pycubed_mram_v05/mpconfigboard.mk index 2da146ae46..ac8af21dcb 100644 --- a/ports/atmel-samd/boards/pycubed_mram_v05/mpconfigboard.mk +++ b/ports/atmel-samd/boards/pycubed_mram_v05/mpconfigboard.mk @@ -15,9 +15,9 @@ CIRCUITPY_BINASCII = 1 CIRCUITPY_SDCARDIO = 1 CIRCUITPY_JSON = 1 CIRCUITPY_MSGPACK = 1 +CIRCUITPY_ALARM = 1 # no SAMD51 support... yet ;) -# CIRCUITPY_ALARM = 1 # CIRCUITPY_DUALBANK=1 # Not needed diff --git a/ports/atmel-samd/boards/pycubed_v05/board.c b/ports/atmel-samd/boards/pycubed_v05/board.c index 032ad7b2e7..41e4655102 100644 --- a/ports/atmel-samd/boards/pycubed_v05/board.c +++ b/ports/atmel-samd/boards/pycubed_v05/board.c @@ -42,3 +42,6 @@ bool board_requests_safe_mode(void) { void reset_board(void) { } + +void board_deinit(void) { +} diff --git a/ports/atmel-samd/boards/pycubed_v05/mpconfigboard.mk b/ports/atmel-samd/boards/pycubed_v05/mpconfigboard.mk index 95916db076..923ab2eaae 100644 --- a/ports/atmel-samd/boards/pycubed_v05/mpconfigboard.mk +++ b/ports/atmel-samd/boards/pycubed_v05/mpconfigboard.mk @@ -16,9 +16,9 @@ CIRCUITPY_BINASCII = 1 CIRCUITPY_SDCARDIO = 1 CIRCUITPY_JSON = 1 CIRCUITPY_MSGPACK = 1 +CIRCUITPY_ALARM = 1 # no SAMD51 support... yet ;) -# CIRCUITPY_ALARM = 1 # CIRCUITPY_DUALBANK=1 # Not needed diff --git a/ports/atmel-samd/common-hal/alarm/SleepMemory.c b/ports/atmel-samd/common-hal/alarm/SleepMemory.c new file mode 100644 index 0000000000..cb3b1b3aa9 --- /dev/null +++ b/ports/atmel-samd/common-hal/alarm/SleepMemory.c @@ -0,0 +1,50 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Lucian Copeland for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include + +#include "py/runtime.h" +#include "common-hal/alarm/SleepMemory.h" +#include "shared-bindings/nvm/ByteArray.h" + +void alarm_sleep_memory_reset(void) { + +} + +uint32_t common_hal_alarm_sleep_memory_get_length(alarm_sleep_memory_obj_t *self) { + mp_raise_NotImplementedError(translate("Sleep Memory not available")); + return 0; +} + +bool common_hal_alarm_sleep_memory_set_bytes(alarm_sleep_memory_obj_t *self, uint32_t start_index, const uint8_t *values, uint32_t len) { + mp_raise_NotImplementedError(translate("Sleep Memory not available")); + return false; +} + +void common_hal_alarm_sleep_memory_get_bytes(alarm_sleep_memory_obj_t *self, uint32_t start_index, uint8_t *values, uint32_t len) { + mp_raise_NotImplementedError(translate("Sleep Memory not available")); + return; +} diff --git a/ports/atmel-samd/common-hal/alarm/SleepMemory.h b/ports/atmel-samd/common-hal/alarm/SleepMemory.h new file mode 100644 index 0000000000..a922ff324a --- /dev/null +++ b/ports/atmel-samd/common-hal/alarm/SleepMemory.h @@ -0,0 +1,40 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Lucian Copeland for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef MICROPY_INCLUDED_ATMEL_SAMD_COMMON_HAL_ALARM_SLEEPMEMORY_H +#define MICROPY_INCLUDED_ATMEL_SAMD_COMMON_HAL_ALARM_SLEEPMEMORY_H + +#include "py/obj.h" + +typedef struct { + mp_obj_base_t base; + uint8_t *start_address; + uint8_t len; +} alarm_sleep_memory_obj_t; + +extern void alarm_sleep_memory_reset(void); + +#endif // MICROPY_INCLUDED_ATMEL_SAMD_COMMON_HAL_ALARM_SLEEPMEMORY_H diff --git a/ports/atmel-samd/common-hal/alarm/__init__.c b/ports/atmel-samd/common-hal/alarm/__init__.c new file mode 100644 index 0000000000..66b42a3062 --- /dev/null +++ b/ports/atmel-samd/common-hal/alarm/__init__.c @@ -0,0 +1,261 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Lucian Copeland for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "py/gc.h" +#include "py/obj.h" +#include "py/objtuple.h" +#include "py/runtime.h" + +#include "shared-bindings/alarm/__init__.h" +#include "shared-bindings/alarm/SleepMemory.h" +#include "shared-bindings/alarm/pin/PinAlarm.h" +#include "shared-bindings/alarm/time/TimeAlarm.h" +#include "shared-bindings/microcontroller/__init__.h" +#include "shared/runtime/interrupt_char.h" +#include "samd/external_interrupts.h" +#include "supervisor/port.h" +#include "supervisor/workflow.h" + +// Singleton instance of SleepMemory. +const alarm_sleep_memory_obj_t alarm_sleep_memory_obj = { + .base = { + .type = &alarm_sleep_memory_type, + }, +}; +// TODO: make a custom enum to avoid weird values like PM_SLEEPCFG_SLEEPMODE_BACKUP_Val? +STATIC volatile uint32_t _target; +STATIC bool fake_sleep; +STATIC bool pin_wake; + +void alarm_reset(void) { + // Reset the alarm flag + SAMD_ALARM_FLAG = 0x00; + alarm_pin_pinalarm_reset(); + alarm_time_timealarm_reset(); +} + +samd_sleep_source_t alarm_get_wakeup_cause(void) { + // If in light/fake sleep, check modules + if (alarm_pin_pinalarm_woke_this_cycle()) { + return SAMD_WAKEUP_GPIO; + } + if (alarm_time_timealarm_woke_this_cycle()) { + return SAMD_WAKEUP_RTC; + } + if (!fake_sleep && RSTC->RCAUSE.bit.BACKUP) { + // This is checked during rtc_init to cache TAMPID if necessary + if (pin_wake || RTC->MODE0.TAMPID.reg) { + pin_wake = true; + return SAMD_WAKEUP_GPIO; + } + return SAMD_WAKEUP_RTC; + } + return SAMD_WAKEUP_UNDEF; +} + +bool common_hal_alarm_woken_from_sleep(void) { + return alarm_get_wakeup_cause() != SAMD_WAKEUP_UNDEF; +} + +mp_obj_t common_hal_alarm_create_wake_alarm(void) { + // If woken from deep sleep, create a copy alarm similar to what would have + // been passed in originally. Otherwise, just return none + samd_sleep_source_t cause = alarm_get_wakeup_cause(); + switch (cause) { + case SAMD_WAKEUP_RTC: { + return alarm_time_timealarm_create_wakeup_alarm(); + } + case SAMD_WAKEUP_GPIO: { + return alarm_pin_pinalarm_create_wakeup_alarm(); + } + case SAMD_WAKEUP_UNDEF: + default: + // Not a deep sleep reset. + break; + } + return mp_const_none; +} + +// Set up light sleep or deep sleep alarms. +STATIC void _setup_sleep_alarms(bool deep_sleep, size_t n_alarms, const mp_obj_t *alarms) { + alarm_pin_pinalarm_set_alarms(deep_sleep, n_alarms, alarms); + alarm_time_timealarm_set_alarms(deep_sleep, n_alarms, alarms); + fake_sleep = false; +} + +mp_obj_t common_hal_alarm_light_sleep_until_alarms(size_t n_alarms, const mp_obj_t *alarms) { + _setup_sleep_alarms(false, n_alarms, alarms); + mp_obj_t wake_alarm = mp_const_none; + + while (!mp_hal_is_interrupted()) { + RUN_BACKGROUND_TASKS; + // Detect if interrupt was alarm or ctrl-C interrupt. + if (common_hal_alarm_woken_from_sleep()) { + samd_sleep_source_t cause = alarm_get_wakeup_cause(); + switch (cause) { + case SAMD_WAKEUP_RTC: { + wake_alarm = alarm_time_timealarm_find_triggered_alarm(n_alarms,alarms); + break; + } + case SAMD_WAKEUP_GPIO: { + wake_alarm = alarm_pin_pinalarm_find_triggered_alarm(n_alarms,alarms); + break; + } + default: + // Should not reach this, if all light sleep types are covered correctly + break; + } + shared_alarm_save_wake_alarm(wake_alarm); + break; + } + // ATTEMPT ------------------------------ + // This works but achieves same power consumption as time.sleep() + + // Clear the FPU interrupt because it can prevent us from sleeping. + if (__get_FPSCR() & ~(0x9f)) { + __set_FPSCR(__get_FPSCR() & ~(0x9f)); + (void)__get_FPSCR(); + } + + // Disable RTC interrupts + NVIC_DisableIRQ(RTC_IRQn); + // Set standby power domain stuff + PM->STDBYCFG.reg = PM_STDBYCFG_RAMCFG_OFF; + // Set-up Sleep Mode + PM->SLEEPCFG.reg = PM_SLEEPCFG_SLEEPMODE_STANDBY; + while (PM->SLEEPCFG.bit.SLEEPMODE != PM_SLEEPCFG_SLEEPMODE_STANDBY_Val) { + ; + } + + __DSB(); // Data Synchronization Barrier + __WFI(); // Wait For Interrupt + // Enable RTC interrupts + NVIC_EnableIRQ(RTC_IRQn); + // END ATTEMPT ------------------------------ + } + if (mp_hal_is_interrupted()) { + return mp_const_none; // Shouldn't be given to python code because exception handling should kick in. + } + + alarm_reset(); + return wake_alarm; +} + +void common_hal_alarm_set_deep_sleep_alarms(size_t n_alarms, const mp_obj_t *alarms) { + _setup_sleep_alarms(true, n_alarms, alarms); +} + +void NORETURN common_hal_alarm_enter_deep_sleep(void) { + alarm_pin_pinalarm_prepare_for_deep_sleep(); + alarm_time_timealarm_prepare_for_deep_sleep(); + _target = RTC->MODE0.COMP[1].reg; + port_disable_tick(); // TODO: Required for SAMD? + + // cache alarm flag since backup registers about to be reset + uint32_t _SAMD_ALARM_FLAG = SAMD_ALARM_FLAG; + + // Clear the FPU interrupt because it can prevent us from sleeping. + if (__get_FPSCR() & ~(0x9f)) { + __set_FPSCR(__get_FPSCR() & ~(0x9f)); + (void)__get_FPSCR(); + } + + NVIC_DisableIRQ(RTC_IRQn); + // Must disable the RTC before writing to EVCTRL and TMPCTRL + RTC->MODE0.CTRLA.bit.ENABLE = 0; // Disable the RTC + while (RTC->MODE0.SYNCBUSY.bit.ENABLE) { // Wait for synchronization + ; + } + RTC->MODE0.CTRLA.bit.SWRST = 1; // Software reset the RTC + while (RTC->MODE0.SYNCBUSY.bit.SWRST) { // Wait for synchronization + ; + } + RTC->MODE0.CTRLA.reg = RTC_MODE0_CTRLA_PRESCALER_DIV1024 | // Set prescaler to 1024 + RTC_MODE0_CTRLA_MODE_COUNT32; // Set RTC to mode 0, 32-bit timer + + // Check if we're setting TimeAlarm + if (_SAMD_ALARM_FLAG & SAMD_ALARM_FLAG_TIME) { + RTC->MODE0.COMP[1].reg = (_target / 1024) * 32; + while (RTC->MODE0.SYNCBUSY.reg) { + ; + } + } + // Check if we're setting PinAlarm + if (_SAMD_ALARM_FLAG & SAMD_ALARM_FLAG_PIN) { + RTC->MODE0.TAMPCTRL.bit.DEBNC2 = 1; // Edge triggered when INn is stable for 4 CLK_RTC_DEB periods + RTC->MODE0.TAMPCTRL.bit.TAMLVL2 = 1; // rising edge + // PA02 = IN2 + RTC->MODE0.TAMPCTRL.bit.IN2ACT = 1; // WAKE on IN2 (doesn't save timestamp) + } + // Enable interrupts + NVIC_SetPriority(RTC_IRQn, 0); + NVIC_EnableIRQ(RTC_IRQn); + if (_SAMD_ALARM_FLAG & SAMD_ALARM_FLAG_TIME) { + // Set interrupts for COMPARE1 + RTC->MODE0.INTENSET.reg = RTC_MODE0_INTENSET_CMP1; + } + if (_SAMD_ALARM_FLAG & SAMD_ALARM_FLAG_PIN) { + // Set interrupts for TAMPER pins + RTC->MODE0.INTENSET.reg = RTC_MODE0_INTENSET_TAMPER; + } + + // Set-up Deep Sleep Mode & RAM retention + PM->BKUPCFG.reg = PM_BKUPCFG_BRAMCFG(0x2); // No RAM retention 0x2 partial:0x1 + while (PM->BKUPCFG.bit.BRAMCFG != 0x2) { // Wait for synchronization + ; + } + PM->SLEEPCFG.reg = PM_SLEEPCFG_SLEEPMODE_BACKUP; + while (PM->SLEEPCFG.bit.SLEEPMODE != PM_SLEEPCFG_SLEEPMODE_BACKUP_Val) { + ; + } + RTC->MODE0.CTRLA.bit.ENABLE = 1; // Enable the RTC + while (RTC->MODE0.SYNCBUSY.bit.ENABLE) { // Wait for synchronization + ; + } + + __DSB(); // Data Synchronization Barrier + __WFI(); // Wait For Interrupt + + // The above shuts down RAM and triggers a reset, so we should never hit this + while (1) { + ; + } +} + +void common_hal_alarm_pretending_deep_sleep(void) { + // TODO: + // If tamper detect interrupts cannot be used to wake from the Idle tier of sleep, + // This section will need to re-initialize the pins to allow the PORT peripheral + // to generate external interrupts again. See STM32 for reference. + + if (!fake_sleep) { + fake_sleep = true; + } +} + +void common_hal_alarm_gc_collect(void) { + gc_collect_ptr(shared_alarm_get_wake_alarm()); +} diff --git a/ports/atmel-samd/common-hal/alarm/__init__.h b/ports/atmel-samd/common-hal/alarm/__init__.h new file mode 100644 index 0000000000..5e777bdf87 --- /dev/null +++ b/ports/atmel-samd/common-hal/alarm/__init__.h @@ -0,0 +1,52 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Lucian Copeland for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef MICROPY_INCLUDED_ATMEL_SAMD_COMMON_HAL_ALARM__INIT__H +#define MICROPY_INCLUDED_ATMEL_SAMD_COMMON_HAL_ALARM__INIT__H + +#include "common-hal/alarm/SleepMemory.h" + +extern const alarm_sleep_memory_obj_t alarm_sleep_memory_obj; + +// This is the first byte of the BKUP register bank. +// We use it to store which alarms are set. +#ifndef SAMD_ALARM_FLAG +#define SAMD_ALARM_FLAG (RTC->MODE0.BKUP[0].reg) +#define SAMD_ALARM_FLAG_TIME (_U_(0x1) << 0) +#define SAMD_ALARM_FLAG_PIN (_U_(0x1) << 1) +#endif + +typedef enum { + SAMD_WAKEUP_UNDEF, + SAMD_WAKEUP_GPIO, + SAMD_WAKEUP_RTC +} samd_sleep_source_t; + +extern void alarm_set_wakeup_reason(samd_sleep_source_t reason); +samd_sleep_source_t alarm_get_wakeup_cause(void); +extern void alarm_reset(void); + +#endif // MICROPY_INCLUDED_ATMEL_SAMD_COMMON_HAL_ALARM__INIT__H diff --git a/ports/atmel-samd/common-hal/alarm/pin/PinAlarm.c b/ports/atmel-samd/common-hal/alarm/pin/PinAlarm.c new file mode 100644 index 0000000000..2e1d596c39 --- /dev/null +++ b/ports/atmel-samd/common-hal/alarm/pin/PinAlarm.c @@ -0,0 +1,276 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Lucian Copeland for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "py/runtime.h" +#include "samd/external_interrupts.h" +#include "eic_handler.h" +#include "atmel_start_pins.h" +#include "hal/include/hal_gpio.h" +// #include + +#include "shared-bindings/alarm/pin/PinAlarm.h" +#include "shared-bindings/microcontroller/__init__.h" +#include "shared-bindings/microcontroller/Pin.h" +#include "common-hal/alarm/__init__.h" + +// This variable stores whether a PinAlarm woke in light sleep or fake deep sleep +// It CANNOT detect if the program woke from deep sleep. +STATIC volatile bool woke_up; +// TODO: replace pinalarm_on with SAMD_ALARM_FLAG bit flags +STATIC volatile bool pinalarm_on; + +// TODO: Create tables here reserving IRQ instances, and for the IRQ +// callback to store what pin triggered the interrupt +// STATIC bool reserved_alarms[SOME_VAL]; +// STATIC uint16_t triggered_pins[SOME_VAL]; + +void pin_alarm_callback(uint8_t num) { // parameters can be changed + // TODO: This is responsible for resetting the IRQ (so it doesn't + // go off constantly, and recording what pin was responsible for + // the trigger. This will only work for light sleep/fake deep + // sleep, in conjunction with the find_triggered_alarm function + + if (pinalarm_on) { + // clear flag and interrupt setting + RTC->MODE0.INTENCLR.reg = RTC_MODE0_INTENCLR_TAMPER; + pinalarm_on = false; + // QUESTION: How to reference the correct EIC? + // set_eic_handler(self->channel, EIC_HANDLER_NO_INTERRUPT); + // turn_off_eic_channel(self->channel); + // reset_pin_number(self->pin); + woke_up = true; + } +} + +void common_hal_alarm_pin_pinalarm_construct(alarm_pin_pinalarm_obj_t *self, const mcu_pin_obj_t *pin, bool value, bool edge, bool pull) { + // Tamper Pins: IN0:PB00; IN1:PB02; IN2:PA02; IN3:PC00; IN4:PC01; OUT:PB01 + // TODO: Catch edge or level mode if not supported + if (!pin->has_extint) { + mp_raise_RuntimeError(translate("No hardware support on pin")); + } + if (eic_get_enable()) { + if (!eic_channel_free(pin->extint_channel)) { + mp_raise_RuntimeError(translate("A hardware interrupt channel is already in use")); + } + } else { + turn_on_external_interrupt_controller(); + } + + // TODO: determine if pin has an interrupt channel available + // TODO: set pin pull settings, input/output, etc + // QUESTION: can PORT/EVSYS interrupts (lightsleep) coexist with RTC->TAMPER (deepsleep) settings? + // Actual initialization of the interrupt should be delayed until set_alarm + + self->channel = pin->extint_channel; + self->pin = pin; + self->value = value; + self->pull = pull; + + gpio_set_pin_function(pin->number, GPIO_PIN_FUNCTION_A); + if (self->pull) { + if (self->value) { + // detect rising edge means pull down + gpio_set_pin_pull_mode(pin->number, GPIO_PULL_DOWN); + } else { + // detect falling edge means pull up + gpio_set_pin_pull_mode(pin->number, GPIO_PULL_UP); + } + } + set_eic_channel_data(self->channel, (void *)self); + + claim_pin(self->pin); +} + +const mcu_pin_obj_t *common_hal_alarm_pin_pinalarm_get_pin(alarm_pin_pinalarm_obj_t *self) { + return self->pin; +} + +bool common_hal_alarm_pin_pinalarm_get_value(alarm_pin_pinalarm_obj_t *self) { + return self->value; +} + +bool common_hal_alarm_pin_pinalarm_get_edge(alarm_pin_pinalarm_obj_t *self) { + return true; +} + +bool common_hal_alarm_pin_pinalarm_get_pull(alarm_pin_pinalarm_obj_t *self) { + return self->pull; +} + +bool alarm_pin_pinalarm_woke_this_cycle(void) { + if (pinalarm_on && RTC->MODE0.INTFLAG.bit.TAMPER) { + woke_up = true; + } + return woke_up; +} + +mp_obj_t alarm_pin_pinalarm_find_triggered_alarm(size_t n_alarms, const mp_obj_t *alarms) { + for (size_t i = 0; i < n_alarms; i++) { + if (!mp_obj_is_type(alarms[i], &alarm_pin_pinalarm_type)) { + continue; + } + alarm_pin_pinalarm_obj_t *alarm = MP_OBJ_TO_PTR(alarms[i]); + (void)alarm; + + + // TODO: Determine whether any pins have been marked as + // triggering the alarm (using the static vars at + // start of file) and if so return that alarm. + } + // Return nothing if no matching alarms are found. + return mp_const_none; +} + +mp_obj_t alarm_pin_pinalarm_create_wakeup_alarm(void) { + alarm_pin_pinalarm_obj_t *alarm = m_new_obj(alarm_pin_pinalarm_obj_t); + alarm->base.type = &alarm_pin_pinalarm_type; + // TODO: Extract information about what pin woke the device. + // Because this interrupt occurs in deep sleep, the callback + // cannot be used to store this information. It must be extracted + // from the registers, if possible. It may be impossible to + // obtain, in which case return a dummy value. + return alarm; +} + +void alarm_pin_pinalarm_reset(void) { + // TODO: this is responsible for resetting ALL pinalarms. Make + // sure to clear any reserved tables, deinit both PORT and TAMPER + // settings, etc. If flags are set to indicate this module is in + // use, reset them. + pinalarm_on = false; + woke_up = false; + SAMD_ALARM_FLAG &= ~SAMD_ALARM_FLAG_PIN; // clear flag + // Disable TAMPER interrupt + RTC->MODE0.INTENCLR.bit.TAMPER = 1; + // Disable TAMPER control + common_hal_mcu_disable_interrupts(); + RTC->MODE0.CTRLA.bit.ENABLE = 0; // Disable the RTC + while (RTC->MODE0.SYNCBUSY.bit.ENABLE) { // Wait for synchronization + ; + } + RTC->MODE0.TAMPCTRL.reg = 0; // reset everything + RTC->MODE0.CTRLA.bit.ENABLE = 1; // Enable the RTC + while (RTC->MODE0.SYNCBUSY.bit.ENABLE) { // Wait for synchronization + ; + } + common_hal_mcu_enable_interrupts(); +} + +void alarm_pin_pinalarm_set_alarms(bool deep_sleep, size_t n_alarms, const mp_obj_t *alarms) { + // The outer layer of this loop simply checks if there are any pin + // alarms in the parameter array + for (size_t i = 0; i < n_alarms; i++) { + if (mp_obj_is_type(alarms[i], &alarm_pin_pinalarm_type)) { + alarm_pin_pinalarm_obj_t *alarm = MP_OBJ_TO_PTR(alarms[i]); + gpio_set_pin_function(alarm->pin->number, GPIO_PIN_FUNCTION_A); + if (alarm->pull) { + if (alarm->value) { + // detect rising edge means pull down + gpio_set_pin_pull_mode(alarm->pin->number, GPIO_PULL_DOWN); + } else { + // detect falling edge means pull up + gpio_set_pin_pull_mode(alarm->pin->number, GPIO_PULL_UP); + } + } + if (deep_sleep) { + // Tamper Pins: IN0:PB00; IN1:PB02; IN2:PA02; IN3:PC00; IN4:PC01; OUT:PB01 + // Only these pins can do TAMPER + if (alarm->pin != &pin_PB00 && alarm->pin != &pin_PB02 && + alarm->pin != &pin_PA02) { + mp_raise_ValueError(translate("Pin cannot wake from Deep Sleep")); + } + pinalarm_on = true; + SAMD_ALARM_FLAG |= SAMD_ALARM_FLAG_PIN; + + // Set tamper interrupt so deep sleep knows that's the intent + RTC->MODE0.INTENSET.reg = RTC_MODE0_INTENSET_TAMPER; + common_hal_mcu_disable_interrupts(); + RTC->MODE0.CTRLA.bit.ENABLE = 0; // Disable the RTC + while (RTC->MODE0.SYNCBUSY.bit.ENABLE) { // Wait for synchronization + ; + } + // TODO: map requested pin to limited selection of TAMPER pins + // PA02 is n=2: IN2, LVL2, etc... + RTC->MODE0.TAMPCTRL.bit.DEBNC2 = 1; // Edge triggered when INn is stable for 4 CLK_RTC_DEB periods + RTC->MODE0.TAMPCTRL.bit.TAMLVL2 = alarm->value; // rising or falling edge + RTC->MODE0.TAMPCTRL.bit.IN2ACT = 1; // WAKE on IN2 (doesn't save timestamp) + common_hal_mcu_enable_interrupts(); + RTC->MODE0.INTENSET.reg = RTC_MODE0_INTENSET_TAMPER; + RTC->MODE0.CTRLA.bit.ENABLE = 1; // Enable the RTC + while (RTC->MODE0.SYNCBUSY.bit.ENABLE) { // Wait for synchronization + ; + } + // TODO: Set up deep sleep alarms. + // For deep sleep alarms, first check if the + // alarm pin value is valid for RTC->TAMPER. Ensure + // that the PULL and VALUE values are possible to + // implement with TAMPER, and throw value errors if + // not. + // A VM reset will occur before deep sleep + // starts, so either init these settings now and + // protect them with a `never_reset` function, or + // store them all in static variables and only + // actually implement the settings in + // `alarm_pin_pinalarm_prepare_for_deep_sleep` + // below. + } // use else-if here if RTC-TAMPER can wake from IDLE + else { + // Light sleep so turn on EIC channel + set_eic_handler(alarm->channel, EIC_HANDLER_ALARM); + turn_on_eic_channel(alarm->channel, EIC_CONFIG_SENSE0_RISE_Val); + } + + // TODO: Set up light sleep / fake deep sleep alarms. + // PORT/EVSYS should have more valid pin combinations + // than the TAMPER system. Check if there is a valid + // PORT/EVSYS combination, set it up, and reserve it with + // the tables at the start of this file so it can't be + // reused. Also set up IRQ callbacks and any other + // busywork. + + // TODO: Might want to reserve or otherwise interact with + // peripherals/sam_d5x/external_interrupts.c or events.c here + + // TODO: Even if an alarm is being set for deep sleep, it + // still needs to be able to wake from fake deep sleep, + // which is actually just like a light sleep. If the + // RTC Tamper IRQs are capable of waking from IDLE mode, + // this isn't a big deal, and there can be a strict + // if-else statement here. Otherwise, it will need to + // either set PORT and TAMPER IRQs simultaniously, or if that + // isn't possible, make a new function that can shunt fake deep + // sleep setup to common_hal_alarm_pretending_deep_sleep + } + } +} + +void alarm_pin_pinalarm_prepare_for_deep_sleep(void) { + // TODO: This function is called after the VM reset, right before + // the program actually enters deep sleep. If the VM reset + // (see port_reset) resets any pins/EVSYS settings/RTC values + // needed by the pinalarms, this function is responsible for setting them + // back up again. +} diff --git a/ports/atmel-samd/common-hal/alarm/pin/PinAlarm.h b/ports/atmel-samd/common-hal/alarm/pin/PinAlarm.h new file mode 100644 index 0000000000..f3cd4cf27b --- /dev/null +++ b/ports/atmel-samd/common-hal/alarm/pin/PinAlarm.h @@ -0,0 +1,50 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Lucian Copeland for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef MICROPY_INCLUDED_ATMEL_SAMD_COMMON_HAL_ALARM_PINALARM_H +#define MICROPY_INCLUDED_ATMEL_SAMD_COMMON_HAL_ALARM_PINALARM_H + +#include "py/obj.h" +#include "py/objtuple.h" + +typedef struct { + mp_obj_base_t base; + const mcu_pin_obj_t *pin; + bool value; + bool pull; + uint8_t channel; +} alarm_pin_pinalarm_obj_t; + +mp_obj_t alarm_pin_pinalarm_find_triggered_alarm(size_t n_alarms, const mp_obj_t *alarms); +mp_obj_t alarm_pin_pinalarm_create_wakeup_alarm(void); + +void pin_alarm_callback(uint8_t num); +void alarm_pin_pinalarm_reset(void); +void alarm_pin_pinalarm_set_alarms(bool deep_sleep, size_t n_alarms, const mp_obj_t *alarms); +void alarm_pin_pinalarm_prepare_for_deep_sleep(void); +bool alarm_pin_pinalarm_woke_this_cycle(void); + +#endif // MICROPY_INCLUDED_ATMEL_SAMD_COMMON_HAL_ALARM_PINALARM_H diff --git a/ports/atmel-samd/common-hal/alarm/time/TimeAlarm.c b/ports/atmel-samd/common-hal/alarm/time/TimeAlarm.c new file mode 100644 index 0000000000..49ce90a0b6 --- /dev/null +++ b/ports/atmel-samd/common-hal/alarm/time/TimeAlarm.c @@ -0,0 +1,168 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Lucian Copeland for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "py/runtime.h" +#include "hpl/pm/hpl_pm_base.h" +// #include + +#include "shared-bindings/alarm/time/TimeAlarm.h" +#include "shared-bindings/time/__init__.h" +#include "common-hal/alarm/__init__.h" +#include "supervisor/port.h" + +STATIC volatile bool woke_up; +STATIC uint32_t deep_sleep_ticks; +// TODO: replace timealarm_on with SAMD_ALARM_FLAG bit flags +STATIC volatile bool timealarm_on; + +void common_hal_alarm_time_timealarm_construct(alarm_time_timealarm_obj_t *self, mp_float_t monotonic_time) { + // TODO: throw a ValueError if the input time exceeds the maximum + // value of the Compare register. This must be calculated from the + // setup values in port.c. Should be ~3 days. Give it some margin. + // + // UPDATE: for deep sleep at least, it's far more than 3 days since + // prescalar is set to 1024. (2^32)/32 seconds so >1500 days? + + self->monotonic_time = monotonic_time; +} + +mp_float_t common_hal_alarm_time_timealarm_get_monotonic_time(alarm_time_timealarm_obj_t *self) { + return self->monotonic_time; +} + +mp_obj_t alarm_time_timealarm_find_triggered_alarm(size_t n_alarms, const mp_obj_t *alarms) { + // TODO: this function currently assumes you can only have a single TimeAlarm + // If you want to support more, it will need to somehow detect which one went off. + for (size_t i = 0; i < n_alarms; i++) { + if (mp_obj_is_type(alarms[i], &alarm_time_timealarm_type)) { + return alarms[i]; + } + } + return mp_const_none; +} + +mp_obj_t alarm_time_timealarm_create_wakeup_alarm(void) { + alarm_time_timealarm_obj_t *timer = m_new_obj(alarm_time_timealarm_obj_t); + timer->base.type = &alarm_time_timealarm_type; + // TODO: Set monotonic_time based on the RTC state. + // Or don't, most of the other ports don't have this either. + timer->monotonic_time = 0.0f; + return timer; +} + +void time_alarm_callback(void) { + if (timealarm_on) { + RTC->MODE0.INTENCLR.reg = RTC_MODE0_INTENCLR_CMP1; // clear flags + woke_up = true; + timealarm_on = false; + } +} + +bool alarm_time_timealarm_woke_this_cycle(void) { + if (timealarm_on && (((uint32_t)port_get_raw_ticks(NULL) << 4) > RTC->MODE0.COMP[1].reg)) { + woke_up = true; + } + return woke_up; +} + +void alarm_time_timealarm_reset(void) { + timealarm_on = false; + woke_up = false; + SAMD_ALARM_FLAG &= ~SAMD_ALARM_FLAG_TIME; // clear flag +} + +void alarm_time_timealarm_set_alarms(bool deep_sleep, size_t n_alarms, const mp_obj_t *alarms) { + // Turn on debug control + // RTC->MODE0.DBGCTRL.bit.DBGRUN = 1; + // Search through alarms for TimeAlarm instances, and check that there's only one + bool timealarm_set = false; + alarm_time_timealarm_obj_t *timealarm = MP_OBJ_NULL; + for (size_t i = 0; i < n_alarms; i++) { + if (!mp_obj_is_type(alarms[i], &alarm_time_timealarm_type)) { + continue; + } + if (timealarm_set) { + mp_raise_ValueError(translate("Only one alarm.time alarm can be set.")); + } + timealarm = MP_OBJ_TO_PTR(alarms[i]); + timealarm_set = true; + } + if (!timealarm_set) { + return; + } + + // Compute how long to actually sleep, considering the time now. + mp_float_t now_secs = uint64_to_float(common_hal_time_monotonic_ms()) / 1000.0f; + uint32_t wakeup_in_secs = MAX(0.0f, timealarm->monotonic_time - now_secs); + uint32_t wakeup_in_ticks = wakeup_in_secs * 1024; + + // In the deep sleep case, we can't start the timer until the USB delay has finished + // (otherwise it will go off while USB enumerates, and we'll risk entering deep sleep + // without any way to wake up again) + if (deep_sleep) { + deep_sleep_ticks = wakeup_in_ticks; + } else { + deep_sleep_ticks = 0; + } + timealarm_on = true; + // Set COMP1 for fake sleep. This will be read and reset for real deep sleep anyways. + // RTC->MODE0.COMP[1].reg = wakeup_in_ticks; + RTC->MODE0.COMP[1].reg = ((uint32_t)port_get_raw_ticks(NULL) + wakeup_in_ticks) << 4; + while ((RTC->MODE0.SYNCBUSY.reg & (RTC_MODE0_SYNCBUSY_COMP1)) != 0) { + } + RTC->MODE0.INTFLAG.reg = RTC_MODE0_INTFLAG_CMP1; + RTC->MODE0.INTENSET.reg = RTC_MODE0_INTENSET_CMP1; + SAMD_ALARM_FLAG |= SAMD_ALARM_FLAG_TIME; // set TimeAlarm flag + + // This is set for fake sleep. Max fake sleep time is ~72 hours + // True deep sleep isn't limited by this + // port_interrupt_after_ticks(wakeup_in_ticks); + // printf("second t %lu, cmp0 %lu, cmp1 %lu\n", (uint32_t)port_get_raw_ticks(NULL),RTC->MODE0.COMP[0].reg,RTC->MODE0.COMP[1].reg); + // TODO: set up RTC->COMP[1] and create a callback pointing to + // time_alarm_callback. See atmel-samd/supervisor/port.c -> _port_interrupt_after_ticks() + // for how to set this up. I don't know how you do the callback, though. You MUST use + // COMP[1], since port.c uses COMP[0] already and needs to set that for + // things like the USB enumeration delay. + + // If true deep sleep is called, it will either ignore or overwrite the above setup depending on + // whether it is shorter or longer than the USB delay + // printf("set deep alarm finished\n"); + +} + +void alarm_time_timealarm_prepare_for_deep_sleep(void) { + if (deep_sleep_ticks) { + // TODO: set up RTC->COMP[1] again, since it needs to start AFTER the USB enumeration delay. + // Just do the exact same setup as alarm_time_timealarm_set_alarms(). Note, this + // is used for both fake and real deep sleep, so it still needs the callback. + // See STM32 for reference. + + RTC->MODE0.COMP[1].reg = deep_sleep_ticks; + while ((RTC->MODE0.SYNCBUSY.reg & (RTC_MODE0_SYNCBUSY_COMP1)) != 0) { + } + deep_sleep_ticks = 0; + } +} diff --git a/ports/atmel-samd/common-hal/alarm/time/TimeAlarm.h b/ports/atmel-samd/common-hal/alarm/time/TimeAlarm.h new file mode 100644 index 0000000000..a6102dee6b --- /dev/null +++ b/ports/atmel-samd/common-hal/alarm/time/TimeAlarm.h @@ -0,0 +1,46 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Lucian Copeland for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef MICROPY_INCLUDED_ATMEL_SAMD_COMMON_HAL_ALARM_TIMEALARM_H +#define MICROPY_INCLUDED_ATMEL_SAMD_COMMON_HAL_ALARM_TIMEALARM_H + +#include "py/obj.h" + +typedef struct { + mp_obj_base_t base; + mp_float_t monotonic_time; // values compatible with time.monotonic_time() +} alarm_time_timealarm_obj_t; + +mp_obj_t alarm_time_timealarm_find_triggered_alarm(size_t n_alarms, const mp_obj_t *alarms); +mp_obj_t alarm_time_timealarm_create_wakeup_alarm(void); +void time_alarm_callback(void); +bool alarm_time_timealarm_woke_this_cycle(void); +void alarm_time_timealarm_set_alarms(bool deep_sleep, size_t n_alarms, const mp_obj_t *alarms); +void alarm_time_timealarm_reset(void); + +void alarm_time_timealarm_prepare_for_deep_sleep(void); + +#endif // MICROPY_INCLUDED_ATMEL_SAMD_COMMON_HAL_ALARM_TIMEALARM_H diff --git a/ports/atmel-samd/common-hal/alarm/touch/TouchAlarm.c b/ports/atmel-samd/common-hal/alarm/touch/TouchAlarm.c new file mode 100644 index 0000000000..88c73726ee --- /dev/null +++ b/ports/atmel-samd/common-hal/alarm/touch/TouchAlarm.c @@ -0,0 +1,32 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Lucian Copeland for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "shared-bindings/alarm/touch/TouchAlarm.h" +#include "shared-bindings/microcontroller/__init__.h" + +void common_hal_alarm_touch_touchalarm_construct(alarm_touch_touchalarm_obj_t *self, const mcu_pin_obj_t *pin) { + mp_raise_NotImplementedError(translate("Touch alarms not available")); +} diff --git a/ports/atmel-samd/common-hal/alarm/touch/TouchAlarm.h b/ports/atmel-samd/common-hal/alarm/touch/TouchAlarm.h new file mode 100644 index 0000000000..d7f0f8cf1d --- /dev/null +++ b/ports/atmel-samd/common-hal/alarm/touch/TouchAlarm.h @@ -0,0 +1,35 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Lucian Copeland for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef MICROPY_INCLUDED_ATMEL_SAMD_COMMON_HAL_ALARM_TOUCHALARM_H +#define MICROPY_INCLUDED_ATMEL_SAMD_COMMON_HAL_ALARM_TOUCHALARM_H + +typedef struct { + mp_obj_base_t base; + const mcu_pin_obj_t *pin; +} alarm_touch_touchalarm_obj_t; + +#endif // MICROPY_INCLUDED_ATMEL_SAMD_COMMON_HAL_ALARM_TOUCHALARM_H diff --git a/ports/atmel-samd/eic_handler.c b/ports/atmel-samd/eic_handler.c index 226dafc765..48824031c0 100644 --- a/ports/atmel-samd/eic_handler.c +++ b/ports/atmel-samd/eic_handler.c @@ -29,6 +29,7 @@ #include "common-hal/rotaryio/IncrementalEncoder.h" #include "common-hal/countio/Counter.h" #include "shared-bindings/microcontroller/__init__.h" +#include "common-hal/alarm/pin/PinAlarm.h" // #include "samd/external_interrupts.h" #include "eic_handler.h" @@ -66,6 +67,12 @@ void shared_eic_handler(uint8_t channel) { break; #endif + #if CIRCUITPY_ALARM + case EIC_HANDLER_ALARM: + pin_alarm_callback(channel); + break; + #endif + default: break; } diff --git a/ports/atmel-samd/eic_handler.h b/ports/atmel-samd/eic_handler.h index 08da7ea34d..d73ef2a0f7 100644 --- a/ports/atmel-samd/eic_handler.h +++ b/ports/atmel-samd/eic_handler.h @@ -31,6 +31,7 @@ #define EIC_HANDLER_INCREMENTAL_ENCODER 0x2 #define EIC_HANDLER_PS2 0x3 #define EIC_HANDLER_COUNTER 0x04 +#define EIC_HANDLER_ALARM 0x05 void set_eic_handler(uint8_t channel, uint8_t eic_handler); void shared_eic_handler(uint8_t channel); diff --git a/ports/atmel-samd/mpconfigport.mk b/ports/atmel-samd/mpconfigport.mk index 4a5c7a41d6..818c9f111a 100644 --- a/ports/atmel-samd/mpconfigport.mk +++ b/ports/atmel-samd/mpconfigport.mk @@ -98,6 +98,8 @@ CIRCUITPY_TOUCHIO_USE_NATIVE = 0 # The ?='s allow overriding in mpconfigboard.mk. + +CIRCUITPY_ALARM ?= 0 CIRCUITPY_PS2IO ?= 1 CIRCUITPY_SAMD ?= 1 CIRCUITPY_RGBMATRIX ?= $(CIRCUITPY_FULL_BUILD) diff --git a/ports/atmel-samd/supervisor/port.c b/ports/atmel-samd/supervisor/port.c index 932252a0d7..d2211d4c8f 100644 --- a/ports/atmel-samd/supervisor/port.c +++ b/ports/atmel-samd/supervisor/port.c @@ -90,6 +90,12 @@ #include "common-hal/rtc/RTC.h" #endif +#if CIRCUITPY_ALARM +#include "common-hal/alarm/__init__.h" +#include "common-hal/alarm/time/TimeAlarm.h" +#include "common-hal/alarm/pin/PinAlarm.h" +#endif + #if CIRCUITPY_TOUCHIO_USE_NATIVE #include "common-hal/touchio/TouchIn.h" #endif @@ -215,6 +221,10 @@ static void rtc_init(void) { #endif #ifdef SAM_D5X_E5X hri_mclk_set_APBAMASK_RTC_bit(MCLK); + #if CIRCUITPY_ALARM + // Cache TAMPID for wake up cause + (void)alarm_get_wakeup_cause(); + #endif RTC->MODE0.CTRLA.bit.SWRST = true; while (RTC->MODE0.SYNCBUSY.bit.SWRST != 0) { } @@ -512,6 +522,18 @@ void RTC_Handler(void) { // Do things common to all ports when the tick occurs supervisor_tick(); } + #if CIRCUITPY_ALARM + if (intflag & RTC_MODE0_INTFLAG_CMP1) { + // Likely TimeAlarm fake sleep wake + time_alarm_callback(); + RTC->MODE0.INTFLAG.reg = RTC_MODE0_INTFLAG_CMP1; + } + if (intflag & RTC_MODE0_INTFLAG_TAMPER) { + // Likely PinAlarm fake sleep wake + pin_alarm_callback(1); // TODO: set channel? + RTC->MODE0.INTFLAG.reg = RTC_MODE0_INTFLAG_TAMPER; + } + #endif #endif if (intflag & RTC_MODE0_INTFLAG_CMP0) { // Clear the interrupt because we may have hit a sleep