Implementation使用 ports/mimxrt/machine_timer.c作为模板,完成mm32的Timer模块的实现。 STATIC mp_obj_t machine_timer_obj_init_helper () 函数中的实例化参数清单
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_REQUIRED | 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} },
};
Timer 对象的类型定义,要存放好硬件相关信息,同时能保存这些动态配置信息
typedef struct
{
void * callback;
uint32_t period;
uint32_t tick_hz;
uint32_t freq;
uint32_t mode;
} machine_timer_conf_t;
typedef struct
{
mp_obj_base_t base; // object base class.
TIM_BASIC_Type * timer_port;
IRQn_Type timer_irqn;
uint32_t timer_id;
machine_timer_conf_t *conf;
} machine_timer_obj_t;
这里的 callback 搞了个无差别指针,后面用强制类型转换骗过编译器吧。后来通过实际调试发现问题,改用“mp_obj_t callback”。
把machine_timer_obj_t的实例化放在machine_timer.c中吧,因为没有涉及引脚相关的定义,就不放在 machine_pin_board_pins.c 中了。
machine_timer_conf_t timer_conf[MACHINE_TIMER_NUM];
const machine_timer_obj_t timer0 = {.base = {&machine_timer_type}, .timer_port = TIM6, .timr_irqn = TIM6_IRQn, .timer_id = 0u, .conf = &timer_conf[0]};
const machine_timer_obj_t timer1 = {.base = {&machine_timer_type}, .timer_port = TIM7, .timr_irqn = TIM7_IRQn, .timer_id = 1u, .conf = &timer_conf[1]};
const machine_timer_obj_t * machine_timer_objs[] =
{
&timer0 ,
&timer1 ,
};
再实现 timer_find()函数:
const machine_timer_obj_t *timer_find(mp_obj_t user_obj)
{
/* 如果传入参数本身就是一个Timer的实例,则直接送出这个Timer。 */
if ( mp_obj_is_type(user_obj, &machine_timer_type) )
{
return user_obj;
}
/* 如果传入参数是一个Timer ID号,则通过索引在Timer清单中找到这个通道,然后送出这个通道的实例化对象。 */
if ( mp_obj_is_small_int(user_obj) )
{
uint8_t timer_idx = MP_OBJ_SMALL_INT_VALUE(user_obj);
if ( timer_idx < MACHINE_TIMER_NUM )
{
return machine_timer_objs[timer_idx];
}
}
mp_raise_ValueError(MP_ERROR_TEXT("Timer doesn't exist"));
}
突然发现,对于硬件模块,在 make_new 函数中安排开模块时钟挺好的,对应配引脚复用功能也不错。通常make_new函数中会调用init_helper函数,init_helper通常还会被init直接调用,暴露给用户。但实际上,init函数不仅仅是作为首次配置,更多是修改模块硬件属性,在运行时配置。因此,像开时钟、配引脚这种一次性的操作,放到make_new里更合适。
对于Timer,不涉及到引脚。涉及到引脚也不能在这里写死。目前的类对象结构体中,都已经包含了引脚复用功能的选项,而且要考虑动态复用的可能性,所以不会再pin_init.c或者make_new中一次性写死,而是放在init_helper中,每次在使用init配置类功能,都重新配置一遍引脚。至于访问时钟,我直接在clock_init.c中全部开启,也浪费不了多少电。
mp_obj_t machine_timer_obj_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args)
{
mp_arg_check_num(n_args, n_kw, 1, MP_OBJ_FUN_ARGS_MAX, true);
const machine_timer_obj_t *timer = timer_find(args[0]);
if ( (n_args >= 1) || (n_kw >= 0) )
{
mp_map_t kw_args;
mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); /* 将关键字参数从总的参数列表中提取出来,单独封装成kw_args。 */
machine_timer_obj_init_helper(timer, n_args - 1, args + 1, &kw_args);
}
return (mp_obj_t)timer;
}
其余函数都是常规实现。 特别需要注意中断服务程序的设计,这也是Timer模块实现的一个难点。
static void machine_timer_irq_handler(uint32_t timer_id) { const machine_timer_obj_t * self = machine_timer_objs[timer_id];
uint32_t flags = TIM_BASIC_GetInterruptStatus(self->timer_port); if (0u != (flags & TIM_BASIC_STATUS_UPDATE_PERIOD) ) { if (self->conf->callback) { mp_sched_schedule(self->conf->callback, MP_OBJ_FROM_PTR(self)); } } TIM_BASIC_ClearInterruptStatus(self->timer_port, flags);
if (self->conf->mode == TIMER_MODE_ONE_SHOT) { TIM_BASIC_Stop(self->timer_port); } } void TIM6_IRQHandler(void) { machine_timer_irq_handler(0); } void TIM7_IRQHandler(void) { machine_timer_irq_handler(1); }
要在于调用 mp_sched_schedule() 函数。若要使用 mp_sched_schedule() 函数,需要在mpconfigport.h文件中启用 MICROPY_ENABLE_SCHEDULER。
#define MICROPY_ENABLE_SCHEDULER (1) /* enable scheduler components, using in irq. */
关于 mp_sched_schedule() 函数,其定义在 py/scheduler.c 文件中:
bool MICROPY_WRAP_MP_SCHED_SCHEDULE(mp_sched_schedule)(mp_obj_t function, mp_obj_t arg) {
mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION();
bool ret;
if (!mp_sched_full()) {
if (MP_STATE_VM(sched_state) == MP_SCHED_IDLE) {
MP_STATE_VM(sched_state) = MP_SCHED_PENDING;
}
uint8_t iput = IDX_MASK(MP_STATE_VM(sched_idx) + MP_STATE_VM(sched_len)++);
MP_STATE_VM(sched_queue)[iput].func = function;
MP_STATE_VM(sched_queue)[iput].arg = arg;
MICROPY_SCHED_HOOK_SCHEDULED;
ret = true;
} else {
// schedule queue is full
ret = false;
}
MICROPY_END_ATOMIC_SECTION(atomic_state);
return ret;
}
从源代码中可以看出,这个函数要求在第一个参数传入回调的函数,第二个参数传入回调函数的参数。在 machine_timer_irq_handler() 函数中,将make_new中存入的callback和timer对象本身作为参数传入python的调度器。这个要特别注意,在python中,也要定义格式相同的函数作为回调函数传入。在callback内部,可以通过传入的参数访问到timer对象实例的所有资源。
|