Skip to content

[RFC] digital::OutputPort, atomically drive several pins #30

@japaric

Description

@japaric
Member

In the v0.1.0 release of this crate we have a digital::OutputPin that represents a single digital output pin. That trait is useful for, e.g., implementing the NSS pin of a SPI interface but not enough to implement 8 or 16 bit parallel port interfaces where all the pins need to change at the same time (atomically) to meet timing requirements (e.g. LCD interfaces) -- using 16 impl OutputPin would result in them changing state at different time intervals.

I think the obvious trait to use would be the following:

/// A digital output "port"
///
/// `Width` is the size of the port; it could be `u8` for an 8-bit parallel
/// port, `u16` for a 16-bit one, etc.
///
/// **NOTE** The "port" doesn't necessarily has to match a hardware GPIO port;
/// it could for instance be a 4-bit ports made up of non contiguous pins, say
/// `PA0`, `PA3`, `PA10` and `PA13`.
pub trait OutputPort<Width> {
    /// Outputs `word` on the port pins
    ///
    /// # Contract
    ///
    /// The state of all the port pins will change atomically ("at the same time"). This usually
    /// means that state of all the pins will be changed in a single register operation.
    fn output(&mut self, word: Width);
}

cc @kunerd

Activity

kunerd

kunerd commented on Jan 22, 2018

@kunerd

Never thought about this, but you are right, without an atomic set the communication timing could become broken by interrupts. But, wouldn't we than need an InputPort and an InputOutputPort, too?

japaric

japaric commented on Jan 22, 2018

@japaric
MemberAuthor

But, wouldn't we than need an InputPort and an InputOutputPort, too?

Possibly. You let me know what you need for your driver. :-)

idubrov

idubrov commented on Feb 2, 2018

@idubrov

I like the idea of being able to drive multiple pins separately as a single atomic thing -- for the timing reasons you've mentioned.

However, for the purpose of HD44780 LCD, I think, timings on data output are less important than being able to use pins from different GPIO ports (like GPIOA/GPIOB).

Even if bit operation is interrupted by, well, interrupt, it does not matter for HD44780 as data is transferred via dedicated "pulse" on EN bit after all data pins are set.

Given that flexibility of LCD, it would be tempting to assign pins to it which are "left" from the more demanding devices (like those requiring timer PWM, atomic operations on across multiple pins, etc), which could lead to the situation when all pins are taken from different ports.

Theoretically, you can design this trait to be able to span multiple ports and make it "do its best" to do as little operations as possible...

japaric

japaric commented on Feb 3, 2018

@japaric
MemberAuthor

@idubrov in that case I think we can split the atomic property into a marker trait so that where T: digital::OutputPort + Atomic will toggle all the pins atomically whereas where T: digital::Output may or may not change the state of the pins atomically. A driver for the HD44780 could make use of the latter bound.

kunerd

kunerd commented on Feb 8, 2018

@kunerd

I have done some quick testing and @idubrov seems to be right, for the HD44780 LCD driver the timing doesn't matter. The only situation where the output can become corrupted is an Interrupt that changes the state of the display Pins. Now, the question is, should preventing that be a part of the embedded-hal crate? If not I will go by with the InputPin, OutputPin and IoPin traits.

idubrov

idubrov commented on Feb 8, 2018

@idubrov

Well, in an ideal world, where you can have a perfect cut & slice on your GPIO (like all pins of GPIOA+GPIOB+GPIOC => Slice1 + Slice2 + Slice3), your interrupt should not have "mutable"/"exclusive" access to the same slice as your non-interrupt code writing to the display.

Regarding Atomic, my concern (could be unfounded, I haven't really done a lot of thinking), is that if you go for that "perfect solution" so to speak, with all these Atomic traits, ability to mix and match multiple pins, etc, wouldn't the complexity make it hard to use/implement?

idubrov

idubrov commented on Feb 8, 2018

@idubrov

Though, if you can have it, I would certainly love the solution that allows me to:

  1. Mix multiple GPIO's
  2. Split them into individual "slices" of pins/ranges of pins (like one slice could be "GPIOA1/GPIOA2/GPIOB3" without Atomic trait and another "GPIOC1/GPIOC2" with Atomic trait)
  3. Have these slices moveable, so I can pass them as a token of "exclusive" access
  4. Have them re-configurable, so I can reconfigure each individual slice as needed, without requiring any exclusive token for configuration registers (if memory bit-banding is available) and with requiring token (ownership of CRL/CRH, for example) if memory bit-banding is not-available (although, personally, I don't care about this case -- but it's probably important for those working with different devices).
  5. Maybe atomic reconfiguration, which would require configuration registers ownership and would only work for ranges with pins from the same register. Or even same half-register (as there are usually two different configuration registers for "high" pins and "low" pins.
  6. Don't know about integration with other peripherals. I think, as you start thinking of other peripherals, like timers, it becomes way too complicated (not all pins could be used as PWM outputs, but you can use alternative pin, or maybe inverted pin, or maybe pin from a different channel, etc).
  7. Reasonable compilation time/IDE ergonomics

@kunerd @japaric

therealprof

therealprof commented on Feb 8, 2018

@therealprof
Contributor

I see you guys are all very ST centric. ;) Are few things are more awkward in the ST world (like having different GPIO banks and sometimes high/low registers) while others are quite a bit more comfortable, like not having to use 10 different registers to send/receive data on an I2C port or having to manually reset events after them being triggered.

However one thing that pretty much all devices have in common is that often registers have to be split into individual bits or group of bits and ideally the control over the bits should be movable into abstracted types which cannot easily be done at the moment.

This should be abstracted in a way that allows a type to exercise control over exactly the required bits but nothing more which in some cases may mean it be done atomically (either due to the availability of bitbanding or separate set/clear registers) while in other cases it will mean that RMW is required.

japaric

japaric commented on Feb 13, 2018

@japaric
MemberAuthor

@idubrov all those sound like configuration details that both the trait and the generic driver author don't have to concern themselves about. Do you think the proposed trait would get in the way of implementing any / all of that configurability? I don't think so but won't know for sure until I sit down and try to implement it. I don't have a use case that requires implementing something so elaborated though so don't count on me implementing that.

requiring token (ownership of CRL/CRH, for example) if memory bit-banding is not-available

if you don't have bit banding you can do the RMW operation in a critical section and not require the token.

18 remaining items

Loading
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @eldruin@idubrov@pitaj@ryankurte@kunerd

        Issue actions

          [RFC] `digital::OutputPort`, atomically drive several pins · Issue #30 · rust-embedded/embedded-hal