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

Expose GPIO matrix to users #1662

Closed
Dominaezzz opened this issue Jun 5, 2024 · 7 comments · Fixed by #2128
Closed

Expose GPIO matrix to users #1662

Dominaezzz opened this issue Jun 5, 2024 · 7 comments · Fixed by #2128
Assignees
Labels
peripheral:gpio GPIO peripheral

Comments

@Dominaezzz
Copy link
Collaborator

At the moment, the drivers in the HAL take GPIO pins for each peripheral signal, which works for most cases.

However the ESP32 can also:

  1. Route a GPIO pin to multiple peripheral inputs.
  2. Route a peripheral output to multiple GPIO pins.
  3. Route a fixed 1 or 0 to peripheral's input.
  4. Route a GPIO's input to another GPIO's output. i.e. loopback

A use case for number 3 is #1515. Users should have the option to set a signal to a constant value instead of a GPIO pin.
A use case for number 1 is to be able to route the vsync pin to the I2sCamera driver and also listen for interrupts on the pin with Input.
Also see bjoernQ#1 .

@jessebraham jessebraham added the peripheral:gpio GPIO peripheral label Jun 10, 2024
@Dominaezzz
Copy link
Collaborator Author

See #1684 . Inversion shouldn't be implemented per peripheral but should be a general thing.

@jonored
Copy link

jonored commented Jun 23, 2024

We also really should have "route a peripheral output and some number of inputs to the same pin" - currently I'm hacking around esp-hal when doing 1-wire with the rmt peripheral to get the right configuration and it'd be good to have a proper solution.

@ProfFan
Copy link
Contributor

ProfFan commented Jun 23, 2024

GPIO matrix will be a great addition, many great hacks around RMT/I2S etc etc can be enabled by the GPIO matrix.

@Dominaezzz
Copy link
Collaborator Author

Another peripheral specific example of GPIO matrix features.

/// PcntPin can be always high, always low, or an actual pin
#[derive(Clone, Copy)]
pub struct PcntSource {
source: u8,
}
impl PcntSource {
pub fn from_pin<'a, P: InputPin>(
pin: impl Peripheral<P = P> + 'a,
pin_config: PcntInputConfig,
) -> Self {
crate::into_ref!(pin);
pin.init_input(
pin_config.pull == Pull::Down,
pin_config.pull == Pull::Up,
crate::private::Internal,
);
Self {
source: pin.number(crate::private::Internal),
}
}
pub fn always_high() -> Self {
Self { source: ONE_INPUT }
}
pub fn always_low() -> Self {
Self { source: ZERO_INPUT }
}
}

@ProfFan
Copy link
Contributor

ProfFan commented Jun 28, 2024

Another use case is I2C/SPI on multiple pins. This significantly improves board routing and allows much better signal integrity by avoiding long buses.

@jessebraham jessebraham added the status:needs-attention This should be prioritized label Jul 4, 2024
@Dominaezzz
Copy link
Collaborator Author

Another one #1769 . Number 3 is needed for this.

@tom-borcin tom-borcin removed the status:needs-attention This should be prioritized label Jul 15, 2024
@Dominaezzz
Copy link
Collaborator Author

Dominaezzz commented Jul 15, 2024

Some notes on the topic after reading the TRMs of the ESP32-S2, ESP32-S3 and ESP32-C6. (I won't be apologizing for the wall of text this time 🙂 )

Hardware facts

Every GPIO pad has 4 settings/control signals.

  • Input enable: If false, any reads from the pin/pad return false. If true, reads return the pin value.
  • Output enable: If false, pin/pad is disconnected. If true, it is connected.
  • (Weak) Pull up enable: If true, pin/pad is connected to the pull up.
  • (Weak) Pull down enable: If true, pin/pad is connected to the pull down.

Assuming input is enabled, the value/signal from the the pin is sent to the IO_MUX, RTC_IO_MUX and GPIO Matrix. (This means a peripheral can read from a pin via IO_MUX whilst the others read via the GPIO Matrix)
Assuming output is enabled, its value/signal must be configured to come from IO_MUX (or RTC_IO_MUX if main cpu if off) or GPIO Matrix.

Each peripheral input signal must be configured to come from either IO_MUX or GPIO Matrix.
Each peripheral output signal is sent to both the IO_MUX and GPIO Matrix.

If a signal between a pad and peripheral is to be inverted, filtered or synced, it must go through the GPIO Matrix.
If a pin is to be read/written directly it happens via the matrix.

Having laid out the hardware facts, how does this translate into Rust?

Software representation

If we were to take the purist approach we would have a separate object per gpio pin and peripheral input signal.

struct Io {
    gpio0: GpioPin<0>,
    gpio1: GpioPin<1>,
    gpioN: GpioPin<N>,

    signal0: PeripheralInput<0>,
    signal1: PeripheralInput<1>,
    signalN: PeripheralInput<N>,
}

GpioPin let's you configure the output settings and control signals.
PeripheralInput let's you configure the input settings for the input signal.
Each of these objects would be Send without needed any synchronization.

However this is rather cumbersome to deal with and we ideally want each driver to deal with the configuration of the pin and signal where possible, as this feels more natural/practical/convenient.

Unfortunately I wasn't able to come up with a sane/sensible model that allows you to change the pin/signal settings after a peripheral driver has been created. I sadly kept coming back to the purist model above (which I'm not happy with) so I will leave that as a future enhancement and settle for the configure once design below which is still strictly better than the current model.

Proposal (WIP)

With the restriction that we can only setup the pins/signals once at driver creation time, I propose this design (heavily inspired from @bjoernQ 's code/ideas) that solves the following goals.

  • Each driver should be able to express that the signals they need can come from GPIO Matrix and/or IO_MUX.
  • Each pin/pad should be able to express what functions it can perform.
  • Any attempt to use a pin/pad must enable it's corresponding input/output bit.
  • Use IO_MUX where possible.

The simple case

Giving full ownership of the pin the a driver. Ideally the driver will configure the pin to be in the perfect mode for operation, i.e. Use IO_MUX if possible.

let io = Io::new(....);

let driver = Driver::new(io.pins.gpio3);

The shared input case

Multiple peripherals want to read (not write) from a gpio pin.

let io = Io::new(....);

// Takes shared (not full) ownership of the pin and enables input mode.
let shared_pin = SharedPin::new(io.pins.gpio3);

let shared_pin2 = shared_pin.clone();
let shared_pin3 = shared_pin.clone();

let driver1 = Driver::new(shared_pin2);
let driver2 = Driver::new(shared_pin3);

let input = Input::new(shared_pin);
if input.is_high() {
    // do things
}

The fixed input case

A peripheral wants to read a constant 1 or 0.

let driver = Driver::new(GPIO_MATRIX_ONE);
let driver2 = Driver::new(GPIO_MATRIX_ZERO);

Perhaps the Level enum could be used for this instead. 🤔

The inverted input case

A peripheral is receiving input via the GPIO Matrix and the signal needs to be inverted.

let io = Io::new(....);

let inverted_pin = InvertedInput::new(io.pins.gpio3);

let driver = Driver::new(inverted_pin);

or even

let io = Io::new(....);

let shared_pin = SharedPin::new(io.pins.gpio3);
let shared_pin2 = shared_pin.clone();

let driver1 = Driver::new(shared_pin2);
let driver2 = Driver::new(InvertedInput::new(shared_pin3));

The interconnect case

User wants a pin to be connected to one output and multiple inputs. Useful for HIL and SPI 3Wire

let io = Io::new(....);

// Takes exclusive (not full) ownership of the pin and enables (undone in `Drop`) input and output mode.
let inter_pin = InterconnectPin::new(io.pins.gpio3);
let (output_pin, input_pin) = inter_pin.split();
let input_pin2 = input_pin.clone();

let driver1 = Driver::new(output_pin);
let driver2 = Driver::new(input_pin);
let driver3 = Driver::new(InvertedInput::new(input_pin2));

The multiple output case

User wants to send a peripheral's output to multiple pins.

TODO

The inverted output case

User wants to invert a peripheral's output to a specific pin.

TODO

The interconnect case but the output goes via IO_MUX

TODO

Required structs and traits

There will need to be a series of struct and traits that achieve the above and ensure that a user can't do the wrong/impossible thing and that there are no surprises.

WIP Pseudo code

// Existing structs and traits
pub enum Level { /* ... */ }
pub struct GpioPin<const GPIONUM: u8>;
pub trait Pin { /* ... */ };
pub trait InputPin: Pin { /* ... */ };
pub trait OutputPin: Pin { /* ... */ };


// New structs and traits

pub trait GpioMatrixInput<'a> {
    /// Returns pin number, 0x38 or 0x39
    fn source(&self) -> u8;
    fn is_inverted(&self) -> bool;
    fn can_use_io_mux(&self, signal: InputSignal) -> bool;
}

pub trait GpioMatrixOuput<'a> {
    fn configure(&mut self, signal: OutputSignal);
}


impl GpioMatrixInput for Level;


pub struct SharedPin<'d, P: InputPin> {
    source_pin: &'d P,
}
impl Clone for SharedPin;
impl GpioMatrixInput for SharedPin;
// impl InputPin for SharedPin;


pub struct InterconnectPin<'d, P: InputPin + OutputPin> {
    source_pin: &'d mut P,
}
impl InterconnectPin {
    pub fn split(self) -> (SharedPin<'d, P>, InterconnectedOutputPin<'d, P>);
}

pub struct InterconnectedOutputPin<'d, P: InputPin + OutputPin> {
    source_pin: &'d mut P,
}
impl GpioMatrixOutput for InterconnectedOutputPin;
// impl OutputPin for InterconnectedOutputPin;


pub struct Inverted<S> {
    s: S,
}

impl<S: GpioMatrixInput> GpioMatrixInput for Inverted<S>;
impl<S: GpioMatrixOutput> GpioMatrixOutput for Inverted<S>;

@bugadani bugadani self-assigned this Sep 9, 2024
@bugadani bugadani mentioned this issue Sep 9, 2024
6 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
peripheral:gpio GPIO peripheral
Projects
Archived in project
Development

Successfully merging a pull request may close this issue.

6 participants