diff --git a/docs/rp2/quickref.rst b/docs/rp2/quickref.rst index 23071d77215d5..ec31442990ff4 100644 --- a/docs/rp2/quickref.rst +++ b/docs/rp2/quickref.rst @@ -135,6 +135,12 @@ Use the :mod:`machine.Timer` class:: tim = Timer(period=5000, mode=Timer.ONE_SHOT, callback=lambda t:print(1)) tim.init(period=2000, mode=Timer.PERIODIC, callback=lambda t:print(2)) +By default, timer callbacks run as soft IRQs so they can allocate but +are prone to GC jitter and delays. Pass ``hard=True`` to the ``Timer()`` +constructor or ``init()`` method to run the callback in hard-IRQ context +instead. This reduces delay and jitter, but see :ref:`isr_rules` for the +restrictions that apply to hard-IRQ handlers. + .. _rp2_Pins_and_GPIO: diff --git a/ports/rp2/machine_timer.c b/ports/rp2/machine_timer.c index 6f8b04f10eab0..ffb4c7024308a 100644 --- a/ports/rp2/machine_timer.c +++ b/ports/rp2/machine_timer.c @@ -27,6 +27,7 @@ #include "py/runtime.h" #include "py/mperrno.h" #include "py/mphal.h" +#include "py/gc.h" #include "pico/time.h" #define ALARM_ID_INVALID (-1) @@ -40,13 +41,36 @@ typedef struct _machine_timer_obj_t { uint32_t mode; uint64_t delta_us; // for periodic mode mp_obj_t callback; + bool ishard; } machine_timer_obj_t; const mp_obj_type_t machine_timer_type; static int64_t alarm_callback(alarm_id_t id, void *user_data) { machine_timer_obj_t *self = user_data; - mp_sched_schedule(self->callback, MP_OBJ_FROM_PTR(self)); + + if (self->ishard) { + // When executing code within a handler we must lock the scheduler to + // prevent any scheduled callbacks from running, and lock the GC to + // prevent any memory allocations. + mp_sched_lock(); + gc_lock(); + nlr_buf_t nlr; + if (nlr_push(&nlr) == 0) { + mp_call_function_1(self->callback, MP_OBJ_FROM_PTR(self)); + nlr_pop(); + } else { + // Uncaught exception; disable the callback so it doesn't run again. + self->mode = TIMER_MODE_ONE_SHOT; + mp_printf(MICROPY_ERROR_PRINTER, "uncaught exception in timer callback\n"); + mp_obj_print_exception(MICROPY_ERROR_PRINTER, MP_OBJ_FROM_PTR(nlr.ret_val)); + } + gc_unlock(); + mp_sched_unlock(); + } else { + mp_sched_schedule(self->callback, MP_OBJ_FROM_PTR(self)); + } + if (self->mode == TIMER_MODE_ONE_SHOT) { return 0; } else { @@ -66,13 +90,14 @@ static void machine_timer_print(const mp_print_t *print, mp_obj_t self_in, mp_pr } static mp_obj_t machine_timer_init_helper(machine_timer_obj_t *self, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - enum { ARG_mode, ARG_callback, ARG_period, ARG_tick_hz, ARG_freq, }; + enum { ARG_mode, ARG_callback, ARG_period, ARG_tick_hz, ARG_freq, ARG_hard, }; static const mp_arg_t allowed_args[] = { { MP_QSTR_mode, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = TIMER_MODE_PERIODIC} }, { MP_QSTR_callback, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, { MP_QSTR_period, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0xffffffff} }, { MP_QSTR_tick_hz, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 1000} }, { MP_QSTR_freq, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_hard, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = false} }, }; // Parse args @@ -96,6 +121,7 @@ static mp_obj_t machine_timer_init_helper(machine_timer_obj_t *self, size_t n_ar } self->callback = args[ARG_callback].u_obj; + self->ishard = args[ARG_hard].u_bool; self->alarm_id = alarm_pool_add_alarm_in_us(self->pool, self->delta_us, alarm_callback, self, true); if (self->alarm_id == -1) { mp_raise_OSError(MP_ENOMEM); diff --git a/tests/ports/rp2/rp2_machine_timer.py b/tests/ports/rp2/rp2_machine_timer.py new file mode 100644 index 0000000000000..ac4efcf7f3835 --- /dev/null +++ b/tests/ports/rp2/rp2_machine_timer.py @@ -0,0 +1,20 @@ +from machine import Timer +from time import sleep_ms + +# Test the rp2-specific adjustable tick_hz and hard/soft IRQ handlers +# for both one-shot and periodic timers. + +modes = {Timer.ONE_SHOT: "one-shot", Timer.PERIODIC: "periodic"} +kinds = {False: "soft", True: "hard"} + +for mode in modes: + for hard in kinds: + for period in 2, 4: + timer = Timer( + mode=mode, + period=period, + hard=hard, + callback=lambda t: print("callback", modes[mode], kinds[hard], period), + ) + sleep_ms(9) + timer.deinit() diff --git a/tests/ports/rp2/rp2_machine_timer.py.exp b/tests/ports/rp2/rp2_machine_timer.py.exp new file mode 100644 index 0000000000000..b3dd93dfabfa8 --- /dev/null +++ b/tests/ports/rp2/rp2_machine_timer.py.exp @@ -0,0 +1,16 @@ +callback one-shot soft 2 +callback one-shot soft 4 +callback one-shot hard 2 +callback one-shot hard 4 +callback periodic soft 2 +callback periodic soft 2 +callback periodic soft 2 +callback periodic soft 2 +callback periodic soft 4 +callback periodic soft 4 +callback periodic hard 2 +callback periodic hard 2 +callback periodic hard 2 +callback periodic hard 2 +callback periodic hard 4 +callback periodic hard 4
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: