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 <cwalther@gmx.ch>
pull/13340/head
Christian Walther 2024-01-02 00:02:14 +01:00
rodzic 4cef128793
commit e1a6da0f9d
6 zmienionych plików z 192 dodań i 0 usunięć

Wyświetl plik

@ -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 \

Wyświetl plik

@ -0,0 +1,100 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2021 "Krzysztof Adamski" <k@japko.eu>
* 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

Wyświetl plik

@ -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) }, \

Wyświetl plik

@ -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,

Wyświetl plik

@ -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)

Wyświetl plik

@ -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);
}