Let’s talk about GPIO (part 1)

Pray, Mr. Babbage, if you put into the machine wrong figures, will the right answers come out?’ I am not able rightly to apprehend the kind of confusion of ideas that could provoke such a question.” 

Charles Babbage

General Purpose Input Output (GPIO) is the primary way by which microcontrollers sense and change their environment. GPIO comes in both binary and analog forms, and any Microcontroller Unit (MCU) will have at least a few of each. GPIO originally was quite simple – a register in the MCU was connected directly to a pin on the MCU package, and it could be used to set the voltage of the pin to high or low, or to sense which level of voltage is present. Often there was a third option ‘X’ which means high-impendence and effectively disconnected the pin. Usually there are two registers associated with the GPIO pins, one to set the direction of the pin (I/O/X) and one to either set or report the voltage level.

Since then, however, GPIO has become much more sophisticated, and correspondingly more complicated. Let us take a deeper dive into modern GPIO, specifically that of the ESP32-S3 module. ESP32S3 pins use the 3v3 voltage level.

The place to start is as always the technical reference manual for the ESP32S3, specifically page 457 which introduces the GPIO and Input-Output Multiplexer (IOMUX) subsystem. Modern GPIO allows us to do more than have a single register bit hard mapped to a specific GPIO pin. The IOMUX allows us to map external GPIO pins to many different functions inside the chip. For example, we may map a pin as a digital output and connect it to a Pulse Width Modulation generator to output a motor control signal.

There is also the RTC IOMUX, which allows a subset of pins to be mapped to the signals of the ultra low power co-processor (either a RISC-V or an FSM implementation) .

In order for us to use a digital IO pin on the ESP32S3 then, we need to do several things:

  1. Decide which hardware pin we wish to use. This is not trivial, as some functions are only available on certain pins, some pins (strap) have special meaning on boot, etc. Each model and/or board will have its own pin map. The pin map for the ESP32S3 looks like this (from the ESP32-S3-DevKitC-1 documentation):
    Image of the pins on an ESP32S3 annotated with their function
  2. Having chosen our pin, we then need to instruct the ESP32S3 IOMUX as to the direction – input or output – which we wish to use that pin as. This may be done via several routes but the common one is the GPIO library from the ESP-IDF Peripherals API documentation.
    • gpio_reset_pin is the first function to be familiar with as it returns the pin to a known state.
    • gpio_config sets up the pin as a basic GPIO pin, without connecting it to any more complicated internal peripherals. It also allows us to configure the pin to trigger an interrupt when the pin state changes, assuming the pin is configured as an input. Interrupts may be level or edge triggered, and leading or trailing edge. Note also that the internal pin circuit of the ESP32S3 has weak pullup and pulldown resistors built-in, and they may be enabled or disabled. Don’t enable both at the same time! Always first disable them then enable the one you require. gpio_reset_pin sets the weak pullup to be enabled.
    • GPIO Mode may be one of several modes described by the enumeration here : gpio_mode_t. These modes configure the transistors and resistors in the pin hardware. The diagram for that is on page 459 of the Technical Reference Manual:

    • In output mode the maximum current a pin may provide is 40mA, but 20mA is preferred to keep temperature on the module lower.
    • There are individual functions to set the parts of the pin configuration, e.g. interrupt type or enable/disable interrupt.
    • Some peripherals such as SPI have dedicated pins, or may be mapped to other pins. Using the dedicated pins is preferable for performance reasons as the IOMUX may be bypassed in that case.
    • Additional functions allow us to configure the requested behavior of the pin in deep sleep power management mode, and to determine if activity on the pin will wake the SoC from deep sleep.
  3. In the case where we wish to connect a pin to an onboard peripheral such as a PWM generator, we need to invoke the IOMUX. The rather scary component looks like this:

    Most of the time, the library for the specific peripheral will accept a pin index as part of its configuration and will configure the pin and IOMUX correctly for us. Should we wish to do this directly there are functions in the GPIO library which support that.
    • We may also note on this diagram that the pins are equipped with a GPIO Filter and a GPIO Sync block. These blocks may be configured to clean up the digital signal on an input pin.
    • A subset of the full pin suite may be configured for use by the RTC GPIO MUX instead, which maps them to the ultra-low power co-processor.
    • A subset of the pins may also be configured as analog input or as capacitive touch sensors.
    • Also note that there are two inputs which are constant at 1 and 0 respectively.

This is the end of the first byte sized look at the ESP32S3 GPIO. The next article with cover more of the capabilities of this complex but important component.

4 responses to “Let’s talk about GPIO (part 1)”

  1. If I wanted to do something like have a on/off switch, or a momentary switch that as normally off, but rising edge triggered, how does one know what the best pins are for stuff like that?

    • Good question. For that, you could choose any of the digital IO pins which do not have dedicated usage such as an onboard peripheral or a bootstrapping (strap) purpose. Then you can use the gpio_config function to setup the type of interrupt, connect an Interrupt Service Routine (ISR) callback with gpio_intr_register and call the gpio_inter_enable function to start listening. Note that ISRs should be very short, and usually just set a flag or post an event to a proper RTOS task. ISR routines may also be stored in fast RAM instead of flash using ESP_INTR_FLAG_IRAM

  2. For those who will come across this, what do you think about representing “3.3 Volts” as both “3V3” and “also known as 3.3 Volts,” or something similar.

    • Good point, I used 3V3 as it is the notation often used on circuit diagrams but elaborating on the equivalent to 3.3v would make sense.