From 15574cd665a8e66157aa97447e3869513da81d45 Mon Sep 17 00:00:00 2001 From: Martin Fischer Date: Thu, 2 Jul 2020 22:19:00 +0200 Subject: [PATCH] nrf: Add support for time.ticks_xxx functions using RTC1. This commit adds time.ticks_ms/us support using RTC1 as the timebase. It also adds the time.ticks_add/diff helper functions. This feature can be enabled using MICROPY_PY_TIME_TICKS. If disabled the system uses the legacy sleep methods and does not have any ticks functions. In addition support for MICROPY_EVENT_POLL_HOOK was added to the time.sleep_ms(x) function, making this function more power efficient and allows support for select.poll/asyncio. To support this, the RTC's CCR0 was used to schedule a ~1msec event to wakeup the CPU. Some important notes about the RTC timebase: - Since the granularity of RTC1's ticks are approx 30usec, time.ticks_us is not perfect, does not have 1us resolution, but is otherwise quite usable. For tighter measurments the ticker's 1MHz counter should be used. - time.ticks_ms(x) should *not* be called in an IRQ with higher prio than the RTC overflow irq (3). If so it introduces a race condition and possibly leads to wrong tick calculations. See #6171 and #6202. --- ports/nrf/main.c | 5 + ports/nrf/modules/machine/rtcounter.c | 7 ++ ports/nrf/modules/utime/modutime.c | 4 + ports/nrf/mpconfigport.h | 10 ++ ports/nrf/mphalport.c | 143 +++++++++++++++++++++++++- ports/nrf/mphalport.h | 13 ++- 6 files changed, 174 insertions(+), 8 deletions(-) diff --git a/ports/nrf/main.c b/ports/nrf/main.c index 3c5d0a05d..670c88e7c 100644 --- a/ports/nrf/main.c +++ b/ports/nrf/main.c @@ -52,6 +52,8 @@ #include "i2c.h" #include "adc.h" #include "rtcounter.h" +#include "mphalport.h" + #if MICROPY_PY_MACHINE_HW_PWM #include "pwm.h" #endif @@ -101,6 +103,9 @@ int main(int argc, char **argv) { soft_reset: + #if MICROPY_PY_TIME_TICKS + rtc1_init_time_ticks(); + #endif led_init(); diff --git a/ports/nrf/modules/machine/rtcounter.c b/ports/nrf/modules/machine/rtcounter.c index 5fb28557d..c9f907774 100644 --- a/ports/nrf/modules/machine/rtcounter.c +++ b/ports/nrf/modules/machine/rtcounter.c @@ -153,6 +153,13 @@ STATIC mp_obj_t machine_rtc_make_new(const mp_obj_type_t *type, size_t n_args, s int rtc_id = rtc_find(args[ARG_id].u_obj); + #if MICROPY_PY_TIME_TICKS + if (rtc_id == 1) { + // time module uses RTC1, prevent using it + mp_raise_ValueError(MP_ERROR_TEXT("RTC1 reserved by time module")); + } + #endif + // const and non-const part of the RTC object. const machine_rtc_obj_t * self = &machine_rtc_obj[rtc_id]; machine_rtc_config_t *config = self->config; diff --git a/ports/nrf/modules/utime/modutime.c b/ports/nrf/modules/utime/modutime.c index 60cdbe4f3..bb2914101 100644 --- a/ports/nrf/modules/utime/modutime.c +++ b/ports/nrf/modules/utime/modutime.c @@ -42,6 +42,10 @@ STATIC const mp_rom_map_elem_t time_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_sleep_ms), MP_ROM_PTR(&mp_utime_sleep_ms_obj) }, { MP_ROM_QSTR(MP_QSTR_sleep_us), MP_ROM_PTR(&mp_utime_sleep_us_obj) }, + { MP_ROM_QSTR(MP_QSTR_ticks_ms), MP_ROM_PTR(&mp_utime_ticks_ms_obj) }, + { MP_ROM_QSTR(MP_QSTR_ticks_us), MP_ROM_PTR(&mp_utime_ticks_us_obj) }, + { MP_ROM_QSTR(MP_QSTR_ticks_add), MP_ROM_PTR(&mp_utime_ticks_add_obj) }, + { MP_ROM_QSTR(MP_QSTR_ticks_diff), MP_ROM_PTR(&mp_utime_ticks_diff_obj) }, }; STATIC MP_DEFINE_CONST_DICT(time_module_globals, time_module_globals_table); diff --git a/ports/nrf/mpconfigport.h b/ports/nrf/mpconfigport.h index 28076114f..fc34a7cf2 100644 --- a/ports/nrf/mpconfigport.h +++ b/ports/nrf/mpconfigport.h @@ -174,6 +174,9 @@ #define MICROPY_PY_MACHINE_RTCOUNTER (0) #endif +#ifndef MICROPY_PY_TIME_TICKS +#define MICROPY_PY_TIME_TICKS (0) +#endif #define MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF (1) #define MICROPY_EMERGENCY_EXCEPTION_BUF_SIZE (0) @@ -317,6 +320,13 @@ extern const struct _mp_obj_module_t ble_module; /* micro:bit root pointers */ \ void *async_data[2]; \ +#define MICROPY_EVENT_POLL_HOOK \ + do { \ + extern void mp_handle_pending(bool); \ + mp_handle_pending(true); \ + __WFI(); \ + } while (0); + #define MP_PLAT_PRINT_STRN(str, len) mp_hal_stdout_tx_strn_cooked(str, len) // We need to provide a declaration/definition of alloca() diff --git a/ports/nrf/mphalport.c b/ports/nrf/mphalport.c index 4469adc80..b8e4c2e4d 100644 --- a/ports/nrf/mphalport.c +++ b/ports/nrf/mphalport.c @@ -35,6 +35,121 @@ #include "nrfx_errors.h" #include "nrfx_config.h" +#if MICROPY_PY_TIME_TICKS +#include "nrfx_rtc.h" +#include "nrf_clock.h" +#endif + +#if MICROPY_PY_TIME_TICKS + +// Use RTC1 for time ticks generation (ms and us) with 32kHz tick resolution +// and overflow handling in RTC IRQ. + +#define RTC_TICK_INCREASE_MSEC (33) + +#define RTC_RESCHEDULE_CC(rtc, cc_nr, ticks) \ + do { \ + nrfx_rtc_cc_set(&rtc, cc_nr, nrfx_rtc_counter_get(&rtc) + ticks, true); \ + } while (0); + +// RTC overflow irq handling notes: +// - If has_overflowed is set it could be before or after COUNTER is read. +// If before then an adjustment must be made, if after then no adjustment is necessary. +// - The before case is when COUNTER is very small (because it just overflowed and was set to zero), +// the after case is when COUNTER is very large (because it's just about to overflow +// but we read it right before it overflows). +// - The extra check for counter is to distinguish these cases. 1<<23 because it's halfway +// between min and max values of COUNTER. +#define RTC1_GET_TICKS_ATOMIC(rtc, overflows, counter) \ + do { \ + rtc.p_reg->INTENCLR = RTC_INTENCLR_OVRFLW_Msk; \ + overflows = rtc_overflows; \ + counter = rtc.p_reg->COUNTER; \ + uint32_t has_overflowed = rtc.p_reg->EVENTS_OVRFLW; \ + if (has_overflowed && counter < (1 << 23)) { \ + overflows += 1; \ + } \ + rtc.p_reg->INTENSET = RTC_INTENSET_OVRFLW_Msk; \ + } while (0); + +nrfx_rtc_t rtc1 = NRFX_RTC_INSTANCE(1); +volatile mp_uint_t rtc_overflows = 0; + +const nrfx_rtc_config_t rtc_config_time_ticks = { + .prescaler = 0, + .reliable = 0, + .tick_latency = 0, + #ifdef NRF51 + .interrupt_priority = 1, + #else + .interrupt_priority = 3, + #endif +}; + +STATIC void rtc_irq_time(nrfx_rtc_int_type_t event) { + // irq handler for overflow + if (event == NRFX_RTC_INT_OVERFLOW) { + rtc_overflows += 1; + } + // irq handler for wakeup from WFI (~1msec) + if (event == NRFX_RTC_INT_COMPARE0) { + RTC_RESCHEDULE_CC(rtc1, 0, RTC_TICK_INCREASE_MSEC) + } +} + +void rtc1_init_time_ticks(void) { + // Start the low-frequency clock (if it hasn't been started already) + if (!nrf_clock_lf_is_running(NRF_CLOCK)) { + nrf_clock_task_trigger(NRF_CLOCK, NRF_CLOCK_TASK_LFCLKSTART); + } + // 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); + nrfx_rtc_overflow_enable(&rtc1, true); + RTC_RESCHEDULE_CC(rtc1, 0, RTC_TICK_INCREASE_MSEC) + nrfx_rtc_enable(&rtc1); +} + +mp_uint_t mp_hal_ticks_ms(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). + // + // Make sure not to call this function within an irq with higher prio than the + // RTC's irq. This would introduce the danger of preempting the RTC irq and + // calling mp_hal_ticks_ms() at that time would return a false result. + uint32_t overflows; + uint32_t counter; + // guard against overflow irq + RTC1_GET_TICKS_ATOMIC(rtc1, overflows, counter) + return (overflows << 9) * 1000 + (counter * 125 / 4096); +} + +mp_uint_t mp_hal_ticks_us(void) { + // Compute: ticks_us = (overflows << 24 + counter) * 1000000 / 32768 + // = (overflows << 15 * 15625) + (counter * 15625 / 512) + // Since this function is likely to be called in a poll loop it must + // be fast, using an optimized 64bit mult/divide. + uint32_t overflows; + uint32_t counter; + // guard against overflow irq + RTC1_GET_TICKS_ATOMIC(rtc1, overflows, counter) + // first compute counter * 15625 + uint32_t counter_lo = (counter & 0xffff) * 15625; + uint32_t counter_hi = (counter >> 16) * 15625; + // actual value is counter_hi << 16 + counter_lo + return ((overflows << 15) * 15625) + ((counter_hi << 7) + (counter_lo >> 9)); +} + +#else + +mp_uint_t mp_hal_ticks_ms(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, @@ -70,7 +185,7 @@ int mp_hal_stdin_rx_chr(void) { if (MP_STATE_PORT(board_stdio_uart) != NULL && uart_rx_any(MP_STATE_PORT(board_stdio_uart))) { return uart_rx_char(MP_STATE_PORT(board_stdio_uart)); } - __WFI(); + MICROPY_EVENT_POLL_HOOK } return 0; @@ -93,6 +208,31 @@ void mp_hal_stdout_tx_str(const char *str) { mp_hal_stdout_tx_strn(str, strlen(str)); } +#if MICROPY_PY_TIME_TICKS + +void mp_hal_delay_us(mp_uint_t us) { + uint32_t now; + if (us == 0) { + return; + } + now = mp_hal_ticks_us(); + while (mp_hal_ticks_us() - now < us) { + } +} + +void mp_hal_delay_ms(mp_uint_t ms) { + uint32_t now; + if (ms == 0) { + return; + } + now = mp_hal_ticks_ms(); + while (mp_hal_ticks_ms() - now < ms) { + MICROPY_EVENT_POLL_HOOK + } +} + +#else + void mp_hal_delay_us(mp_uint_t us) { if (us == 0) { return; @@ -175,6 +315,7 @@ void mp_hal_delay_ms(mp_uint_t ms) { mp_hal_delay_us(999); } } +#endif #if defined(NRFX_LOG_ENABLED) && (NRFX_LOG_ENABLED == 1) diff --git a/ports/nrf/mphalport.h b/ports/nrf/mphalport.h index 5614be29f..15b37b7ef 100644 --- a/ports/nrf/mphalport.h +++ b/ports/nrf/mphalport.h @@ -41,12 +41,6 @@ typedef enum HAL_TIMEOUT = 0x03 } HAL_StatusTypeDef; -static inline uint32_t hal_tick_fake(void) { - return 0; -} - -#define mp_hal_ticks_ms hal_tick_fake // TODO: implement. Right now, return 0 always - extern const unsigned char mp_hal_status_to_errno_table[4]; NORETURN void mp_hal_raise(HAL_StatusTypeDef status); @@ -70,10 +64,15 @@ const char *nrfx_error_code_lookup(uint32_t err_code); #define mp_hal_pin_od_high(p) mp_hal_pin_high(p) #define mp_hal_pin_open_drain(p) nrf_gpio_cfg_input(p->pin, NRF_GPIO_PIN_NOPULL) +#if MICROPY_PY_TIME_TICKS +void rtc1_init_time_ticks(); +#else +mp_uint_t mp_hal_ticks_ms(void); +#define mp_hal_ticks_us() (0) +#endif // TODO: empty implementation for now. Used by machine_spi.c:69 #define mp_hal_delay_us_fast(p) -#define mp_hal_ticks_us() (0) #define mp_hal_ticks_cpu() (0) #endif