# Parallel IO TX Driver

[中文]

This document describes the functionality of the Parallel IO TX driver in ESP-IDF. The table of contents is as follows:

## Introduction

The Parallel IO TX unit is part of the general parallel interface, hereinafter referred to as the TX unit. It supports data communication between external devices and internal memory via GDMA on a parallel bus. Given the flexibility of IO data, the TX unit can be used as a general interface to connect various peripherals. The main application scenarios of this driver include:

- Driving LCD, LED displays

- High-speed parallel communication with other devices

- Simulating the timing of other peripherals when the number of peripherals is insufficient.

## Quick Start

This section will quickly guide you on how to use the TX unit driver. Through a simple example simulating QPI (Quad Peripheral Interface) transmission timing, it demonstrates how to create and start a TX unit, initiate a transmission transaction, and register event callback functions. The general usage process is as follows:

### Creating and Enabling the TX Unit

First, we need to create a TX unit instance. The following code shows how to create a TX unit instance to simulate QPI:

    parlio_tx_unit_handle_t tx_unit = NULL;
    parlio_tx_unit_config_t config = {
        .clk_src = PARLIO_CLK_SRC_DEFAULT,      // Select the default clock source
        .data_width = 4,                        // Data width is 4 bits
        .clk_in_gpio_num = -1,                  // Do not use an external clock source
        .valid_gpio_num = EXAMPLE_PIN_CS,       // Use the valid signal as chip select
        .clk_out_gpio_num = EXAMPLE_PIN_CLK,
        .data_gpio_nums = {
            EXAMPLE_PIN_DATA0,
            EXAMPLE_PIN_DATA1,
            EXAMPLE_PIN_DATA2,
            EXAMPLE_PIN_DATA3,
        },
        .output_clk_freq_hz = 10 * 1000 * 1000, // Output clock frequency is 10 MHz
        .trans_queue_depth = 32,                // Transaction queue depth is 32
        .max_transfer_size = 256,               // Maximum transfer size is 256 bytes
        .sample_edge = PARLIO_SAMPLE_EDGE_NEG,  // Sample data on the falling edge of the clock
        .flags = {
            .invert_valid_out = true, // The valid signal is high by default, inverted to simulate the chip select signal CS in QPI timing
        }
    };
    // Create TX unit instance
    ESP_ERROR_CHECK(parlio_new_tx_unit(&config, &tx_unit));
    // Enable TX unit
    ESP_ERROR_CHECK(parlio_tx_unit_enable(tx_unit));

When creating a TX unit instance, we need to configure parameters such as the clock source, data width, and output clock frequency through `parlio_tx_unit_config_t`. Then call the `parlio_new_tx_unit()` function to create a new TX unit instance, which will return a handle pointing to the new instance. The instance handle is essentially a pointer to the TX unit memory object, of type `parlio_tx_unit_handle_t`.

The following are the configuration parameters of the `parlio_tx_unit_config_t` structure and their explanations:

- `parlio_tx_unit_config_t::clk_src` Sets the clock source of the TX unit. Available clock sources are listed in `parlio_clock_source_t`, and only one can be selected. Different clock sources vary in resolution, accuracy, and power consumption.

- `parlio_tx_unit_config_t::clk_in_gpio_num` Uses an external clock as the clock source, setting the corresponding GPIO number for clock input. Otherwise, set to -1, and the driver will use the internal `parlio_tx_unit_config_t::clk_src` as the clock source. This option has higher priority than `parlio_tx_unit_config_t::clk_src`.

- `parlio_tx_unit_config_t::input_clk_src_freq_hz` The frequency of the external input clock source, valid only when `parlio_tx_unit_config_t::clk_in_gpio_num` is not -1.

- `parlio_tx_unit_config_t::output_clk_freq_hz` Sets the frequency of the output clock, derived from the internal or external clock source. Note that not all frequencies can be achieved, and the driver will automatically adjust to the nearest frequency when the set frequency cannot be achieved.

- `parlio_tx_unit_config_t::clk_out_gpio_num` The GPIO number for the output clock signal.

- `parlio_tx_unit_config_t::data_width` The data bus width of the TX unit, must be a power of 2 and not greater than 8.

- `parlio_tx_unit_config_t::data_gpio_nums` The GPIO numbers for TX data, unused GPIOs should be set to -1.

- `parlio_tx_unit_config_t::valid_gpio_num` The GPIO number for the valid signal, set to -1 if not used. The valid signal stays high level when the TX unit is transmitting data. Note that enabling the valid signal in some specific chips will occupy the MSB data bit, reducing the maximum data width of the TX unit by 1 bit. In this case, the maximum configurable data bus width is `SOC_PARLIO_TX_UNIT_MAX_DATA_WIDTH` / 2. Please check the return value of `parlio_new_tx_unit()`.

- `parlio_tx_unit_config_t::valid_start_delay` The number of clock cycles the valid signal will stay high level before the TX unit starts transmitting data. This configuration option depends on specific hardware features, and if enabled on unsupported chips or configured with invalid values, you will see an error message like `invalid valid delay`.

- `parlio_tx_unit_config_t::valid_stop_delay` The number of clock cycles the valid signal will stay high level after the TX unit finishes transmitting data. This configuration option depends on specific hardware features, and if enabled on unsupported chips or configured with invalid values, you will see an error message like `invalid valid delay`.

- `parlio_tx_unit_config_t::trans_queue_depth` The depth of the internal transaction queue. The deeper the queue, the more transactions can be prepared in the pending queue.

- `parlio_tx_unit_config_t::max_transfer_size` The maximum transfer size per transaction (in bytes).

- `parlio_tx_unit_config_t::dma_burst_size` The DMA burst transfer size (in bytes), must be a power of 2.

- `parlio_tx_unit_config_t::sample_edge` The data sampling edge of the TX unit.

- `parlio_tx_unit_config_t::bit_pack_order` Sets the order of data bits within a byte (valid only when data width < 8).

- `parlio_tx_unit_config_t::flags` Usually used to fine-tune some behaviors of the driver, including the following options

- `parlio_tx_unit_config_t::flags::invert_valid_out` Determines whether to invert the valid signal before sending it to the GPIO pin.

- `parlio_tx_unit_config_t::flags::clk_gate_en` Enables TX unit clock gating, the output clock will be controlled by the MSB bit of the data bus, i.e., by writing a high level to `parlio_tx_unit_config_t::data_gpio_nums` [`SOC_PARLIO_TX_UNIT_MAX_DATA_WIDTH` - 1] to enable clock output, and a low level to disable it. In this case, the data bus width needs to be configured as `SOC_PARLIO_TX_UNIT_MAX_DATA_WIDTH`. Note that if both the valid signal output and clock gating are enabled, clock gating can come from the valid signal. there is no limit on the data bus width. (Note that in some chips, the valid signal occupies the MSB data bit, so the maximum configurable data bus width is `SOC_PARLIO_TX_UNIT_MAX_DATA_WIDTH` / 2)

- `parlio_tx_unit_config_t::flags::allow_pd` Configures whether the driver allows the system to turn off the peripheral power in sleep mode. Before entering sleep, the system will back up the TX unit register context, and these contexts will be restored when the system exits sleep mode. Turning off the peripheral can save more power, but at the cost of consuming more memory to save the register context. You need to balance power consumption and memory usage. This configuration option depends on specific hardware features, and if enabled on unsupported chips, you will see an error message like `register back up is not supported`.

Note

If all TX units in the current chip have been requested, the `parlio_new_tx_unit()` function will return the `ESP_ERR_NOT_FOUND` error.

The TX unit must be enabled before use. The enable function `parlio_tx_unit_enable()` can switch the internal state machine of the driver to the active state, which also includes some system service requests/registrations, such as requesting a power management lock. The corresponding disable function is `parlio_tx_unit_disable()`, which will release all system services.

Note

When calling the `parlio_tx_unit_enable()` and `parlio_tx_unit_disable()` functions, they need to be used in pairs. This means you cannot call the `parlio_tx_unit_enable()` or `parlio_tx_unit_disable()` function twice in a row. This paired calling principle ensures the correct management and release of resources.

Note

Please note that after the TX unit is enabled, it will check the current work queue. If there are pending transmission transactions in the queue, the driver will immediately initiate a transmission.

### Initiating TX Transmission Transactions

After enabling the TX unit, we can configure some parameters for the transmission and call the `parlio_tx_unit_transmit()` to start the TX transaction. The following code shows how to initiate a TX unit transmission transaction:

    #define PAYLOAD_SIZE 128

    // Configure TX unit transmission parameters
    parlio_transmit_config_t transmit_config = {
        .idle_value = 0x00, // All data lines are low in idle state
    };

    // Prepare the data to be sent
    uint8_t payload[PAYLOAD_SIZE] = {0};
    for (int i = 0; i < PAYLOAD_SIZE; i++) {
        payload[i] = i;
    }

    // The first call to parlio_tx_unit_transmit will start the transmission immediately as there is no ongoing transaction
    ESP_ERROR_CHECK(parlio_tx_unit_transmit(tx_unit, payload, PAYLOAD_SIZE * sizeof(uint8_t) * 8, &transmit_config));
    // The second call to parlio_tx_unit_transmit may queue the transaction if the previous one is not completed, and it will be scheduled in the ISR context after the previous transaction is completed
    ESP_ERROR_CHECK(parlio_tx_unit_transmit(tx_unit, payload, PAYLOAD_SIZE * sizeof(uint8_t) * 8, &transmit_config));
    // (Optional) Wait for the TX unit to complete all transactions
    ESP_ERROR_CHECK(parlio_tx_unit_wait_all_done(tx_unit, -1));

The TX unit transmits data in bits, and the transmission bit length must be a multiple of the corresponding bus width. Calling `parlio_tx_unit_transmit()` to start the TX transaction, which requires parameters such as the unit handle, payload buffer, and payload size (in bits). Additionally, specific configurations for the transmission should be provided in `parlio_transmit_config_t`.

The following are the configuration parameters of the `parlio_transmit_config_t` structure and their explanations:

- `parlio_transmit_config_t::idle_value` Sets the value on the data lines when the TX unit is idle after transmission. This value will remain even after calling `parlio_tx_unit_disable()` to disable the TX unit.

- `parlio_transmit_config_t::bitscrambler_program` The pointer to the bitscrambler program binary file. Set to `NULL` if the bitscrambler is not used in this transmission.

- `parlio_transmit_config_t::flags` Usually used to fine-tune some behaviors of the transmission, including the following options

- `parlio_transmit_config_t::flags::queue_nonblocking` Sets whether the function needs to wait when the transmission queue is full. If this value is set to `true`, the function will immediately return the error code `ESP_ERR_INVALID_STATE` when the queue is full. Otherwise, the function will block the current thread until there is space in the transmission queue.

- `parlio_transmit_config_t::flags::loop_transmission` Setting this to `true` enables infinite loop transmission. In this case, the transmission will not stop unless manually calling `parlio_tx_unit_disable()`, and no "trans_done" event will be generated. Since the loop is controlled by DMA, the TX unit can generate periodic sequences with minimal CPU intervention.

`parlio_tx_unit_transmit()` internally constructs a transaction descriptor and sends it to the work queue, which is usually scheduled in the ISR context. Therefore, when `parlio_tx_unit_transmit()` returns, the transaction may not have started yet. Note that you cannot recycle or modify the contents of the payload before the transaction ends. By registering event callbacks through `parlio_tx_unit_register_event_callbacks()`, you can be notified when the transaction is complete. To ensure all pending transactions are completed, you can also call `parlio_tx_unit_wait_all_done()`, providing a blocking send function.

With simple configuration, we can send data in QPI format, as shown in the waveform below:

### Registering Event Callbacks

Since `parlio_tx_unit_transmit()` is an asynchronous interface, we may want to know when the transmission transaction is complete. The following code shows how to register an event callback for the transmission transaction done:

    static bool test_parlio_tx_done_callback(parlio_tx_unit_handle_t tx_unit, const parlio_tx_done_event_data_t *edata, void *user_ctx)
    {
        // General process for handling event callbacks:
        // 1. Retrieve user context data from user_ctx (passed in from test_parlio_tx_done_callback)
        // 2. Perform user-defined operations
        // 3. Return whether a high-priority task was woken up during the above operations to notify the scheduler to switch tasks

        BaseType_t high_task_wakeup = pdFalse;
        // Use FreeRTOS task handle as user context
        TaskHandle_t task = (TaskHandle_t)user_ctx;
        // Send task notification to the specified task upon transmission done
        vTaskNotifyGiveFromISR(task, &high_task_wakeup);
        // Return whether a high-priority task was woken up by this function
        return (high_task_wakeup == pdTRUE);
    }

    parlio_tx_event_callbacks_t cbs = {
        // Set test_parlio_tx_done_callback as the event callback function for transmission done
        .on_trans_done = test_parlio_tx_done_callback,
    };
    ESP_ERROR_CHECK(parlio_tx_unit_register_event_callbacks(tx_unit, &cbs, xTaskGetCurrentTaskHandle()));

When the TX unit generates events such as transmission done, it will notify the CPU via interrupts. If you need to call a function when a specific event occurs, you can call `parlio_tx_unit_register_event_callbacks()` to register event callbacks to the TX unit driver's interrupt service routine (ISR). Since the callback function is called in the ISR, complex operations (including any operations that may cause blocking) should be avoided in the callback function to avoid affecting the system's real-time performance. `parlio_tx_unit_register_event_callbacks()` also allows users to pass a context pointer to access user-defined data in the callback function.

For the event callbacks supported by the TX unit, refer to `parlio_tx_event_callbacks_t`:

- `parlio_tx_event_callbacks_t::on_trans_done` Sets the callback function for the "transmission complete" event, with the function prototype declared as `parlio_tx_done_callback_t`.

- `parlio_tx_event_callbacks_t::on_buffer_switched` Sets the callback function for the "buffer switch" event, with the function prototype declared as `parlio_tx_buffer_switched_callback_t`.

### Resource Recycling

When the TX unit is no longer needed, the `parlio_del_tx_unit()` function should be called to release software and hardware resources. Ensure the TX unit is disabled before deletion.

    ESP_ERROR_CHECK(parlio_tx_unit_disable(tx_unit));
    ESP_ERROR_CHECK(parlio_del_tx_unit(tx_unit));

## Advanced Features

After understanding the basic usage, we can further explore more advanced features of the TX unit driver.

### Using an External Clock as the TX Unit Clock Source

The TX unit can choose various clock sources, among which the external clock source is special. We enable the external clock source input by configuring `parlio_tx_unit_config_t::clk_src`, `parlio_tx_unit_config_t::clk_in_gpio_num`, and `parlio_tx_unit_config_t::input_clk_src_freq_hz`:

     parlio_tx_unit_handle_t tx_unit = NULL;
     parlio_tx_unit_config_t config = {
         .clk_src = PARLIO_CLK_SRC_EXTERNAL,         // Select external clock source
         .data_width = 4,                            // Data width is 4 bits
         .clk_in_gpio_num = EXAMPLE_PIN_CLK_IN,      // Set external clock source input pin
         .input_clk_src_freq_hz = 10 * 1000 * 1000,  // External clock source frequency is 10 MHz
         .valid_gpio_num = -1,                       // Do not use valid signal
         .clk_out_gpio_num = EXAMPLE_PIN_CLK_OUT,
         .data_gpio_nums = {
             EXAMPLE_PIN_DATA0,
             EXAMPLE_PIN_DATA1,
             EXAMPLE_PIN_DATA2,
             EXAMPLE_PIN_DATA3,
         },
         .output_clk_freq_hz = 5 * 1000 * 1000, // Output clock frequency is 5 MHz. Note that it cannot exceed the input clock frequency
         .trans_queue_depth = 32,
         .max_transfer_size = 256,
         .sample_edge = PARLIO_SAMPLE_EDGE_NEG,  // Sample data on the falling edge of the clock
     };
     // Create TX unit instance
     ESP_ERROR_CHECK(parlio_new_tx_unit(&config, &tx_unit));
     // Enable TX unit
     ESP_ERROR_CHECK(parlio_tx_unit_enable(tx_unit));

    #define PAYLOAD_SIZE 64

     // Configure TX unit transmission parameters
     parlio_transmit_config_t transmit_config = {
         .idle_value = 0x00, // All data lines are low in idle state
     };

     // Prepare the data to be sent
     uint8_t payload[PAYLOAD_SIZE] = {0};
     for (int i = 0; i < PAYLOAD_SIZE; i++) {
         payload[i] = i;
     }

     // Start transmission transaction
     ESP_ERROR_CHECK(parlio_tx_unit_transmit(tx_unit, payload, PAYLOAD_SIZE * sizeof(uint8_t) * 8, &transmit_config));

The waveform of the external clock input is shown below:

Note

The ratio of `parlio_tx_unit_config_t::input_clk_src_freq_hz` to `parlio_tx_unit_config_t::output_clk_freq_hz` determines the internal clock division factor of the TX unit. When the actual frequency of the external clock differs from `parlio_tx_unit_config_t::input_clk_src_freq_hz`, the actual output clock frequency generated by the TX unit will also change accordingly.

### Infinite Loop Transmission

ESP32-C5 supports infinite loop transmission, where the TX unit can generate periodic sequences without CPU intervention. By configuring `parlio_transmit_config_t::flags::loop_transmission`, we can enable infinite loop transmission

     parlio_tx_unit_handle_t tx_unit = NULL;
     parlio_tx_unit_config_t config = {
         .clk_src = PARLIO_CLK_SRC_DEFAULT,      // Select the default clock source
         .data_width = 4,                        // Data width is 4 bits
         .clk_in_gpio_num = -1,                  // Do not use an external clock source
         .valid_gpio_num = -1,                   // Do not use valid signal
         .clk_out_gpio_num = EXAMPLE_PIN_CLK,
         .data_gpio_nums = {
             EXAMPLE_PIN_DATA0,
             EXAMPLE_PIN_DATA1,
             EXAMPLE_PIN_DATA2,
             EXAMPLE_PIN_DATA3,
         },
         .output_clk_freq_hz = 10 * 1000 * 1000, // Output clock frequency is 10 MHz
         .trans_queue_depth = 32,
         .max_transfer_size = 256,
         .sample_edge = PARLIO_SAMPLE_EDGE_NEG,  // Sample data on the falling edge of the clock
         .flags = {
             .invert_valid_out = true,  // The valid signal is high by default, inverted to simulate the chip select signal CS in QPI timing
         }
     };
     // Create TX unit instance
     ESP_ERROR_CHECK(parlio_new_tx_unit(&config, &tx_unit));
     // Enable TX unit
     ESP_ERROR_CHECK(parlio_tx_unit_enable(tx_unit));

     #define PAYLOAD_SIZE 64

     // Configure TX unit transmission parameters
     parlio_transmit_config_t transmit_config = {
         .idle_value = 0x00, // All data lines are low in idle state
         .loop_transmission = true, // Enable infinite loop transmission
     };

     // Prepare the data to be sent
     uint8_t payload[PAYLOAD_SIZE] = {0};
     for (int i = 0; i < PAYLOAD_SIZE; i++) {
         payload[i] = i;
     }

     // Start loop transmission transaction
     ESP_ERROR_CHECK(parlio_tx_unit_transmit(tx_unit, payload, PAYLOAD_SIZE * sizeof(uint8_t) * 8, &transmit_config));

The waveform of the loop transmission is shown below:

In this case, the transmission will not stop unless manually calling `parlio_tx_unit_disable()`, and no "trans_done" event will be generated.

Note

If you need to modify the transmission payload after enabling infinite loop transmission, you can configure `parlio_transmit_config_t::flags::loop_transmission` and call `parlio_tx_unit_transmit()` again with a new payload buffer. The driver will switch to the new buffer after the old buffer is completely transmitted. You can register `parlio_tx_event_callbacks_t::on_buffer_switched` to set the callback function for the "buffer switch" event, and need to maintain two buffers to avoid data inconsistency caused by premature modification or recycling of the old buffer.

### Custom Bitstream Generation with BitScrambler

We can use the BitScrambler assembly code to control the data on the DMA path, thereby implementing some simple encoding work. Compared to using the CPU for encoding, the BitScrambler has higher performance and does not consume CPU resources, but is limited by the limited instruction memory of the BitScrambler, so it cannot implement complex encoding work.

After writing the BitScrambler program, we can enable it by calling `parlio_tx_unit_decorate_bitscrambler()`. And configure the `parlio_transmit_config_t::bitscrambler_program` to point to the binary file of the BitScrambler program. Different transmission transactions can use different BitScrambler programs. The binary file must conform to the BitScrambler assembly language specification, and will be loaded into the BitScrambler's instruction memory at runtime. For details on how to write and compile the BitScrambler program, please refer to BitScrambler Programming Guide.

`parlio_tx_unit_decorate_bitscrambler()` and `parlio_tx_unit_undecorate_bitscrambler()` need to be used in pairs. When deleting the TX unit, you need to call `parlio_tx_unit_undecorate_bitscrambler()` first to remove the BitScrambler.

### Power Management

When power management CONFIG_PM_ENABLE is enabled, the system may adjust or disable the clock source before entering sleep, causing the TX unit's internal time base to not work as expected.

To prevent this, the TX unit driver internally creates a power management lock. The type of lock is set according to different clock sources. The driver will acquire the lock in `parlio_tx_unit_enable()` and release the lock in `parlio_tx_unit_disable()`. This means that regardless of the power management policy, the system will not enter sleep mode, and the clock source will not be disabled or adjusted between these two functions, ensuring that any TX transaction can work normally.

In addition to turning off the clock source, the system can also turn off the TX unit's power to further reduce power consumption when entering sleep mode. To achieve this, set `parlio_tx_unit_config_t::allow_pd` to `true`. Before the system enters sleep mode, the TX unit's register context will be backed up to memory and restored when the system wakes up. Note that enabling this option can reduce power consumption but will increase memory usage. Therefore, when using this feature, you need to balance power consumption and memory usage.

### Thread Safety

The driver uses critical sections to ensure atomic operations on registers. Key members in the driver handle are also protected by critical sections. The driver's internal state machine uses atomic instructions to ensure thread safety, and use thread-safe FreeRTOS queues to manage transmit transactions. Therefore, TX unit driver APIs can be used in a multi-threaded environment without extra locking.

### Cache Safety

When the file system performs Flash read/write operations, the system temporarily disables the Cache function to avoid errors when loading instructions and data from Flash. This will cause the TX unit's interrupt handler to be unresponsive during this period, preventing user callback functions from being executed in time. If you want the interrupt handler to run normally while the Cache is disabled, you can enable the CONFIG_PARLIO_TX_ISR_CACHE_SAFE option.

Note

Note that after enabling this option, all interrupt callback functions and their context data must reside in internal memory. Because when the Cache is disabled, the system cannot load data and instructions from external memory.

Note

When the following options are enabled, the Cache will not be disabled automatically during Flash read/write operations. You don't have to enable the CONFIG_PARLIO_TX_ISR_CACHE_SAFE.

- CONFIG_SPI_FLASH_AUTO_SUSPEND

- CONFIG_SPIRAM_XIP_FROM_PSRAM

### Performance

To improve the real-time response capability of interrupt handling, the TX unit driver provides the CONFIG_PARLIO_TX_ISR_HANDLER_IN_IRAM option. Enabling this option will place the interrupt handler in internal RAM, reducing the latency caused by cache misses when loading instructions from Flash.

Note

However, user callback functions and context data called by the interrupt handler may still be located in Flash, and cache miss issues will still exist. Users need to place callback functions and data in internal RAM, for example, using `IRAM_ATTR` and `DRAM_ATTR`.

### Other Kconfig Options

- CONFIG_PARLIO_ENABLE_DEBUG_LOG option allows forcing the enablement of all debug logs of the TX unit driver, regardless of the global log level setting. Enabling this option can help developers obtain more detailed log information during debugging, making it easier to locate and solve problems. This option is shared with the RX unit driver.

### Resource Consumption

Use the IDF Size tool to view the code and data consumption of the TX unit driver. The following are the test conditions (taking ESP32-H2 as an example):

- The compiler optimization level is set to `-Os` to ensure the minimum code size.

- The default log level is set to `ESP_LOG_INFO` to balance debugging information and performance.

- The following driver optimization options are disabled:

    - CONFIG_PARLIO_TX_ISR_HANDLER_IN_IRAM - The interrupt handler is not placed in IRAM.

    - CONFIG_PARLIO_TX_ISR_CACHE_SAFE - The Cache safety option is not enabled.

Note that the following data is not precise and is for reference only. The data may vary on different chip models and different versions of IDF.

Component Layer | Total Size | DIRAM | .bss | .data | .text | Flash Code | .rodata | .text
--- | --- | --- | --- | --- | --- | --- | --- | ---
soc | 92 | 0 | 0 | 0 | 0 | 92 | 0 | 92
hal | 18 | 0 | 0 | 0 | 0 | 18 | 0 | 18
driver | 6478 | 12 | 12 | 0 | 0 | 6466 | 586 | 5880

In addition, each TX unit handle dynamically allocates about `800` bytes of memory from the heap (transmission queue depth is 4). If the `parlio_tx_unit_config_t::flags::allow_pd` option is enabled, each TX unit will consume an additional `32` bytes of memory during sleep to save the register context.

## Application Examples

- peripherals/parlio/parlio_tx/simple_rgb_led_matrix demonstrates how to use the TX unit driver of ESP32-C5 to support HUB75 interface RGB LED matrix panels and use the LVGL library to display simple UI elements.

- peripherals/parlio/parlio_tx/advanced_rgb_led_matrix demonstrates how to use the infinite loop transmission feature of the TX unit of ESP32-C5 to support HUB75 interface RGB LED matrix panels. Compared to the simple_rgb_led_matrix example, it does not require manual loop scanning and is more flexible.

- peripherals/lcd/parlio_simulate demonstrates how to use the TX unit driver of the parallel IO peripheral to drive screens with SPI or I80 interfaces.

## API Reference

### Header File

- components/esp_driver_parlio/include/driver/parlio_tx.h

- This header file can be included with:

  >     #include "driver/parlio_tx.h"

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

  >     REQUIRES esp_driver_parlio

  > or

  >     PRIV_REQUIRES esp_driver_parlio

### Functions

esp_err_t parlio_new_tx_unit(const parlio_tx_unit_config_t *config, parlio_tx_unit_handle_t *ret_unit)

  Create a Parallel IO TX unit.

  Parameters:

    - config -- [in] Parallel IO TX unit configuration

    - ret_unit -- [out] Returned Parallel IO TX unit handle

  Returns:

    - ESP_OK: Create Parallel IO TX unit successfully

    - ESP_ERR_INVALID_ARG: Create Parallel IO TX unit failed because of invalid argument

    - ESP_ERR_NO_MEM: Create Parallel IO TX unit failed because of out of memory

    - ESP_ERR_NOT_FOUND: Create Parallel IO TX unit failed because all TX units are used up and no more free one

    - ESP_ERR_NOT_SUPPORTED: Create Parallel IO TX unit failed because some feature is not supported by hardware, e.g. clock gating

    - ESP_FAIL: Create Parallel IO TX unit failed because of other error

esp_err_t parlio_del_tx_unit(parlio_tx_unit_handle_t unit)

  Delete a Parallel IO TX unit.

  Parameters:

    unit -- [in] Parallel IO TX unit that created by `parlio_new_tx_unit`

  Returns:

    - ESP_OK: Delete Parallel IO TX unit successfully

    - ESP_ERR_INVALID_ARG: Delete Parallel IO TX unit failed because of invalid argument

    - ESP_ERR_INVALID_STATE: Delete Parallel IO TX unit failed because it is still in working

    - ESP_FAIL: Delete Parallel IO TX unit failed because of other error

esp_err_t parlio_tx_unit_enable(parlio_tx_unit_handle_t unit)

  Enable the Parallel IO TX unit.

  Note

  This function will transit the driver state from init to enable

  Note

  This function will acquire a PM lock that might be installed during channel allocation

  Note

  If there're transaction pending in the queue, this function will pick up the first one and start the transfer

  Parameters:

    unit -- [in] Parallel IO TX unit that created by `parlio_new_tx_unit`

  Returns:

    - ESP_OK: Enable Parallel IO TX unit successfully

    - ESP_ERR_INVALID_ARG: Enable Parallel IO TX unit failed because of invalid argument

    - ESP_ERR_INVALID_STATE: Enable Parallel IO TX unit failed because it is already enabled

    - ESP_FAIL: Enable Parallel IO TX unit failed because of other error

esp_err_t parlio_tx_unit_disable(parlio_tx_unit_handle_t unit)

  Disable the Parallel IO TX unit.

  Note

  This function will transit the driver state from enable to init

  Note

  This function will release the PM lock that might be installed during channel allocation

  Note

  If one transaction is undergoing, this function will terminate it immediately

  Parameters:

    unit -- [in] Parallel IO TX unit that created by `parlio_new_tx_unit`

  Returns:

    - ESP_OK: Disable Parallel IO TX unit successfully

    - ESP_ERR_INVALID_ARG: Disable Parallel IO TX unit failed because of invalid argument

    - ESP_ERR_INVALID_STATE: Disable Parallel IO TX unit failed because it's not enabled yet

    - ESP_FAIL: Disable Parallel IO TX unit failed because of other error

esp_err_t parlio_tx_unit_register_event_callbacks(parlio_tx_unit_handle_t tx_unit, const parlio_tx_event_callbacks_t *cbs, void *user_data)

  Set event callbacks for Parallel IO TX unit.

  Note

  User can deregister a previously registered callback by calling this function and setting the callback member in the `cbs` structure to NULL.

  Note

  When CONFIG_PARLIO_TX_ISR_CACHE_SAFE is enabled, the callback itself and functions called by it should be placed in IRAM. The variables used in the function should be in the SRAM as well. The `user_data` should also reside in SRAM.

  Parameters:

    - tx_unit -- [in] Parallel IO TX unit that created by `parlio_new_tx_unit`

    - cbs -- [in] Group of callback functions

    - user_data -- [in] User data, which will be passed to callback functions directly

  Returns:

    - ESP_OK: Set event callbacks successfully

    - ESP_ERR_INVALID_ARG: Set event callbacks failed because of invalid argument

    - ESP_FAIL: Set event callbacks failed because of other error

esp_err_t parlio_tx_unit_transmit(parlio_tx_unit_handle_t tx_unit, const void *payload, size_t payload_bits, const parlio_transmit_config_t *config)

  Transmit data on by Parallel IO TX unit.

  Note

  After the function returns, it doesn't mean the transaction is finished. This function only constructs a transaction structure and push into a queue.

  Parameters:

    - tx_unit -- [in] Parallel IO TX unit that created by `parlio_new_tx_unit`

    - payload -- [in] Pointer to the data to be transmitted

    - payload_bits -- [in] Length of the data to be transmitted, in bits

    - config -- [in] Transmit configuration

  Returns:

    - ESP_OK: Transmit data successfully

    - ESP_ERR_INVALID_ARG: Transmit data failed because of invalid argument

    - ESP_ERR_INVALID_STATE: Transmit data failed because the Parallel IO TX unit is not enabled

    - ESP_FAIL: Transmit data failed because of other error

esp_err_t parlio_tx_unit_wait_all_done(parlio_tx_unit_handle_t tx_unit, int timeout_ms)

  Wait for all pending TX transactions done.

  Parameters:

    - tx_unit -- [in] Parallel IO TX unit that created by `parlio_new_tx_unit`

    - timeout_ms -- [in] Timeout in milliseconds, `-1` means to wait forever

  Returns:

    - ESP_OK: All pending TX transactions is finished and recycled

    - ESP_ERR_INVALID_ARG: Wait for all pending TX transactions done failed because of invalid argument

    - ESP_ERR_TIMEOUT: Wait for all pending TX transactions done timeout

    - ESP_FAIL: Wait for all pending TX transactions done failed because of other error

### Structures

struct parlio_tx_unit_config_t

  Parallel IO TX unit configuration.

  Public Members

  parlio_clock_source_t clk_src

    Parallel IO internal clock source

  gpio_num_t clk_in_gpio_num

    If the clock source is input from external, set the corresponding GPIO number. Otherwise, set to `-1` and the driver will use the internal `clk_src` as clock source. This option has higher priority than `clk_src`

  uint32_t input_clk_src_freq_hz

    Frequency of the input clock source, valid only if `clk_in_gpio_num` is not `-1`

  uint32_t output_clk_freq_hz

    Frequency of the output clock. It's divided from either internal `clk_src` or external clock source

  size_t data_width

    Parallel IO data width, can set to 1/2/4/8/..., but can't bigger than PARLIO_TX_UNIT_MAX_DATA_WIDTH

  gpio_num_t data_gpio_nums[PARLIO_TX_UNIT_MAX_DATA_WIDTH]

    Parallel IO data GPIO numbers, if any GPIO is not used, you can set it to `-1`

  gpio_num_t clk_out_gpio_num

    GPIO number of the output clock signal, the clock is synced with TX data

  gpio_num_t valid_gpio_num

    GPIO number of the valid signal, which stays high when transferring data. Note that, the valid signal will always occupy the MSB data bit

  uint16_t valid_start_delay

    The clock cycles that the valid signal becomes active before data start

  uint16_t valid_stop_delay

    The clock cycles that the valid signal keeps active after data end

  size_t trans_queue_depth

    Depth of internal transaction queue

  size_t max_transfer_size

    Maximum transfer size in one transaction, in bytes. This decides the number of DMA nodes will be used for each transaction

  size_t dma_burst_size

    DMA burst size, in bytes

  parlio_sample_edge_t sample_edge

    Parallel IO sample edge

  parlio_bit_pack_order_t bit_pack_order

    Set the order of packing the bits into bytes (only works when `data_width` < 8)

  uint32_t clk_gate_en

    Enable TX clock gating, the output clock will be controlled by the MSB bit of the data bus, i.e. by data_gpio_nums[PARLIO_TX_UNIT_MAX_DATA_WIDTH-1]. High level to enable the clock output, low to disable

  uint32_t io_loop_back

    For debug/test, the signal output from the GPIO will be fed to the input path as well

  uint32_t allow_pd

    Set to allow power down. When this flag set, the driver will backup/restore the PARLIO registers before/after entering/exist sleep mode. By this approach, the system can power off PARLIO's power domain. This can save power, but at the expense of more RAM being consumed.

  uint32_t invert_valid_out

    Invert the output valid signal

  struct parlio_tx_unit_config_t flags

    Extra configuration flags

struct parlio_tx_event_callbacks_t

  Group of Parallel IO TX callbacks.

  Note

  The callbacks are all running under ISR environment

  Note

  When CONFIG_PARLIO_TX_ISR_CACHE_SAFE is enabled, the callback itself and functions called by it should be placed in IRAM. The variables used in the function should be in the SRAM as well.

  Public Members

  parlio_tx_done_callback_t on_trans_done

    Event callback, invoked when one transmission is finished

  parlio_tx_buffer_switched_callback_t on_buffer_switched

    Event callback, invoked when the buffer is switched in loop transmission

struct parlio_transmit_config_t

  Parallel IO transmit configuration.

  Public Members

  uint32_t idle_value

    The value on the data line when the parallel IO is in idle state

  const void *bitscrambler_program

    BitScrambler program binary, NULL if not use BitScrambler

  uint32_t queue_nonblocking

    If set, when the transaction queue is full, driver will not block the thread but return directly

  uint32_t loop_transmission

    If set, the transmission will be repeated continuously, until the tx_unit is disabled by `parlio_tx_unit_disable`

  struct parlio_transmit_config_t flags

    Transmit specific config flags

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.