-
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
Feature/hlist pins #748
Feature/hlist pins #748
Conversation
147db0f
to
c0c3c74
Compare
Just had a very brief look - so just my first impressions, might be inaccurate or not thought through
t.b.h. I don't think we should have this in the HAL (but I am happy to hear other's voices) - the approach is interesting in user code ... if we can do something to create such lists in user code that's certainly fine As said - just a first impression and I'd like to hear more opinions |
Apart from the tie-in to the macro that generates the initial list, everything should be doable in an external utility crate. That initial list seems to be, however, rather annoying to define outside of the HAL currently. I only worry that not including this in esp-hal would make the feature practically invisible, as it's unlikely people search for the functionality.
It remains possible to pluck a pin and pass it to a peripheral. I believe the example is mostly useful to show an example user-defined wrapper. The HAL drivers could define a "pluck constructor" that take ownership over pins and pass them to the current constructors, but again, I believe this should be doable externally. Also, some peripherals, like SPI have way too many constructors already, doubling the number would be an API nightmare for newcomers to decode. As I see it, the point is that users don't have to name the pin to move out of the IO struct (i.e. the struct field isn't hardcoded any more). This is a small advantage that is visible when supporting multiple hardware configurations in a single codebase. |
As it currently stands, absolutely correct. I had this question in the periphery of my mind, but now that the initial draft is up, it needs to be front-and center. Something like this, I think, is needed: // Previously, USB::new would take 3 specific pins where the pin_list argument is.
// These three pins are restricted with marker traits/structs, so pins can't get mixed up.
// For example, esp32s3, the pins must be 18, 19 and 20 in arument 2, 3 and 4 respectively.
let (usb, pin_list) = USB::hl_new(
peripherals.USB0,
pin_list,
&mut system.peripheral_clock_control,
); inside impl<'d, S, P, M> USB<'d, S, P, M>
where
S: UsbSel + Send + Sync,
P: UsbDp + Send + Sync,
M: UsbDm + Send + Sync,
{
pub fn hl_new<T, U, ???>(
usb0: impl Peripheral<P = peripherals::USB0> + 'd,
io: IO<T>,
peripheral_clock_control: &mut PeripheralClockControl,
) -> (Self, IO<U>)
where
T: ???,
U: ???,
{
let (sel_pin, io) = io.pluck_pin();
let (dp_pin, io) = io.pluck_pin();
let (dm_pin, io) = io.pluck_pin();
let res = USB::new(usb0, sel_pin, dp_pin, dm_pin, peripheral_clock_control);
(res, io)
} Currently, the type-system constrains the user such that if they choose the wrong pin-number, there will be a compile failure. The benefit I hope to introduce, is to be able to just pass in the pin-list, and the type-system will automagically select the correct pins, by way of the marker trait/structs, and to raise an error if the pins are unavailable.
From my understanding, a specific pin must always be selected, but it can then be
The goal is for generics to only be needed by the user when they want to explicitly state a pin-number, or a pin property.
use hal::some_module::UsesPinN
let (pin_n, io) = io.pluck_pin();
let thing = UsesPinN::new(peripheral.SOME_PERIPH, pin_n, &mut system.peripheral_clock_control); in cross-platform code, where different esp32s use different
I agree that this can be improved. An initial thought is to alias it to
the backing crate, frunk, has the ability to obtain mutable references instead of managing ownership through types. When you have a moment, could you please refer me to an example so I can make sure this issue is addressed?
Thanks for taking the time to respond and critique. I've added some TODO points based on your feedback. |
Correct. I can look into initial list options as I move into the D of R&D
Consider this "Vision" example:#[entry]
fn main() -> ! {
// `default_init()` could be defined at the bottom of the generated file. The nature of hlist means that it should
// be inherently cross platform
// let resource_hlist = default_init();
// get a type-list collection of resources such as `peripherals`, `clocks`, `system`
let resource_hlist = user_defined_init();
// No shared resources unless explicitly required:
// - Can move pins into other structs
// - Pins can be owned directly by the peripheral device
// - This allows for easy seperation of concerns, and other maintain/readibility patterns.
//
// AppPeripherals would own each peripheral struct, which in turn would own their respective
// pins.
let (
AppPeripherals {
uart,
usb,
wifi,
blinker,
mouse,
},
resource_hlist
) = AppPeripherals::init_periphs(resource_hlist);
// The type can be inferred through the pluck constrants, and the way the result is used
let (io, resource_hlist)/*(IO<hal::InitialPinList>, InitialResources)*/ = resource_hlist.pluck();
// pins used by these peripherals are owned by them entirely. No needing to satisfy the
// borrow-checker with shared [mut] references, unless the nature of your app requires it.
let conns = init_conectivity(uart, usb, wifi);
run_app(conns, blinker, mouse, Some(io), resource_hlist)
} ^^^ that e.g. is a "far-future" thing, but it's something that could emerge out of efforts such as this one. Of course, that could all be lsp-inlined, and it would look a lot like the init-code in the examples. My hope and ambitionAllow a new design/implementation pattern that's considered maintainable, readable, "correct by default", contributes to lowering the barrier-of-entry, fun to use, and results in as significant an impact as it can merit. To give that dream the best chance of reality, I'm aiming for it to be included in the code base on the merit of its features, (potentially as a "proving ground" for upstreaming into In the meantime...I hope it makes sense to live in a draft PR so it can easily be kept up to date, iterated on, commented, etc. I welcome any suggestions.
Precisely. "All that's needed is for the type-system to infer the required pin". In this case: The blinky example approach of explicitly stating a const-generic value. Marker-traits/structs can also be used. Either user, or hal-defined.
I anticipate the vast majority of cases doing the following, which is basically just automating the ownership-moves that's currently forced onto the user:
I have some ideas to test/validate this, and added to the top-level todo.
I'm still working on finding the best way to articulate the full set of potential advantages. Here's what I have so far:
There are other potential advantages, which might prove significant:
|
Thank you for the contribution, I really am sorry that this fell off of my radar for so long. I think at this point we will not move forward with this; the maintainers will soon be meeting to discuss redesigning some of our core APIs, include GPIO, and I think our goals may not necessarily align with these changes. In the future perhaps this is something that can be revisited, however until we have made some concrete decisions regarding the future of our GPIO driver I do not think this makes sense right now. |
Still in draft. Need to expand examples, and check a few things, but the core of the idea is there.
The highlight: compare the traditional way with the hlist-way
Essentially boils down to this:
Instead of managing pins through struct-semantics, it's more like a
TupleSet
, with some special properties:O(0)
runtime-complexity (to be confirmed)TODOs
Plucker
andHList
that are more self-documenting.IO
struct toUSB::hl_new
, instead of selecting the correct 3 pins, then callingUSB::new
--release
performance of the hlist-based patterns is equivilent to/lighter than the struct-based approachMust
errors
orwarnings
.cargo fmt
was run.CHANGELOG.md
in the proper section.Nice to have