esp-idf/components/driver/test_apps/parlio/main/test_parlio_rx.c

514 lines
18 KiB
C

/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <string.h>
#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);
}