8000 nrf: Implement time.time() and machine.RTC by cwalther · Pull Request #13340 · micropython/micropython · GitHub
[go: up one dir, main page]

Skip to content

nrf: Implement time.time() and machine.RTC #13340

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ports/nrf/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down
7 changes: 7 additions & 0 deletions ports/nrf/drivers/bluetooth/ble_drv.c
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,13 @@ uint32_t ble_drv_stack_enable(void) {
.rc_temp_ctiv = 2,
.accuracy = NRF_CLOCK_LF_ACCURACY_250_PPM
};
#elif BLUETOOTH_LFCLK_SYNTH
nrf_clock_lf_cfg_t clock_config = {
.source = NRF_CLOCK_LF_SRC_SYNTH,
.rc_ctiv = 0,
.rc_temp_ctiv = 0,
.accuracy = NRF_CLOCK_LF_ACCURACY_50_PPM
};
#else
nrf_clock_lf_cfg_t clock_config = {
.source = NRF_CLOCK_LF_SRC_XTAL,
Expand Down
100 changes: 100 additions & 0 deletions ports/nrf/modules/machine/machine_rtc.c
Original file line number Diff line number Diff line change
@@ -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
7 changes: 7 additions & 0 deletions ports/nrf/modules/machine/modmachine.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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) }, \
Expand Down
51 changes: 49 additions & 2 deletions ports/nrf/mphalport.c
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,24 @@ void mp_nrf_start_lfclk(void) {
// Check if the clock was recently stopped but is still running.
#if USE_WORKAROUND_FOR_ANOMALY_132
bool was_running = nrf_clock_lf_is_running(NRF_CLOCK);
629A // If so, wait for it to stop. This ensures that the delay for anomaly 132 workaround does
// not land us in the middle of the forbidden interval.
#endif
// If so, wait for it to stop, otherwise the source cannot be changed. This also ensures
// that the delay for anomaly 132 workaround does not land us in the middle of the forbidden
// interval.
while (nrf_clock_lf_is_running(NRF_CLOCK)) {
}
// Use the same LFCLK source as for bluetooth so that enabling the softdevice will not cause
// an interruption.
nrf_clock_lf_src_set(NRF_CLOCK,
#if BLUETOOTH_LFCLK_RC
NRF_CLOCK_LFCLK_RC
#elif BLUETOOTH_LFCLK_SYNTH
NRF_CLOCK_LFCLK_Synth
#else
NRF_CLOCK_LFCLK_Xtal
#endif
);
#if USE_WORKAROUND_FOR_ANOMALY_132
// If the clock just stopped, we can start it again right away as we are certainly before
// the forbidden 66-138us interval. Otherwise, apply a delay of 138us to make sure we are
// after the interval.
Expand All @@ -68,6 +82,14 @@ void mp_nrf_start_lfclk(void) {
#endif
nrf_clock_task_trigger(NRF_CLOCK, NRF_CLOCK_TASK_LFCLKSTART);
}
// When synthesizing LFCLK from HFCLK, start HFXO if it hasn't been started yet, otherwise we
// would be synthesizing from HFINT, which is pointless as it's even less accurate than LFRC.
// Must come after starting LFCLK, otherwise the LFCLK source reverts to RC.
#if BLUETOOTH_LFCLK_SYNTH
if (!nrf_clock_hf_start_task_status_get(NRF_CLOCK)) {
nrf_clock_task_trigger(NRF_CLOCK, NRF_CLOCK_TASK_HFCLKSTART);
}
#endif
}

#if MICROPY_PY_TIME_TICKS
Expand Down Expand Up @@ -178,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,
Expand Down
4 changes: 4 additions & 0 deletions ports/nrf/mphalport.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
55 changes: 55 additions & 0 deletions shared/timeutils/modtime_mphal.h
Original file line number Diff line number Diff line change
@@ -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);
}
0