micropython / micropython

MicroPython - a lean and efficient Python implementation for microcontrollers and constrained systems
https://micropython.org
Other
19.22k stars 7.7k forks source link

Cannot import user defined module on Raspberry Pico #8125

Open guilhermebene opened 2 years ago

guilhermebene commented 2 years ago

I am trying to compile micropython for my Raspberry Pico with an "user defined module" based on pimoroni's implementation of an Encoder class that uses the Pico's PIOs to interface with motor encoders.

I have followed the tutorials explaining how to compile an user defined module for the Pico and everything seems to work just fine, I get the expected messages while compiling with make USER_C_MODULES=../../../modules/micropython.cmake all:

...
Including User C Module from ../../examples/usercmodule/cexample
Including User C Module from ../../examples/usercmodule/cppexample
...

My files to interface the Encoder class and MicroPython are (I am omitting the cpp to avoid flooding this issue even further):

#include "encoder-pio-wrapper.h"

/***** Methods *****/
MP_DEFINE_CONST_FUN_OBJ_0(Encoder___del___obj, Encoder___del__);

MP_DEFINE_CONST_FUN_OBJ_0(Encoder_get_state_a_obj, Encoder_get_state_a);
MP_DEFINE_CONST_FUN_OBJ_0(Encoder_get_state_b_obj, Encoder_get_state_b);

MP_DEFINE_CONST_FUN_OBJ_0(Encoder_get_count_obj, Encoder_get_count);
MP_DEFINE_CONST_FUN_OBJ_0(Encoder_get_revolutions_obj, Encoder_get_revolutions);
MP_DEFINE_CONST_FUN_OBJ_0(Encoder_get_angle_degrees_obj, Encoder_get_angle_degrees);
MP_DEFINE_CONST_FUN_OBJ_0(Encoder_get_angle_radians_obj, Encoder_get_angle_radians);

MP_DEFINE_CONST_FUN_OBJ_0(Encoder_get_frequency_obj, Encoder_get_frequency);
MP_DEFINE_CONST_FUN_OBJ_0(Encoder_get_revolutions_per_second_obj, Encoder_get_revolutions_per_second);
MP_DEFINE_CONST_FUN_OBJ_0(Encoder_get_revolutions_per_minute_obj, Encoder_get_revolutions_per_minute);
MP_DEFINE_CONST_FUN_OBJ_0(Encoder_get_degrees_per_second_obj, Encoder_get_degrees_per_second);
MP_DEFINE_CONST_FUN_OBJ_0(Encoder_get_radians_per_second_obj, Encoder_get_radians_per_second);

MP_DEFINE_CONST_FUN_OBJ_0(Encoder_zero_count_obj, Encoder_zero_count);

/***** Binding of Methods *****/
STATIC const mp_rom_map_elem_t Encoder_locals_dict_table[] = {
    { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&Encoder___del___obj) },
    { MP_ROM_QSTR(MP_QSTR_get_state_a), MP_ROM_PTR(&Encoder_get_state_a_obj) },
    { MP_ROM_QSTR(MP_QSTR_get_state_b), MP_ROM_PTR(&Encoder_get_state_b_obj) },
    { MP_ROM_QSTR(MP_QSTR_get_count), MP_ROM_PTR(&Encoder_get_count_obj) },
    { MP_ROM_QSTR(MP_QSTR_get_revolutions), MP_ROM_PTR(&Encoder_get_revolutions_obj) },
    { MP_ROM_QSTR(MP_QSTR_get_angle_degrees), MP_ROM_PTR(&Encoder_get_angle_degrees_obj) },
    { MP_ROM_QSTR(MP_QSTR_get_angle_radians), MP_ROM_PTR(&Encoder_get_angle_radians_obj) },
    { MP_ROM_QSTR(MP_QSTR_get_frequency), MP_ROM_PTR(&Encoder_get_frequency_obj) },
    { MP_ROM_QSTR(MP_QSTR_get_revolutions_per_second), MP_ROM_PTR(&Encoder_get_revolutions_per_second_obj) },
    { MP_ROM_QSTR(MP_QSTR_get_revolutions_per_minute), MP_ROM_PTR(&Encoder_get_revolutions_per_minute_obj) },
    { MP_ROM_QSTR(MP_QSTR_get_degrees_per_second), MP_ROM_PTR(&Encoder_get_degrees_per_second_obj) },
    { MP_ROM_QSTR(MP_QSTR_get_radians_per_second), MP_ROM_PTR(&Encoder_get_radians_per_second_obj) },
};

STATIC MP_DEFINE_CONST_DICT(Encoder_locals_dict, Encoder_locals_dict_table);

/***** Class Definition *****/
const mp_obj_type_t Encoder_type = {
    { &mp_type_type },
    .name = MP_QSTR_Encoder,
    .print = Encoder_print,
    .make_new = Encoder_make_new,
    .locals_dict = (mp_obj_dict_t*)&Encoder_locals_dict,
};

/***** Globals Table *****/

STATIC const mp_map_elem_t Encoder_globals_table[] = {
    { MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_Encoder) },
    { MP_OBJ_NEW_QSTR(MP_QSTR_Encoder), (mp_obj_t)&Encoder_type },
};

STATIC MP_DEFINE_CONST_DICT(mp_module_Encoder_globals, Encoder_globals_table);

/***** Module Definition *****/
const mp_obj_module_t Encoder_user_cmodule = {
    .base = { &mp_type_module },
    .globals = (mp_obj_dict_t*)&mp_module_Encoder_globals,
};
MP_REGISTER_MODULE(MP_QSTR_Encoder, Encoder_user_cmodule, MODULE_ENCODER_ENABLED);
// Include MicroPython API.
#include "../../micropython/py/obj.h"
#include "../../micropython/py/runtime.h"

/***** Extern of Class Definition *****/
extern const mp_obj_type_t breakout_encoder_BreakoutEncoder_type;
extern const mp_obj_type_t encoder_pio_Encoder_type;

/***** Extern of Class Methods *****/
extern void Encoder_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind);

extern mp_obj_t Encoder_init(mp_obj_t self_in);

extern mp_obj_t Encoder_get_state_a(mp_obj_t self_in);
extern mp_obj_t Encoder_get_state_b(mp_obj_t self_in);

extern mp_obj_t Encoder_get_count(mp_obj_t self_in);
extern mp_obj_t Encoder_get_revolutions(mp_obj_t self_in);
extern mp_obj_t Encoder_get_angle_degrees(mp_obj_t self_in);
extern mp_obj_t Encoder_get_angle_radians(mp_obj_t self_in);

extern mp_obj_t Encoder_get_frequency(mp_obj_t self_in);
extern mp_obj_t Encoder_get_revolutions_per_second(mp_obj_t self_in);
extern mp_obj_t Encoder_get_revolutions_per_minute(mp_obj_t self_in);
extern mp_obj_t Encoder_get_degrees_per_second(mp_obj_t self_in);
extern mp_obj_t Encoder_get_radians_per_second(mp_obj_t self_in);

extern void mp_obj_t Encoder_zero_count(mp_obj_t self_in);
// extern mp_obj_t Encoder_perform_capture(mp_obj_t self_in);

However, I cannot import my module by just using import Encoder. I suspect my makefiles and cmakes are ok because I succeed to compile and link but I am not so sure about it.

peterhinch commented 2 years ago

There are some Python solutions here.

guilhermebene commented 2 years ago

Thank you @peterhinch, you did an interesting implementation there, but I was hoping to achieve even better performance by working with C++. Do you think it would be possible to improve performance compared to your last implementations from #6894 ?

iabdalkader commented 2 years ago

Hi, take a look at this PR #8016 it adds a user C module with a pio program, the same thing that you're trying to do, please ignore all the irreverent parts, lib etc..it's very likely that you're missing something because I tested this module and it can be imported just fine. Note USER_C_MODULES is set in an mpconfigboard.cmake file not on the command line, but it works just the same if set on the command line.

peterhinch commented 2 years ago

@guilhermebene C will always beat Python, even with the Viper emitter.

guilhermebene commented 2 years ago

I managed to solve this problem in the meantime, but actually got a new one. This encoder.cpp I am trying to use has an init() function that calls an irq_set_exclusive_handler() (defined in pico-sdk) to set an interruption callback, but it seems it ends up blocking and freezes MicroPython. @iabdalkader I see that you are also using PIOs in your PR, do you see a specific reason for that? The original function is here.

iabdalkader commented 2 years ago

I'm not sure, maybe the IRQ is needed by micropython, or there's already an exclusive handler, if it's used by pico libs or micropython try adding a shared handler, if it's not used your handler might be doing something invalid like blocking.

guilhermebene commented 2 years ago

I tried a shared handler, but it did not work though.

iabdalkader commented 2 years ago

See rp2_pio.c 1) There's already an exclusive handler installed for pio0 and pio1, I think you should remove them first because irq_set_exclusive_handler() may assert an error if you don't 2) More importantly, you should handle/ack/clear the irq in your irq handler, see how that is done in rp2_pio.c in pio_irq0()