From 7be85fd8ba635df7d1c3a484612402dbb218b9c2 Mon Sep 17 00:00:00 2001 From: Chen Jichang Date: Wed, 9 Apr 2025 12:03:22 +0800 Subject: [PATCH] feat(rmt_tx): allow to switch gpio in tx channal --- .../esp_driver_rmt/include/driver/rmt_tx.h | 16 +++- components/esp_driver_rmt/src/rmt_private.h | 4 +- components/esp_driver_rmt/src/rmt_rx.c | 8 +- components/esp_driver_rmt/src/rmt_tx.c | 60 ++++++++++++--- .../test_apps/rmt/main/test_rmt_tx.c | 75 ++++++++++++++++++- 5 files changed, 145 insertions(+), 18 deletions(-) diff --git a/components/esp_driver_rmt/include/driver/rmt_tx.h b/components/esp_driver_rmt/include/driver/rmt_tx.h index 57d625770e..cd6c6661bd 100644 --- a/components/esp_driver_rmt/include/driver/rmt_tx.h +++ b/components/esp_driver_rmt/include/driver/rmt_tx.h @@ -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 */ @@ -181,6 +181,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/esp_driver_rmt/src/rmt_private.h b/components/esp_driver_rmt/src/rmt_private.h index 90be4e5ac0..3d69249f4c 100644 --- a/components/esp_driver_rmt/src/rmt_private.h +++ b/components/esp_driver_rmt/src/rmt_private.h @@ -88,12 +88,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/esp_driver_rmt/src/rmt_rx.c b/components/esp_driver_rmt/src/rmt_rx.c index 54e0787ea4..a83e620410 100644 --- a/components/esp_driver_rmt/src/rmt_rx.c +++ b/components/esp_driver_rmt/src/rmt_rx.c @@ -408,7 +408,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 @@ -502,7 +502,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; @@ -538,11 +538,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/esp_driver_rmt/src/rmt_tx.c b/components/esp_driver_rmt/src/rmt_tx.c index 9ccd5544b5..ad0f0d9e82 100644 --- a/components/esp_driver_rmt/src/rmt_tx.c +++ b/components/esp_driver_rmt/src/rmt_tx.c @@ -579,7 +579,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); @@ -764,7 +764,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); @@ -792,7 +792,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); @@ -816,11 +816,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); @@ -939,7 +939,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); @@ -963,7 +963,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); @@ -1017,7 +1017,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) { @@ -1040,7 +1040,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); @@ -1134,3 +1134,45 @@ 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_output_disable(tx_chan->base.gpio_num); + esp_gpio_revoke(BIT64(tx_chan->base.gpio_num)); + } + + // Reserve the new GPIO + uint64_t old_gpio_rsv_mask = esp_gpio_reserve(BIT64(gpio_num)); + if (old_gpio_rsv_mask & BIT64(gpio_num)) { + ESP_LOGW(TAG, "GPIO %d is not usable, maybe conflict with others", gpio_num); + } + + // Configure the new GPIO + gpio_func_sel(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/esp_driver_rmt/test_apps/rmt/main/test_rmt_tx.c b/components/esp_driver_rmt/test_apps/rmt/main/test_rmt_tx.c index 15cf1a5512..1bd85af51b 100644 --- a/components/esp_driver_rmt/test_apps/rmt/main/test_rmt_tx.c +++ b/components/esp_driver_rmt/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,75 @@ 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_A) | 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 + }; + 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)); +}