From e1a6da0f9d0dc5d122055779f8a27f0f34ce8f59 Mon Sep 17 00:00:00 2001 From: Christian Walther Date: Tue, 2 Jan 2024 00:02:14 +0100 Subject: [PATCH] nrf: Implement time.time() and machine.RTC. Optionally adds time() and time_ns() to the time module, as well as a machine.RTC class that only implements the datetime() method (following the example of the rp2 port), whose sole purpose is to provide the ability to set the time. Also provides the basis for enabling gmtime(), localtime(), mktime() in the time module. The nRF52 does not have a dedicated real-time clock peripheral, but timekeeping can be done by the same real-time counter that already powers the time.ticks_* and related functions. For reasonable accuracy, a suitable LFCLK source is required: The internal RC oscillator (BLUETOOTH_LFCLK_RC) by itself is insufficient, but any of the following work fine: - external 32kHz crystal (default) - synthesis from HFCLK (BLUETOOTH_LFCLK_SYNTH) when HFXO (external 32MHz crystal) is enabled - BLUETOOTH_LFCLK_RC + periodical calibration from HFXO (automatically done by the SoftDevice while enabled using ble.enable()) Boards can enable this by defining both configuration options MICROPY_PY_TIME_TICKS and MICROPY_PY_TIME_TIME_TIME_NS. Additionally, they may want to enable MICROPY_PY_TIME_GMTIME_LOCALTIME_MKTIME. This includes a generic implementation of mp_time_localtime_get() and mp_time_time_get() in terms of mp_hal_time_ns(), which could also be used by other ports. In particular by the embed port, for which I originally wrote it, noting the following: "I'm unsure where to put modtime_mphal.h, it could also be in extmod. The important thing is that for MICROPY_PY_TIME_INCLUDEFILE to work it must be at the same path in both the port build (original source tree) and the application build (micropython_embed distribution), therefore not in ports/embed/port. It is named .h, mismatching the corresponding ports/*/modtime.c, because it must not be compiled separately, which naming it .c would make harder for users of the embed port - they would need to explicitly exclude it, whereas this way they can continue to just compile all the .c files found in the micropython_embed distribution except those in lib." Signed-off-by: Christian Walther --- ports/nrf/Makefile | 1 + ports/nrf/modules/machine/machine_rtc.c | 100 ++++++++++++++++++++++++ ports/nrf/modules/machine/modmachine.c | 7 ++ ports/nrf/mphalport.c | 25 ++++++ ports/nrf/mphalport.h | 4 + shared/timeutils/modtime_mphal.h | 55 +++++++++++++ 6 files changed, 192 insertions(+) create mode 100644 ports/nrf/modules/machine/machine_rtc.c create mode 100644 shared/timeutils/modtime_mphal.h diff --git a/ports/nrf/Makefile b/ports/nrf/Makefile index 4fe921f0fa..5f93d5b45e 100644 --- a/ports/nrf/Makefile +++ b/ports/nrf/Makefile @@ -264,6 +264,7 @@ endif DRIVERS_SRC_C += $(addprefix modules/,\ machine/spi.c \ machine/i2c.c \ + machine/machine_rtc.c \ machine/pin.c \ machine/timer.c \ machine/rtcounter.c \ diff --git a/ports/nrf/modules/machine/machine_rtc.c b/ports/nrf/modules/machine/machine_rtc.c new file mode 100644 index 0000000000..2b2f8b2dc5 --- /dev/null +++ b/ports/nrf/modules/machine/machine_rtc.c @@ -0,0 +1,100 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 "Krzysztof Adamski" + * Copyright (c) 2024 Christian Walther + * + * 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" + +// Timekeeping is handled by the ticks machinery, so only enable the +// machine.RTC type (whose sole purpose is to provide the ability to set the +// time) if that is enabled. +#if MICROPY_PY_TIME_TIME_TIME_NS && MICROPY_PY_TIME_TICKS + +#include "extmod/modmachine.h" +#include "shared/timeutils/timeutils.h" + +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) { + uint64_t nanoseconds = mp_hal_time_ns(); + uint64_t seconds = nanoseconds / 1000000000; + nanoseconds -= seconds * 1000000000; + timeutils_struct_time_t tm; + timeutils_seconds_since_epoch_to_struct_time(seconds, &tm); + mp_obj_t tuple[8] = { + mp_obj_new_int(tm.tm_year), + mp_obj_new_int(tm.tm_mon), + mp_obj_new_int(tm.tm_mday), + mp_obj_new_int(tm.tm_wday), + mp_obj_new_int(tm.tm_hour), + mp_obj_new_int(tm.tm_min), + mp_obj_new_int(tm.tm_sec), + mp_obj_new_int(nanoseconds) + }; + return mp_obj_new_tuple(8, tuple); + } else { + mp_obj_t *items; + mp_obj_get_array_fixed_n(args[1], 8, &items); + uint64_t t = 1000000000ULL * timeutils_seconds_since_epoch( + mp_obj_get_int(items[0]), // year + mp_obj_get_int(items[1]), // month + mp_obj_get_int(items[2]), // date + mp_obj_get_int(items[4]), // hour + mp_obj_get_int(items[5]), // minute + mp_obj_get_int(items[6]) // second + ) + mp_obj_get_int(items[7]); // nanoseconds + mp_hal_set_time_ns(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 diff --git a/ports/nrf/modules/machine/modmachine.c b/ports/nrf/modules/machine/modmachine.c index de1d0e3124..8317ec84c2 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_TIME_TIME_TIME_NS && MICROPY_PY_TIME_TICKS +#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 @@ -91,6 +97,7 @@ { MP_ROM_QSTR(MP_QSTR_Pin), MP_ROM_PTR(&pin_type) }, \ \ MICROPY_PY_MACHINE_RTCOUNTER_ENTRY \ + MICROPY_PY_MACHINE_RTC_ENTRY \ MICROPY_PY_MACHINE_TIMER_ENTRY \ MICROPY_PY_MACHINE_TEMP_ENTRY \ { MP_ROM_QSTR(MP_QSTR_HARD_RESET), MP_ROM_INT(PYB_RESET_HARD) }, \ diff --git a/ports/nrf/mphalport.c b/ports/nrf/mphalport.c index 899c8318ab..efa53ef312 100644 --- a/ports/nrf/mphalport.c +++ b/ports/nrf/mphalport.c @@ -200,10 +200,35 @@ mp_uint_t mp_hal_ticks_ms(void) { #endif +#if MICROPY_PY_TIME_TIME_TIME_NS && MICROPY_PY_TIME_TICKS + +// nanoseconds between 2000-01-01 and tick counter zero, to adjust clock +static uint64_t epoch_offset = 0; + +uint64_t mp_hal_time_ns(void) { + // Range of `overflows` is sufficient: nanoseconds overflow 64 bits before `overflows` overflows + // 32 bits. + // Same logic as in mp_hal_ticks_ms, no need to worry about intermediate result overflows if we + // do everything in 64-bit (this probably needn't be fast). + uint32_t overflows; + uint32_t counter; + // guard against overflow irq + RTC1_GET_TICKS_ATOMIC(rtc1, overflows, counter) + return (((uint64_t)overflows << 18) * 1953125) + (((uint64_t)counter * 1953125) >> 6) + epoch_offset; +} + +void mp_hal_set_time_ns(uint64_t ns_since_epoch) { + epoch_offset += ns_since_epoch - mp_hal_time_ns(); +} + +#else + uint64_t mp_hal_time_ns(void) { return 0; } +#endif + // this table converts from HAL_StatusTypeDef to POSIX errno const byte mp_hal_status_to_errno_table[4] = { [HAL_OK] = 0, diff --git a/ports/nrf/mphalport.h b/ports/nrf/mphalport.h index 7efe05a15f..21d7848584 100644 --- a/ports/nrf/mphalport.h +++ b/ports/nrf/mphalport.h @@ -75,6 +75,10 @@ mp_uint_t mp_hal_ticks_ms(void); #define mp_hal_ticks_us() (0) #endif +#if MICROPY_PY_TIME_TIME_TIME_NS && MICROPY_PY_TIME_TICKS +void mp_hal_set_time_ns(uint64_t ns_since_epoch); +#endif + // TODO: empty implementation for now. Used by machine_spi.c:69 #define mp_hal_delay_us_fast(p) #define mp_hal_ticks_cpu() (0) diff --git a/shared/timeutils/modtime_mphal.h b/shared/timeutils/modtime_mphal.h new file mode 100644 index 0000000000..78832506b4 --- /dev/null +++ b/shared/timeutils/modtime_mphal.h @@ -0,0 +1,55 @@ +/* + * 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. + */ + +// This file is never compiled standalone, it's included directly from +// extmod/modtime.c via MICROPY_PY_TIME_INCLUDEFILE. + +#include "py/obj.h" +#include "py/mphal.h" +#include "shared/timeutils/timeutils.h" + +// Return the localtime as an 8-tuple. +static mp_obj_t mp_time_localtime_get(void) { + mp_int_t seconds = mp_hal_time_ns() / 1000000000; + 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); +} + +// Returns the number of seconds, as an integer, since the Epoch. +static mp_obj_t mp_time_time_get(void) { + return mp_obj_new_int(mp_hal_time_ns() / 1000000000); +}