-
Notifications
You must be signed in to change notification settings - Fork 216
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
The Io
struct has multiple constructors which makes it confusing to use correctly
#2009
Comments
IIRC correctly this driver is a bit different than all the others because we want people to easily mix
Maybe there are other options I just can't think of right now |
Can we allow per-pin ISR binding (i.e. similar to how we bind interrupts currently, put function pointers into a big table)? Would that help here? |
Then an additional parameter to Then maybe this "pinky promise" type can be easily acquired by either calling For the easy/common path, I'm picturing code that looks like this. let io = Io::new_async(peripherals.GPIO, peripherals.IO_MUX);
let button = Input::new_async(io.pins.gpio7, Pull::Up, io.handler); // name pending bikeshed Then for the uncommon path. let io = Io::new(peripherals.GPIO, peripherals.IO_MUX);
let handler = unsafe { GpioHandlerRegisteration::new() }; // name pending bikeshed
let button = Input::new_async(io.pins.gpio7, Pull::Up, handler); |
Why would you allow overriding the handler of the async API? Why would you require the async api to allow registering an irq handler? And thirdly, why would you need unsafe code for this? We can add an unsafe way to override the global handler, sure, but this is some unnecessary complication on the per-pin API. |
What do you mean? In my comment I described an "either or" situation.
I was trying to allow mixing of both async and custom irq.
To raise some eyebrows. I agree it's not strictly necessary. Having your async program hang is perfectly sound Rust.
I suppose that works. It ensures that users are aware of the fact that the async API won't work as expected if the handler is overridden. |
So what I have in mind:
This is simple as it has sane defaults, at least as far as my midnight brain is concerned. This is also flexible as we can mix and match async pins with blocking pins + irq handlers. There may be a slight perf hit caused by the fn table, but if the default handlers are in RAM, it shouldn't be too bad? What am I missing? |
These are all very good ideas 👍 However I think the original problem with RTIC is this one: To handle the GPIO interrupt they generate a function In the constructor the GPIO driver changes that handler address - so their implementation won't get called So maybe all it needs would be to use https://docs.esp-rs.org/esp-hal/esp-hal/0.20.0/esp32c3/esp_hal/interrupt/fn.bind_interrupt.html to replace the handler after creating the driver in RTIC (no need for the additional constructor then)? Or they could use https://docs.esp-rs.org/esp-hal/esp-hal/0.20.0/esp32c3/esp_hal/interrupt/fn.enable_direct.html since they want direct vectoring 🤔 But I have to admit I don't know enough about RTIC - I think last time I tried to use it was when it still was named RTFM |
This also means that RTIC, when it sits on the GPIO interrupt, is incompatible with a significant portion of our API (any async GPIO, any GPIO interrupt handling). How can we teach the users about this? |
We can add a enum IoInterrupt {
DoNotBind,
Default(Priority),
User(InterruptHandler)
} |
I think I would like to have it initialized in Users will still need to know when to do what but the enum is definitely more intuitive than a weird named second constructor. And while writing this, even if we don't want to move initialization to |
What exactly is "IO_MUX initialisation" ? It seems to be just setting the interrupt handler. |
Are you really asking why I want to hide something that the users need to type out currently, but don't really need? |
Yes that's what I'm asking sans the "but don't really need" bit. Just because most users tend to do something one particular way, doesn't mean it's "boilerplate" that should be pruned with alacrity. Since there is a consistent/uniform way to set an interrupt handler on all peripheral drivers, GPIO should follow suit. |
GPIOs can't really follow suit. To do so would require the ability to set a handler for each pin, which we can't really do (well, unless hackery, but that again removes the need for Io), or not in the same form as other peripherals. We have a single GPIO interrupt source, but users rarely use the peripheral as a single unit. A user-provided GPIO interrupt handler currently is hugely problematic as we don't have any guarantees against messing up. Advertising that as the normal way to use interrupts, just like in the case of other peripherals is currently a mistake. |
GPIO is currently following suit albeit not exactly since it's missing the My suggestion (which always prefer flexibility over ergonomics) would be to have an all or nothing API. Either you have the async API or you can set your handler. No mixing, just like all the other peripherals do. This way there's no room for mistakes or messing up. Btw, this isn't the only peripheral where a handler is shared between multiple objects, LCD_CAM and GDMA (on the chips with fewer channels) also have this issue. Do you want those handlers moved to Ideally, interrupt handler setting would be separate from the driver. Similar to how esp-idf does it and how embassy-stm32 (I cannot overstate how much I love their approach to interrupts) also does it. Just to be clear, removing |
Yes - it all boils down to this. Doing that would be an easy fix I guess But GPIO is also a bit special - e.g. a user might totally want to use async for almost everything but also wants to bit-bang a not too slow protocol where the penalty of asynchronously handling state changes is too much (ok - sounds a little bit made up but that was my understanding why we do things this way for GPIO (and only for GPIO)) |
Definitely not made up, I had the same problem with SPI (but the move API solved it for me).
I'm curious if this protocol could be implemented using one of the other peripherals. I still like my "I promise I'm calling the handler" type idea here tbh, though I'll admit it's somewhat verbose. |
As a workaround, in #1861 the
Io::new_no_bind_interrupt
constructor was added to allow initializing theIo
struct without binding theGPIO
interrupt.This is a bit of a footgun, and is too easy to mess up. We should try to find a better way to handle this scenario.
The text was updated successfully, but these errors were encountered: