Skip to content

rp2/machine_timer: Support hard IRQ timer callbacks. #17363

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

Merged
merged 3 commits into from
Jun 16, 2025
Merged
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
6 changes: 6 additions & 0 deletions docs/rp2/quickref.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down
30 changes: 28 additions & 2 deletions ports/rp2/machine_timer.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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 {
Expand All @@ -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
Expand All @@ -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);
Expand Down
20 changes: 20 additions & 0 deletions tests/ports/rp2/rp2_machine_timer.py
Original file line number Diff line number Diff line change
@@ -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()
16 changes: 16 additions & 0 deletions tests/ports/rp2/rp2_machine_timer.py.exp
Original file line number Diff line number Diff line change
@@ -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
Loading
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

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:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy