# Dedicated GPIO

[中文]

## Overview

The dedicated GPIO is designed for CPU interaction with GPIO matrix and IO MUX. Any GPIO that is configured as "dedicated" can be access by CPU instructions directly, which makes it easy to achieve a high GPIO flip speed, and simulate serial/parallel interface in a bit-banging way. As toggling a GPIO in this "CPU Dedicated" way costs few overhead, it would be great for cases like performance measurement using an oscilloscope.

## Create/Destroy GPIO Bundle

A GPIO bundle is a group of GPIOs, which can be manipulated at the same time in one CPU cycle. The maximal number of GPIOs that a bundle can contain is limited by each CPU. What's more, the GPIO bundle has a strong relevance to the CPU which it derives from. Any operations on the GPIO bundle should be put inside a task which is running on the same CPU core to the GPIO bundle belongs to. Likewise, only those ISRs who are installed on the same CPU core are allowed to do operations on that GPIO bundle.

Note

Dedicated GPIO is more like a CPU peripheral, it has a strong relationship with CPU core. It's highly recommended to install and operate GPIO bundle in the same task, and the task should be pined to a CPU core. For example, if GPIO_A is connected to CPU_0, but the dedicated GPIO instruction is issued from CPU_1, then it's impossible to control GPIO_A.

To install a GPIO bundle, one needs to call `dedic_gpio_new_bundle()` to allocate the software resources and connect the dedicated channels to user selected GPIOs. Configurations for a GPIO bundle are covered in `dedic_gpio_bundle_config_t` structure:

- `dedic_gpio_bundle_config_t::gpio_array`: An array that contains GPIO number.

- `dedic_gpio_bundle_config_t::array_size`: Element number of `dedic_gpio_bundle_config_t::gpio_array`.

- `dedic_gpio_bundle_config_t::in_en` and `dedic_gpio_bundle_config_t::out_en` are used to configure whether to enable the input and output ability of the GPIO(s).

- `dedic_gpio_bundle_config_t::in_invert` and `dedic_gpio_bundle_config_t::out_invert` are used to configure whether to invert the GPIO signal.

The following code shows how to install an output only GPIO bundle:

    // Create bundleA, output only
    dedic_gpio_bundle_handle_t bundleA = NULL;
    dedic_gpio_bundle_config_t bundleA_config = {
        .gpio_array = bundleA_gpios,
        .array_size = sizeof(bundleA_gpios) / sizeof(bundleA_gpios[0]),
        .flags = {
            .out_en = 1,
        },
    };
    ESP_ERROR_CHECK(dedic_gpio_new_bundle(&bundleA_config, &bundleA));

To uninstall the GPIO bundle, you should call `dedic_gpio_del_bundle()`.

## GPIO Bundle Operations

Operations | Functions
--- | ---
Write to GPIOs in the bundle by mask | `dedic_gpio_bundle_write()`
Read the value that output from the given GPIO bundle | `dedic_gpio_bundle_read_out()`
Read the value that input to the given GPIO bundle | `dedic_gpio_bundle_read_in()`

Note

Using the above functions might not get a high GPIO flip speed because of the overhead of function calls and the bit operations involved inside. Users can try Manipulate GPIOs by Writing Assembly Code instead to reduce the overhead but should take care of the thread safety by themselves.

## Manipulate GPIOs by Writing Assembly Code

For advanced users, they can always manipulate the GPIOs by writing assembly code or invoking CPU Low Level APIs. The usual procedure could be:

1. Allocate a GPIO bundle: `dedic_gpio_new_bundle()`

2. Query the mask occupied by that bundle: `dedic_gpio_get_out_mask()` or/and `dedic_gpio_get_in_mask()`

3. Call CPU LL apis (e.g., dedic_gpio_cpu_ll_write_mask) or write assembly code with that mask

4. The fastest way of toggling IO is to use the dedicated "set/clear" instructions:

   > - Set bits of GPIO: `set_bit_gpio_out imm[7:0]`

   > - Clear bits of GPIO: `clr_bit_gpio_out imm[7:0]`

   > - Note: Immediate value width depends on the number of dedicated GPIO channels

For details of supported dedicated GPIO instructions, please refer to ESP32-S2 Technical Reference Manual > IO MUX and GPIO Matrix (GPIO, IO_MUX) [PDF].

Some of the dedicated CPU instructions are also wrapped inside `hal/dedic_gpio_cpu_ll.h` as helper inline functions.

Note

Writing assembly code in application could make your code hard to port between targets, because those customized instructions are not guaranteed to remain the same format on different targets.

## Interrupt Handling

Dedicated GPIO can also trigger interrupt on specific input event. All supported events are defined in `dedic_gpio_intr_type_t`.

One can enable and register interrupt callback by calling `dedic_gpio_bundle_set_interrupt_and_callback()`. The prototype of the callback function is defined in `dedic_gpio_isr_callback_t`. Keep in mind, the callback should return true if there's some high priority task woken up.

    // user defined ISR callback
    IRAM_ATTR bool dedic_gpio_isr_callback(dedic_gpio_bundle_handle_t bundle, uint32_t index, void *args)
    {
        SemaphoreHandle_t sem = (SemaphoreHandle_t)args;
        BaseType_t high_task_wakeup = pdFALSE;
        xSemaphoreGiveFromISR(sem, &high_task_wakeup);
        return high_task_wakeup == pdTRUE;
    }

    // enable positive edge interrupt on the second GPIO in the bundle (i.e., index 1)
    ESP_ERROR_CHECK(dedic_gpio_bundle_set_interrupt_and_callback(bundle, BIT(1), DEDIC_GPIO_INTR_POS_EDGE, dedic_gpio_isr_callback, sem));

    // wait for done semaphore
    xSemaphoreTake(sem, portMAX_DELAY);

## Application Example

- Software emulation (bit banging) of the UART/I2C/SPI protocols in assembly using the dedicated GPIOs and their associated CPU instructions: peripherals/dedicated_gpio.

- peripherals/gpio/matrix_keyboard demonstrates how to drive a matrix keyboard using the dedicated GPIO APIs, including manipulating the level on a group of GPIOs, triggering edge interrupt, and reading level on a group of GPIOs.

- peripherals/dedicated_gpio/soft_i2c demonstrates how to configure and use dedicated/fast GPIOs to emulate an I2C master, perform write-read transactions on the bus, and handle strict timing requirements by placing certain functions in IRAM.

- peripherals/dedicated_gpio/soft_uart demonstrates how to emulate a UART bus using dedicated/fast GPIOs on ESP32-S2, which can send and receive characters on the UART bus using a TX pin and an RX pin, with the baud rate and other configurations adjustable via menuconfig.

## API Reference

### Header File

- components/esp_driver_gpio/include/driver/dedic_gpio.h

- This header file can be included with:

  >     #include "driver/dedic_gpio.h"

- This header file is a part of the API provided by the `esp_driver_gpio` component. To declare that your component depends on `esp_driver_gpio`, add the following to your CMakeLists.txt:

  >     REQUIRES esp_driver_gpio

  > or

  >     PRIV_REQUIRES esp_driver_gpio

### Functions

esp_err_t dedic_gpio_get_out_mask(dedic_gpio_bundle_handle_t bundle, uint32_t *mask)

  Get allocated channel mask.

  Note

  Each bundle should have at least one mask (in or/and out), based on bundle configuration.

  Note

  With the returned mask, user can directly invoke LL function like "dedic_gpio_cpu_ll_write_mask" or write assembly code with dedicated GPIO instructions, to get better performance on GPIO manipulation.

  Parameters:

    - bundle -- [in] Handle of GPIO bundle that returned from "dedic_gpio_new_bundle"

    - mask -- [out] Returned mask value for on specific direction (in or out)

  Returns:

    - ESP_OK: Get channel mask successfully

    - ESP_ERR_INVALID_ARG: Get channel mask failed because of invalid argument

    - ESP_FAIL: Get channel mask failed because of other error

esp_err_t dedic_gpio_get_in_mask(dedic_gpio_bundle_handle_t bundle, uint32_t *mask)

esp_err_t dedic_gpio_get_out_offset(dedic_gpio_bundle_handle_t bundle, uint32_t *offset)

  Get the channel offset of the GPIO bundle.

  A GPIO bundle maps the GPIOS of a particular direction to a consecutive set of channels within a particular GPIO bank of a particular CPU. This function returns the offset to the bundle's first channel of a particular direction within the bank.

  Parameters:

    - bundle -- [in] Handle of GPIO bundle that returned from "dedic_gpio_new_bundle"

    - offset -- [out] Offset value to the first channel of a specific direction (in or out)

  Returns:

    - ESP_OK: Get channel offset successfully

    - ESP_ERR_INVALID_ARG: Get channel offset failed because of invalid argument

    - ESP_FAIL: Get channel offset failed because of other error

esp_err_t dedic_gpio_get_in_offset(dedic_gpio_bundle_handle_t bundle, uint32_t *offset)

esp_err_t dedic_gpio_new_bundle(const dedic_gpio_bundle_config_t *config, dedic_gpio_bundle_handle_t *ret_bundle)

  Create GPIO bundle and return the handle.

  Note

  One has to enable at least input or output mode in "config" parameter.

  Parameters:

    - config -- [in] Configuration of GPIO bundle

    - ret_bundle -- [out] Returned handle of the new created GPIO bundle

  Returns:

    - ESP_OK: Create GPIO bundle successfully

    - ESP_ERR_INVALID_ARG: Create GPIO bundle failed because of invalid argument

    - ESP_ERR_NO_MEM: Create GPIO bundle failed because of no capable memory

    - ESP_ERR_NOT_FOUND: Create GPIO bundle failed because of no enough continuous dedicated channels

    - ESP_FAIL: Create GPIO bundle failed because of other error

esp_err_t dedic_gpio_del_bundle(dedic_gpio_bundle_handle_t bundle)

  Destroy GPIO bundle.

  Parameters:

    bundle -- [in] Handle of GPIO bundle that returned from "dedic_gpio_new_bundle"

  Returns:

    - ESP_OK: Destroy GPIO bundle successfully

    - ESP_ERR_INVALID_ARG: Destroy GPIO bundle failed because of invalid argument

    - ESP_FAIL: Destroy GPIO bundle failed because of other error

void dedic_gpio_bundle_write(dedic_gpio_bundle_handle_t bundle, uint32_t mask, uint32_t value)

  Write value to GPIO bundle.

  Note

  The mask is seen from the view of GPIO bundle. For example, bundleA contains [GPIO10, GPIO12, GPIO17], to set GPIO17 individually, the mask should be 0x04.

  Note

  For performance reasons, this function doesn't check the validity of any parameters, and is placed in IRAM.

  Parameters:

    - bundle -- [in] Handle of GPIO bundle that returned from "dedic_gpio_new_bundle"

    - mask -- [in] Mask of the GPIOs to be written in the given bundle

    - value -- [in] Value to write to given GPIO bundle, low bit represents low member in the bundle

uint32_t dedic_gpio_bundle_read_out(dedic_gpio_bundle_handle_t bundle)

  Read the value that output from the given GPIO bundle.

  Note

  For performance reasons, this function doesn't check the validity of any parameters, and is placed in IRAM.

  Parameters:

    bundle -- [in] Handle of GPIO bundle that returned from "dedic_gpio_new_bundle"

  Returns:

    Value that output from the GPIO bundle, low bit represents low member in the bundle

uint32_t dedic_gpio_bundle_read_in(dedic_gpio_bundle_handle_t bundle)

  Read the value that input to the given GPIO bundle.

  Note

  For performance reasons, this function doesn't check the validity of any parameters, and is placed in IRAM.

  Parameters:

    bundle -- [in] Handle of GPIO bundle that returned from "dedic_gpio_new_bundle"

  Returns:

    Value that input to the GPIO bundle, low bit represents low member in the bundle

esp_err_t dedic_gpio_bundle_set_interrupt_and_callback(dedic_gpio_bundle_handle_t bundle, uint32_t mask, dedic_gpio_intr_type_t intr_type, dedic_gpio_isr_callback_t cb_isr, void *cb_args)

  Set interrupt and callback function for GPIO bundle.

  Note

  This function is only valid for bundle with input mode enabled. See "dedic_gpio_bundle_config_t"

  Note

  The mask is seen from the view of GPIO Bundle. For example, bundleA contains [GPIO10, GPIO12, GPIO17], to set GPIO17 individually, the mask should be 0x04.

  Parameters:

    - bundle -- [in] Handle of GPIO bundle that returned from "dedic_gpio_new_bundle"

    - mask -- [in] Mask of the GPIOs in the given bundle

    - intr_type -- [in] Interrupt type, set to DEDIC_GPIO_INTR_NONE can disable interrupt

    - cb_isr -- [in] Callback function, which got invoked in ISR context. A NULL pointer here will bypass the callback

    - cb_args -- [in] User defined argument to be passed to the callback function

  Returns:

    - ESP_OK: Set GPIO interrupt and callback function successfully

    - ESP_ERR_INVALID_ARG: Set GPIO interrupt and callback function failed because of invalid argument

    - ESP_FAIL: Set GPIO interrupt and callback function failed because of other error

### Structures

struct dedic_gpio_bundle_config_t

  Type of Dedicated GPIO bundle configuration.

  Public Members

  const int *gpio_array

    Array of GPIO numbers, gpio_array[0] ~ gpio_array[size-1] <=> low_dedic_channel_num ~ high_dedic_channel_num

  size_t array_size

    Number of GPIOs in gpio_array

  unsigned int in_en

    Enable input

  unsigned int in_invert

    Invert input signal

  unsigned int out_en

    Enable output

  unsigned int out_invert

    Invert output signal

  struct dedic_gpio_bundle_config_t::[anonymous] flags

    Flags to control specific behaviour of GPIO bundle

### Type Definitions

typedef struct dedic_gpio_bundle_t *dedic_gpio_bundle_handle_t

  Type of Dedicated GPIO bundle.

typedef bool (*dedic_gpio_isr_callback_t)(dedic_gpio_bundle_handle_t bundle, uint32_t index, void *args)

  Type of dedicated GPIO ISR callback function.

  Param bundle:

    Handle of GPIO bundle that returned from "dedic_gpio_new_bundle"

  Param index:

    Index of the GPIO in its corresponding bundle (count from 0)

  Param args:

    User defined arguments for the callback function. It's passed through `dedic_gpio_bundle_set_interrupt_and_callback`

  Return:

    If a high priority task is woken up by the callback function

### Enumerations

enum dedic_gpio_intr_type_t

  Supported type of dedicated GPIO interrupt.

  Values:

  enumerator DEDIC_GPIO_INTR_NONE

    No interrupt

  enumerator DEDIC_GPIO_INTR_LOW_LEVEL

    Interrupt on low level

  enumerator DEDIC_GPIO_INTR_HIGH_LEVEL

    Interrupt on high level

  enumerator DEDIC_GPIO_INTR_NEG_EDGE

    Interrupt on negedge

  enumerator DEDIC_GPIO_INTR_POS_EDGE

    Interrupt on posedge

  enumerator DEDIC_GPIO_INTR_BOTH_EDGE

    Interrupt on both negedge and posedge

Was this page helpful?

- Thank you! We received your feedback.
- If you have any comments, fill in Espressif Documentation Feedback Form.

- We value your feedback.
- Let us know how we can improve this page by filling in Espressif Documentation Feedback Form.