forked from espressif/esp-idf
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:
@@ -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;
|
||||
|
||||
|
@@ -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:
|
||||
|
@@ -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
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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;
|
||||
|
@@ -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
|
||||
*
|
||||
|
@@ -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
|
||||
*
|
||||
|
@@ -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;
|
||||
|
@@ -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
|
||||
|
||||
|
@@ -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 */
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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 */
|
||||
|
||||
|
@@ -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.
|
||||
*/
|
||||
|
@@ -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.
|
||||
*/
|
||||
|
@@ -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
|
||||
|
||||
|
@@ -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 */
|
||||
|
@@ -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
|
||||
|
@@ -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)
|
@@ -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.
|
@@ -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 ".")
|
@@ -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
|
@@ -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();
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
dependencies:
|
||||
idf: ">=5.5"
|
||||
lvgl/lvgl: "9.2.2"
|
@@ -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);
|
||||
}
|
@@ -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')
|
@@ -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
|
@@ -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
|
@@ -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
|
||||
|
@@ -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
|
Reference in New Issue
Block a user