Skip to content
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

Open Drain Output #217

Closed
wants to merge 4 commits into from
Closed

Open Drain Output #217

wants to merge 4 commits into from

Conversation

knoby
Copy link
Contributor

@knoby knoby commented Aug 31, 2021

After the redesign of the crate you can't use the open drain output as discussed in #16 .

Perhaps this also solves the point " Re-implement tri-state mode in the new port API" in #130.

@Rahix
Copy link
Owner

Rahix commented Sep 8, 2021

Hi, thanks a lot for the PR.

I think the previous "tristate" mode was a bit misnamed, because it wasn't a full tristate implementation. I think what we actually want is two different modes:

1. OutputOpenDrain

Open-Drain should behave exactly as open-drain outputs on other platforms do. That means, strictly being an output where set_high() puts the pin into HIGHZ and set_low() pulls it to GND. This mode should just implement OutputPin and StatefulOutputPin.

2. Tristate

The tristate mode should be a flexible mode where there are actually 3 states and thus also 3 methods for setting the state. Those are:

  • set_high() for actually setting the pin HIGH (i.e. pull it to VCC).
  • set_tristate() for putting the pin into HIGHZ/tristate mode.
  • set_low() for pulling to GND.

Pins in this mode should now implement OutputPin (using the real set_high() and set_low()) and InputPin, but not StatefulOutputPin because the pin can be in a state which cannot be represented by that trait.

Still, outside the e-h traits, we should provide is_set_high(), is_set_low(), and is_set_tristate() methods for checking the current state. Importantly, these should reflect the actual state set, not the electrical value read from the pin. This means that even if an external pull-down is connected, after set_tristate(), is_set_tristate() should return true (but is_low() is also true, of course).


What do you think?

@Rahix Rahix added hal-api API design for the different components of avr-hal hal-generic Related to MCU generic parts of avr-hal labels Sep 8, 2021
Comment on lines +333 to +337
/// Set the pin to tristate mode (Input without PullUp)
#[inline]
pub fn set_tristate(&mut self) {
unsafe { self.pin.make_input(false) }
}
Copy link
Owner

Choose a reason for hiding this comment

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

To rephrase my previous comment: I think for the "open drain" mode, this method should be called set_high() for consistency. Reason is:

  • "open drain" and "push pull" are both used for binary outputs and thus just represent different "encodings" of the information. To make user-code not reliant on the used "encoding", I think the methods should have the same name.
  • in the embedded-hal trait impl, the method is already called set_high()
  • other HALs also keep the same method names irregardless of whether the pin is "push pull" or "open drain"


/// Convert this pin into an output pin with open drain, setting the state to tristate
/// See [Digital Output Open Drain](#digital-output-open-drain)
pub fn into_opendrain_tristate(mut self) -> Pin<mode::OpenDrain, PIN> {
Copy link
Owner

Choose a reason for hiding this comment

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

Similarly, this should then be into_opendrain_high().

Comment on lines +345 to +351
/// Check whether the pin is set high.
///
/// *Note*: The electrical state of the pin might differ due to external circuitry.
#[inline]
pub fn is_set_high(&self) -> bool {
unsafe { self.pin.in_get() }
}
Copy link
Owner

Choose a reason for hiding this comment

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

The is_set_high() method is supposed to return a deterministic answer to whether the pin was previously "set high" (= tristated for open-drain). Reading the pin input register here makes the result dependent on whether an outside source is driving the pin high or low.

The correct implementation would be one checking the bit in the DDR register for this pin. If it is set (= pin is output), we are currently driving the pin low. If it is cleared (= pin is input), the pin is tristated and is_set_high() should return true.

Unfortunately, PinOps does not yet expose any method for reading the DDR bit, so you'll need to add something like a dir_get() method there and also implement it in the implementation macro.

Comment on lines +357 to +359
pub fn is_set_low(&self) -> bool {
!unsafe { self.pin.in_get() }
}
Copy link
Owner

Choose a reason for hiding this comment

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

Following the above, is_set_low() should probably just be implemented in terms of is_set_high():

Suggested change
pub fn is_set_low(&self) -> bool {
!unsafe { self.pin.in_get() }
}
pub fn is_set_low(&self) -> bool {
!self.is_set_high()
}

The compiler will optimize it to be equivalent to a handrolled implementation.

Comment on lines +345 to +359
/// Check whether the pin is set high.
///
/// *Note*: The electrical state of the pin might differ due to external circuitry.
#[inline]
pub fn is_set_high(&self) -> bool {
unsafe { self.pin.in_get() }
}

/// Check whether the pin is set low.
///
/// *Note*: The electrical state of the pin might differ due to external circuitry.
#[inline]
pub fn is_set_low(&self) -> bool {
!unsafe { self.pin.in_get() }
}
Copy link
Owner

Choose a reason for hiding this comment

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

I think what you actually wanted to do here is implement is_high() and is_low(). Those return the actual electrical state on the pin and would be implemented the way you did it here (reading the PIN bit for each pin)

Comment on lines +381 to +387
fn is_high(&self) -> Result<bool, Self::Error> {
Ok(self.is_set_high())
}

fn is_low(&self) -> Result<bool, Self::Error> {
Ok(self.is_set_low())
}
Copy link
Owner

Choose a reason for hiding this comment

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

And these should then call self.is_high() / self.is_low() respectively.

@nikklassen
Copy link

@knoby do you think you could make the changes mentioned here? I'm had to fork this repo myself and implement Tristate to get some Arduino projects working.

@Rahix
Copy link
Owner

Rahix commented Feb 17, 2022

@nikklassen, if the author of this PR doesn't respond, feel free to send a new PR superseding this one. Especially if you already continued work in this direction in your fork!

@Rahix
Copy link
Owner

Rahix commented May 9, 2022

Superseeded by #263. Thanks for your initial work on this @knoby!

@Rahix Rahix closed this May 9, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
hal-api API design for the different components of avr-hal hal-generic Related to MCU generic parts of avr-hal
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants