-
Notifications
You must be signed in to change notification settings - Fork 141
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] Enable/disable functions #279
Comments
I'd be in favor of this. I think we shouldn't require the user to call
Since the purpose of the feature is reducing current draw, this seems like a good idea (but should be documented). |
Review of the peripherals:
At the moment, I don't see a preferred model. |
Thinking about it for a while, there are two models I could see yielding a structure that is intuitive to me: Model 1The HAL peripheral instance is kept around for a long time and can be enabled and disabled. That would require to consistently implement PROS
CONS
Model 2The HAL peripheral instance is only kept for the time it is actually used (e.g. to communicate). In the case of a system that sleeps most of the time, this would mean that the instance is created when the system wakes up and dropped before the system goes back to sleep. PROS
CONS
Any thoughts? |
Thank you for the table, that’s a really nice overview! I agree with all your points and would like to extend on some :) Model 1 is the most intuitive one to me. Good usability (without typestate) and you can get results quickly. Model 2 has the same shortcomings when used with Because I prefer easy to use APIs, I would go with Model 1 with assertions instead of typestate. Typestate tends to confuse newcomers. Not in the scope of |
I prefer model 1, because believe that HALs should provide a single (I always planned to take the HAL in that direction, but didn't get that far during my Oxidize 2019 refactoring, and never again found the time to pick it back up.) I also prefer using type state to track enabling/disabling (and other configuration, as appropriate), because it allows for APIs that eliminate the potential for user error, and because it's potentially zero-cost (unlike any form of runtime checking). I acknowledge that this is harder to learn and doesn't play well with RTIC, but I believe type state is such a useful tool (in this case, and in general) that making the API slightly harder to learn is a worthwhile trade-off (and a good investment for those learning). As for RTIC, as @timokroeger mentioned, there are (ugly and cumbersome) workarounds for right now. For the future, I think it would be great to find solutions that make RTIC play better with type state. I think indirectly putting some pressure on it by insisting on writing APIs based on type state, rather than preemptively accepting that type state is not viable, might be a better strategy to assist in achieving that 🙂 |
Thank you all for your comments. I think it is fair to say that there is a consensus to go with Model 1. As for the implementation, it seems that we have two main options: runtime checks or type states. I prefer the type state solution for the reasons pointed out by @hannobraun. However, I can see that an API based on type states might be harder to learn for people new to Rust and that some parts of the ecosystem are not ready (yet). @jonas-schievink and @hannobraun: As members of the nRF Rust organization, could you share some insight on what is planned for the future of this crate or how the future direction of the API is decided? I feel that going with type states would require a considerable rework of the crate. Hence, I would only make a push in that direction, if there is some agreement among the stewards of this crate that this is the desired future direction. |
I think we don't really need either. Disabling peripherals is something I don't expect too many people to do (only if you care about power usage), so always paying for the runtime checks is undesirable. Typestate complicates both the implementation and usage of most types provided by this crate, and I don't think it's fair to pressure the RTIC developers to making it easier to use by using it so aggressively (encoding state in the typesystem fundamentally makes it impossible to store a value in a variable and change the type-level state later on, so I don't really see what they could do here – if typestate can change dynamically you must lift it to a runtime value, for example by putting the type into an enum). The simplest possible solution would be to add enable and disable methods to all peripherals where they make sense, and maybe make them put GPIOs in the least energy-intensive state if needed. It seems reasonable to make them "advanced" APIs and to make it the user's responsibility to call |
Before I answer the comments, I'd like to clarify my role here: Yes, I'm a member of this org, yes I'm officially a maintainer here, but I haven't really done anything in forever. Please don't put any value in my opinions, unless you are convinced by my arguments. I certainly won't try to veto any decisions made here, even if I disagree with them. As for future plans, I've shared some of the plans I had for this HAL in my comment above, but since I never implemented them am unlikely to do so going forward, they are not any better than the plans anyone else here might come up with.
I'd say being able to enable and disable peripherals consistently would be an improvement, no matter what future vision we might or might not agree on. I certainly wouldn't object to a PR that introduced this capability, even if it doesn't follow my preferred style.
Okay, first off I'd like to clarify my use of the word "pressure" since that might be misunderstood. There are certainly many ways to pressure the RTIC developers that would be unfair, rude, or plain wrong. But I'm not talking about calling them at home every night, or hounding them on their issue tracker. We are certainly allowed to make the technical decisions that we think are right, even if those decisions don't play perfectly well with RTIC. Just like the RTIC developers were fully within their rights to make technical decisions that don't play well with type state, which now puts pressure on us not to use it (and there's certainly nothing unfair about that). And I object to the word "aggressively" in this context. You might reasonably disagree with the use of type state for certain things, but there's nothing inherently aggressive about using it to track peripheral state.
Storing types using type state in a variable and changing them is not fundamentally impossible; you mention enums in the same sentence 🙂 And if we agreed that using type state to track peripheral state were desirable, we could even provide these enums as part of the peripheral API. (And it might even be possible to use macro magic to generate them automatically, but I'm trailing off topic.) As for what RTIC could do, I made some suggestions in the issue that @timokroeger linked above (rtic-rs/rtic#345).
As I said above, I won't object to anyone adding those enable/disable methods (even without type state or checks), as I think having this capability would be an improvement either way. But I definitely think "let the user worry about it" is not a good solution. I've spent way too much time in my life tracking down problems that were caused by really simple mistakes like forgetting to enable something in the right place. I also object to relying on debug assertions. That's fine for relatively simple programs, but as soon as your program gets non-trivial, it suddenly becomes likely to miss a bug in a rarely called branch, causing an issue in production that could have easily been prevented at compile-time, if type state were used. I fully acknowledge that everything is a trade-off, and we can reasonably come to different conclusions. Due to my experience being stupid and making simple mistakes that took me way too much time to find, I'm firmly in the "make it reliable, and be willing to add (compile-time) complexity to do that" camp. |
I do object to adding struct Resources {
uarte: UARTE0,
pins: uarte::Pins,
}
struct Foo {
resources: Option<Resources>
}
impl Foo{
fn do_something_with_uart(&mut self, ...) {
// Take the resources, replacing with None
// unwrap will panic if resources already taken
let resources = self.resources.take().unwrap();
// Construct the uart
let mut uarte = uarte::Uarte::new(resources.uarte, resources.pins, Self::PARITY, Self::BAUDRATE);
// Use it
uarte.send(...).await;
// Deconstruct it
let (uarte, pins) = uarte.free();
// Put back the resources
self.resources = Some(Resources { uarte, pins }));
}
} The overhead from this is quite small. If it's unacceptable to the user, they can unsafely conjure the uart and pins from thin air with What is missing is having |
The use case supported by I think it is a good idea to make |
If user wants to store the peripheral somewhere such as a RTIC resource, they could do this: enum DisableableUart {
Disabled(UARTE0, uarte::Pins),
Enabled(Uarte<UARTE0, ..>),
} IMO this is much nicer than adding enable/disable.
|
I always imagined that one use case for |
I certainly have done that and I think we should have a way to access the PAC. Maybe with a different name and marked as unsafe: |
I am guilty of that too, hehe. However if you're willing to use unsafe you can already do this with |
@Dirbaio: Could you please expand on this some more? I find it intriguing, but I have tried to implement it and could not get it to work. |
There's another use-case that I just hit:
At the moment I'm stuck because I can't disable the SPI0 so that I can use the TWI0. |
I have not considered this use case until now. Thank you for bringing it to my attention. I have done some experiments in the last few weeks with typestates and async functions to get a better understanding of the problems. My current thinking is that for peripherals like SPI and TWI there is no reason to enable them in the constructor. They can be enabled when the communication is started and disabled right afterwards. In blocking functions, this would give the same behavior we see now, but with the additional benefit that less power is used. @pietgeursen, I would think that that would also cover your use case. |
Fwiw needed a way to enable/disable saadc. No strong opinions on the API. |
Background: I am working on a battery powered sensor based on the nRF52810. Hence, I am interested in having the chip consume the lowest amount of energy possible while it is sleeping.
Most peripherals start off disabled (in the nRF52810 all but the radio) and are enabled when the HAL instances are initialized. However, there is no way of (temporarily) disabling the function. Even when the structure is dropped, the peripheral is not disabled. I see three different options on how to move forward on this topic:
Bonus questions:
I'd be happy to work on these improvements and I would like to get some guidance how you would like to handle these topics (if at all). Please let me know what you think.
The text was updated successfully, but these errors were encountered: