Skip to content

Implement Observable POC #157

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

Merged
merged 11 commits into from
May 8, 2025
Merged

Conversation

usbalbin
Copy link
Member

@usbalbin usbalbin commented Dec 21, 2024

Alternative to #138

@AdinAck is this somewhere close to what you discribed in #138 ?

TODO

  • Test
    • ADC
    • Comp
    • DAC
    • GPIO
    • Opamp

Comment on lines 4 to 7
pub trait IntoObservationToken: Sized + crate::Sealed {
type Peripheral;
fn into_ot(self) -> ObservationToken<Self::Peripheral>;
}
Copy link
Member Author

@usbalbin usbalbin Dec 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had to do this since we can not do this due to the trait impl rules

impl<T: Observable> Into<ObservationToken<T>> for T {
    fn into(self) -> ObservationToken<T> {
        ObservationToken {
            _p: PhantomData
        }
    }
}

do you know if there is a better way?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The most recent version of this concept (with a few name changes and being used in a brand new context) is here. The simple solution to this is to implement From rather than Into. This is a good idea in general (save some rare edge cases).

impl<P> From<P> for ObservationToken<P>
where
    P: Observable,
{
    fn from(_: P) -> Self {
        Self { _p: PhantomData }
    }
}

Another option - which eliminates the need for calling .into() to directly pass a peripheral as an observation token - is to create a new trait called ObservationLock which is implemented by all P: Observable and ObservationTokens. Interfaces would thus hold impl ObservationLocks rather than concrete ObservationTokens. This is described in this gist.

The former is a solution I am putting to use right now in proto-hal, the latter I suspect is a better solution, but have not implemented quite yet.

Hope this helps!

Copy link
Contributor

@AdinAck AdinAck Dec 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe the ObservationLock trait is unneeded since the interfaces can accept an Into<ObservationToken<P>>. Clever!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we want the user to be able to destruct the consumer of the ObservationToken how does this affect things?

If the user passed an actual owned peripheral than would it not be best to just not convert it and let the consumer store the peripheral as is until it is destructed?

In that case would ObservationLock make more sense since that better signals what we are doing?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes! That was why ObservationLock was good. Since the peripherals would store an impl ObservationLock, the release() would return back whatever was originally passed in.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The former is a solution I am putting to use right now in proto-hal

Very interesting!

Copy link
Contributor

@AdinAck AdinAck left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great! Have you run any integration tests?

Copy link
Contributor

@AdinAck AdinAck left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Every occurrence of P should be P: Observable.

@@ -59,7 +61,7 @@ fn main() -> ! {
// * 0V at p1 => 0% duty
// * VDDA at p1 => 100% duty
loop {
dac.set_value(val);
dac.as_mut().set_value(val);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we even go as far as implementing Deref{Mut} for Observed?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, Deref will be a really good QoL feature.

@usbalbin
Copy link
Member Author

This looks great! Have you run any integration tests?

Thanks :) No, no tests at all yet

src/adc.rs Outdated
Comment on lines 139 to 142
impl Channel<stm32::$adc> for $pin {
impl<P> Channel<stm32::$adc> for P
where P: stasis::EntitlementLock<Resource = $pin>
{
Copy link
Member Author

@usbalbin usbalbin Dec 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not work. Both Channel and EntitlementLock (or rather P i suppose) are from outside this crate. There are also errors about conflicting implementations with the other opamps below.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the simplest solution is to make a local trait like AdcChannel which requires embedded_hal::adc::Channel and then it would work.

I'm not very familiar with what you're doing here though. I'll think on it.

You could also do concrete implementations since this is macro generated anyway.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could also do concrete implementations since this is macro generated anyway.

Yeah that would probably be the simplest solution. I'll give it a go

@usbalbin
Copy link
Member Author

@burrbull do you have any thoughts on this as a concept?

TLDR(please @AdinAck correct me where I am wrong here):

There are several cases where it is useful to have multiple observers of the same resource while preventing one observer from reconfiguring the resource in a way not compatible with the others.

For example a gpio pin may be "observed" by a comparator while still being usable for AD readings at the same time. However we want to prevent the user from reconfiguring the pin to something other than Analog mode.

Another example would be a Dac. We want it to be observable from the comparator and/or opamp while we still have access to change its output.

This proposes to solve this by introducing a trait Freeze that enables the user to freeze a resource that implements it. By freezing the resource you get a Frozen<YourResource, N> and an array of N number of Entitlement<YourResource> which are proof that the resource exists and is indeed frozen in the expected state. Observers like the comparator or opamp may then take Entitlement<YourResource> instead of YourResource as inputs.

By recombining all N Entitlement with the Frozen, the resource may once again be unfrozen(defrosted?)

@AdinAck
Copy link
Contributor

AdinAck commented Apr 19, 2025

You are exactly right!

Additional background: this is exactly what references are, the compiler just automatically tracks the "recombination" (dropping of references) and forbids moving or dropping the resource if a reference to it exists. We are electing not to use references for two reasons:

  1. Dealing with lifetimes.
  2. These resources are of zero size, and we leverage that to produce really good compiler optimizations. Currently, references to ZSTs are not ZSTs themselves so we cannot express composite structures of these resources as ZSTs as well.

The drawbacks of this approach is we need to explicitly dispatch "entitlements" which are our reference analog, and explicitly recombine them, which is a bit of a hassle.

@usbalbin usbalbin force-pushed the observable_peripherals branch 2 times, most recently from ffa9af6 to fcfef77 Compare April 21, 2025 15:08
@usbalbin usbalbin changed the base branch from staged-pac to main April 28, 2025 21:05
@usbalbin
Copy link
Member Author

usbalbin commented May 3, 2025

@AdinAck do you have any suggestions for how to move forward with this? Are you planning to release your stasis thing so we can depend on it from stm32g4xx-hal (and later I would probably want to use it for usbalbin/stm32-hrtim too). Or do you think I should have a local copy in stm32g4xx-hal for now? :)

@AdinAck
Copy link
Contributor

AdinAck commented May 3, 2025

I am still actively developing proto-hal and haven't reached a stable release point yet, so I wouldn't want my changes to affect you. I think a local copy makes sense, for now :)

@usbalbin
Copy link
Member Author

usbalbin commented May 3, 2025

Makes sense, thanks

Is it ok if I copy your stasis.rs? 8df2caf

@AdinAck
Copy link
Contributor

AdinAck commented May 3, 2025

Yep!

@usbalbin
Copy link
Member Author

usbalbin commented May 3, 2025

Yep!

Awesome! Thank you!

usbalbin and others added 6 commits May 4, 2025 00:54
copy proto-hal/src/stasis.rs from https://github.com/AdinAck/proto-hal/tree/main

Add `Ad` wrapper around ADC-type in Channel and OneShot impl to avoid problems with orphan rule"

Co-authored-by: Adin Ackerman <adinackerman@gmail.com>
@usbalbin usbalbin force-pushed the observable_peripherals branch from 917d672 to f1b56df Compare May 3, 2025 23:24
@usbalbin usbalbin marked this pull request as ready for review May 3, 2025 23:33
@@ -0,0 +1,316 @@
#![no_std]
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AdinAck

This looks great! Have you run any integration tests?

I have now added some more tests. These ones require a jumper(I took the one at CN12 at the top left corner) when using a Nucleo-G474 between A1 and A2. But I do believe that single modification opens up for some pretty interesting self tests

@usbalbin usbalbin merged commit a34ae60 into stm32-rs:main May 8, 2025
38 checks passed
@usbalbin usbalbin deleted the observable_peripherals branch May 8, 2025 13:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants