diff --git a/components/driver/rmt/include/driver/rmt_tx.h b/components/driver/rmt/include/driver/rmt_tx.h index 3cf273cfc4..1b9885e876 100644 --- a/components/driver/rmt/include/driver/rmt_tx.h +++ b/components/driver/rmt/include/driver/rmt_tx.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -178,6 +178,20 @@ esp_err_t rmt_del_sync_manager(rmt_sync_manager_handle_t synchro); */ esp_err_t rmt_sync_reset(rmt_sync_manager_handle_t synchro); +/** + * @brief Switch GPIO for RMT TX channel + * + * @param[in] channel RMT TX channel handle + * @param[in] gpio_num New GPIO number to be used + * @param[in] invert_out Whether to invert the output signal + * @return + * - ESP_OK: Switch GPIO successfully + * - ESP_ERR_INVALID_ARG: Switch GPIO failed because of invalid argument + * - ESP_ERR_INVALID_STATE: Switch GPIO failed because channel is not disabled + * - ESP_FAIL: Switch GPIO failed because of other error + */ +esp_err_t rmt_tx_switch_gpio(rmt_channel_handle_t channel, gpio_num_t gpio_num, bool invert_out); + #ifdef __cplusplus } #endif diff --git a/components/driver/rmt/rmt_private.h b/components/driver/rmt/rmt_private.h index 93aa67fc02..73febc7f0d 100644 --- a/components/driver/rmt/rmt_private.h +++ b/components/driver/rmt/rmt_private.h @@ -82,12 +82,10 @@ typedef enum { } rmt_channel_direction_t; typedef enum { - RMT_FSM_INIT_WAIT, RMT_FSM_INIT, - RMT_FSM_ENABLE_WAIT, RMT_FSM_ENABLE, - RMT_FSM_RUN_WAIT, RMT_FSM_RUN, + RMT_FSM_WAIT, } rmt_fsm_t; enum { diff --git a/components/driver/rmt/rmt_rx.c b/components/driver/rmt/rmt_rx.c index 831f057ff0..0757e45284 100644 --- a/components/driver/rmt/rmt_rx.c +++ b/components/driver/rmt/rmt_rx.c @@ -379,7 +379,7 @@ esp_err_t rmt_receive(rmt_channel_handle_t channel, void *buffer, size_t buffer_ // check if we're in a proper state to start the receiver rmt_fsm_t expected_fsm = RMT_FSM_ENABLE; - ESP_RETURN_ON_FALSE_ISR(atomic_compare_exchange_strong(&channel->fsm, &expected_fsm, RMT_FSM_RUN_WAIT), + ESP_RETURN_ON_FALSE_ISR(atomic_compare_exchange_strong(&channel->fsm, &expected_fsm, RMT_FSM_WAIT), ESP_ERR_INVALID_STATE, TAG, "channel not in enable state"); // fill in the transaction descriptor @@ -460,7 +460,7 @@ static esp_err_t rmt_rx_enable(rmt_channel_handle_t channel) { // can only enable the channel when it's in "init" state rmt_fsm_t expected_fsm = RMT_FSM_INIT; - ESP_RETURN_ON_FALSE(atomic_compare_exchange_strong(&channel->fsm, &expected_fsm, RMT_FSM_ENABLE_WAIT), + ESP_RETURN_ON_FALSE(atomic_compare_exchange_strong(&channel->fsm, &expected_fsm, RMT_FSM_WAIT), ESP_ERR_INVALID_STATE, TAG, "channel not in init state"); rmt_group_t *group = channel->group; @@ -496,11 +496,11 @@ static esp_err_t rmt_rx_disable(rmt_channel_handle_t channel) // can disable the channel when it's in `enable` or `run` state bool valid_state = false; rmt_fsm_t expected_fsm = RMT_FSM_ENABLE; - if (atomic_compare_exchange_strong(&channel->fsm, &expected_fsm, RMT_FSM_INIT_WAIT)) { + if (atomic_compare_exchange_strong(&channel->fsm, &expected_fsm, RMT_FSM_WAIT)) { valid_state = true; } expected_fsm = RMT_FSM_RUN; - if (atomic_compare_exchange_strong(&channel->fsm, &expected_fsm, RMT_FSM_INIT_WAIT)) { + if (atomic_compare_exchange_strong(&channel->fsm, &expected_fsm, RMT_FSM_WAIT)) { valid_state = true; } ESP_RETURN_ON_FALSE(valid_state, ESP_ERR_INVALID_STATE, TAG, "channel not in enable or run state"); diff --git a/components/driver/rmt/rmt_tx.c b/components/driver/rmt/rmt_tx.c index 08027a4eef..d5cb2efc14 100644 --- a/components/driver/rmt/rmt_tx.c +++ b/components/driver/rmt/rmt_tx.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -528,7 +528,7 @@ esp_err_t rmt_transmit(rmt_channel_handle_t channel, rmt_encoder_t *encoder, con // check if we need to start one pending transaction rmt_fsm_t expected_fsm = RMT_FSM_ENABLE; - if (atomic_compare_exchange_strong(&channel->fsm, &expected_fsm, RMT_FSM_RUN_WAIT)) { + if (atomic_compare_exchange_strong(&channel->fsm, &expected_fsm, RMT_FSM_WAIT)) { // check if we need to start one transaction if (xQueueReceive(tx_chan->trans_queues[RMT_TX_QUEUE_PROGRESS], &t, 0) == pdTRUE) { atomic_store(&channel->fsm, RMT_FSM_RUN); @@ -713,7 +713,7 @@ static esp_err_t rmt_tx_enable(rmt_channel_handle_t channel) { // can enable the channel when it's in "init" state rmt_fsm_t expected_fsm = RMT_FSM_INIT; - ESP_RETURN_ON_FALSE(atomic_compare_exchange_strong(&channel->fsm, &expected_fsm, RMT_FSM_ENABLE_WAIT), + ESP_RETURN_ON_FALSE(atomic_compare_exchange_strong(&channel->fsm, &expected_fsm, RMT_FSM_WAIT), ESP_ERR_INVALID_STATE, TAG, "channel not in init state"); rmt_tx_channel_t *tx_chan = __containerof(channel, rmt_tx_channel_t, base); @@ -741,7 +741,7 @@ static esp_err_t rmt_tx_enable(rmt_channel_handle_t channel) // check if we need to start one pending transaction rmt_tx_trans_desc_t *t = NULL; expected_fsm = RMT_FSM_ENABLE; - if (atomic_compare_exchange_strong(&channel->fsm, &expected_fsm, RMT_FSM_RUN_WAIT)) { + if (atomic_compare_exchange_strong(&channel->fsm, &expected_fsm, RMT_FSM_WAIT)) { if (xQueueReceive(tx_chan->trans_queues[RMT_TX_QUEUE_PROGRESS], &t, 0) == pdTRUE) { // sanity check assert(t); @@ -765,11 +765,11 @@ static esp_err_t rmt_tx_disable(rmt_channel_handle_t channel) // can disable the channel when it's in `enable` or `run` state bool valid_state = false; rmt_fsm_t expected_fsm = RMT_FSM_ENABLE; - if (atomic_compare_exchange_strong(&channel->fsm, &expected_fsm, RMT_FSM_INIT_WAIT)) { + if (atomic_compare_exchange_strong(&channel->fsm, &expected_fsm, RMT_FSM_WAIT)) { valid_state = true; } expected_fsm = RMT_FSM_RUN; - if (atomic_compare_exchange_strong(&channel->fsm, &expected_fsm, RMT_FSM_INIT_WAIT)) { + if (atomic_compare_exchange_strong(&channel->fsm, &expected_fsm, RMT_FSM_WAIT)) { valid_state = true; // disable the hardware portENTER_CRITICAL(&channel->spinlock); @@ -888,7 +888,7 @@ static bool IRAM_ATTR rmt_isr_handle_tx_done(rmt_tx_channel_t *tx_chan) bool need_yield = false; rmt_fsm_t expected_fsm = RMT_FSM_RUN; - if (atomic_compare_exchange_strong(&channel->fsm, &expected_fsm, RMT_FSM_ENABLE_WAIT)) { + if (atomic_compare_exchange_strong(&channel->fsm, &expected_fsm, RMT_FSM_WAIT)) { trans_desc = tx_chan->cur_trans; // move current finished transaction to the complete queue xQueueSendFromISR(tx_chan->trans_queues[RMT_TX_QUEUE_COMPLETE], &trans_desc, &awoken); @@ -912,7 +912,7 @@ static bool IRAM_ATTR rmt_isr_handle_tx_done(rmt_tx_channel_t *tx_chan) // let's try start the next pending transaction expected_fsm = RMT_FSM_ENABLE; - if (atomic_compare_exchange_strong(&channel->fsm, &expected_fsm, RMT_FSM_RUN_WAIT)) { + if (atomic_compare_exchange_strong(&channel->fsm, &expected_fsm, RMT_FSM_WAIT)) { if (xQueueReceiveFromISR(tx_chan->trans_queues[RMT_TX_QUEUE_PROGRESS], &trans_desc, &awoken) == pdTRUE) { // sanity check assert(trans_desc); @@ -966,7 +966,7 @@ static bool IRAM_ATTR rmt_isr_handle_tx_loop_end(rmt_tx_channel_t *tx_chan) // loop transaction finished rmt_fsm_t expected_fsm = RMT_FSM_RUN; - if (atomic_compare_exchange_strong(&channel->fsm, &expected_fsm, RMT_FSM_ENABLE_WAIT)) { + if (atomic_compare_exchange_strong(&channel->fsm, &expected_fsm, RMT_FSM_WAIT)) { // move current finished transaction to the complete queue xQueueSendFromISR(tx_chan->trans_queues[RMT_TX_QUEUE_COMPLETE], &trans_desc, &awoken); if (awoken == pdTRUE) { @@ -989,7 +989,7 @@ static bool IRAM_ATTR rmt_isr_handle_tx_loop_end(rmt_tx_channel_t *tx_chan) // let's try start the next pending transaction expected_fsm = RMT_FSM_ENABLE; - if (atomic_compare_exchange_strong(&channel->fsm, &expected_fsm, RMT_FSM_RUN_WAIT)) { + if (atomic_compare_exchange_strong(&channel->fsm, &expected_fsm, RMT_FSM_WAIT)) { if (xQueueReceiveFromISR(tx_chan->trans_queues[RMT_TX_QUEUE_PROGRESS], &trans_desc, &awoken) == pdTRUE) { // sanity check assert(trans_desc); @@ -1080,3 +1080,38 @@ static bool IRAM_ATTR rmt_dma_tx_eof_cb(gdma_channel_handle_t dma_chan, gdma_eve return false; } #endif // SOC_RMT_SUPPORT_DMA + +esp_err_t rmt_tx_switch_gpio(rmt_channel_handle_t channel, gpio_num_t gpio_num, bool invert_out) +{ + ESP_RETURN_ON_FALSE(channel && channel->direction == RMT_CHANNEL_DIRECTION_TX, ESP_ERR_INVALID_ARG, TAG, "invalid channel"); + ESP_RETURN_ON_FALSE(GPIO_IS_VALID_OUTPUT_GPIO(gpio_num), ESP_ERR_INVALID_ARG, TAG, "invalid GPIO number %d", gpio_num); + + // only can switch the GPIO when it's in INIT state + rmt_fsm_t expected_fsm = RMT_FSM_INIT; + if (atomic_compare_exchange_strong(&channel->fsm, &expected_fsm, RMT_FSM_WAIT)) { + + rmt_tx_channel_t *tx_chan = __containerof(channel, rmt_tx_channel_t, base); + rmt_group_t *group = channel->group; + int group_id = group->group_id; + int channel_id = channel->channel_id; + + // Disable the old GPIO + if (tx_chan->base.gpio_num >= 0) { + gpio_ll_output_disable(&GPIO, tx_chan->base.gpio_num); + } + + // Configure the new GPIO + gpio_ll_func_sel(&GPIO, gpio_num, PIN_FUNC_GPIO); + esp_rom_gpio_connect_out_signal(gpio_num, + rmt_periph_signals.groups[group_id].channels[channel_id + RMT_TX_CHANNEL_OFFSET_IN_GROUP].tx_sig, + invert_out, false); + tx_chan->base.gpio_num = gpio_num; + + // finally we return to the INIT state + atomic_store(&channel->fsm, RMT_FSM_INIT); + ESP_LOGD(TAG, "switch tx channel(%d,%d) gpio to %d", group_id, channel_id, gpio_num); + } else { + ESP_RETURN_ON_FALSE(false, ESP_ERR_INVALID_STATE, TAG, "channel is not in init state"); + } + return ESP_OK; +} diff --git a/components/driver/test_apps/rmt/main/test_rmt_tx.c b/components/driver/test_apps/rmt/main/test_rmt_tx.c index 8fbca6f6a2..c108b57bbd 100644 --- a/components/driver/test_apps/rmt/main/test_rmt_tx.c +++ b/components/driver/test_apps/rmt/main/test_rmt_tx.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -10,6 +10,7 @@ #include "freertos/task.h" #include "unity.h" #include "driver/rmt_tx.h" +#include "driver/gpio.h" #include "esp_timer.h" #include "soc/soc_caps.h" #include "test_util_rmt_encoders.h" @@ -654,3 +655,76 @@ TEST_CASE("rmt multiple channels transaction", "[rmt]") test_rmt_multi_channels_trans(1024, SOC_RMT_MEM_WORDS_PER_CHANNEL, true, false); #endif } + +TEST_CASE("rmt tx gpio switch test", "[rmt]") +{ + // use the GPIO to check the IO level after transaction + gpio_config_t io_conf = { + .pin_bit_mask = BIT(TEST_RMT_GPIO_NUM_B), + .mode = GPIO_MODE_INPUT, + .pull_up_en = GPIO_PULLUP_DISABLE, + .pull_down_en = GPIO_PULLDOWN_ENABLE, + .intr_type = GPIO_INTR_DISABLE, + }; + TEST_ESP_OK(gpio_config(&io_conf)); + + rmt_tx_channel_config_t tx_channel_cfg = { + .mem_block_symbols = SOC_RMT_MEM_WORDS_PER_CHANNEL, + .clk_src = RMT_CLK_SRC_DEFAULT, + .resolution_hz = 1000000, // 1MHz, 1 tick = 1us + .trans_queue_depth = 4, + .gpio_num = TEST_RMT_GPIO_NUM_A, + .intr_priority = 3, + .flags.io_loop_back = true, // use the GPIO to check the IO level after transaction + }; + printf("install tx channel\r\n"); + rmt_channel_handle_t tx_channel = NULL; + TEST_ESP_OK(rmt_new_tx_channel(&tx_channel_cfg, &tx_channel)); + printf("install bytes encoder\r\n"); + rmt_encoder_handle_t copy_encoder = NULL; + rmt_copy_encoder_config_t copy_encoder_config = {}; + TEST_ESP_OK(rmt_new_copy_encoder(©_encoder_config, ©_encoder)); + + printf("enable tx channel\r\n"); + TEST_ESP_OK(rmt_enable(tx_channel)); + + printf("start transaction\r\n"); + rmt_transmit_config_t transmit_config = { + .loop_count = 0, // no loop + .flags.eot_level = 1, + }; + rmt_symbol_word_t rmt_symbol = { + .level0 = 0, + .duration0 = 50, + .level1 = 1, + .duration1 = 100, + }; + + // check the IO level after transaction + TEST_ASSERT_EQUAL(0, gpio_get_level(TEST_RMT_GPIO_NUM_A)); + TEST_ESP_OK(rmt_transmit(tx_channel, copy_encoder, &rmt_symbol, sizeof(rmt_symbol), &transmit_config)); + TEST_ESP_OK(rmt_tx_wait_all_done(tx_channel, -1)); + TEST_ASSERT_EQUAL(1, gpio_get_level(TEST_RMT_GPIO_NUM_A)); + + // switch the GPIO + TEST_ESP_ERR(rmt_tx_switch_gpio(tx_channel, TEST_RMT_GPIO_NUM_B, false), ESP_ERR_INVALID_STATE); + TEST_ESP_OK(rmt_disable(tx_channel)); + TEST_ESP_OK(rmt_tx_switch_gpio(tx_channel, TEST_RMT_GPIO_NUM_B, false)); + TEST_ESP_OK(rmt_enable(tx_channel)); + + // check the IO level after transaction + // note: the IO level is 1 before the transaction, because the GPIO level will be inherited from the eot_level + TEST_ASSERT_EQUAL(1, gpio_get_level(TEST_RMT_GPIO_NUM_B)); + transmit_config.flags.eot_level = 0; + TEST_ESP_OK(rmt_transmit(tx_channel, copy_encoder, &rmt_symbol, sizeof(rmt_symbol), &transmit_config)); + TEST_ESP_OK(rmt_tx_wait_all_done(tx_channel, -1)); + TEST_ASSERT_EQUAL(0, gpio_get_level(TEST_RMT_GPIO_NUM_B)); + + printf("disable tx channel\r\n"); + TEST_ESP_OK(rmt_disable(tx_channel)); + printf("remove tx channel and encoder\r\n"); + TEST_ESP_OK(rmt_del_channel(tx_channel)); + TEST_ESP_OK(rmt_del_encoder(copy_encoder)); + TEST_ESP_OK(gpio_reset_pin(TEST_RMT_GPIO_NUM_A)); + TEST_ESP_OK(gpio_reset_pin(TEST_RMT_GPIO_NUM_B)); +}