-
Notifications
You must be signed in to change notification settings - Fork 200
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
Common architecture for .enable()
APIs
#614
Comments
I like the general idea but isn't this As for |
@bradleyharden, I can see value in that approach. I like that all the register writes happen in only one place. I'll look at #613 and give my impressions over there. |
@bradleyharden ping ping |
You're absolutely right on this. I forgot about that. I think I had in mind that most use cases for |
@vccggorski, I updated #613 to correct the |
@jbeaurivage, @vccggorski and @vcchtjader,
I (think?) I had some useful insights recently, while trying to solve #609. I wanted to lay out the problem and my proposed solution, to see if we can come up with a common approach that we all like. That would also help keep the APIs consistent across the HAL.
I'll start with the
spi
module. Right now, the two mainstruct
s look like this:Users create an
spi::Config
and use a builder API to configure it, before calling.enable()
, which consumes theConfig
and places it inside theSpi
. TheAnyKind
trait pattern is used (viaValidConfig
) to reduce the repetition of type parameters on theSpi
struct.Right now, each call of a builder API function on
Config
corresponds to a register write. I liked this approach, because it gives users maximum freedom, and it means you don't need to store any of the configuration withinConfig
; it is all stored directly in the registers.On the other hand, @vccggorski preferred a different approach for the
clock::v2
module. There, we define thesestruct
s.In this API, users create instances of
Gclk
directly.Gclk
also uses a builder API, but the configuration is stored within thestruct
. None of the builder-API functions actually write the registers. Only when calling.enable()
are the registers actually written. Then, theGclk
is packed inside theEnabled
struct, which provides compile-time counting of dependent clocks using theN
type parameter.@vccggorski and I have had a few debates about this approach. I don't really like that the configuration data is stored permanently within the
Gclk
. To me, it seems redundant and less compact than it could be.However, I have to admit that, on at least one occasion, I have run into a problem with initialization order in the
spi
module. Moreover, I suspect #609 is also caused by initialization order. From that, I conclude that we should probably store the configuration parameters locally, and perform all the register writes in.enable()
, so that we can guarantee the correct initialization order.So I was left with a problem. I want to store all the configuration options within
spi::Config
and perform all register writes in the.enable()
function, but I also don't want to bloat the size ofspi::Spi
. I think that is a fairly legitimate concern, too, because the fullConfig
struct ends up looking like this:You end up carrying around a minimum of something like 9 extra bytes for no real reason.
That's when I realized that the
Config
struct doesn't have to be stored withinSpi
. Instead, I can unpack the useful contents ofConfig
in the.enable()
call, and then drop the rest. With this approach,Spi
ends up looking like thiswhere its fields are limited to keep it as compact as possible. The only downside is that the type parameters have to be repeated on
Spi
. But I think that actually ended up simplifying things in this particular case.At this point, @jbeaurivage might ask how I handle reconfiguring an enabled
Spi
. The existing API has anSpi::reconfigure
function that will temporarily disable the peripheral, call a user-provided closure, and then re-enable the peripheral. The user-provided closure effectively had the signatureFnOnce(&mut Config)
. Users could then call methods of a non-builder API, with signatures likeWith the new approach, the
Config
struct no longer has the non-builder API, because it is never used for anything other than constructing anSpi
struct. Instead, I created a separateReconfig
struct that hasget_
andset_
methods, and I changed theSpi::reconfigure
function to take anFnOnce(Reconfig)
closure.I like this approach, because it provides the best of both worlds. It gives HAL authors complete control over creation and initialization, but it doesn't burden users with an unnecessarily bloated struct.
Moreover, I think the approach could be equally applicable to the
clock::v2
API. There, we could provide agclk::Config
struct that acts likespi::Config
. It would store all of the configuration options and perform all register writes in.enable()
. Thegclk::Gclk
struct could then be stripped down to only the necessary fields.At this point, I think @vccggorski might object, because he probably doesn't like the idea of calling
gclk::Config::new
to create a newGclk
. I also don't really like that, which is why I think we could provideExisting calls to
Gclk::new
would instead become calls toGclk::config
. I actually think that's better, because it indicates to the user that theGclk
still needs configuration and is not yet enabled.What do you all think of this approach? Do you see any problems or have any concerns? If it satisfies everyone, I would like to use this common approach in all of our code. I think that would give the HAL a very consistent feel.
Also, please see #613 for a draft of the new approach with the
spi
module. I think it's pretty much done, but I still need to update the documentation. I also don't want to go too far with it before I get buy-in from the rest of you.The text was updated successfully, but these errors were encountered: