Merge branch 'feat/parlio_use_gdma_eof' into 'master'

feat(parlio_tx): support loop transmission

Closes IDF-8223 and IDF-11397

See merge request espressif/esp-idf!35733
This commit is contained in:
Chen Ji Chang
2025-03-27 17:10:59 +08:00
30 changed files with 733 additions and 66 deletions

View File

@@ -157,6 +157,7 @@ typedef struct {
uint32_t idle_value; /*!< The value on the data line when the parallel IO is in idle state */
struct {
uint32_t queue_nonblocking : 1; /*!< If set, when the transaction queue is full, driver will not block the thread but return directly */
uint32_t loop_transmission : 1; /*!< If set, the transmission will be repeated continuously, until the tx_unit is disabled by `parlio_tx_unit_disable` */
} flags; /*!< Transmit specific config flags */
} parlio_transmit_config_t;

View File

@@ -4,6 +4,7 @@ entries:
if PARLIO_TX_ISR_HANDLER_IN_IRAM = y:
parlio_tx: parlio_tx_default_isr (noflash)
parlio_tx: parlio_tx_do_transaction (noflash)
parlio_tx: parlio_mount_buffer (noflash)
if PARLIO_RX_ISR_HANDLER_IN_IRAM = y:
parlio_rx: parlio_rx_default_eof_callback (noflash)
parlio_rx: parlio_rx_default_desc_done_callback (noflash)
@@ -15,6 +16,7 @@ archive: libesp_hw_support.a
entries:
if PARLIO_TX_ISR_HANDLER_IN_IRAM = y:
gdma_link: gdma_link_mount_buffers (noflash)
gdma_link: gdma_link_concat (noflash)
gdma_link: gdma_link_get_head_addr (noflash)
gdma: gdma_start (noflash)
if PARLIO_RX_ISR_HANDLER_IN_IRAM = y:

View File

@@ -61,6 +61,9 @@ typedef dma_descriptor_align8_t parlio_dma_desc_t;
#endif
#endif // defined(SOC_GDMA_TRIG_PERIPH_PARLIO0_BUS)
// loop transmission requires ping-pong link to prevent data tearing.
#define PARLIO_DMA_LINK_NUM 2
#if SOC_PERIPH_CLK_CTRL_SHARED
#define PARLIO_CLOCK_SRC_ATOMIC() PERIPH_RCC_ATOMIC()
#else

View File

@@ -46,6 +46,10 @@ typedef struct {
uint32_t idle_value; // Parallel IO bus idle value
const void *payload; // payload to be transmitted
size_t payload_bits; // payload size in bits
int dma_link_idx; // index of DMA link list
struct {
uint32_t loop_transmission : 1; // whether the transmission is in loop mode
} flags; // Extra configuration flags
} parlio_tx_trans_desc_t;
typedef struct parlio_tx_unit_t {
@@ -54,7 +58,7 @@ typedef struct parlio_tx_unit_t {
intr_handle_t intr; // allocated interrupt handle
esp_pm_lock_handle_t pm_lock; // power management lock
gdma_channel_handle_t dma_chan; // DMA channel
gdma_link_list_handle_t dma_link; // DMA link list handle
gdma_link_list_handle_t dma_link[PARLIO_DMA_LINK_NUM]; // DMA link list handle
size_t int_mem_align; // Alignment for internal memory
size_t ext_mem_align; // Alignment for external memory
#if CONFIG_PM_ENABLE
@@ -129,8 +133,10 @@ static esp_err_t parlio_destroy_tx_unit(parlio_tx_unit_t *tx_unit)
// de-register from group
parlio_unregister_unit_from_group(&tx_unit->base);
}
if (tx_unit->dma_link) {
ESP_RETURN_ON_ERROR(gdma_del_link_list(tx_unit->dma_link), TAG, "delete dma link list failed");
for (int i = 0; i < PARLIO_DMA_LINK_NUM; i++) {
if (tx_unit->dma_link[i]) {
ESP_RETURN_ON_ERROR(gdma_del_link_list(tx_unit->dma_link[i]), TAG, "delete dma link list failed");
}
}
free(tx_unit);
return ESP_OK;
@@ -204,8 +210,9 @@ static esp_err_t parlio_tx_unit_init_dma(parlio_tx_unit_t *tx_unit, const parlio
ESP_RETURN_ON_ERROR(PARLIO_GDMA_NEW_CHANNEL(&dma_chan_config, &tx_unit->dma_chan), TAG, "allocate TX DMA channel failed");
gdma_connect(tx_unit->dma_chan, GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_PARLIO, 0));
gdma_strategy_config_t gdma_strategy_conf = {
.auto_update_desc = true,
.owner_check = true,
.auto_update_desc = false, // for loop transmission, we have no chance to change the owner
.owner_check = false,
.eof_till_data_popped = true,
};
gdma_apply_strategy(tx_unit->dma_chan, &gdma_strategy_conf);
@@ -227,7 +234,9 @@ static esp_err_t parlio_tx_unit_init_dma(parlio_tx_unit_t *tx_unit, const parlio
};
// throw the error to the caller
ESP_RETURN_ON_ERROR(gdma_new_link_list(&dma_link_config, &tx_unit->dma_link), TAG, "create DMA link list failed");
for (int i = 0; i < PARLIO_DMA_LINK_NUM; i++) {
ESP_RETURN_ON_ERROR(gdma_new_link_list(&dma_link_config, &tx_unit->dma_link[i]), TAG, "create DMA link list failed");
}
return ESP_OK;
}
@@ -316,8 +325,6 @@ esp_err_t parlio_new_tx_unit(const parlio_tx_unit_config_t *config, parlio_tx_un
// data_width must not conflict with the valid signal
ESP_RETURN_ON_FALSE(!(config->valid_gpio_num >= 0 && data_width > PARLIO_LL_TX_DATA_LINE_AS_VALID_SIG),
ESP_ERR_INVALID_ARG, TAG, "valid signal conflicts with data signal");
ESP_RETURN_ON_FALSE(config->max_transfer_size && config->max_transfer_size <= PARLIO_LL_TX_MAX_BITS_PER_FRAME / 8,
ESP_ERR_INVALID_ARG, TAG, "invalid max transfer size");
#if SOC_PARLIO_TX_CLK_SUPPORT_GATING
// clock gating is controlled by either the MSB bit of data bus or the valid signal
ESP_RETURN_ON_FALSE(!(config->flags.clk_gate_en && config->valid_gpio_num < 0 && config->data_width <= PARLIO_LL_TX_DATA_LINE_AS_CLK_GATE),
@@ -386,10 +393,8 @@ esp_err_t parlio_new_tx_unit(const parlio_tx_unit_config_t *config, parlio_tx_un
// set sample clock edge
parlio_ll_tx_set_sample_clock_edge(hal->regs, config->sample_edge);
#if SOC_PARLIO_TX_SIZE_BY_DMA
// Always use DATA LEN EOF as the Parlio TX EOF
// In default, use DATA LEN EOF as the Parlio TX EOF
parlio_ll_tx_set_eof_condition(hal->regs, PARLIO_LL_TX_EOF_COND_DATA_LEN);
#endif // SOC_PARLIO_TX_SIZE_BY_DMA
// clear any pending interrupt
parlio_ll_clear_interrupt_status(hal->regs, PARLIO_LL_EVENT_TX_MASK);
@@ -461,10 +466,38 @@ esp_err_t parlio_tx_unit_register_event_callbacks(parlio_tx_unit_handle_t tx_uni
return ESP_OK;
}
static void parlio_mount_buffer(parlio_tx_unit_t *tx_unit, parlio_tx_trans_desc_t *t)
{
// DMA transfer data based on bytes not bits, so convert the bit length to bytes, round up
gdma_buffer_mount_config_t mount_config = {
.buffer = (void *)t->payload,
.length = (t->payload_bits + 7) / 8,
.flags = {
// if transmission is loop, we don't need to generate the EOF, as well as the final mark
.mark_eof = !t->flags.loop_transmission,
.mark_final = !t->flags.loop_transmission,
}
};
int next_link_idx = t->flags.loop_transmission ? 1 - t->dma_link_idx : t->dma_link_idx;
gdma_link_mount_buffers(tx_unit->dma_link[next_link_idx], 0, &mount_config, 1, NULL);
if (t->flags.loop_transmission) {
// concatenate the DMA linked list of the next frame transmission with the DMA linked list of the current frame to realize the reuse of the current transmission transaction
gdma_link_concat(tx_unit->dma_link[t->dma_link_idx], -1, tx_unit->dma_link[next_link_idx], 0);
t->dma_link_idx = next_link_idx;
}
}
static void parlio_tx_do_transaction(parlio_tx_unit_t *tx_unit, parlio_tx_trans_desc_t *t)
{
parlio_hal_context_t *hal = &tx_unit->base.group->hal;
if (t->flags.loop_transmission) {
// Once a loop transmission is started, it cannot be stopped until it is disabled
parlio_ll_tx_set_eof_condition(hal->regs, PARLIO_LL_TX_EOF_COND_DMA_EOF);
}
tx_unit->cur_trans = t;
// If the external clock is a non-free-running clock, it needs to be switched to the internal free-running clock first.
@@ -478,21 +511,10 @@ static void parlio_tx_do_transaction(parlio_tx_unit_t *tx_unit, parlio_tx_trans_
PARLIO_RCC_ATOMIC() {
parlio_ll_tx_reset_clock(hal->regs);
}
// DMA transfer data based on bytes not bits, so convert the bit length to bytes, round up
gdma_buffer_mount_config_t mount_config = {
.buffer = (void *)t->payload,
.length = (t->payload_bits + 7) / 8,
.flags = {
.mark_eof = true,
.mark_final = true, // singly link list, mark final descriptor
}
};
// Since the threshold of the clock divider counter is not updated simultaneously with the clock source switching.
// The update of the threshold relies on the moment when the counter reaches the threshold each time.
// We place gdma_link_mount_buffers between reset clock and disable clock to ensure enough time for updating the threshold of the clock divider counter.
gdma_link_mount_buffers(tx_unit->dma_link, 0, &mount_config, 1, NULL);
// We place parlio_mount_buffer between reset clock and disable clock to ensure enough time for updating the threshold of the clock divider counter.
parlio_mount_buffer(tx_unit, t);
if (switch_clk) {
PARLIO_CLOCK_SRC_ATOMIC() {
parlio_ll_tx_set_clock_source(hal->regs, PARLIO_CLK_SRC_EXTERNAL);
@@ -506,7 +528,7 @@ static void parlio_tx_do_transaction(parlio_tx_unit_t *tx_unit, parlio_tx_trans_
parlio_ll_tx_set_idle_data_value(hal->regs, t->idle_value);
parlio_ll_tx_set_trans_bit_len(hal->regs, t->payload_bits);
gdma_start(tx_unit->dma_chan, gdma_link_get_head_addr(tx_unit->dma_link));
gdma_start(tx_unit->dma_chan, gdma_link_get_head_addr(tx_unit->dma_link[t->dma_link_idx]));
// wait until the data goes from the DMA to TX unit's FIFO
while (parlio_ll_tx_is_ready(hal->regs) == false);
// turn on the core clock after we start the TX unit
@@ -592,6 +614,10 @@ esp_err_t parlio_tx_unit_disable(parlio_tx_unit_handle_t tx_unit)
parlio_ll_tx_start(hal->regs, false);
parlio_ll_enable_interrupt(hal->regs, PARLIO_LL_EVENT_TX_MASK, false);
// Once a loop teansmission transaction is started, it can only be stopped in disable function
// change the EOF condition to be the data length, so the EOF will be triggered normally
parlio_ll_tx_set_eof_condition(hal->regs, PARLIO_LL_TX_EOF_COND_DATA_LEN);
// release power management lock
if (tx_unit->pm_lock) {
esp_pm_lock_release(tx_unit->pm_lock);
@@ -612,6 +638,21 @@ esp_err_t parlio_tx_unit_transmit(parlio_tx_unit_handle_t tx_unit, const void *p
ESP_RETURN_ON_FALSE((payload_bits % 8) == 0, ESP_ERR_INVALID_ARG, TAG, "payload bit length must be multiple of 8");
#endif // !SOC_PARLIO_TRANS_BIT_ALIGN
#if SOC_PARLIO_TX_SUPPORT_LOOP_TRANSMISSION
if (config->flags.loop_transmission) {
ESP_RETURN_ON_FALSE(parlio_ll_tx_support_dma_eof(NULL), ESP_ERR_NOT_SUPPORTED, TAG, "loop transmission is not supported by this chip revision");
}
#else
ESP_RETURN_ON_FALSE(config->flags.loop_transmission == false, ESP_ERR_NOT_SUPPORTED, TAG, "loop transmission is not supported on this chip");
#endif
// check the max payload size if it's not a loop transmission
// workaround for EOF limitation, when DMA EOF issue is fixed, we can remove this check
if (!config->flags.loop_transmission) {
ESP_RETURN_ON_FALSE(tx_unit->max_transfer_bits <= PARLIO_LL_TX_MAX_BITS_PER_FRAME,
ESP_ERR_INVALID_ARG, TAG, "invalid transfer size");
}
size_t cache_line_size = 0;
size_t alignment = 0;
uint8_t cache_type = 0;
@@ -627,39 +668,48 @@ esp_err_t parlio_tx_unit_transmit(parlio_tx_unit_handle_t tx_unit, const void *p
ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_UNALIGNED), TAG, "cache sync failed");
}
TickType_t queue_wait_ticks = portMAX_DELAY;
if (config->flags.queue_nonblocking) {
queue_wait_ticks = 0;
}
parlio_tx_trans_desc_t *t = NULL;
// acquire one transaction description from ready queue or complete queue
if (xQueueReceive(tx_unit->trans_queues[PARLIO_TX_QUEUE_READY], &t, 0) != pdTRUE) {
if (xQueueReceive(tx_unit->trans_queues[PARLIO_TX_QUEUE_COMPLETE], &t, queue_wait_ticks) == pdTRUE) {
tx_unit->num_trans_inflight--;
// check if to start a new transaction or update the current loop transaction
bool no_trans_pending_in_queue = uxQueueMessagesWaiting(tx_unit->trans_queues[PARLIO_TX_QUEUE_PROGRESS]) == 0;
if (tx_unit->cur_trans && tx_unit->cur_trans->flags.loop_transmission && config->flags.loop_transmission && no_trans_pending_in_queue) {
tx_unit->cur_trans->payload = payload;
tx_unit->cur_trans->payload_bits = payload_bits;
parlio_mount_buffer(tx_unit, tx_unit->cur_trans);
} else {
TickType_t queue_wait_ticks = portMAX_DELAY;
if (config->flags.queue_nonblocking) {
queue_wait_ticks = 0;
}
}
ESP_RETURN_ON_FALSE(t, ESP_ERR_INVALID_STATE, TAG, "no free transaction descriptor, please consider increasing trans_queue_depth");
parlio_tx_trans_desc_t *t = NULL;
// acquire one transaction description from ready queue or complete queue
if (xQueueReceive(tx_unit->trans_queues[PARLIO_TX_QUEUE_READY], &t, 0) != pdTRUE) {
if (xQueueReceive(tx_unit->trans_queues[PARLIO_TX_QUEUE_COMPLETE], &t, queue_wait_ticks) == pdTRUE) {
tx_unit->num_trans_inflight--;
}
}
ESP_RETURN_ON_FALSE(t, ESP_ERR_INVALID_STATE, TAG, "no free transaction descriptor, please consider increasing trans_queue_depth");
// fill in the transaction descriptor
memset(t, 0, sizeof(parlio_tx_trans_desc_t));
t->payload = payload;
t->payload_bits = payload_bits;
t->idle_value = config->idle_value & tx_unit->idle_value_mask;
// fill in the transaction descriptor
memset(t, 0, sizeof(parlio_tx_trans_desc_t));
t->payload = payload;
t->payload_bits = payload_bits;
t->idle_value = config->idle_value & tx_unit->idle_value_mask;
t->flags.loop_transmission = config->flags.loop_transmission;
// send the transaction descriptor to progress queue
ESP_RETURN_ON_FALSE(xQueueSend(tx_unit->trans_queues[PARLIO_TX_QUEUE_PROGRESS], &t, 0) == pdTRUE,
ESP_ERR_INVALID_STATE, TAG, "failed to send transaction descriptor to progress queue");
tx_unit->num_trans_inflight++;
// send the transaction descriptor to progress queue
ESP_RETURN_ON_FALSE(xQueueSend(tx_unit->trans_queues[PARLIO_TX_QUEUE_PROGRESS], &t, 0) == pdTRUE,
ESP_ERR_INVALID_STATE, TAG, "failed to send transaction descriptor to progress queue");
tx_unit->num_trans_inflight++;
// check if we need to start one pending transaction
parlio_tx_fsm_t expected_fsm = PARLIO_TX_FSM_ENABLE;
if (atomic_compare_exchange_strong(&tx_unit->fsm, &expected_fsm, PARLIO_TX_FSM_RUN_WAIT)) {
// check if we need to start one transaction
if (xQueueReceive(tx_unit->trans_queues[PARLIO_TX_QUEUE_PROGRESS], &t, 0) == pdTRUE) {
atomic_store(&tx_unit->fsm, PARLIO_TX_FSM_RUN);
parlio_tx_do_transaction(tx_unit, t);
} else {
atomic_store(&tx_unit->fsm, PARLIO_TX_FSM_ENABLE);
// check if we need to start one pending transaction
parlio_tx_fsm_t expected_fsm = PARLIO_TX_FSM_ENABLE;
if (atomic_compare_exchange_strong(&tx_unit->fsm, &expected_fsm, PARLIO_TX_FSM_RUN_WAIT)) {
// check if we need to start one transaction
if (xQueueReceive(tx_unit->trans_queues[PARLIO_TX_QUEUE_PROGRESS], &t, 0) == pdTRUE) {
atomic_store(&tx_unit->fsm, PARLIO_TX_FSM_RUN);
parlio_tx_do_transaction(tx_unit, t);
} else {
atomic_store(&tx_unit->fsm, PARLIO_TX_FSM_ENABLE);
}
}
}

View File

@@ -291,7 +291,7 @@ TEST_CASE("parallel_tx_clock_gating", "[paralio_tx]")
#endif // SOC_PARLIO_TX_CLK_SUPPORT_GATING
#if SOC_PSRAM_DMA_CAPABLE
TEST_CASE("parlio can transmit PSRAM buffer", "[parlio_tx]")
TEST_CASE("parlio_tx_can_transmit_PSRAM_buffer", "[parlio_tx]")
{
printf("install parlio tx unit\r\n");
parlio_tx_unit_handle_t tx_unit = NULL;
@@ -447,3 +447,85 @@ TEST_CASE("parallel tx unit use external non-free running clock", "[parlio_tx]")
TEST_ESP_OK(gpio_reset_pin(config.data_gpio_nums[i]));
}
};
#if SOC_PARLIO_TX_SUPPORT_LOOP_TRANSMISSION
TEST_CASE("parlio_tx_loop_transmission", "[parlio_tx]")
{
printf("install parlio tx unit\r\n");
parlio_tx_unit_handle_t tx_unit = NULL;
parlio_tx_unit_config_t config = {
.clk_src = PARLIO_CLK_SRC_DEFAULT,
.data_width = 8,
.clk_in_gpio_num = -1, // use internal clock source
.valid_gpio_num = -1,
.clk_out_gpio_num = TEST_CLK_GPIO,
.data_gpio_nums = {
TEST_DATA0_GPIO,
TEST_DATA1_GPIO,
TEST_DATA2_GPIO,
TEST_DATA3_GPIO,
TEST_DATA4_GPIO,
TEST_DATA5_GPIO,
TEST_DATA6_GPIO,
TEST_DATA7_GPIO,
},
.output_clk_freq_hz = 10 * 1000 * 1000,
.trans_queue_depth = 3,
.max_transfer_size = 256,
.bit_pack_order = PARLIO_BIT_PACK_ORDER_LSB,
.sample_edge = PARLIO_SAMPLE_EDGE_POS,
};
TEST_ESP_OK(parlio_new_tx_unit(&config, &tx_unit));
TEST_ESP_OK(parlio_tx_unit_enable(tx_unit));
printf("send packets and check event is fired\r\n");
parlio_transmit_config_t transmit_config = {
.idle_value = 0x00,
.flags.loop_transmission = true,
};
__attribute__((aligned(64))) uint8_t payload_loop1[256] = {0};
__attribute__((aligned(64))) uint8_t payload_loop2[256] = {0};
__attribute__((aligned(64))) uint8_t payload_oneshot[256] = {0};
for (int i = 0; i < 256; i++) {
payload_loop1[i] = i;
payload_loop2[i] = 255 - i;
payload_oneshot[i] = i * 2 + 1;
}
if (parlio_ll_tx_support_dma_eof(NULL)) { // for some chips, only support in particular ECO version
transmit_config.flags.loop_transmission = true;
int lopp_count = 3;
while (lopp_count--) {
TEST_ESP_OK(parlio_tx_unit_transmit(tx_unit, payload_loop1, 256 * sizeof(uint8_t) * 8, &transmit_config));
vTaskDelay(pdMS_TO_TICKS(10));
// Should be sent after the previous frame has been completely sent
TEST_ESP_OK(parlio_tx_unit_transmit(tx_unit, payload_loop2, 256 * sizeof(uint8_t) * 8, &transmit_config));
vTaskDelay(pdMS_TO_TICKS(10));
}
transmit_config.flags.loop_transmission = false;
// should be pending in queue
TEST_ESP_OK(parlio_tx_unit_transmit(tx_unit, payload_oneshot, 256 * sizeof(uint8_t) * 8, &transmit_config));
transmit_config.flags.loop_transmission = true;
// there is a oneshot trans in queue, should also be pending in queue
TEST_ESP_OK(parlio_tx_unit_transmit(tx_unit, payload_loop1, 256 * sizeof(uint8_t) * 8, &transmit_config));
TEST_ESP_ERR(ESP_ERR_TIMEOUT, parlio_tx_unit_wait_all_done(tx_unit, 50));
// stop infinite loop transmission
parlio_tx_unit_disable(tx_unit);
// We should see 1 oneshot frame and 1 loop transmission (both pending in queue)
parlio_tx_unit_enable(tx_unit);
vTaskDelay(pdMS_TO_TICKS(10));
// stop the second infinite loop transmission
parlio_tx_unit_disable(tx_unit);
parlio_tx_unit_enable(tx_unit);
} else {
TEST_ESP_ERR(ESP_ERR_NOT_SUPPORTED, parlio_tx_unit_transmit(tx_unit, payload_loop1, 256 * sizeof(uint8_t) * 8, &transmit_config));
}
TEST_ESP_OK(parlio_tx_unit_wait_all_done(tx_unit, -1));
TEST_ESP_OK(parlio_tx_unit_disable(tx_unit));
TEST_ESP_OK(parlio_del_tx_unit(tx_unit));
}
#endif // SOC_PARLIO_TX_SUPPORT_LOOP_TRANSMISSION

View File

@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -474,12 +474,24 @@ static inline void parlio_ll_tx_set_trans_bit_len(parl_io_dev_t *dev, uint32_t b
dev->tx_data_cfg.tx_bitlen = bitlen;
}
/**
* @brief Check if tx size can be determined by DMA
*
* @param dev Parallel IO register base address (not used)
*/
static inline bool parlio_ll_tx_support_dma_eof(parl_io_dev_t *dev)
{
(void)dev;
return true;
}
/**
* @brief Set the condition to generate the TX EOF event
*
* @param dev Parallel IO register base address
* @param cond TX EOF condition
*/
__attribute__((always_inline))
static inline void parlio_ll_tx_set_eof_condition(parl_io_dev_t *dev, parlio_ll_tx_eof_cond_t cond)
{
dev->tx_genrl_cfg.tx_eof_gen_sel = cond;

View File

@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -44,6 +44,11 @@ typedef enum {
PARLIO_LL_RX_EOF_COND_EN_INACTIVE, /*!< RX unit generates EOF event when the external enable signal becomes inactive */
} parlio_ll_rx_eof_cond_t;
typedef enum {
PARLIO_LL_TX_EOF_COND_DATA_LEN, /*!< TX unit generates EOF event when it transmits particular data bit length that specified in `tx_bitlen`. */
PARLIO_LL_TX_EOF_COND_DMA_EOF, /*!< TX unit generates EOF event when the DMA EOF takes place */
} parlio_ll_tx_eof_cond_t;
/**
* @brief Enable or disable the parlio peripheral APB clock
*
@@ -450,6 +455,19 @@ static inline void parlio_ll_tx_set_trans_bit_len(parl_io_dev_t *dev, uint32_t b
HAL_FORCE_MODIFY_U32_REG_FIELD(dev->tx_cfg0, tx_bytelen, bitlen / 8);
}
/**
* @brief Set the condition to generate the TX EOF event (this chip does not support)
*
* @param dev Parallel IO register base address (not used)
* @param cond TX EOF condition (not used)
*/
__attribute__((always_inline))
static inline void parlio_ll_tx_set_eof_condition(parl_io_dev_t *dev, parlio_ll_tx_eof_cond_t cond)
{
(void) dev;
HAL_ASSERT(cond == PARLIO_LL_TX_EOF_COND_DATA_LEN);
}
/**
* @brief Whether to enable the TX clock gating
*

View File

@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -48,6 +48,11 @@ typedef enum {
PARLIO_LL_RX_EOF_COND_EN_INACTIVE, /*!< RX unit generates EOF event when the external enable signal becomes inactive */
} parlio_ll_rx_eof_cond_t;
typedef enum {
PARLIO_LL_TX_EOF_COND_DATA_LEN, /*!< TX unit generates EOF event when it transmits particular data bit length that specified in `tx_bitlen`. */
PARLIO_LL_TX_EOF_COND_DMA_EOF, /*!< TX unit generates EOF event when the DMA EOF takes place */
} parlio_ll_tx_eof_cond_t;
/**
* @brief Enable or disable the parlio peripheral APB clock
*
@@ -475,6 +480,29 @@ static inline void parlio_ll_tx_set_trans_bit_len(parl_io_dev_t *dev, uint32_t b
dev->tx_data_cfg.tx_bitlen = bitlen;
}
/**
* @brief Check if tx size can be determined by DMA
*
* @param dev Parallel IO register base address (not used)
*/
static inline bool parlio_ll_tx_support_dma_eof(parl_io_dev_t *dev)
{
(void)dev;
return ESP_CHIP_REV_ABOVE(efuse_hal_chip_revision(), 102);
}
/**
* @brief Set the condition to generate the TX EOF event
*
* @param dev Parallel IO register base address
* @param cond TX EOF condition
*/
__attribute__((always_inline))
static inline void parlio_ll_tx_set_eof_condition(parl_io_dev_t *dev, parlio_ll_tx_eof_cond_t cond)
{
dev->tx_genrl_cfg.tx_eof_gen_sel = cond;
}
/**
* @brief Whether to enable the TX clock gating
*

View File

@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -523,12 +523,24 @@ static inline void parlio_ll_tx_set_trans_bit_len(parl_io_dev_t *dev, uint32_t b
dev->tx_data_cfg.tx_bitlen = bitlen;
}
/**
* @brief Check if tx size can be determined by DMA
*
* @param dev Parallel IO register base address (not used)
*/
static inline bool parlio_ll_tx_support_dma_eof(parl_io_dev_t *dev)
{
(void)dev;
return true;
}
/**
* @brief Set the condition to generate the TX EOF event
*
* @param dev Parallel IO register base address
* @param cond TX EOF condition
*/
__attribute__((always_inline))
static inline void parlio_ll_tx_set_eof_condition(parl_io_dev_t *dev, parlio_ll_tx_eof_cond_t cond)
{
dev->tx_genrl_cfg.tx_eof_gen_sel = cond;

View File

@@ -987,7 +987,7 @@ config SOC_PARLIO_TRANS_BIT_ALIGN
bool
default y
config SOC_PARLIO_TX_SIZE_BY_DMA
config SOC_PARLIO_TX_SUPPORT_LOOP_TRANSMISSION
bool
default y

View File

@@ -385,7 +385,7 @@
#define SOC_PARLIO_RX_CLK_SUPPORT_GATING 1 /*!< Support gating RX clock */
#define SOC_PARLIO_RX_CLK_SUPPORT_OUTPUT 1 /*!< Support output RX clock to a GPIO */
#define SOC_PARLIO_TRANS_BIT_ALIGN 1 /*!< Support bit alignment in transaction */
#define SOC_PARLIO_TX_SIZE_BY_DMA 1 /*!< Transaction length is controlled by DMA instead of indicated by register */
#define SOC_PARLIO_TX_SUPPORT_LOOP_TRANSMISSION 1 /*!< Support loop transmission */
#define SOC_PARLIO_SUPPORT_SLEEP_RETENTION 1 /*!< Support back up registers before sleep */
#define SOC_PARLIO_SUPPORT_SPI_LCD 1 /*!< Support to drive SPI interfaced LCD */

View File

@@ -947,6 +947,10 @@ config SOC_PARLIO_TRANS_BIT_ALIGN
bool
default y
config SOC_PARLIO_TX_SUPPORT_LOOP_TRANSMISSION
bool
default y
config SOC_PARLIO_SUPPORT_SLEEP_RETENTION
bool
default y

View File

@@ -374,6 +374,7 @@
#define SOC_PARLIO_RX_CLK_SUPPORT_GATING 1 /*!< Support gating RX clock */
#define SOC_PARLIO_RX_CLK_SUPPORT_OUTPUT 1 /*!< Support output RX clock to a GPIO */
#define SOC_PARLIO_TRANS_BIT_ALIGN 1 /*!< Support bit alignment in transaction */
#define SOC_PARLIO_TX_SUPPORT_LOOP_TRANSMISSION 1 /*!< Support loop transmission. Only avliable in chip version above 1.2 */
#define SOC_PARLIO_SUPPORT_SLEEP_RETENTION 1 /*!< Support back up registers before sleep */
#define SOC_PARLIO_SUPPORT_SPI_LCD 1 /*!< Support to drive SPI interfaced LCD */

View File

@@ -1,5 +1,5 @@
/**
* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -186,6 +186,14 @@ extern "C" {
* Parallel TX general configuration register.
*/
#define PARL_IO_TX_GENRL_CFG_REG (DR_REG_PARL_IO_BASE + 0x18)
/** PARL_IO_TX_EOF_GEN_SEL : R/W; bitpos: [13]; default: 0;
* Configures the tx eof generated mechanism. 1'b0: eof generated by data bit length.
* 1'b1: eof generated by DMA eof.
*/
#define PARL_IO_TX_EOF_GEN_SEL (BIT(13))
#define PARL_IO_TX_EOF_GEN_SEL_M (PARL_IO_TX_EOF_GEN_SEL_V << PARL_IO_TX_EOF_GEN_SEL_S)
#define PARL_IO_TX_EOF_GEN_SEL_V 0x00000001U
#define PARL_IO_TX_EOF_GEN_SEL_S 13
/** PARL_IO_TX_IDLE_VALUE : R/W; bitpos: [29:14]; default: 0;
* Configures bus value of transmitter in IDLE state.
*/

View File

@@ -1,5 +1,5 @@
/**
* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -179,7 +179,12 @@ typedef union {
*/
typedef union {
struct {
uint32_t reserved_0:14;
uint32_t reserved_0:13;
/** tx_eof_gen_sel : R/W; bitpos: [13]; default: 0;
* Configures the tx eof generated mechanism. 1'b0: eof generated by data bit length.
* 1'b1: eof generated by DMA eof.
*/
uint32_t tx_eof_gen_sel:1;
/** tx_idle_value : R/W; bitpos: [29:14]; default: 0;
* Configures bus value of transmitter in IDLE state.
*/

View File

@@ -1371,7 +1371,7 @@ config SOC_PARLIO_TRANS_BIT_ALIGN
bool
default y
config SOC_PARLIO_TX_SIZE_BY_DMA
config SOC_PARLIO_TX_SUPPORT_LOOP_TRANSMISSION
bool
default y

View File

@@ -488,7 +488,7 @@
#define SOC_PARLIO_RX_CLK_SUPPORT_GATING 1 /*!< Support gating RX clock */
#define SOC_PARLIO_RX_CLK_SUPPORT_OUTPUT 1 /*!< Support output RX clock to a GPIO */
#define SOC_PARLIO_TRANS_BIT_ALIGN 1 /*!< Support bit alignment in transaction */
#define SOC_PARLIO_TX_SIZE_BY_DMA 1 /*!< Transaction length is controlled by DMA instead of indicated by register */
#define SOC_PARLIO_TX_SUPPORT_LOOP_TRANSMISSION 1 /*!< Support loop transmission */
#define SOC_PARLIO_SUPPORT_SLEEP_RETENTION 1 /*!< Support back up registers before sleep */
#define SOC_PARLIO_SUPPORT_SPI_LCD 1 /*!< Support to drive SPI interfaced LCD */
#define SOC_PARLIO_SUPPORT_I80_LCD 1 /*!< Support to drive I80 interfaced LCD */

View File

@@ -314,6 +314,12 @@ examples/peripherals/parlio/parlio_rx:
depends_components:
- esp_driver_parlio
examples/peripherals/parlio/parlio_tx/advanced_rgb_led_matrix:
disable:
- if: (SOC_PARLIO_SUPPORTED != 1 or SOC_PARLIO_TX_SUPPORT_LOOP_TRANSMISSION != 1) or SOC_PARLIO_TX_UNIT_MAX_DATA_WIDTH < 16
depends_components:
- esp_driver_parlio
examples/peripherals/parlio/parlio_tx/simple_rgb_led_matrix:
disable:
- if: SOC_PARLIO_SUPPORTED != 1 or SOC_DEDICATED_GPIO_SUPPORTED != 1

View File

@@ -0,0 +1,10 @@
# For more information about build system see
# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html
# The following five lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
set(COMPONENTS main)
project(advanced_rgb_led_matrix)

View File

@@ -0,0 +1,72 @@
| Supported Targets | ESP32-P4 |
| ----------------- | -------- |
# Parallel IO TX Example: Advancde RGB LED Matrix
(See the README.md file in the upper level 'examples' directory for more information about examples.)
The common used LED Matrix board has a so-called HUB75 interface, which is a 16-bit parallel interface. This example shows how to use the Parallel IO TX unit to drive a RGB LED Matrix board. We take use of the Parallel IO TX unit to send the RGB data and control signals and line address to the LED Matrix board.
In the other [basic RGB LED Matrix](../simple_rgb_led_matrix) example, we use the CPU to continuously send refresh transactions for each row. In this advanced example, we let the hardware automatically refresh the entire screen content continuously, without CPU intervention.
The example uses the [LVGL](https://lvgl.io/) library to draw some simple UI elements on the LED Matrix board.
## How to Use Example
### Hardware Required
* A development board with any supported Espressif SOC chip (see `Supported Targets` table above)
* A USB cable for programming
* A RGB LED Matrix board, with HUB75 interface (this example will use the [`64*32` resolution version](https://www.waveshare.net/wiki/RGB-Matrix-P4-64x32))
* A 5V-2A power supply for the LED Matrix board
Connection :
```plain_text
+-----------------------+
| HUB75 |
| +-------------+ |
| | | |
PIN_NUM_R1---+-+---R1 G1 +-------+---PIN_NUM_G1
| | | |
PIN_NUM_B1---+-+---B1 +-GND | |
| | | | |
PIN_NUM_R2---+-+-+-R2 | G2 +-------+---PIN_NUM_G2
| | | | |
PIN_NUM_B2---+---+-B2 +-GND-+-------+---->GND
| | | | |
PIN_NUM_A----+---+-A | B +-------+---PIN_NUM_B
| | | | |
PIN_NUM_C----+-+-+-C | D +-------+---PIN_NUM_D
| | | | |
PIN_NUM_CLK--+-+---CLK | LAT +-------+---PIN_NUM_LAT
| | | | |
PIN_NUM_OE---+-+---OE +-GND | |
| | | PWR--+---->5V
| +-------------+ |
| RGB LED Matrix |
+-----------------------+
```
### Build and Flash
Run `idf.py -p PORT flash monitor` to build, flash and monitor the project.
(To exit the serial monitor, type ``Ctrl-]``.)
See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects.
## Console Output
```plain_text
I (348) main_task: Started on CPU0
I (348) main_task: Calling app_main()
I (358) example: Install parallel IO TX unit
I (458) example: Initialize LVGL library
I (468) example: Install LVGL tick timer
I (468) example: Display LVGL UI
```
## Troubleshooting
For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon.

View File

@@ -0,0 +1,3 @@
idf_component_register(SRCS "advanced_rgb_led_matrix_example_main.c" "lvgl_demo_ui.c"
PRIV_REQUIRES esp_driver_parlio esp_timer
INCLUDE_DIRS ".")

View File

@@ -0,0 +1,55 @@
menu "Example Configuration"
config EXAMPLE_PIN_NUM_R1
int "Red Pin 1"
default 7
config EXAMPLE_PIN_NUM_G1
int "Green Pin 1"
default 4
config EXAMPLE_PIN_NUM_B1
int "Blue Pin 1"
default 1
config EXAMPLE_PIN_NUM_R2
int "Red Pin 2"
default 6
config EXAMPLE_PIN_NUM_G2
int "Green Pin 2"
default 3
config EXAMPLE_PIN_NUM_B2
int "Blue Pin 2"
default 0
config EXAMPLE_PIN_NUM_LATCH
int "Latch Pin"
default 5
config EXAMPLE_PIN_NUM_PCLK
int "PCLK Pin"
default 10
config EXAMPLE_PIN_NUM_OE
int "OE Pin"
default 2
config EXAMPLE_PIN_NUM_A
int "A Pin"
default 20
config EXAMPLE_PIN_NUM_B
int "B Pin"
default 21
config EXAMPLE_PIN_NUM_C
int "C Pin"
default 22
config EXAMPLE_PIN_NUM_D
int "D Pin"
default 23
endmenu

View File

@@ -0,0 +1,214 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_attr.h"
#include "esp_log.h"
#include "driver/parlio_tx.h"
#include "esp_timer.h"
#include "lvgl.h"
static const char *TAG = "example";
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////// Please update the following configuration according to your LED Matrix spec ///////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// color data, clock signal and control signals are powered by parlio_tx unit
#define EXAMPLE_HUB75_WIDTH 16 // 8-bit data width, used for transferring color data (RGB222) and control signals (OE and latch), 4-bit address width, 4-bit reserved
#define EXAMPLE_HUB75_R1_IDX 7 // R1 bit position
#define EXAMPLE_HUB75_R2_IDX 6 // R2 bit position
#define EXAMPLE_HUB75_LATCH_IDX 5 // LATCH bit position
#define EXAMPLE_HUB75_G1_IDX 4 // G1 bit position
#define EXAMPLE_HUB75_G2_IDX 3 // G2 bit position
#define EXAMPLE_HUB75_OE_IDX 2 // OE bit position
#define EXAMPLE_HUB75_B1_IDX 1 // B1 bit position
#define EXAMPLE_HUB75_B2_IDX 0 // B2 bit position
#define EXAMPLE_HUB75_NUM_A_IDX 8 // Address A bit position
#define EXAMPLE_HUB75_NUM_B_IDX 9 // Address B bit position
#define EXAMPLE_HUB75_NUM_C_IDX 10 // Address C bit position
#define EXAMPLE_HUB75_NUM_D_IDX 11 // Address D bit position
#define EXAMPLE_HUB75_RESERVED1_IDX 12 // Reserved bit position
#define EXAMPLE_HUB75_RESERVED2_IDX 13 // Reserved bit position
#define EXAMPLE_HUB75_RESERVED3_IDX 14 // Reserved bit position
#define EXAMPLE_HUB75_RESERVED4_IDX 15 // Reserved bit position
// GPIO assignment
#define EXAMPLE_PIN_NUM_R1 CONFIG_EXAMPLE_PIN_NUM_R1
#define EXAMPLE_PIN_NUM_G1 CONFIG_EXAMPLE_PIN_NUM_G1
#define EXAMPLE_PIN_NUM_B1 CONFIG_EXAMPLE_PIN_NUM_B1
#define EXAMPLE_PIN_NUM_R2 CONFIG_EXAMPLE_PIN_NUM_R2
#define EXAMPLE_PIN_NUM_G2 CONFIG_EXAMPLE_PIN_NUM_G2
#define EXAMPLE_PIN_NUM_B2 CONFIG_EXAMPLE_PIN_NUM_B2
#define EXAMPLE_PIN_NUM_LATCH CONFIG_EXAMPLE_PIN_NUM_LATCH
#define EXAMPLE_PIN_NUM_OE CONFIG_EXAMPLE_PIN_NUM_OE
#define EXAMPLE_PIN_NUM_PCLK CONFIG_EXAMPLE_PIN_NUM_PCLK
#define EXAMPLE_PIN_NUM_A CONFIG_EXAMPLE_PIN_NUM_A
#define EXAMPLE_PIN_NUM_B CONFIG_EXAMPLE_PIN_NUM_B
#define EXAMPLE_PIN_NUM_C CONFIG_EXAMPLE_PIN_NUM_C
#define EXAMPLE_PIN_NUM_D CONFIG_EXAMPLE_PIN_NUM_D
// The pixel clock frequency
#define EXAMPLE_LED_MATRIX_PIXEL_CLOCK_HZ (10 * 1000 * 1000) // 10MHz
// The pixel number in horizontal and vertical
#define EXAMPLE_LED_MATRIX_H_RES 64
#define EXAMPLE_LED_MATRIX_V_RES 32
// We use the gptimer interrupt to generate the LVGL tick interface
#define EXAMPLE_GPTIMER_RESOLUTION_HZ (1 * 1000 * 1000) // 1MHz
#define EXAMPLE_LVGL_TICK_PERIOD_MS 5 // 5ms
// We need to insert gap cycles to enable the line output (OE=0) for some time.
#define EXAMPLE_GAP_CYCLE_PER_LINE 40
// LED Matrix frame buffer, we use ping-pong buffer to prevent the frame from being tearing
static uint16_t s_frame_buffer[2][(EXAMPLE_LED_MATRIX_H_RES + EXAMPLE_GAP_CYCLE_PER_LINE) * EXAMPLE_LED_MATRIX_V_RES / 2] = {};
typedef struct {
parlio_tx_unit_handle_t tx_unit;
parlio_transmit_config_t transmit_config;
} parlio_tx_context_t;
// upper_half: pixel on the upper half screen
// lower_half: pixel on the lower half screen
static void merge_two_pixels(lv_color16_t *upper_half, lv_color16_t *lower_half, uint8_t buffer_idx, uint32_t pixel_idx)
{
s_frame_buffer[buffer_idx][pixel_idx] = (upper_half->red >> 4) << EXAMPLE_HUB75_R1_IDX | (upper_half->green >> 5) << EXAMPLE_HUB75_G1_IDX | (upper_half->blue >> 4) << EXAMPLE_HUB75_B1_IDX;
s_frame_buffer[buffer_idx][pixel_idx] |= (lower_half->red >> 4) << EXAMPLE_HUB75_R2_IDX | (lower_half->green >> 5) << EXAMPLE_HUB75_G2_IDX | (lower_half->blue >> 4) << EXAMPLE_HUB75_B2_IDX;
// OE=1: disable the output
s_frame_buffer[buffer_idx][pixel_idx] |= 1 << EXAMPLE_HUB75_OE_IDX;
// update the address line
s_frame_buffer[buffer_idx][pixel_idx] |= (pixel_idx / (EXAMPLE_LED_MATRIX_H_RES + EXAMPLE_GAP_CYCLE_PER_LINE)) << EXAMPLE_HUB75_NUM_A_IDX;
}
extern void example_lvgl_demo_ui(lv_display_t *disp);
static void example_lvgl_flush_cb(lv_display_t *display, const lv_area_t *area, uint8_t *color_map)
{
static uint8_t buffer_idx = 0;
parlio_tx_context_t *user_ctx = lv_display_get_user_data(display);
parlio_tx_unit_handle_t tx_unit = user_ctx->tx_unit;
parlio_transmit_config_t transmit_config = user_ctx->transmit_config;
lv_color16_t *upper_half = (lv_color16_t *)color_map;
lv_color16_t *lower_half = (lv_color16_t *)color_map + EXAMPLE_LED_MATRIX_V_RES * EXAMPLE_LED_MATRIX_H_RES / 2;
uint32_t pixel_idx = 0;
for (int line = 0; line < EXAMPLE_LED_MATRIX_V_RES / 2; line++) {
for (int col = 0; col < EXAMPLE_LED_MATRIX_H_RES - 1; col++) {
merge_two_pixels(upper_half++, lower_half++, buffer_idx, pixel_idx++);
}
merge_two_pixels(upper_half++, lower_half++, buffer_idx, pixel_idx);
// need special handling for the last pixel in each line
// latch up at the end of each line
s_frame_buffer[buffer_idx][pixel_idx++] |= (1 << EXAMPLE_HUB75_LATCH_IDX);
// insert gap cycles to enable the line output (OE=0) for some time.
for (int i = 0; i < EXAMPLE_GAP_CYCLE_PER_LINE; i++) {
// we need to make sure the output is disabled during the second half of gap cycle, otherwise the screen will be ghosted.
int output_disable = (i > EXAMPLE_GAP_CYCLE_PER_LINE / 2) ? 1 : 0;
s_frame_buffer[buffer_idx][pixel_idx] = output_disable << EXAMPLE_HUB75_OE_IDX;
s_frame_buffer[buffer_idx][pixel_idx] |= (pixel_idx / (EXAMPLE_LED_MATRIX_H_RES + EXAMPLE_GAP_CYCLE_PER_LINE)) << EXAMPLE_HUB75_NUM_A_IDX;
pixel_idx++;
}
}
parlio_tx_unit_transmit(tx_unit, s_frame_buffer[buffer_idx], (EXAMPLE_LED_MATRIX_V_RES / 2 * (EXAMPLE_LED_MATRIX_H_RES + EXAMPLE_GAP_CYCLE_PER_LINE)) * sizeof(uint16_t) * 8, &transmit_config);
// switch to the next frame buffer
buffer_idx ^= 0x01;
lv_display_flush_ready(display);
}
static IRAM_ATTR void timer_alarm_cb_lvgl_tick(void* arg)
{
/* Tell LVGL how many milliseconds has elapsed */
lv_tick_inc(EXAMPLE_LVGL_TICK_PERIOD_MS);
}
void app_main(void)
{
ESP_LOGI(TAG, "Install parallel IO TX unit");
parlio_tx_unit_handle_t tx_unit = NULL;
parlio_tx_unit_config_t config = {
.clk_src = PARLIO_CLK_SRC_DEFAULT,
.data_width = EXAMPLE_HUB75_WIDTH,
.clk_in_gpio_num = -1, // use internal clock source
.valid_gpio_num = -1, // don't generate valid signal
.clk_out_gpio_num = EXAMPLE_PIN_NUM_PCLK,
.data_gpio_nums = {
[EXAMPLE_HUB75_R1_IDX] = EXAMPLE_PIN_NUM_R1,
[EXAMPLE_HUB75_R2_IDX] = EXAMPLE_PIN_NUM_R2,
[EXAMPLE_HUB75_G1_IDX] = EXAMPLE_PIN_NUM_G1,
[EXAMPLE_HUB75_G2_IDX] = EXAMPLE_PIN_NUM_G2,
[EXAMPLE_HUB75_B1_IDX] = EXAMPLE_PIN_NUM_B1,
[EXAMPLE_HUB75_B2_IDX] = EXAMPLE_PIN_NUM_B2,
[EXAMPLE_HUB75_OE_IDX] = EXAMPLE_PIN_NUM_OE,
[EXAMPLE_HUB75_LATCH_IDX] = EXAMPLE_PIN_NUM_LATCH,
[EXAMPLE_HUB75_NUM_A_IDX] = EXAMPLE_PIN_NUM_A,
[EXAMPLE_HUB75_NUM_B_IDX] = EXAMPLE_PIN_NUM_B,
[EXAMPLE_HUB75_NUM_C_IDX] = EXAMPLE_PIN_NUM_C,
[EXAMPLE_HUB75_NUM_D_IDX] = EXAMPLE_PIN_NUM_D,
[EXAMPLE_HUB75_RESERVED1_IDX] = -1,
[EXAMPLE_HUB75_RESERVED2_IDX] = -1,
[EXAMPLE_HUB75_RESERVED3_IDX] = -1,
[EXAMPLE_HUB75_RESERVED4_IDX] = -1,
},
.output_clk_freq_hz = EXAMPLE_LED_MATRIX_PIXEL_CLOCK_HZ,
.trans_queue_depth = 4,
.max_transfer_size = (EXAMPLE_LED_MATRIX_H_RES + EXAMPLE_GAP_CYCLE_PER_LINE) * EXAMPLE_LED_MATRIX_V_RES / 2 * sizeof(uint16_t), // full frame as the maximum transfer size
.sample_edge = PARLIO_SAMPLE_EDGE_POS,
};
parlio_transmit_config_t transmit_config = {
.idle_value = 0x00, // the idle value will take no effect since we are using the loop mode
.flags.loop_transmission = true, // use loop mode. we rely on the LVGL flush callback to update the loop transmission payload
};
ESP_ERROR_CHECK(parlio_new_tx_unit(&config, &tx_unit));
ESP_ERROR_CHECK(parlio_tx_unit_enable(tx_unit));
ESP_LOGI(TAG, "Initialize LVGL library");
lv_init();
lv_display_t *display = lv_display_create(EXAMPLE_LED_MATRIX_H_RES, EXAMPLE_LED_MATRIX_V_RES);
// allocate two full-screen draw buffers
size_t draw_buffer_sz = EXAMPLE_LED_MATRIX_H_RES * EXAMPLE_LED_MATRIX_V_RES * sizeof(lv_color16_t);
void *buf1 = heap_caps_calloc(1, draw_buffer_sz, MALLOC_CAP_INTERNAL);
assert(buf1);
void *buf2 = heap_caps_calloc(1, draw_buffer_sz, MALLOC_CAP_INTERNAL);
assert(buf2);
// Use RGB565 because RGB332 is not supported in LVGL9.2
lv_display_set_color_format(display, LV_COLOR_FORMAT_RGB565);
// initialize LVGL draw buffers
// Since the rgb matrix needs to be refreshed dynamically, use fullmode
lv_display_set_buffers(display, buf1, buf2, draw_buffer_sz, LV_DISPLAY_RENDER_MODE_FULL);
// set the callback which can copy the rendered image to an area of the display
lv_display_set_flush_cb(display, example_lvgl_flush_cb);
// we do parlio transmission in the flush callback
parlio_tx_context_t user_ctx = {
.tx_unit = tx_unit,
.transmit_config = transmit_config,
};
lv_display_set_user_data(display, &user_ctx);
ESP_LOGI(TAG, "Install LVGL tick timer");
// increase the LVGL tick in the esp_timer alarm callback
const esp_timer_create_args_t timer_args = {
.callback = &timer_alarm_cb_lvgl_tick,
.name = "lvgl_tick_timer"
};
esp_timer_handle_t lvgl_tick_timer = NULL;
ESP_ERROR_CHECK(esp_timer_create(&timer_args, &lvgl_tick_timer));
ESP_LOGI(TAG, "Display LVGL UI");
example_lvgl_demo_ui(display);
ESP_ERROR_CHECK(esp_timer_start_periodic(lvgl_tick_timer, EXAMPLE_LVGL_TICK_PERIOD_MS * 1000));
uint32_t time_till_next_ms = 0;
while (1) {
if (time_till_next_ms > 500) {
time_till_next_ms = 500;
} else if (time_till_next_ms < 10) {
time_till_next_ms = 10;
}
vTaskDelay(pdMS_TO_TICKS(time_till_next_ms));
time_till_next_ms = lv_timer_handler();
}
}

View File

@@ -0,0 +1,3 @@
dependencies:
idf: ">=5.5"
lvgl/lvgl: "9.2.2"

View File

@@ -0,0 +1,32 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
#include "lvgl.h"
void example_lvgl_demo_ui(lv_display_t *disp)
{
lv_obj_t *scr = lv_display_get_screen_active(disp);
static lv_style_t up_style;
lv_style_init(&up_style);
lv_obj_t *up_label = lv_label_create(scr);
lv_style_set_text_color(&up_style, lv_palette_main(LV_PALETTE_RED));
lv_obj_add_style(up_label, &up_style, 0);
lv_label_set_text(up_label, "Hello World");
lv_label_set_long_mode(up_label, LV_LABEL_LONG_SCROLL_CIRCULAR); /* Circular scroll */
/* Size of the screen */
lv_obj_set_width(up_label, lv_display_get_horizontal_resolution(disp));
lv_obj_align_to(up_label, scr, LV_ALIGN_TOP_MID, 0, 0);
static lv_style_t low_style;
lv_style_init(&low_style);
lv_obj_t *low_label = lv_label_create(scr);
lv_style_set_text_color(&low_style, lv_palette_main(LV_PALETTE_GREEN));
lv_obj_add_style(low_label, &low_style, 0);
lv_label_set_text(low_label, LV_SYMBOL_WIFI LV_SYMBOL_GPS LV_SYMBOL_BATTERY_2 LV_SYMBOL_AUDIO);
/* Size of the screen */
lv_obj_set_width(low_label, lv_display_get_horizontal_resolution(disp));
lv_obj_align_to(low_label, scr, LV_ALIGN_BOTTOM_MID, 0, 0);
}

View File

@@ -0,0 +1,14 @@
# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: CC0-1.0
import pytest
from pytest_embedded import Dut
from pytest_embedded_idf.utils import idf_parametrize
@pytest.mark.generic
@idf_parametrize('target', ['esp32p4'], indirect=['target'])
def test_advanced_rgb_led_matrix_example(dut: Dut) -> None:
dut.expect_exact('example: Install parallel IO TX unit')
dut.expect_exact('example: Initialize LVGL library')
dut.expect_exact('example: Install LVGL tick timer')
dut.expect_exact('example: Display LVGL UI')

View File

@@ -0,0 +1,4 @@
# This file was generated using idf.py save-defconfig. It can be edited manually.
# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration
#
CONFIG_LV_THEME_DEFAULT_DARK=y

View File

@@ -0,0 +1,13 @@
CONFIG_EXAMPLE_PIN_NUM_R1=25
CONFIG_EXAMPLE_PIN_NUM_G1=3
CONFIG_EXAMPLE_PIN_NUM_B1=29
CONFIG_EXAMPLE_PIN_NUM_R2=27
CONFIG_EXAMPLE_PIN_NUM_G2=1
CONFIG_EXAMPLE_PIN_NUM_B2=31
CONFIG_EXAMPLE_PIN_NUM_LATCH=0
CONFIG_EXAMPLE_PIN_NUM_OE=30
CONFIG_EXAMPLE_PIN_NUM_PCLK=28
CONFIG_EXAMPLE_PIN_NUM_A=24
CONFIG_EXAMPLE_PIN_NUM_B=4
CONFIG_EXAMPLE_PIN_NUM_C=26
CONFIG_EXAMPLE_PIN_NUM_D=2

View File

@@ -7,6 +7,8 @@
The common used LED Matrix board has a so-called HUB75 interface, which is a 16-bit parallel interface. This example shows how to use the Parallel IO TX unit to drive a RGB LED Matrix board. We take use of the Parallel IO TX unit to send the RGB data and control signals to the LED Matrix board, and use the fast GPIO bundle to control the line address.
In the other [advanced RGB LED Matrix](../advanced_rgb_led_matrix) example, we let the hardware automatically refresh the entire screen content continuously, without CPU intervention. In this basic example, we use the CPU to continuously send refresh transactions for each row
The example uses the [LVGL](https://lvgl.io/) library to draw some simple UI elements on the LED Matrix board.
## How to Use Example

View File

@@ -0,0 +1,13 @@
CONFIG_EXAMPLE_PIN_NUM_R1=25
CONFIG_EXAMPLE_PIN_NUM_G1=3
CONFIG_EXAMPLE_PIN_NUM_B1=29
CONFIG_EXAMPLE_PIN_NUM_R2=27
CONFIG_EXAMPLE_PIN_NUM_G2=1
CONFIG_EXAMPLE_PIN_NUM_B2=31
CONFIG_EXAMPLE_PIN_NUM_LATCH=0
CONFIG_EXAMPLE_PIN_NUM_OE=30
CONFIG_EXAMPLE_PIN_NUM_PCLK=28
CONFIG_EXAMPLE_PIN_NUM_A=24
CONFIG_EXAMPLE_PIN_NUM_B=4
CONFIG_EXAMPLE_PIN_NUM_C=26
CONFIG_EXAMPLE_PIN_NUM_D=2