diff --git a/components/esp_driver_i2s/i2s_common.c b/components/esp_driver_i2s/i2s_common.c index a6ad14f15f..732a213312 100644 --- a/components/esp_driver_i2s/i2s_common.c +++ b/components/esp_driver_i2s/i2s_common.c @@ -303,6 +303,40 @@ err: return ret; } +#if SOC_I2S_HW_VERSION_1 +esp_err_t i2s_channel_change_port(i2s_chan_handle_t handle, int id) +{ + I2S_NULL_POINTER_CHECK(TAG, handle); + ESP_RETURN_ON_FALSE(id >= 0 && id < SOC_I2S_NUM, ESP_ERR_INVALID_ARG, TAG, "invalid I2S port id"); + if (id == handle->controller->id) { + return ESP_OK; + } + i2s_controller_t *i2s_obj = i2s_acquire_controller_obj(id); + if (!i2s_obj || !i2s_take_available_channel(i2s_obj, handle->dir)) { + return ESP_ERR_NOT_FOUND; + } + i2s_controller_t *old_i2s_obj = handle->controller; + portENTER_CRITICAL(&g_i2s.spinlock); + if (handle->dir == I2S_DIR_TX) { + i2s_obj->tx_chan = handle; + i2s_obj->chan_occupancy |= I2S_DIR_TX; + old_i2s_obj->tx_chan = NULL; + old_i2s_obj->full_duplex = false; + old_i2s_obj->chan_occupancy &= ~I2S_DIR_TX; + } else { + i2s_obj->rx_chan = handle; + i2s_obj->chan_occupancy |= I2S_DIR_RX; + old_i2s_obj->rx_chan = NULL; + old_i2s_obj->full_duplex = false; + old_i2s_obj->chan_occupancy &= ~I2S_DIR_RX; + } + handle->controller = i2s_obj; + portEXIT_CRITICAL(&g_i2s.spinlock); + + return ESP_OK; +} +#endif + #ifndef __cplusplus /* To make sure the i2s_event_callbacks_t is same size as i2s_event_callbacks_internal_t */ _Static_assert(sizeof(i2s_event_callbacks_t) == sizeof(i2s_event_callbacks_internal_t), "Invalid size of i2s_event_callbacks_t structure"); @@ -901,6 +935,7 @@ esp_err_t i2s_new_channel(const i2s_chan_config_t *chan_cfg, i2s_chan_handle_t * ESP_GOTO_ON_ERROR(i2s_register_channel(i2s_obj, I2S_DIR_TX, chan_cfg->dma_desc_num), err, TAG, "register I2S tx channel failed"); i2s_obj->tx_chan->role = chan_cfg->role; + i2s_obj->tx_chan->is_port_auto = id == I2S_NUM_AUTO; i2s_obj->tx_chan->intr_prio_flags = chan_cfg->intr_priority ? BIT(chan_cfg->intr_priority) : ESP_INTR_FLAG_LOWMED; i2s_obj->tx_chan->dma.auto_clear_after_cb = chan_cfg->auto_clear_after_cb; i2s_obj->tx_chan->dma.auto_clear_before_cb = chan_cfg->auto_clear_before_cb; @@ -916,6 +951,7 @@ esp_err_t i2s_new_channel(const i2s_chan_config_t *chan_cfg, i2s_chan_handle_t * ESP_GOTO_ON_ERROR(i2s_register_channel(i2s_obj, I2S_DIR_RX, chan_cfg->dma_desc_num), err, TAG, "register I2S rx channel failed"); i2s_obj->rx_chan->role = chan_cfg->role; + i2s_obj->rx_chan->is_port_auto = id == I2S_NUM_AUTO; i2s_obj->rx_chan->intr_prio_flags = chan_cfg->intr_priority ? BIT(chan_cfg->intr_priority) : ESP_INTR_FLAG_LOWMED; i2s_obj->rx_chan->dma.desc_num = chan_cfg->dma_desc_num; i2s_obj->rx_chan->dma.frame_num = chan_cfg->dma_frame_num; diff --git a/components/esp_driver_i2s/i2s_private.h b/components/esp_driver_i2s/i2s_private.h index 1675baf9f3..d91248cbdf 100644 --- a/components/esp_driver_i2s/i2s_private.h +++ b/components/esp_driver_i2s/i2s_private.h @@ -136,6 +136,7 @@ struct i2s_channel_obj_t { int intr_prio_flags;/*!< i2s interrupt priority flags */ void *mode_info; /*!< Slot, clock and gpio information of each mode */ bool full_duplex_slave; /*!< whether the channel is forced to switch to slave role for full duplex */ + bool is_port_auto; /*!< Whether the port is auto-assigned */ #if SOC_I2S_SUPPORTS_APLL bool apll_en; /*!< Flag of whether APLL enabled */ #endif @@ -274,6 +275,19 @@ void i2s_output_gpio_reserve(i2s_chan_handle_t handle, int gpio_num); */ void i2s_output_gpio_revoke(i2s_chan_handle_t handle, uint64_t gpio_mask); +#if SOC_I2S_HW_VERSION_1 +/** + * @brief Change the port of the I2S channel + * + * @param handle I2S channel handle + * @param id I2S port id + * @return + * - ESP_OK Change port success + * - ESP_ERR_NOT_FOUND No available I2S port found + */ +esp_err_t i2s_channel_change_port(i2s_chan_handle_t handle, int id); +#endif + #ifdef __cplusplus } #endif diff --git a/components/esp_driver_i2s/i2s_std.c b/components/esp_driver_i2s/i2s_std.c index 9e3cc017cd..c5256b6616 100644 --- a/components/esp_driver_i2s/i2s_std.c +++ b/components/esp_driver_i2s/i2s_std.c @@ -237,13 +237,34 @@ static esp_err_t s_i2s_channel_try_to_constitude_std_duplex(i2s_chan_handle_t ha if (memcmp(another_handle->mode_info, &curr_cfg, sizeof(i2s_std_config_t)) == 0) { handle->controller->full_duplex = true; ESP_LOGD(TAG, "Constitude full-duplex on port %d", handle->controller->id); - } + } else { #if SOC_I2S_HW_VERSION_1 - else { - ESP_LOGE(TAG, "Can't set different channel configurations on a same port"); - return ESP_ERR_INVALID_ARG; - } + bool port_changed = false; + if (handle->is_port_auto) { + ESP_LOGD(TAG, "TX & RX on I2S%d are simplex", handle->controller->id); + for (int i = 0; i < SOC_I2S_NUM; i++) { + if (i == handle->controller->id) { + continue; + } + ESP_LOGD(TAG, "Trying to move %s channel from port %d to %d", + handle->dir == I2S_DIR_TX ? "TX" : "RX", handle->controller->id, i); + if (i2s_channel_change_port(handle, i) == ESP_OK) { + ESP_LOGD(TAG, "Move success!"); + port_changed = true; + break; + } else { + ESP_LOGD(TAG, "Move failed..."); + } + } + } + if (!port_changed) { + ESP_LOGE(TAG, "Can't set different channel configurations on a same port"); + return ESP_ERR_INVALID_ARG; + } +#else + ESP_LOGD(TAG, "TX & RX on I2S%d are simplex", handle->controller->id); #endif + } } /* Switch to the slave role if needed */ if (handle->controller->full_duplex && diff --git a/components/esp_driver_i2s/i2s_tdm.c b/components/esp_driver_i2s/i2s_tdm.c index 19ad9c9774..374a190188 100644 --- a/components/esp_driver_i2s/i2s_tdm.c +++ b/components/esp_driver_i2s/i2s_tdm.c @@ -245,6 +245,8 @@ static void s_i2s_channel_try_to_constitude_tdm_duplex(i2s_chan_handle_t handle, if (memcmp(another_handle->mode_info, &curr_cfg, sizeof(i2s_tdm_config_t)) == 0) { handle->controller->full_duplex = true; ESP_LOGD(TAG, "Constitude full-duplex on port %d", handle->controller->id); + } else { + ESP_LOGD(TAG, "TX & RX on I2S%d are simplex", handle->controller->id); } } /* Switch to the slave role if needed */ diff --git a/components/esp_driver_i2s/test_apps/i2s/main/test_i2s.c b/components/esp_driver_i2s/test_apps/i2s/main/test_i2s.c index 6339056686..cf40ad0a6f 100644 --- a/components/esp_driver_i2s/test_apps/i2s/main/test_i2s.c +++ b/components/esp_driver_i2s/test_apps/i2s/main/test_i2s.c @@ -263,6 +263,8 @@ TEST_CASE("I2S_basic_channel_allocation_reconfig_deleting_test", "[i2s]") } static volatile bool task_run_flag; +static volatile bool read_task_success = true; +static volatile bool write_task_success = true; #define TEST_I2S_DATA 0x78 @@ -295,6 +297,7 @@ static void i2s_read_task(void *args) ret = i2s_channel_read(rx_handle, recv_buf, 2000, &recv_size, 300); if (ret == ESP_ERR_TIMEOUT) { printf("Read timeout count: %"PRIu32"\n", cnt++); + read_task_success = false; } } @@ -316,6 +319,7 @@ static void i2s_write_task(void *args) ret = i2s_channel_write(tx_handle, send_buf, 2000, &send_size, 300); if (ret == ESP_ERR_TIMEOUT) { printf("Write timeout count: %"PRIu32"\n", cnt++); + write_task_success = false; } } @@ -456,6 +460,7 @@ TEST_CASE("I2S_lazy_duplex_test", "[i2s]") }, }, }; + /* Part 1: test common lazy duplex mode */ TEST_ESP_OK(i2s_new_channel(&chan_cfg, &tx_handle, NULL)); TEST_ESP_OK(i2s_channel_init_std_mode(tx_handle, &std_cfg)); TEST_ESP_OK(i2s_channel_enable(tx_handle)); @@ -476,7 +481,7 @@ TEST_CASE("I2S_lazy_duplex_test", "[i2s]") xTaskCreate(i2s_read_check_task, "i2s_read_check_task", 4096, rx_handle, 5, NULL); printf("RX started\n"); - /* Wait 3 seconds to see if any failures occur */ + /* Wait 1 seconds to see if any failures occur */ vTaskDelay(pdMS_TO_TICKS(1000)); printf("Finished\n"); @@ -492,6 +497,72 @@ TEST_CASE("I2S_lazy_duplex_test", "[i2s]") /* Delete the channels */ TEST_ESP_OK(i2s_del_channel(tx_handle)); TEST_ESP_OK(i2s_del_channel(rx_handle)); + + /* Part 2: Test no lazy duplex mode with port auto assignment */ + chan_cfg.id = I2S_NUM_AUTO; + TEST_ESP_OK(i2s_new_channel(&chan_cfg, NULL, &rx_handle)); + TEST_ESP_OK(i2s_new_channel(&chan_cfg, &tx_handle, NULL)); + TEST_ESP_OK(i2s_channel_init_std_mode(rx_handle, &std_cfg)); + + /* Change the config to not constitute full-duplex */ + std_cfg.gpio_cfg.mclk = I2S_GPIO_UNUSED; + std_cfg.gpio_cfg.bclk = I2S_GPIO_UNUSED; + std_cfg.gpio_cfg.ws = I2S_GPIO_UNUSED; + std_cfg.gpio_cfg.dout = I2S_GPIO_UNUSED; + std_cfg.gpio_cfg.din = I2S_GPIO_UNUSED; +#if CONFIG_IDF_TARGET_ESP32S2 + TEST_ESP_ERR(ESP_ERR_INVALID_ARG, i2s_channel_init_std_mode(tx_handle, &std_cfg)); + /* Delete the channels */ + TEST_ESP_OK(i2s_del_channel(tx_handle)); + TEST_ESP_OK(i2s_del_channel(rx_handle)); + return; +#else + TEST_ESP_OK(i2s_channel_init_std_mode(tx_handle, &std_cfg)); +#endif + +#if CONFIG_IDF_TARGET_ESP32 + /* On ESP32, if failed to constitute full-duplex with `I2S_NUM_AUTO`, + the channel will be re-assigned to the next availableport */ + i2s_chan_info_t chan_info; + TEST_ESP_OK(i2s_channel_get_info(rx_handle, &chan_info)); + TEST_ASSERT(chan_info.id == I2S_NUM_0); + TEST_ESP_OK(i2s_channel_get_info(tx_handle, &chan_info)); + TEST_ASSERT(chan_info.id == I2S_NUM_1); +#endif + + TEST_ESP_OK(i2s_channel_enable(tx_handle)); + TEST_ESP_OK(i2s_channel_enable(rx_handle)); + + task_run_flag = true; + read_task_success = true; + write_task_success = true; + /* writing task to keep writing */ + xTaskCreate(i2s_write_task, "i2s_write_task", 4096, tx_handle, 5, NULL); + printf("TX started\n"); + vTaskDelay(pdMS_TO_TICKS(1000)); + /* reading task to keep reading */ + xTaskCreate(i2s_read_task, "i2s_read_task", 4096, rx_handle, 5, NULL); + printf("RX started\n"); + + /* Wait 1 seconds to see if any failures occur */ + vTaskDelay(pdMS_TO_TICKS(1000)); + printf("Finished\n"); + + /* Stop those three tasks */ + task_run_flag = false; + + /* Wait for the three thread deleted */ + vTaskDelay(pdMS_TO_TICKS(1000)); + + /* Disable the channels, they will keep waiting until the current reading / writing finished */ + TEST_ESP_OK(i2s_channel_disable(tx_handle)); + TEST_ESP_OK(i2s_channel_disable(rx_handle)); + /* Delete the channels */ + TEST_ESP_OK(i2s_del_channel(tx_handle)); + TEST_ESP_OK(i2s_del_channel(rx_handle)); + /* Check if the reading and writing tasks are successful */ + TEST_ASSERT(read_task_success); + TEST_ASSERT(write_task_success); } static bool whether_contains_exapected_data(uint16_t *src, uint32_t src_len, uint32_t src_step, uint32_t start_val, uint32_t val_step)