-
Notifications
You must be signed in to change notification settings - Fork 201
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
[RFC] digital::IoPin
, pins that can switch between input and output modes at runtime
#29
Comments
@japaric I don't think it is quite common to switch a Pin between reading and writing. However often used is tje ability to "tri-state" a pin i.e. set high, low and floating. Reading is not necessarily the same as setting it floating because often a pull-up or pull-down can be configured and in that case the pin would not be floating but sink or source current, driving the pin useless. Also for the "Implicit / Automatic API" case above I can imagine that the read might not be optimised away, although the result is actually irrelevant, because it is done via volatile read. For my tri-state uses the "Implicit / Automatic API" is actually very intriguing but instead of providing the ability to read the value I'd add |
@therealprof OK, that sounds like a different use case from what was mentioned on IRC. A different trait for a tri-state pin makes sense. |
One other example that requires switching between in- and output of a pin is when implementing drivers for 1-wire protocols, like the one that the DS1820 temperature sensor uses I think. |
@japaric I agree. However it's a very important one because it's required for multiplexing and especially charlieplexing which is very common. Not sure how often direction change is used in real life -- I'm just very sure I've never encountered a use for that before. So we're going to have a TriPin then? 😁 |
I think, closure-base API places significant limitation on when you can change pin direction. It would be fine for localized direction changes (like in case of LCD, you would change the direction, wait for busy flag and switch it back to output), but I wonder, would it work for longer pin direction changes (walkie-talkie style)? Implicit API requires exclusive ownership, which is also somewhat limiting (although, I can't think of the realistic scenario) With result-based API, I wonder, what would be the realistic scenario of checking that error? Wouldn't it be better to simply panic instead of reporting an error? |
No. All those have different use cases and can be used independently.
Yeah, we could chalk it up as a programmer error and simply panic. I think (hope) the compiler will be able to optimize away the panic branches. Yet another option is to encode the type state change in the trait. I think it would look like this: pub trait IoPin {
type Input: InputPin + IoPin<Input = Self::Input, Output = Self::Output>;
type Output: OutputPin + IoPin<Input = Self::Input, Output = Self::Output>;
fn into_input(self) -> Self::Input;
fn into_output(self) -> Self::Output;
}
fn example<IO>(io: IO) -> IO::Output
where
IO: IoPin,
{
let input /* : IO::Input */ = io.into_input();
if input.is_low() {
// ..
}
let mut output = input.into_output();
output.set_high();
output
} It seems to compile at least. |
👍 Not quite sure why the |
I like the ability to encode direction in the type. Automatic switching at run-time requires checking overhead. The presence of |
Here is a DHT22 driver using proposal |
I strongly dislike the automatic mode switching variant. I've fought with it before in Particle land, where I wanted an analog input to be configured with a pulldown to present the proper output impedance to an external analog circuit. However, because Of the Result and closure based approaches, I favor the Result-based one. However, I would propose having the signatures of /// A pin that can switch between input and output modes at runtime
pub trait IoPin {
// ...
/// Checks if the pin is being driven low
fn is_low(&self) -> bool;
/// Checks if the pin is being driven high
fn is_high(&self) -> bool;
} Consider, for instance, the case where a pin configured as an open-drain input implements Alternatively, an |
In that case the state can be stored in the
The compiler doesn't allow ambiguous calls like that. You'll get an error about two methods, that come from different traits, named the same being in scope. You'll have to disambiguate using UFCS: |
Hello, I haven't read the full thread yet but let me list a few related things:
|
As @therealprof pointed out there might be platforms which does not support this is_high/is_low at all. In that case I would propose to introduce a
Or at least to rename the is_low / is_high in the current OutputPin to:
Because in OpenDrain mode both the InputPin and OutputPin traits need to be implemented and they work in the same time. The naming conflict between InputPin and OutputPin is not nice. |
Second @tib888's proposal to resolve the I'm assuming you meant, in the first case: trait StatefulOutputPin: OutputPin {
fn is_set_high(&self) -> bool;
fn is_set_low(&self) -> bool;
fn toggle();
} |
@austinglaser, thank you, I have fixed the typo. |
This is behind a flag for now, since it's incredibly hacky. Hopefully, rust-embedded/embedded-hal#29 will eventually have a nicer fix.
If we have an actionable PR that'd be much easier to discuss then a potpourri of ideas floating in free space.
Due to lack of interest. Feel free to reignite the discussion... |
Well here is my PR. |
thanks for the PR! i've added some further comments over there.
i've recently basically stopped using type-state for this reason, it's a nice abstraction for compile-time configuration, but, ends up requiring inordinate amounts of boilerplate when you have to wrap the states in enums and re-expose methods etc. |
I love this API for following reasons. There are some MCUs in market that could lock the input/output type of pins. Some of them may switch between input and output even after locked, where the input/output type cannot change. For example, I can set pin type to BTW, is there any other good words other than |
Hi, I would like to reignite this discussion. I also came to a roadblock when trying to implement a generic LCD interface that requires changing pins from output to input and vice versa. I personally favor this proposal by @japaric , and it's something that I see fitting well with I've seen so far of Rust: pub trait IoPin {
type Input: InputPin + IoPin<Input = Self::Input, Output = Self::Output>;
type Output: OutputPin + IoPin<Input = Self::Input, Output = Self::Output>;
fn into_input(self) -> Self::Input;
fn into_output(self) -> Self::Output;
}
fn example<IO>(io: IO) -> IO::Output
where
IO: IoPin,
{
let input /* : IO::Input */ = io.into_input();
if input.is_low() {
// ..
}
let mut output = input.into_output();
output.set_high();
output
} I even saw that @astro posted some sample code here that implements that proposal. |
+1 for @japaric's proposal. It uses Rust's type system to make errors impossible, i.e. to catch them at compile time. This is one of the main reasons why I find Rust great. I greatly dislike closure-based solutions because I find them quite unreadable. |
Before discovering this issue, I made a PR for a similar API in the The closure based one is good since it still preserves all the safety we're used to, but is not as flexible and slightly messy. |
They do return |
Reviewing the proposals again there's also another problem with the closure approach: In which mode will be GPIO be after the end of the closure? In some usecases you might want to stay in the previous state mode until you explicitly change it, in other cases you may want to switch modes. The |
Hmm, interesting point. I would assume that the pin is only in the new mode while the closure is running. Otherwise, would it change mode the first time you try to use it as the original mode? |
When working on an LCD driver I ran into the same issue with needing an I/O pin (that's switchable). I ended up going with one of @japaric's options. The pub trait IoPin {
type InputPinError: Debug;
type OutputPinError: Debug;
type Input: InputPin<Error = Self::InputPinError>
+ IoPin<
Input = Self::Input,
Output = Self::Output,
InputPinError = Self::InputPinError,
OutputPinError = Self::OutputPinError,
>;
type Output: OutputPin<Error = Self::OutputPinError>
+ IoPin<
Input = Self::Input,
Output = Self::Output,
InputPinError = Self::InputPinError,
OutputPinError = Self::OutputPinError,
>;
/// Switch to input mode
fn into_input(&mut self) -> &mut Self::Input;
/// Switch to output mode
fn into_output(&mut self) -> &mut Self::Output;
} An implementation of the above Getting an let pb9 = GPIOB::PB9::<PullDownInput, PushPullOutput>(
gpiob.pb9.into_dynamic(&mut gpiob_crh_ref.borrow_mut()),
&gpiob_crh_ref,
);
This is definitely an important issue though, there are many instances where a Rust device library would want switchable/RW pin functionality, and to have it cross-platform supported via |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This still hasn't been resolved...can we consider adding this API finally, or at least adding it and marking pub trait IoPin {
type Input: InputPin + IoPin<Input = Self::Input, Output = Self::Output>;
type Output: OutputPin + IoPin<Input = Self::Input, Output = Self::Output>;
fn into_input(self) -> Self::Input;
fn into_output(self) -> Self::Output;
} Thoughts? |
This comment has been minimized.
This comment has been minimized.
@chrismoos don't ask, do it. This a bit of a do-o-cracy. Ideally, also add PRs to some actual hal implementations and show an example project where this is needed. |
I've implemented a trait based loosely on the suggestion above by @chrismoos: #269 This is also implemented in linux-embedded-hal (PR), and its use is demonstrated here (no-std DHT11 driver). |
269: Add digital::IoPin to resolve issue 29 r=therealprof a=MorganR Closes #29. This implementation is inspired by [chrismoos's comment](#29 (comment)) in #29. I've demonstrated using this trait for a [no-std DHT11 driver](https://github.com/MorganR/rust-simple-sensors/blob/main/src/dht11.rs), and verified this on a Raspberry Pi 3B+ using an implementation in linux-embedded-hal (PR to follow). I'll also be sending a PR to implement this in at least one additional HAL implementation library (recommendations welcome). One question I have is how copyright/authorship is tracked in this repo? I believe the copyright owner for my code would technically be Google LLC. When patching repos, I'm meant to add that to the appropriate copyright/authors list. Co-authored-by: Morgan Roff <mroff@google.com>
269: Add digital::IoPin to resolve issue 29 r=therealprof a=MorganR Closes #29. This implementation is inspired by [chrismoos's comment](#29 (comment)) in #29. I've demonstrated using this trait for a [no-std DHT11 driver](https://github.com/MorganR/rust-simple-sensors/blob/main/src/dht11.rs), and verified this on a Raspberry Pi 3B+ using an implementation in linux-embedded-hal (PR to follow). I'll also be sending a PR to implement this in at least one additional HAL implementation library (recommendations welcome). One question I have is how copyright/authorship is tracked in this repo? I believe the copyright owner for my code would technically be Google LLC. When patching repos, I'm meant to add that to the appropriate copyright/authors list. Co-authored-by: Morgan Roff <mroff@google.com>
Some people have expressed interest in a trait like this for writing generic LCD drivers but there are probably other use cases.
We were discussing this on IRC yesterday and I proposed three different APIs:
Proposals
Result based API
Closure based API
Implicit / Automatic API
I have implemented the proposals as branches io-1, io-2, io-3. You can try them out by adding something like this to your Cargo.toml file:
Implementation concerns
There were some concerns about whether these traits can actually be implemented for all possible scenarios given that switching the mode of any pin on say, GPIOA, usually requires a RMW operation on some control register; thus the pins need exclusive access to the control register when switching modes.
Following the
CRL
idea of the Brave new IO blog post it seems that implementations would run into this problem:But you can use a
RefCell
to modify theCRL
register in turns:This limits you to a single context of execution though because
RefCell
is notSync
which means&'_ RefCell
is notSend
which meansIoPin
is notSend
either.But you can recover the
Send
-ness and avoid runtime checks by splittingCRL
in independent parts that can be modified using bit banding, if your chip supports that:Thoughts on these two proposals? Do they fit your use case? Do you have a different proposal?
cc @kunerd @therealprof
The text was updated successfully, but these errors were encountered: