-
-
Notifications
You must be signed in to change notification settings - Fork 8.2k
Initial implementation of machine.Signal #2747
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
Conversation
8da3ac8
to
bd9ba33
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see that you didn't go as far as making time_pulse_us() work with Signal objects. Doing that would require indirection for all mp_hal_pin_XXX method calls and make the software I2C and SPI slower (this could be noticeable in existing code, eg sending frames to a display would give a slower frame rate).
I also wanted to bring up the point that I think PinBase should be removed and instead we should use pure duck typing, ie if an object implements value() then it can be used wherever a Pin is required (and doesn't need to explicitly derive from PinBase). This point doesn't directly impact this PR, but it would require changing how the pin ioctl stuff works.
@@ -245,6 +246,7 @@ STATIC const mp_rom_map_elem_t machine_module_globals_table[] = { | |||
{ MP_ROM_QSTR(MP_QSTR_disable_irq), MP_ROM_PTR(&machine_disable_irq_obj) }, | |||
{ MP_ROM_QSTR(MP_QSTR_enable_irq), MP_ROM_PTR(&machine_enable_irq_obj) }, | |||
|
|||
{ MP_ROM_QSTR(MP_QSTR_Signal), MP_ROM_PTR(&machine_signal_type) }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd group it with the other types/classes below, not with the time_pulse_us function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was grouped by criteria of "generic vs port-specific", but ok, done.
@@ -0,0 +1,118 @@ | |||
#include <stdio.h> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be moved to below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed debug header.
|
||
STATIC mp_obj_t signal_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { | ||
static const mp_arg_t allowed_args[] = { | ||
{ MP_QSTR_, MP_ARG_OBJ | MP_ARG_REQUIRED }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not name it "pin" so it doesn't give strange error messages if the argument is missing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because it's a positional parameter, so naming it wastes memory (without trying to imagine whether "pin" QSTR existed already, and will exist). An error message won't be more strange than for other functions which rightfully use guaranteedly existing empty QSTR.
struct { | ||
mp_arg_val_t pin; | ||
mp_arg_val_t inverted; | ||
} parsed_args; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Passing address of this doesn't guarantee that it can be interpreted as an array of mp_arg_val_t's. Instead better to use an array combined with enums, see, eg, machine_i2c_obj_init_helper.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I remember that discussion and I kinda didn't agree with it, that it makes sense to go thru extra hops given that member of a structure are naturally aligned. There're other cases of using a structure in the codebase.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, it's the C standard you don't agree with then. And there are many more uses of the enum in the code base so we should stick with that (git grep "enum { ARG_").
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The C standard talks about the alignment and the alignment, it doesn't say that they has to be different. The C standard probably doesn't guarantee user's access to preprocessor's output, and yet we rely on that.
And there are many more uses of the enum in the code base so we should stick with that (git grep "enum { ARG_").
Maybe. But then need to consistently convert all the cases. Then perhaps this case can be left until then too?
mp_obj_base_t base; | ||
mp_obj_t pin; | ||
bool inverted; | ||
} mp_signal_t; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be better to name it mp_machine_signal_t
(or even machine_signal_t
) so not to confuse with a possible internal signal mechanism of the core (like unix signals)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
mp_arg_check_num(n_args, n_kw, 0, 1, false); | ||
if (n_args == 0) { | ||
// get pin | ||
return MP_OBJ_NEW_SMALL_INT(mp_virtual_pin_read(self_in)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is going to be slow.... why not do the inversion here and call virtual_pin_read on self->pin?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps this was done to save code size, or just as a copy-paste of existing code. Optimizations can be left for later.
Well, this is initial step of the implementation, and it doesn't implement all ideas discussed in #2603. And I do keep in mind that bitbanging SPI/I2C needs to be fast. Then at the introduction of PinBase we agreed that mp_hal_pin_read/write() can be implemented per-port either as using generic polymorphic mp_hal_pin_read(), etc., or using fast port-adhoc versions. So, looking forward, we'd need to split it up into 2 API layers, by doing the same trick as you did with mp_hal_delay_us_fast() - mp_hal_pin_read() would guaranteedly support virtual pins (and thus Signals) and mp_hal_pin_read_fast() won't support Signals.
Well, that's not how it works. To make virtual pin API as fast as possible, it's a single indirect function call from a pointer stored in a structure: |
It can still be a single call. When accessing an object that you want to use as a pin (or stream, or block protocol for mounting, etc) the first thing you need to do is check that it has the required functionality. This is generally done "once" the first time the object is accessed. This check can check if it is native, or duck-typed. In the latter case it just returns an adaptor object that turns the o->type->protocol->ioctl() call into an underlying call to the relevant Python method. Note that this checking whether an object has the required functionality is needed so that you don't get crashes with, eg, passing a pin where a stream is expected. Using pure duck-typing (with no PinBase etc) also allows a single object to implement multiple protocols (not possible with subclassing native because you can't derive from multiple native classes). And this would be desirable, eg a custom pin object that can be selected/polled (needs both pin protocol and stream/poll protocol). |
bd9ba33
to
01b9db3
Compare
I'm not sure I follow. It needs to be stored somewhere. Do you mean, in addition to mp_obj_t pin, also store ioctl vmethod ptr? That's storing more. Here we have space, but in general...
Anyway, PinBase exists in the codebase, and this patch adds single new feature, using what exists in the codebase. If there's an idea how to get rid of PinBase without losing anything and even gaining something, it's still a separate idea. |
struct { | ||
mp_arg_val_t pin; | ||
mp_arg_val_t inverted; | ||
} parsed_args; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, it's the C standard you don't agree with then. And there are many more uses of the enum in the code base so we should stick with that (git grep "enum { ARG_").
@@ -374,6 +375,23 @@ STATIC mp_obj_t pyb_pin_irq(size_t n_args, const mp_obj_t *pos_args, mp_map_t *k | |||
} | |||
STATIC MP_DEFINE_CONST_FUN_OBJ_KW(pyb_pin_irq_obj, 1, pyb_pin_irq); | |||
|
|||
STATIC mp_uint_t pin_ioctl(mp_obj_t self_in, mp_uint_t request, uintptr_t arg, int *errcode); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason for these declarations? They are static functions so there shouldn't be any compiler warning without them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There should not be, but apparently, there were, thanks to over-zealous compilation warnings-errors in some builds.
return MP_OBJ_FROM_PTR(o); | ||
} | ||
|
||
STATIC mp_uint_t signal_ioctl(mp_obj_t self_in, mp_uint_t request, uintptr_t arg, int *errcode); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Extra declaration.
If I get time I'll make up a proof-of-concept patch. |
Maybe it's worth to open an RFC with description of how it would work to make sure it scales beyond just PinBase, e.g. to stream-in-python case mentioned above. |
For polymorphic interfacing on C level.
…ule. A signal is like a pin, but ca also be inverted (active low). As such, it abstracts properties of various physical devices, like LEDs, buttons, relays, buzzers, etc. To instantiate a Signal: pin = machine.Pin(...) signal = machine.Signal(pin, inverted=True) signal has the same .value() and __call__() methods as a pin.
01b9db3
to
297af60
Compare
Ok, fixed remaining concerns (array/struct, extra prototype) and merged. Thanks. |
This PR and the surrounding RFC discussion is really a good inspiration and lesson. Not that i can follow you both into all those details, but thanks anyways! Besides that it is quite annoying to learn from these discussions the steep and stony way, just because one of the two maintainers of MicroPython has blocked you on Github completely. |
See #2824 (it was in my git stash). |
mimxrt1011: Only re-init SPI when it's actually needed
This implements a bare minimum of #2603 . machine.Signal is a cornerstone feature allowing to write portable applications interacting with hardware. As an immediate goal, it allows to fix currently broken (not working per specification) examples in examples/hwapi/ . An example of fix for soft_pwm.py for ESP8266 ESP-12 module is included - with it, LED fading happens as a smooth continuous wave, whereas currently it's discontinuous pulsations with blackouts.