diff --git a/ports/nrf/Makefile b/ports/nrf/Makefile index 4fe921f0fa..688c021233 100644 --- a/ports/nrf/Makefile +++ b/ports/nrf/Makefile @@ -267,6 +267,7 @@ DRIVERS_SRC_C += $(addprefix modules/,\ machine/pin.c \ machine/timer.c \ machine/rtcounter.c \ + machine/rtc.c \ machine/temp.c \ os/microbitfs.c \ board/modboard.c \ diff --git a/ports/nrf/modtime.c b/ports/nrf/modtime.c new file mode 100644 index 0000000000..d1448a4141 --- /dev/null +++ b/ports/nrf/modtime.c @@ -0,0 +1,51 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013-2023 Damien P. George + * + * 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/obj.h" +#include "shared/timeutils/timeutils.h" +#include "ports/nrf/modules/machine/rtc.h" + +// Return the localtime as an 8-tuple. +static mp_obj_t mp_time_localtime_get(void) { + mp_int_t seconds = ticks_ms_64() / 1000; + timeutils_struct_time_t tm; + timeutils_seconds_since_epoch_to_struct_time(seconds, &tm); + mp_obj_t tuple[8] = { + tuple[0] = mp_obj_new_int(tm.tm_year), + tuple[1] = mp_obj_new_int(tm.tm_mon), + tuple[2] = mp_obj_new_int(tm.tm_mday), + tuple[3] = mp_obj_new_int(tm.tm_hour), + tuple[4] = mp_obj_new_int(tm.tm_min), + tuple[5] = mp_obj_new_int(tm.tm_sec), + tuple[6] = mp_obj_new_int(tm.tm_wday), + tuple[7] = mp_obj_new_int(tm.tm_yday), + }; + return mp_obj_new_tuple(8, tuple); +} + +static mp_obj_t mp_time_time_get(void) { + return mp_obj_new_int((ticks_ms_64() + rtc_offset[1]) / 1000); +} diff --git a/ports/nrf/modules/machine/modmachine.c b/ports/nrf/modules/machine/modmachine.c index de1d0e3124..1683da8410 100644 --- a/ports/nrf/modules/machine/modmachine.c +++ b/ports/nrf/modules/machine/modmachine.c @@ -65,6 +65,12 @@ #define MICROPY_PY_MACHINE_RTCOUNTER_ENTRY #endif +#if MICROPY_PY_MACHINE_RTC +#define MICROPY_PY_MACHINE_RTC_ENTRY { MP_ROM_QSTR(MP_QSTR_RTC), MP_ROM_PTR(&machine_rtc_type) }, +#else +#define MICROPY_PY_MACHINE_RTC_ENTRY +#endif + #if MICROPY_PY_MACHINE_TIMER_NRF #define MICROPY_PY_MACHINE_TIMER_ENTRY { MP_ROM_QSTR(MP_QSTR_Timer), MP_ROM_PTR(&machine_timer_type) }, #else diff --git a/ports/nrf/modules/machine/modmachine.h b/ports/nrf/modules/machine/modmachine.h index 4574dc0010..9f2b3d0791 100644 --- a/ports/nrf/modules/machine/modmachine.h +++ b/ports/nrf/modules/machine/modmachine.h @@ -30,6 +30,10 @@ #include "py/obj.h" +#if MICROPY_PY_MACHINE_RTC +extern const mp_obj_type_t machine_rtc_type; +#endif + void machine_init(void); #endif // __MICROPY_INCLUDED_NRF5_MODMACHINE_H__ diff --git a/ports/nrf/modules/machine/rtc.c b/ports/nrf/modules/machine/rtc.c new file mode 100644 index 0000000000..369b0e1b51 --- /dev/null +++ b/ports/nrf/modules/machine/rtc.c @@ -0,0 +1,144 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2019 Nick Moore for Adafruit Industries + * Copyright (c) 2021 "Krzysztof Adamski" + * + * 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 "modmachine.h" + +#if MICROPY_PY_MACHINE_RTC + +#include "py/mphal.h" +#include "shared/timeutils/timeutils.h" +#include "rtc.h" + + +// These values are placed before and after the current RTC count. They are +// used to determine if the RTC count is valid. These randomly-generated values +// will be set when the RTC value is set in order to mark the RTC as valid. If +// the system crashes or reboots, these values will remain undisturbed and the +// RTC offset will remain valid. +// +// If MicroPython is updated or these symbols shift around, the prefix and +// suffix will no longer match, and the time will no longer be valid. +#define RTC_OFFSET_CHECK_PREFIX 0x25ea7e2a +#define RTC_OFFSET_CHECK_SUFFIX 0x2b80b69e + +void rtc_offset_check(void) { + // If the prefix and suffix are not valid, zero-initialize the RTC offset. + if ((rtc_offset[0] != RTC_OFFSET_CHECK_PREFIX) || (rtc_offset[2] != RTC_OFFSET_CHECK_SUFFIX)) { + rtc_offset[1] = 0; + } +} + +void rtc_get_time(timeutils_struct_time_t *tm) { + uint64_t ticks_s = ticks_ms_64() / 1000; + timeutils_seconds_since_2000_to_struct_time(rtc_offset[1] + ticks_s, tm); +} + +void rtc_set_time(timeutils_struct_time_t *tm) { + uint64_t ticks_s = ticks_ms_64() / 1000; + uint32_t epoch_s = timeutils_seconds_since_2000( + tm->tm_year, tm->tm_mon, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec + ); + rtc_offset[1] = epoch_s - ticks_s; + + // Set the prefix and suffix in order to indicate the time is valid. This + // must be done after the offset is updated, in case there is a crash or + // power failure. + rtc_offset[0] = RTC_OFFSET_CHECK_PREFIX; + rtc_offset[2] = RTC_OFFSET_CHECK_SUFFIX; +} + +typedef struct _machine_rtc_obj_t { + mp_obj_base_t base; +} machine_rtc_obj_t; + +// singleton RTC object +static const machine_rtc_obj_t machine_rtc_obj = {{&machine_rtc_type}}; + +static mp_obj_t machine_rtc_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + // check arguments + mp_arg_check_num(n_args, n_kw, 0, 0, false); + + // return constant object + return (mp_obj_t)&machine_rtc_obj; +} + +static mp_obj_t machine_rtc_datetime(mp_uint_t n_args, const mp_obj_t *args) { + if (n_args == 1) { + timeutils_struct_time_t t; + + rtc_get_time(&t); + + mp_obj_t tuple[8] = { + mp_obj_new_int(t.tm_year), + mp_obj_new_int(t.tm_mon), + mp_obj_new_int(t.tm_mday), + mp_obj_new_int(t.tm_wday), + mp_obj_new_int(t.tm_hour), + mp_obj_new_int(t.tm_min), + mp_obj_new_int(t.tm_sec), + mp_obj_new_int(0) + }; + + return mp_obj_new_tuple(8, tuple); + } else { + mp_obj_t *items; + + mp_obj_get_array_fixed_n(args[1], 8, &items); + + timeutils_struct_time_t t = { + .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]), + }; + // Deliberately ignore the weekday argument and compute the proper value + t.tm_wday = timeutils_calc_weekday(t.tm_year, t.tm_mon, t.tm_mday); + + rtc_set_time(&t); + + } + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_rtc_datetime_obj, 1, 2, machine_rtc_datetime); + +static const mp_rom_map_elem_t machine_rtc_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_datetime), MP_ROM_PTR(&machine_rtc_datetime_obj) }, +}; +static MP_DEFINE_CONST_DICT(machine_rtc_locals_dict, machine_rtc_locals_dict_table); + +MP_DEFINE_CONST_OBJ_TYPE( + machine_rtc_type, + MP_QSTR_RTC, + MP_TYPE_FLAG_NONE, + make_new, machine_rtc_make_new, + locals_dict, &machine_rtc_locals_dict + ); + +#endif // MICROPY_PY_MACHINE_RTC diff --git a/ports/nrf/modules/machine/rtc.h b/ports/nrf/modules/machine/rtc.h new file mode 100644 index 0000000000..ffdec83265 --- /dev/null +++ b/ports/nrf/modules/machine/rtc.h @@ -0,0 +1,4 @@ +extern uint64_t ticks_ms_64(void); + +// This is the time in seconds since 2000 that the RTC was started. +__attribute__((section(".uninitialized"))) static uint32_t rtc_offset[3]; diff --git a/ports/nrf/mpconfigport.h b/ports/nrf/mpconfigport.h index 37fbdf1eb3..d99c20b6d7 100644 --- a/ports/nrf/mpconfigport.h +++ b/ports/nrf/mpconfigport.h @@ -234,10 +234,26 @@ #define MICROPY_PY_MACHINE_RTCOUNTER (0) #endif +#ifndef MICROPY_PY_MACHINE_RTC +#define MICROPY_PY_MACHINE_RTC (1) +#endif + #ifndef MICROPY_PY_TIME_TICKS #define MICROPY_PY_TIME_TICKS (1) #endif +#ifndef MICROPY_PY_TIME_GMTIME_LOCALTIME_MKTIME +#define MICROPY_PY_TIME_GMTIME_LOCALTIME_MKTIME (1) +#endif + +#ifndef MICROPY_PY_TIME_TIME_TIME_NS +#define MICROPY_PY_TIME_TIME_TIME_NS (1) +#endif + +#ifndef MICROPY_PY_TIME_INCLUDEFILE +#define MICROPY_PY_TIME_INCLUDEFILE "ports/nrf/modtime.c" +#endif + #define MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF (1) #define MICROPY_EMERGENCY_EXCEPTION_BUF_SIZE (0) diff --git a/ports/nrf/mphalport.c b/ports/nrf/mphalport.c index 06c6ba5cc2..8db8053825 100644 --- a/ports/nrf/mphalport.c +++ b/ports/nrf/mphalport.c @@ -129,7 +129,10 @@ static void rtc_irq_time(nrfx_rtc_int_type_t event) { void rtc1_init_time_ticks(void) { // Start the low-frequency clock (if it hasn't been started already) - mp_nrf_start_lfclk(); + if (!nrf_clock_lf_is_running(NRF_CLOCK)) { + nrf_clock_task_trigger(NRF_CLOCK, NRF_CLOCK_TASK_LFCLKSTART); + rtc_offset_check(); + } // Uninitialize first, then set overflow IRQ and first CC event nrfx_rtc_uninit(&rtc1); nrfx_rtc_init(&rtc1, &rtc_config_time_ticks, rtc_irq_time); @@ -138,8 +141,7 @@ void rtc1_init_time_ticks(void) { nrfx_rtc_enable(&rtc1); } -mp_uint_t mp_hal_ticks_ms(void) { - // Compute: (rtc_overflows << 24 + COUNTER) * 1000 / 32768 +uint64_t ticks_ms_64(void) { // Compute: (rtc_overflows << 24 + COUNTER) * 1000 / 32768 // // Note that COUNTER * 1000 / 32768 would overflow during calculation, so use // the less obvious * 125 / 4096 calculation (overflow secure). @@ -151,7 +153,11 @@ mp_uint_t mp_hal_ticks_ms(void) { uint32_t counter; // guard against overflow irq RTC1_GET_TICKS_ATOMIC(rtc1, overflows, counter) - return (overflows << 9) * 1000 + (counter * 125 / 4096); + return ((uint64_t)overflows << 9) * 1000 + (counter * 125 / 4096); +} + +mp_uint_t mp_hal_ticks_ms(void) { + return ticks_ms_64(); } mp_uint_t mp_hal_ticks_us(void) { diff --git a/ports/nrf/mphalport.h b/ports/nrf/mphalport.h index 7efe05a15f..6a1eb5bdd9 100644 --- a/ports/nrf/mphalport.h +++ b/ports/nrf/mphalport.h @@ -74,6 +74,7 @@ void rtc1_init_time_ticks(); mp_uint_t mp_hal_ticks_ms(void); #define mp_hal_ticks_us() (0) #endif +extern void rtc_offset_check(void); // TODO: empty implementation for now. Used by machine_spi.c:69 #define mp_hal_delay_us_fast(p)