-
Notifications
You must be signed in to change notification settings - Fork 105
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
Implement RTC #143
Implement RTC #143
Conversation
Since you are using a custom, self made enum to represent possible clock sources for the RTC: Considering that many peripherals such as for example the ADC do allow multiple clock sources in theory (as of now we limited it to one, but the clock source is actually connected to a MUX we could configure if we so desired inside the chip) would it possibly make sense to have an enum (or a set of structs which implement some trait) which represents clocks we have available to us in general? Since I feel like if we were to actually fully expose the H7 as a Rust interface this is definitely a thing we need to have and reimplementing it over and over again wouldn't be beneficial. An basic idea without thinking it too much through in depth:
This has the advantage of having a centralized representation of clocks we can use across the chip. Furthermore the Rust compiler as well as docs.rs can still easily tell the user which clocks are allowed for this peripheral: docs.rs: Check who implementes the peripheral specific marker trait The disadvantage is of course that we increase our type signature, function signatures etc. by a bit which is not at all an issue for me but I have talked to people that would rather prefer if we did not do that. Any thoughts on this? Or am I just totally overthinking it at this point xD? |
Since you didn't mention it, I'll point you to our existing facilities for changing peripheral clocks: https://docs.rs/stm32h7xx-hal/0.7.1/stm32h7xx_hal/rcc/rec/struct.Cec.html#method.kernel_clk_mux (I didn't know/remember this existed until I started working on the RTC). Looking at the implementing macro you can see that many peripherals have no clock mux, many have group clock muxes where they share a clock source, and some have individual clock muxes: Lines 313 to 404 in e6da7dc
I think it's a reasonable abstraction once you know it exists. I think it'd certainly be possible to improve with some typesystem things, maybe represent the tree-like structure better, but I'm a bit wary about the complication. I remember being a rust noob and finding such things very confusing, and rustdoc is not great at exposing them. Back to the RTC, the reasons I didn't integrate with this existing clock system are:
I can imagine a system where the LSE is moved into the |
This is really useful! I haven't looked at it all yet, but looks reasonable so far. I think implementing Indeed it would be nice to share the LSE state with the CCDR struct somehow, but agreed that's not essential for this PR. |
/// | ||
/// This may only be written one time per peripheral reset. | ||
/// Check `get_kernel_clk_mux()` to see if the write succeeded. | ||
pub fn kernel_clk_mux(&mut self, sel: RtcClkSel) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I changed this from self
to &mut self
to make Rtc::handle_lse_css()
only need to take &mut self
, but it seems safe to me?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, that's reasonable (and certainly not "unsafe" from a memory safety point-of-view).
The reason for requiring self
is it forces the parent to be mutated when the kernel clock is mutated. You could also argue that actually handle_lse_css()
should also take self
, and it should return some new type called RtcFromLsi
or similar. Thinking in terms of a state machine, Rtc
and RtcFromLsi
are the states and handle_lse_css()
is a state transition - this kind of thinking is common in languages like Haskell and Erlang.
So don't think there's a right-or-wrong answer. Requiring self
does make it more difficult to use, especially if you're thinking imperatively. But it makes the program easier to reason about if you're thinking in terms of states.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That makes a lot of sense. I can envison an Rtc
parameterized by the clock source. The reset situation makes changing between clock states a bit tricky. I'd like to use this more to better understand what kind of interface would be helpful.
Thanks for the review! I'll leave this as draft until |
Actually it may be a while until 0.13 so I'll revert to the uglier implementation that works with the undocumented registers for now |
The example still doesn't work for me unfortunately. It actually needs to be loop {
info!("Time: {}", rtc.time().unwrap());
let date = rtc.date().unwrap(); // Added this line
rtc.unpend(rtc::Event::Wakeup);
asm::wfi();
} It appears to be due to RM0433 Rev 7. 46.3.9 Third Paragraph. The read to |
The first two paragraphs also talk about the required frequency of |
I really should've tested the example after I made those changes, but I left my nucleo board at home... I'll get it working. Thanks for the review! |
I think it's nearly there! After the example is working, I'll finish reviewing it 👍 |
Reading through that section of the RM again I see a few options:
It looks like both 1.B and 2 would work, but there's one more thing:
We don't support low-power mode in the HAL, though it's pretty common to use But it would be so much easier to just disable the shadow register. I can't see any reason not to. RSF is convenient to wait for shift and initialization to complete but we can check those flags manually if we have to. |
There were two additional issues which caused the example to not work:
@richardeoin this should be ready for review again when you have the time. |
Great, thanks for fixing up the example. I think that disabling the shadow registers and reading directly (BYPSHAD=1) is a good solution. It does mean every read takes a little longer but it's worth it. Both to avoid blocking waiting for RSF to go high, and needing to do things on wakeup. (BTW for the example in release mode, I measure about 500 cycles for the I just have one comment about |
bors r+ |
@mattico What do you think of the rustfmt check in CI? Having consistent formatting is nice, and it means there should never be any formatting noise in diffs. However it's quite annoying when submitting a PR, unless you have rustfmt-on-save in your editor (a personable choice imo). Could it be an optional check? Even better would be that bors runs rustfmt when merging the PR, but I don't know if bors supports that. |
@richardeoin I like having the code consistently formatted and I think it's good to have a PR check. The reason I have trouble with it is that I primarily develop stm32h7hh-hal in a submodule of my firmware project which uses nightly. The nightly rustfmt formats differently from the stable rustfmt that CI uses so I don't use format-on-save and need to remember to |
The RTC is a bit strange due to the backup power domain. I tried varying amounts of integration with the standard RCC, REC, PWR methods, but the implementation and semantics conflicted. Instead I added
Backup
to manage the backup power domain. Right now it's just the RTC but hypothetically it could help manage the backup SRAM and the LSE.I started out with a minimalDate
/Time
implementation but found myself implementing more and more functionality so I replaced it with thetime
crate. It has ano_std
mode but always requires an allocator. This isn't an issue for me but I might re-introduce the simpleDate
/Time
and maybe genericize functions that take one of those as an argument.I moved to the
chrono
crate since it doesn't require allocation.Interested in any feedback you have.
Blocked on: