-
Notifications
You must be signed in to change notification settings - Fork 208
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
Comments
See #1684 . Inversion shouldn't be implemented per peripheral but should be a general thing. |
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. |
GPIO matrix will be a great addition, many great hacks around RMT/I2S etc etc can be enabled by the GPIO matrix. |
Another peripheral specific example of GPIO matrix features. esp-hal/esp-hal/src/pcnt/channel.rs Lines 69 to 98 in d0cd890
|
Another use case is I2C/SPI on multiple pins. This significantly improves board routing and allows much better signal integrity by avoiding long buses. |
Another one #1769 . Number 3 is needed for this. |
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 factsEvery GPIO pad has 4 settings/control signals.
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) Each peripheral input signal must be configured to come from either IO_MUX or GPIO Matrix. If a signal between a pad and peripheral is to be inverted, filtered or synced, it must go through the GPIO Matrix. Having laid out the hardware facts, how does this translate into Rust? Software representationIf 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>,
}
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.
The simple caseGiving 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 caseMultiple 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 caseA 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 The inverted input caseA 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 caseUser 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 caseUser wants to send a peripheral's output to multiple pins. TODO The inverted output caseUser wants to invert a peripheral's output to a specific pin. TODO The interconnect case but the output goes via IO_MUXTODO Required structs and traitsThere 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>; |
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:
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 .
The text was updated successfully, but these errors were encountered: