stm32/timer: Support soft IRQ timer callbacks.

machine.Timer() has inconsistent behaviour between ports: some run
callbacks in hard IRQ context whereas others schedule them like soft IRQs.

As on the rp2 port, add support to the stm32 port for a hard= argument
to explicitly choose between these, setting the default to True to match
the existing behaviour.

Signed-off-by: Chris Webb <chris@arachsys.com>
This commit is contained in:
Chris Webb
2025-08-25 14:19:52 +01:00
committed by Damien George
parent a69b08b756
commit 49f2a1f0ed

View File

@@ -29,6 +29,7 @@
#include "py/runtime.h"
#include "py/gc.h"
#include "shared/runtime/mpirq.h"
#include "timer.h"
#include "servo.h"
#include "pin.h"
@@ -131,6 +132,7 @@ typedef struct _pyb_timer_obj_t {
uint8_t tim_id;
uint8_t is_32bit;
mp_obj_t callback;
bool ishard;
TIM_HandleTypeDef tim;
IRQn_Type irqn;
pyb_timer_channel_obj_t *channel;
@@ -632,10 +634,14 @@ static void pyb_timer_print(const mp_print_t *print, mp_obj_t self_in, mp_print_
/// BRK_IN input is triggered. It can be set to `BRK_OFF`, `BRK_LOW`
/// and `BRK_HIGH`.
///
/// - `hard` - specifies if the timer and channel callbacks should be run
/// in hard-IRQ context (the default on stm32) or scheduled as a soft
/// handler.
///
///
/// You must either specify freq or both of period and prescaler.
static mp_obj_t pyb_timer_init_helper(pyb_timer_obj_t *self, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
enum { ARG_freq, ARG_prescaler, ARG_period, ARG_tick_hz, ARG_mode, ARG_div, ARG_callback, ARG_deadtime, ARG_brk };
enum { ARG_freq, ARG_prescaler, ARG_period, ARG_tick_hz, ARG_mode, ARG_div, ARG_callback, ARG_deadtime, ARG_brk, ARG_hard };
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_freq, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} },
{ MP_QSTR_prescaler, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0xffffffff} },
@@ -646,6 +652,7 @@ static mp_obj_t pyb_timer_init_helper(pyb_timer_obj_t *self, size_t n_args, cons
{ MP_QSTR_callback, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} },
{ MP_QSTR_deadtime, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} },
{ MP_QSTR_brk, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = BRK_OFF} },
{ MP_QSTR_hard, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = true} },
};
// parse args
@@ -828,6 +835,7 @@ static mp_obj_t pyb_timer_init_helper(pyb_timer_obj_t *self, size_t n_args, cons
self->tim.Instance->CR1 |= TIM_CR1_ARPE;
// Start the timer running
self->ishard = args[ARG_hard].u_bool;
if (args[ARG_callback].u_obj == mp_const_none) {
HAL_TIM_Base_Start(&self->tim);
} else {
@@ -1708,29 +1716,10 @@ static void timer_handle_irq_channel(pyb_timer_obj_t *tim, uint8_t channel, mp_o
// clear the interrupt
__HAL_TIM_CLEAR_IT(&tim->tim, irq_mask);
// execute callback if it's set
if (callback != mp_const_none) {
mp_sched_lock();
// When executing code within a handler we must lock the GC to prevent
// any memory allocations. We must also catch any exceptions.
gc_lock();
nlr_buf_t nlr;
if (nlr_push(&nlr) == 0) {
mp_call_function_1(callback, MP_OBJ_FROM_PTR(tim));
nlr_pop();
} else {
// Uncaught exception; disable the callback so it doesn't run again.
tim->callback = mp_const_none;
__HAL_TIM_DISABLE_IT(&tim->tim, irq_mask);
if (channel == 0) {
mp_printf(MICROPY_ERROR_PRINTER, "uncaught exception in Timer(%u) interrupt handler\n", tim->tim_id);
} else {
mp_printf(MICROPY_ERROR_PRINTER, "uncaught exception in Timer(%u) channel %u interrupt handler\n", tim->tim_id, channel);
}
mp_obj_print_exception(&mp_plat_print, MP_OBJ_FROM_PTR(nlr.ret_val));
}
gc_unlock();
mp_sched_unlock();
if (mp_irq_dispatch(callback, MP_OBJ_FROM_PTR(tim), tim->ishard) < 0) {
// Uncaught exception; disable the callback so it doesn't run again.
tim->callback = mp_const_none;
__HAL_TIM_DISABLE_IT(&tim->tim, irq_mask);
}
}
}