From ce9110dbf0acceed9419b6d229581df01e81ffbd Mon Sep 17 00:00:00 2001 From: laokaiyao Date: Tue, 11 Jul 2023 23:54:06 +0800 Subject: [PATCH] feat(parlio_rx): add test for parlio rx driver --- .../driver/parlio/include/driver/parlio_rx.h | 2 +- components/driver/parlio/parlio_rx.c | 73 ++- .../test_apps/parlio/main/CMakeLists.txt | 8 +- .../driver/test_apps/parlio/main/test_board.h | 10 + .../test_apps/parlio/main/test_parlio_rx.c | 513 ++++++++++++++++++ .../test_apps/parlio/main/test_parlio_tx.c | 6 - .../test_apps/parlio/pytest_parlio_unity.py | 2 +- 7 files changed, 567 insertions(+), 47 deletions(-) create mode 100644 components/driver/test_apps/parlio/main/test_parlio_rx.c diff --git a/components/driver/parlio/include/driver/parlio_rx.h b/components/driver/parlio/include/driver/parlio_rx.h index dab2b72e1a..890049fc4f 100644 --- a/components/driver/parlio/include/driver/parlio_rx.h +++ b/components/driver/parlio/include/driver/parlio_rx.h @@ -22,7 +22,7 @@ extern "C" { typedef struct { size_t trans_queue_depth; /*!< Depth of internal transaction queue */ size_t max_recv_size; /*!< Maximum receive size in one transaction, in bytes. This decides the number of DMA nodes will be used for each transaction */ - size_t data_width; /*!< Parallel IO data width, can set to 1/2/4/8/..., but can't bigger than PARLIO_RX_UNIT_MAX_DATA_WIDTH */ + size_t data_width; /*!< Parallel IO data width, can set to 1/2/4/8/..., but can't be greater than PARLIO_RX_UNIT_MAX_DATA_WIDTH */ parlio_clock_source_t clk_src; /*!< Parallel IO clock source */ uint32_t clk_freq_hz; /*!< The source clock frequency for external when the clock source is selected as PARLIO_CLK_SRC_EXTERNAL; The expected output clock frequency when the clock source is from internal */ diff --git a/components/driver/parlio/parlio_rx.c b/components/driver/parlio/parlio_rx.c index 5a84ad7097..b698a9cde5 100644 --- a/components/driver/parlio/parlio_rx.c +++ b/components/driver/parlio/parlio_rx.c @@ -48,6 +48,7 @@ typedef struct { will be reset when all data filled in the infinite transaction */ struct { uint32_t infinite : 1; /*!< Whether this is an infinite transaction */ + uint32_t indirect_mount : 1; /*!< Whether the user payload mount to the descriptor indirectly via an internal DMA buffer */ } flags; } parlio_rx_transaction_t; @@ -144,6 +145,7 @@ static IRAM_ATTR size_t s_parlio_mount_transaction_buffer(parlio_rx_unit_handle_ } size_t mount_size = 0; size_t offset = 0; + /* Loop the descriptors to assign the data */ for (int i = 0; i < desc_num; i++) { size_t rest_size = trans->size - offset; if (rest_size >= 2 * DMA_DESCRIPTOR_BUFFER_MAX_SIZE_4B_ALIGNED) { @@ -235,13 +237,14 @@ static esp_err_t s_parlio_rx_unit_set_gpio(parlio_rx_unit_handle_t rx_unit, cons { int group_id = rx_unit->group->group_id; int unit_id = rx_unit->unit_id; - + /* Default GPIO configuration */ gpio_config_t gpio_conf = { .intr_type = GPIO_INTR_DISABLE, .pull_down_en = false, .pull_up_en = true, }; + /* When the source clock comes from external, enable the gpio input direction and connect to the clock input signal */ if (config->clk_src == PARLIO_CLK_SRC_EXTERNAL) { ESP_RETURN_ON_FALSE(config->clk_gpio_num >= 0, ESP_ERR_INVALID_ARG, TAG, "clk_gpio_num must be set while the clock input from external"); /* Connect the clock in signal to the GPIO matrix if it is set */ @@ -249,17 +252,17 @@ static esp_err_t s_parlio_rx_unit_set_gpio(parlio_rx_unit_handle_t rx_unit, cons gpio_conf.mode = config->flags.io_loop_back ? GPIO_MODE_INPUT_OUTPUT : GPIO_MODE_INPUT; gpio_conf.pin_bit_mask = BIT64(config->clk_gpio_num); ESP_RETURN_ON_ERROR(gpio_config(&gpio_conf), TAG, "config clk in GPIO failed"); - gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[config->clk_gpio_num], PIN_FUNC_GPIO); } esp_rom_gpio_connect_in_signal(config->clk_gpio_num, parlio_periph_signals.groups[group_id].rx_units[unit_id].clk_in_sig, false); } + /* When the source clock comes from internal and supported to output the internal clock, + * enable the gpio output direction and connect to the clock output signal */ else if (config->clk_gpio_num >= 0) { #if SOC_PARLIO_RX_CLK_SUPPORT_OUTPUT - gpio_conf.mode = GPIO_MODE_OUTPUT; + gpio_conf.mode = config->flags.io_loop_back ? GPIO_MODE_INPUT_OUTPUT : GPIO_MODE_OUTPUT; gpio_conf.pin_bit_mask = BIT64(config->clk_gpio_num); - ESP_RETURN_ON_ERROR(gpio_config(&gpio_conf), TAG, "config clk in GPIO failed"); - gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[config->clk_gpio_num], PIN_FUNC_GPIO); + ESP_RETURN_ON_ERROR(gpio_config(&gpio_conf), TAG, "config clk out GPIO failed"); esp_rom_gpio_connect_out_signal(config->clk_gpio_num, parlio_periph_signals.groups[group_id].rx_units[unit_id].clk_out_sig, false, false); #else @@ -267,16 +270,17 @@ static esp_err_t s_parlio_rx_unit_set_gpio(parlio_rx_unit_handle_t rx_unit, cons #endif // SOC_PARLIO_RX_CLK_SUPPORT_OUTPUT } - gpio_conf.mode = GPIO_MODE_INPUT; + gpio_conf.mode = config->flags.io_loop_back ? GPIO_MODE_INPUT_OUTPUT : GPIO_MODE_INPUT; + /* Initialize the valid GPIO as input */ if (config->valid_gpio_num >= 0) { if (!config->flags.io_no_init) { gpio_conf.pin_bit_mask = BIT64(config->valid_gpio_num); - gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[config->valid_gpio_num], PIN_FUNC_GPIO); + ESP_RETURN_ON_ERROR(gpio_config(&gpio_conf), TAG, "config data GPIO failed"); } - ESP_RETURN_ON_ERROR(gpio_config(&gpio_conf), TAG, "config data GPIO failed"); /* Not connect the signal here, the signal is lazy connected until the delimiter takes effect */ } + /* Initialize the data GPIO as input and bind them to the corresponding data line signals */ for (int i = 0; i < config->data_width; i++) { /* Loop the data_gpio_nums to connect data and valid signals via GPIO matrix */ if (config->data_gpio_nums[i] >= 0) { @@ -295,21 +299,29 @@ static esp_err_t s_parlio_rx_unit_set_gpio(parlio_rx_unit_handle_t rx_unit, cons return ESP_OK; } -static IRAM_ATTR bool s_parlio_rx_default_trans_done_callback(gdma_channel_handle_t dma_chan, gdma_event_data_t *event_data, void *user_data) +static IRAM_ATTR bool s_parlio_rx_default_eof_callback(gdma_channel_handle_t dma_chan, gdma_event_data_t *event_data, void *user_data) { parlio_rx_unit_handle_t rx_unit = (parlio_rx_unit_handle_t )user_data; BaseType_t high_task_woken = pdFALSE; bool need_yield = false; - /* If configured on_receive_done callback and transaction just done */ - if (rx_unit->cbs.on_receive_done) { - parlio_rx_event_data_t evt_data = { - .delimiter = rx_unit->curr_trans.delimiter, - .data = rx_unit->usr_recv_buf, - .size = rx_unit->curr_trans.size, - .recv_bytes = rx_unit->curr_trans.recv_bytes, - }; - need_yield |= rx_unit->cbs.on_receive_done(rx_unit, &evt_data, rx_unit->user_data); + parlio_rx_event_data_t evt_data = { + .delimiter = rx_unit->curr_trans.delimiter, + }; + + if (event_data->flags.abnormal_eof) { + /* If received an abnormal EOF, it's a timeout event on parlio RX */ + if (rx_unit->cbs.on_timeout) { + need_yield |= rx_unit->cbs.on_timeout(rx_unit, &evt_data, rx_unit->user_data); + } + } else { + /* If received a normal EOF, it's a receive done event on parlio RX */ + if (rx_unit->cbs.on_receive_done) { + evt_data.data = rx_unit->usr_recv_buf; + evt_data.size = rx_unit->curr_trans.size; + evt_data.recv_bytes = rx_unit->curr_trans.recv_bytes; + need_yield |= rx_unit->cbs.on_receive_done(rx_unit, &evt_data, rx_unit->user_data); + } } if (rx_unit->curr_trans.flags.infinite) { @@ -333,7 +345,6 @@ static IRAM_ATTR bool s_parlio_rx_default_trans_done_callback(gdma_channel_handl parlio_ll_rx_start(rx_unit->group->hal.regs, true); parlio_ll_rx_enable_clock(rx_unit->group->hal.regs, true); } - } else { /* No more transaction pending to receive, clear the current transaction */ rx_unit->curr_trans.delimiter->under_using = false; @@ -355,6 +366,7 @@ static IRAM_ATTR bool s_parlio_rx_default_desc_done_callback(gdma_channel_handle dma_descriptor_t *finished_desc = rx_unit->curr_desc; parlio_rx_event_data_t evt_data = { .delimiter = rx_unit->curr_trans.delimiter, + // TODO: The current descriptor is not able to access when error EOF occur .data = finished_desc->buffer, .size = finished_desc->dw0.size, .recv_bytes = finished_desc->dw0.length, @@ -363,7 +375,7 @@ static IRAM_ATTR bool s_parlio_rx_default_desc_done_callback(gdma_channel_handle need_yield |= rx_unit->cbs.on_partial_receive(rx_unit, &evt_data, rx_unit->user_data); } /* For the infinite transaction, need to copy the data in DMA buffer to the user receiving buffer */ - if (rx_unit->curr_trans.flags.infinite) { + if (rx_unit->curr_trans.flags.infinite && rx_unit->curr_trans.flags.indirect_mount) { memcpy(rx_unit->usr_recv_buf + rx_unit->curr_trans.recv_bytes, evt_data.data, evt_data.recv_bytes); } else { rx_unit->curr_trans.delimiter->under_using = false; @@ -379,20 +391,6 @@ static IRAM_ATTR bool s_parlio_rx_default_desc_done_callback(gdma_channel_handle return need_yield; } -static IRAM_ATTR bool s_parlio_rx_default_timeout_callback(gdma_channel_handle_t dma_chan, gdma_event_data_t *event_data, void *user_data) -{ - parlio_rx_unit_handle_t rx_unit = (parlio_rx_unit_handle_t )user_data; - bool need_yield = false; - parlio_rx_delimiter_handle_t deli = rx_unit->curr_trans.delimiter; - if (rx_unit->cbs.on_timeout && deli) { - parlio_rx_event_data_t evt_data = { - .delimiter = deli, - }; - need_yield |= rx_unit->cbs.on_timeout(rx_unit, &evt_data, rx_unit->user_data); - } - return need_yield; -} - static esp_err_t s_parlio_rx_create_dma_descriptors(parlio_rx_unit_handle_t rx_unit, uint32_t max_recv_size) { ESP_RETURN_ON_FALSE(rx_unit, ESP_ERR_INVALID_ARG, TAG, "invalid param"); @@ -431,11 +429,8 @@ static esp_err_t s_parlio_rx_unit_init_dma(parlio_rx_unit_handle_t rx_unit) /* Register callbacks */ gdma_rx_event_callbacks_t cbs = { - .on_recv_eof = s_parlio_rx_default_trans_done_callback, + .on_recv_eof = s_parlio_rx_default_eof_callback, .on_recv_done = s_parlio_rx_default_desc_done_callback, - // TODO: not on_err_desc, wait for GDMA supports on_err_eof - // .on_err_eof = s_parlio_rx_default_timeout_callback, - .on_descr_err = s_parlio_rx_default_timeout_callback, }; gdma_register_rx_event_callbacks(rx_unit->dma_chan, &cbs, rx_unit); @@ -808,6 +803,7 @@ static esp_err_t s_parlio_rx_unit_do_transaction(parlio_rx_unit_handle_t rx_unit s_parlio_mount_transaction_buffer(rx_unit, trans); gdma_start(rx_unit->dma_chan, (intptr_t)rx_unit->curr_desc); if (rx_unit->cfg.flags.free_clk) { + printf("enable start\n"); parlio_ll_rx_start(rx_unit->group->hal.regs, true); parlio_ll_rx_enable_clock(rx_unit->group->hal.regs, true); } @@ -863,6 +859,7 @@ esp_err_t parlio_rx_unit_receive(parlio_rx_unit_handle_t rx_unit, .size = payload_size, .recv_bytes = 0, .flags.infinite = recv_cfg->flags.is_infinite, + .flags.indirect_mount = recv_cfg->flags.indirect_mount, }; rx_unit->usr_recv_buf = payload; diff --git a/components/driver/test_apps/parlio/main/CMakeLists.txt b/components/driver/test_apps/parlio/main/CMakeLists.txt index d3cd015683..de6a0905fa 100644 --- a/components/driver/test_apps/parlio/main/CMakeLists.txt +++ b/components/driver/test_apps/parlio/main/CMakeLists.txt @@ -1,8 +1,14 @@ set(srcs "test_app_main.c" + "test_parlio_rx.c" "test_parlio_tx.c") +# TODO: IDF-7840, semaphore in 'spi_bus_lock.c' is not IRAM safe +if(CONFIG_PARLIO_ISR_IRAM_SAFE) + list(REMOVE_ITEM srcs "test_parlio_rx") +endif() + # In order for the cases defined by `TEST_CASE` to be linked into the final elf, # the component can be registered as WHOLE_ARCHIVE idf_component_register(SRCS ${srcs} - PRIV_REQUIRES unity driver + PRIV_REQUIRES unity driver soc WHOLE_ARCHIVE) diff --git a/components/driver/test_apps/parlio/main/test_board.h b/components/driver/test_apps/parlio/main/test_board.h index 08aecfd081..81c64cbc8c 100644 --- a/components/driver/test_apps/parlio/main/test_board.h +++ b/components/driver/test_apps/parlio/main/test_board.h @@ -11,8 +11,17 @@ extern "C" { #endif +#if CONFIG_PARLIO_ISR_IRAM_SAFE +#define TEST_PARLIO_CALLBACK_ATTR IRAM_ATTR +#define TEST_PARLIO_MEM_ALLOC_CAPS (MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT) +#else +#define TEST_PARLIO_CALLBACK_ATTR +#define TEST_PARLIO_MEM_ALLOC_CAPS MALLOC_CAP_DEFAULT +#endif + #if CONFIG_IDF_TARGET_ESP32C6 #define TEST_CLK_GPIO 10 +#define TEST_VALID_GPIO 11 #define TEST_DATA0_GPIO 0 #define TEST_DATA1_GPIO 1 #define TEST_DATA2_GPIO 2 @@ -23,6 +32,7 @@ extern "C" { #define TEST_DATA7_GPIO 7 #elif CONFIG_IDF_TARGET_ESP32H2 #define TEST_CLK_GPIO 10 +#define TEST_VALID_GPIO 11 #define TEST_DATA0_GPIO 0 #define TEST_DATA1_GPIO 1 #define TEST_DATA2_GPIO 2 diff --git a/components/driver/test_apps/parlio/main/test_parlio_rx.c b/components/driver/test_apps/parlio/main/test_parlio_rx.c new file mode 100644 index 0000000000..65b9b3da91 --- /dev/null +++ b/components/driver/test_apps/parlio/main/test_parlio_rx.c @@ -0,0 +1,513 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "sdkconfig.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "unity.h" +#include "esp_log.h" +#include "esp_rom_gpio.h" +#include "driver/parlio_rx.h" +#include "driver/i2s_tdm.h" +#include "driver/spi_master.h" +#include "driver/gpio.h" +#include "hal/gpio_hal.h" +#include "soc/soc_caps.h" +#include "soc/i2s_periph.h" +#include "soc/spi_periph.h" +#include "soc/parlio_periph.h" +#include "esp_attr.h" +#include "test_board.h" + +#define TEST_SPI_HOST SPI2_HOST +#define TEST_I2S_PORT I2S_NUM_0 +#define TEST_VALID_SIG (PARLIO_RX_UNIT_MAX_DATA_WIDTH - 1) +#define TEST_DEFAULT_UNIT_CONFIG(_clk_src, _clk_freq) { \ + .trans_queue_depth = 10, \ + .max_recv_size = 10 * 1024, \ + .data_width = 1, \ + .clk_src = _clk_src, \ + .clk_freq_hz = _clk_freq, \ + .clk_gpio_num = _clk_src == PARLIO_CLK_SRC_EXTERNAL ? TEST_CLK_GPIO : -1, \ + .valid_gpio_num = TEST_VALID_GPIO, \ + .data_gpio_nums = { \ + [0] = TEST_DATA0_GPIO, \ + [1 ... (PARLIO_RX_UNIT_MAX_DATA_WIDTH - 1)] = -1, \ + }, \ + .flags = { \ + .clk_gate_en = false, \ + .io_loop_back = true, \ + } \ +} + +#define TEST_TASK_DATA_READY_BIT 0x01 +#define TEST_TASK_FINISHED_BIT 0x02 + +TEST_CASE("parallel_rx_unit_install_uninstall", "[parlio_rx]") +{ + printf("install rx units exhaustively\r\n"); + parlio_rx_unit_handle_t units[SOC_PARLIO_GROUPS * SOC_PARLIO_RX_UNITS_PER_GROUP]; + int k = 0; + parlio_rx_unit_config_t config = TEST_DEFAULT_UNIT_CONFIG(PARLIO_CLK_SRC_DEFAULT, 1000000); + for (int i = 0; i < SOC_PARLIO_GROUPS; i++) { + for (int j = 0; j < SOC_PARLIO_RX_UNITS_PER_GROUP; j++) { + TEST_ESP_OK(parlio_new_rx_unit(&config, &units[k++])); + } + } + TEST_ESP_ERR(ESP_ERR_NOT_FOUND, parlio_new_rx_unit(&config, &units[0])); + + for (int i = 0; i < k; i++) { + TEST_ESP_OK(parlio_del_rx_unit(units[i])); + } + + // clock from external + config.clk_src = PARLIO_CLK_SRC_EXTERNAL; + // clock gpio must be set when the clock is input from external + TEST_ESP_ERR(ESP_ERR_INVALID_ARG, parlio_new_rx_unit(&config, &units[0])); + + // clock from internal + config.clk_src = PARLIO_CLK_SRC_DEFAULT; + config.clk_gpio_num = TEST_CLK_GPIO; +#if SOC_PARLIO_RX_CLK_SUPPORT_OUTPUT + TEST_ESP_OK(parlio_new_rx_unit(&config, &units[0])); + TEST_ESP_OK(parlio_del_rx_unit(units[0])); +#else + // failed because of not support output the clock to a gpio + TEST_ESP_ERR(ESP_ERR_NOT_SUPPORTED, parlio_new_rx_unit(&config, &units[0])); + config.clk_gpio_num = -1; +#endif + config.data_width = 3; + // data width should be power of 2 + TEST_ESP_ERR(ESP_ERR_INVALID_ARG, parlio_new_rx_unit(&config, &units[0])); + + config.data_width = 4; + TEST_ESP_OK(parlio_new_rx_unit(&config, &units[0])); + TEST_ESP_OK(parlio_rx_unit_enable(units[0], true)); + // delete unit before it's disabled is not allowed + TEST_ESP_ERR(ESP_ERR_INVALID_STATE, parlio_del_rx_unit(units[0])); + TEST_ESP_OK(parlio_rx_unit_disable(units[0])); + TEST_ESP_OK(parlio_del_rx_unit(units[0])); +} + +typedef struct { + uint32_t partial_recv_cnt; + uint32_t recv_done_cnt; + uint32_t timeout_cnt; +} test_data_t; + +TEST_PARLIO_CALLBACK_ATTR +static bool test_parlio_rx_partial_recv_callback(parlio_rx_unit_handle_t rx_unit, const parlio_rx_event_data_t *edata, void *user_data) +{ + test_data_t *test_data = (test_data_t *)user_data; + test_data->partial_recv_cnt++; + return false; +} + +TEST_PARLIO_CALLBACK_ATTR +static bool test_parlio_rx_done_callback(parlio_rx_unit_handle_t rx_unit, const parlio_rx_event_data_t *edata, void *user_data) +{ + test_data_t *test_data = (test_data_t *)user_data; + test_data->recv_done_cnt++; + return false; +} + +TEST_PARLIO_CALLBACK_ATTR +static bool test_parlio_rx_timeout_callback(parlio_rx_unit_handle_t rx_unit, const parlio_rx_event_data_t *edata, void *user_data) +{ + test_data_t *test_data = (test_data_t *)user_data; + test_data->timeout_cnt++; + return false; +} + +#define TEST_PAYLOAD_SIZE 5000 + +// This test case uses soft delimiter +TEST_CASE("parallel_rx_unit_receive_transaction_test", "[parlio_rx]") +{ + parlio_rx_unit_handle_t rx_unit = NULL; + parlio_rx_delimiter_handle_t deli = NULL; + parlio_rx_delimiter_handle_t timeout_deli = NULL; + + parlio_rx_unit_config_t config = TEST_DEFAULT_UNIT_CONFIG(PARLIO_CLK_SRC_DEFAULT, 1000000); + TEST_ESP_OK(parlio_new_rx_unit(&config, &rx_unit)); + + parlio_rx_soft_delimiter_config_t sft_deli_cfg = { + .sample_edge = PARLIO_SAMPLE_EDGE_POS, + .eof_data_len = TEST_PAYLOAD_SIZE, + .timeout_ticks = 0, + }; + TEST_ESP_OK(parlio_new_rx_delimiter(&sft_deli_cfg, &deli)); + parlio_rx_level_delimiter_config_t lvl_deli_cfg = { + .valid_sig_line_id = TEST_VALID_SIG, + .sample_edge = PARLIO_SAMPLE_EDGE_POS, + .bit_pack_order = PARLIO_BIT_PACK_ORDER_MSB, + .eof_data_len = TEST_PAYLOAD_SIZE, + .timeout_ticks = 5, + .flags = { + .active_level = 1, + }, + }; + TEST_ESP_OK(parlio_new_rx_delimiter(&lvl_deli_cfg, &timeout_deli)); + + parlio_rx_event_callbacks_t cbs = { + .on_partial_receive = test_parlio_rx_partial_recv_callback, + .on_receive_done = test_parlio_rx_done_callback, + .on_timeout = test_parlio_rx_timeout_callback, + }; + test_data_t test_data = { + .partial_recv_cnt = 0, + .recv_done_cnt = 0, + }; + TEST_ESP_OK(parlio_rx_unit_register_event_callbacks(rx_unit, &cbs, &test_data)); + TEST_ESP_OK(parlio_rx_unit_enable(rx_unit, true)); + + parlio_receive_config_t recv_config = { + .delimiter = deli, + .flags.is_infinite = false, + }; + uint8_t *payload = heap_caps_calloc(1, TEST_PAYLOAD_SIZE, TEST_PARLIO_MEM_ALLOC_CAPS); + TEST_ASSERT(payload); + + printf("Testing one normal transaction...\n"); + TEST_ESP_OK(parlio_rx_soft_delimiter_start_stop(rx_unit, deli, true)); + TEST_ESP_OK(parlio_rx_unit_receive(rx_unit, payload, TEST_PAYLOAD_SIZE, &recv_config)); + TEST_ESP_OK(parlio_rx_unit_wait_all_done(rx_unit, 5000)); + TEST_ESP_OK(parlio_rx_soft_delimiter_start_stop(rx_unit, deli, false)); + TEST_ASSERT_EQUAL_UINT32(2, test_data.partial_recv_cnt); + TEST_ASSERT_EQUAL_UINT32(1, test_data.recv_done_cnt); + memset(&test_data, 0, sizeof(test_data_t)); + + printf("Testing normal transactions in queue...\n"); + TEST_ESP_OK(parlio_rx_soft_delimiter_start_stop(rx_unit, deli, true)); + // push 5 repeated transactions to the queue + for (int i = 0; i < 5; i++) { + TEST_ESP_OK(parlio_rx_unit_receive(rx_unit, payload, TEST_PAYLOAD_SIZE, &recv_config)); + } + TEST_ESP_OK(parlio_rx_unit_wait_all_done(rx_unit, 5000)); + TEST_ESP_OK(parlio_rx_soft_delimiter_start_stop(rx_unit, deli, false)); + TEST_ASSERT_EQUAL_UINT32(10, test_data.partial_recv_cnt); + TEST_ASSERT_EQUAL_UINT32(5, test_data.recv_done_cnt); + memset(&test_data, 0, sizeof(test_data_t)); + + printf("Testing the infinite transaction...\n"); + recv_config.flags.is_infinite = true; + TEST_ESP_OK(parlio_rx_soft_delimiter_start_stop(rx_unit, deli, true)); + TEST_ESP_OK(parlio_rx_unit_receive(rx_unit, payload, TEST_PAYLOAD_SIZE, &recv_config)); + // Won't receive done semaphore in infinite transaction + TEST_ESP_ERR(ESP_ERR_TIMEOUT, parlio_rx_unit_wait_all_done(rx_unit, 500)); + TEST_ESP_OK(parlio_rx_soft_delimiter_start_stop(rx_unit, deli, false)); + TEST_ASSERT_GREATER_THAN(6, test_data.partial_recv_cnt); + TEST_ASSERT_GREATER_THAN(3, test_data.recv_done_cnt); + memset(&test_data, 0, sizeof(test_data_t)); + + // printf("Testing the timeout callback...\n"); + // recv_config.flags.is_infinite = false; + // recv_config.delimiter = timeout_deli; + // // push 5 repeated transactions to the queue + // for (int i = 0; i < 5; i++) { + // TEST_ESP_OK(parlio_rx_unit_receive(rx_unit, payload, TEST_PAYLOAD_SIZE, &recv_config)); + // gpio_set_level(TEST_VALID_GPIO, 1); + // vTaskDelay(pdMS_TO_TICKS(100)); + // gpio_set_level(TEST_VALID_GPIO, 0); + // vTaskDelay(pdMS_TO_TICKS(50)); + // } + // TEST_ASSERT_TRUE(test_data.timeout_cnt); + + TEST_ESP_OK(parlio_rx_unit_disable(rx_unit)); + TEST_ESP_OK(parlio_del_rx_delimiter(deli)); + TEST_ESP_OK(parlio_del_rx_delimiter(timeout_deli)); + TEST_ESP_OK(parlio_del_rx_unit(rx_unit)); + free(payload); +}; + +static void connect_signal_internally(uint32_t gpio, uint32_t sigo, uint32_t sigi) +{ + gpio_config_t gpio_conf = { + .pin_bit_mask = BIT64(gpio), + .mode = GPIO_MODE_INPUT_OUTPUT, + .intr_type = GPIO_INTR_DISABLE, + .pull_down_en = false, + .pull_up_en = false, + }; + gpio_config(&gpio_conf); + esp_rom_gpio_connect_out_signal(gpio, sigo, false, false); + esp_rom_gpio_connect_in_signal(gpio, sigi, false); +} + +#define TEST_EOF_DATA_LEN 64 + +static void pulse_delimiter_sender_task_i2s(void *args) +{ + uint32_t *task_flags = (uint32_t *)args; + i2s_chan_handle_t tx_chan = NULL; + i2s_chan_config_t tx_chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(TEST_I2S_PORT, I2S_ROLE_MASTER); + TEST_ESP_OK(i2s_new_channel(&tx_chan_cfg, &tx_chan, NULL)); + i2s_tdm_config_t tx_tdm_cfg = { + .clk_cfg = I2S_TDM_CLK_DEFAULT_CONFIG(48000), + .slot_cfg = I2S_TDM_PCM_SHORT_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO, + I2S_TDM_SLOT0 | I2S_TDM_SLOT1 | I2S_TDM_SLOT2 | I2S_TDM_SLOT3), + .gpio_cfg = { + .mclk = I2S_GPIO_UNUSED, + .bclk = TEST_CLK_GPIO, + .ws = TEST_VALID_GPIO, + .dout = TEST_DATA0_GPIO, + .din = -1, + .invert_flags = { + .mclk_inv = false, + .bclk_inv = false, + .ws_inv = false, + }, + }, + }; + TEST_ESP_OK(i2s_channel_init_tdm_mode(tx_chan, &tx_tdm_cfg)); + + uint32_t buf_size = 2048; + uint16_t *w_buf = (uint16_t *)calloc(1, buf_size); + assert(w_buf); // Check if w_buf allocation success + + for (int i = 0; i < buf_size / 2; i += 2) { + w_buf[i] = 0x1234; + w_buf[i + 1] = 0x5678; + } + + size_t w_bytes = buf_size; + + // Preload the data into DMA buffer + while (w_bytes == buf_size) { + TEST_ESP_OK(i2s_channel_preload_data(tx_chan, w_buf, buf_size, &w_bytes)); + } + // Transmission will start after enable the tx channel + TEST_ESP_OK(i2s_channel_enable(tx_chan)); + + // Connect GPIO signals + connect_signal_internally(TEST_CLK_GPIO, + i2s_periph_signal[TEST_I2S_PORT].m_tx_bck_sig, + parlio_periph_signals.groups[0].rx_units[0].clk_in_sig); + connect_signal_internally(TEST_VALID_GPIO, + i2s_periph_signal[TEST_I2S_PORT].m_tx_ws_sig, + parlio_periph_signals.groups[0].rx_units[0].data_sigs[TEST_VALID_SIG]); + connect_signal_internally(TEST_DATA0_GPIO, + i2s_periph_signal[TEST_I2S_PORT].data_out_sig, + parlio_periph_signals.groups[0].rx_units[0].data_sigs[0]); + + while (!((*task_flags) & TEST_TASK_FINISHED_BIT)) { + vTaskDelay(pdMS_TO_TICKS(1)); + *task_flags |= TEST_TASK_DATA_READY_BIT; + } + + TEST_ESP_OK(i2s_channel_disable(tx_chan)); + TEST_ESP_OK(i2s_del_channel(tx_chan)); + free(w_buf); + + *task_flags = 0; + while (1) { + vTaskDelay(portMAX_DELAY); + } +} + +static void cs_high(spi_transaction_t *trans) +{ + gpio_set_level(TEST_VALID_GPIO, 1); +} + +static void cs_low(spi_transaction_t *trans) +{ + gpio_set_level(TEST_VALID_GPIO, 0); +} + +#define TEST_SPI_CLK_FREQ 100000 + +static void level_delimiter_sender_task_spi(void *args) +{ + uint32_t *task_flags = (uint32_t *)args; + spi_device_handle_t dev_handle; + + spi_bus_config_t bus_cfg = { + .miso_io_num = -1, + .mosi_io_num = TEST_DATA0_GPIO, + .sclk_io_num = TEST_CLK_GPIO, + .quadwp_io_num = -1, + .quadhd_io_num = -1, + .max_transfer_sz = 2048, + }; + spi_device_interface_config_t dev_cfg = { + .command_bits = 0, + .address_bits = 0, + .clock_speed_hz = TEST_SPI_CLK_FREQ, + .mode = 0, + .duty_cycle_pos = 128, + .spics_io_num = TEST_VALID_GPIO, + .queue_size = 5, + .flags = SPI_DEVICE_HALFDUPLEX | SPI_DEVICE_POSITIVE_CS, + .pre_cb = cs_high, + .post_cb = cs_low, + }; + //Initialize the SPI bus and add device + TEST_ESP_OK(spi_bus_initialize(TEST_SPI_HOST, &bus_cfg, SPI_DMA_CH_AUTO)); + TEST_ESP_OK(spi_bus_add_device(TEST_SPI_HOST, &dev_cfg, &dev_handle)); + + // Initialize CS gpio + gpio_set_level(TEST_VALID_GPIO, 0); + gpio_config_t cs_cfg = { + .pin_bit_mask = BIT64(TEST_VALID_GPIO), + .mode = GPIO_MODE_OUTPUT, + }; + gpio_config(&cs_cfg); + + // Connect SPI signals to parlio rx signals + connect_signal_internally(TEST_CLK_GPIO, + spi_periph_signal[TEST_SPI_HOST].spiclk_out, + parlio_periph_signals.groups[0].rx_units[0].clk_in_sig); + connect_signal_internally(TEST_VALID_GPIO, + spi_periph_signal[TEST_SPI_HOST].spics_out[0], + parlio_periph_signals.groups[0].rx_units[0].data_sigs[TEST_VALID_SIG]); + connect_signal_internally(TEST_DATA0_GPIO, + spi_periph_signal[TEST_SPI_HOST].spid_out, + parlio_periph_signals.groups[0].rx_units[0].data_sigs[0]); + + // Prepare the data the be transmitted + uint8_t *data = (uint8_t *)calloc(1, TEST_EOF_DATA_LEN); + for (int i = 0; i < TEST_EOF_DATA_LEN; i += 4) { + data[i] = 0x12; + data[i + 1] = 0x34; + data[i + 2] = 0x56; + data[i + 3] = 0x78; + } + spi_transaction_t t = { + .cmd = 0, + .length = TEST_EOF_DATA_LEN * 8, + .flags = 0, + .tx_buffer = data, + .user = NULL, + }; + + // Transmit data every 1ms, until the main test thread finished receiving + while (!((*task_flags) & TEST_TASK_FINISHED_BIT)) { + TEST_ESP_OK(spi_device_transmit(dev_handle, &t)); + vTaskDelay(pdMS_TO_TICKS(1)); + *task_flags |= TEST_TASK_DATA_READY_BIT; + } + + // Remove the SPI device and free the bus + TEST_ESP_OK(spi_bus_remove_device(dev_handle)); + TEST_ESP_OK(spi_bus_free(TEST_SPI_HOST)); + // Free data buffer + free(data); + + // Reset the flag to indicate the sending loop has quit + *task_flags = 0; + // Waiting to be deleted + while (1) { + vTaskDelay(portMAX_DELAY); + } +} + +static bool test_delimiter(parlio_rx_delimiter_handle_t deli, void (*sender_task_thread)(void *args)) +{ + parlio_rx_unit_handle_t rx_unit = NULL; + + parlio_rx_unit_config_t config = TEST_DEFAULT_UNIT_CONFIG(PARLIO_CLK_SRC_EXTERNAL, 1000000); + if (sender_task_thread == pulse_delimiter_sender_task_i2s) { + // I2S offers free-running clock + config.flags.free_clk = 1; + } + TEST_ESP_OK(parlio_new_rx_unit(&config, &rx_unit)); + TEST_ESP_OK(parlio_rx_unit_enable(rx_unit, true)); + + TaskHandle_t sender_task; + /* The flag to transport finish information between main test thread and the sender thread + * Set it as static to make sure it'll be valid in another thread */ + static uint32_t task_flags = 0; + xTaskCreate(sender_task_thread, "sender task", 4096, &task_flags, 5, &sender_task); + // Waiting for the data ready on line + while ((task_flags & TEST_TASK_DATA_READY_BIT)) { + vTaskDelay(1); + } + + parlio_receive_config_t recv_config = { + .delimiter = deli, + .flags.is_infinite = false, + }; + uint8_t recv_buff[TEST_EOF_DATA_LEN]; + bool is_success = false; + // sample 5 times + for (int i = 0; i < 5 && !is_success; i++) { + TEST_ESP_OK(parlio_rx_unit_receive(rx_unit, recv_buff, TEST_EOF_DATA_LEN, &recv_config)); + TEST_ESP_OK(parlio_rx_unit_wait_all_done(rx_unit, 5000)); + for (int k = 0; k < TEST_EOF_DATA_LEN; k++) { + printf("%x ", recv_buff[k]); + if ((k & 0xf) == 0xf) { + printf("\n"); + } + } + printf("\n"); + for (int j = 0; j < TEST_EOF_DATA_LEN; j++) { + // Check if 0x12 0x34 0x56 0x78 appeared in the buffer + if (recv_buff[j] == 0x12 && recv_buff[j+1] == 0x34 && + recv_buff[j+2] == 0x56 && recv_buff[j+3] == 0x78) { + is_success = true; + break; + } + } + } + // Indicate the test finished, no need to send data + task_flags |= TEST_TASK_FINISHED_BIT; + // Waiting for the sender task quit + while (task_flags) { + vTaskDelay(1); + } + // Delete the sender task + vTaskDelete(sender_task); + + TEST_ESP_OK(parlio_rx_unit_disable(rx_unit)); + TEST_ESP_OK(parlio_del_rx_unit(rx_unit)); + return is_success; +} + +// This test case uses level delimiter +TEST_CASE("parallel_rx_unit_level_delimiter_test_via_spi", "[parlio_rx]") +{ + parlio_rx_level_delimiter_config_t lvl_deli_cfg = { + .valid_sig_line_id = TEST_VALID_SIG, + .sample_edge = PARLIO_SAMPLE_EDGE_POS, + .bit_pack_order = PARLIO_BIT_PACK_ORDER_MSB, + .eof_data_len = TEST_EOF_DATA_LEN, + .timeout_ticks = 0, + .flags = { + .active_level = 1, + }, + }; + parlio_rx_delimiter_handle_t deli = NULL; + TEST_ESP_OK(parlio_new_rx_delimiter(&lvl_deli_cfg, &deli)); + bool is_success = test_delimiter(deli, level_delimiter_sender_task_spi); + TEST_ESP_OK(parlio_del_rx_delimiter(deli)); + TEST_ASSERT(is_success); +} + +// This test case uses pulse delimiter +TEST_CASE("parallel_rx_unit_pulse_delimiter_test_via_i2s", "[parlio_rx]") +{ + parlio_rx_pulse_delimiter_config_t pls_deli_cfg = { + .valid_sig_line_id = TEST_VALID_SIG, + .sample_edge = PARLIO_SAMPLE_EDGE_POS, + .bit_pack_order = PARLIO_BIT_PACK_ORDER_MSB, + .eof_data_len = TEST_EOF_DATA_LEN, + .timeout_ticks = 0, + .flags = { + .start_bit_included = 0, + .end_bit_included = 0, + .has_end_pulse = 0, + .pulse_invert = 0, + }, + }; + parlio_rx_delimiter_handle_t deli = NULL; + TEST_ESP_OK(parlio_new_rx_delimiter(&pls_deli_cfg, &deli)); + bool is_success = test_delimiter(deli, pulse_delimiter_sender_task_i2s); + TEST_ESP_OK(parlio_del_rx_delimiter(deli)); + TEST_ASSERT(is_success); +} diff --git a/components/driver/test_apps/parlio/main/test_parlio_tx.c b/components/driver/test_apps/parlio/main/test_parlio_tx.c index 0b5dd59b35..061fefae12 100644 --- a/components/driver/test_apps/parlio/main/test_parlio_tx.c +++ b/components/driver/test_apps/parlio/main/test_parlio_tx.c @@ -15,12 +15,6 @@ #include "esp_attr.h" #include "test_board.h" -#if CONFIG_PARLIO_ISR_IRAM_SAFE -#define TEST_PARLIO_CALLBACK_ATTR IRAM_ATTR -#else -#define TEST_PARLIO_CALLBACK_ATTR -#endif - TEST_CASE("parallel_tx_unit_install_uninstall", "[parlio_tx]") { printf("install tx units exhaustively\r\n"); diff --git a/components/driver/test_apps/parlio/pytest_parlio_unity.py b/components/driver/test_apps/parlio/pytest_parlio_unity.py index 4f572edd20..1bfd4acb7b 100644 --- a/components/driver/test_apps/parlio/pytest_parlio_unity.py +++ b/components/driver/test_apps/parlio/pytest_parlio_unity.py @@ -17,4 +17,4 @@ from pytest_embedded import Dut indirect=True, ) def test_parlio(dut: Dut) -> None: - dut.run_all_single_board_cases() + dut.run_all_single_board_cases(reset=True)