refactor(rmt): set group clock prescale dynamically

Closes https://github.com/espressif/esp-idf/issues/14760
This commit is contained in:
Chen Jichang
2025-02-26 14:17:12 +08:00
committed by Chen Ji Chang
parent 0ba53566fa
commit fc5d2e1bae
15 changed files with 167 additions and 51 deletions
+81 -12
View File
@@ -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
*/
@@ -158,12 +158,64 @@ void rmt_release_group_handle(rmt_group_t *group)
}
}
esp_err_t rmt_select_periph_clock(rmt_channel_handle_t chan, rmt_clock_source_t clk_src)
#if !SOC_RMT_CHANNEL_CLK_INDEPENDENT
static esp_err_t s_rmt_set_group_prescale(rmt_channel_t *chan, uint32_t expect_resolution_hz, uint32_t *ret_channel_prescale)
{
uint32_t periph_src_clk_hz = 0;
rmt_group_t *group = chan->group;
int group_id = group->group_id;
ESP_RETURN_ON_ERROR(esp_clk_tree_src_get_freq_hz(group->clk_src, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &periph_src_clk_hz), TAG, "get clock source freq failed");
uint32_t group_resolution_hz = 0;
uint32_t group_prescale = 0;
uint32_t channel_prescale = 0;
if (group->resolution_hz == 0) {
while (++group_prescale <= RMT_LL_GROUP_CLOCK_MAX_INTEGER_PRESCALE) {
group_resolution_hz = periph_src_clk_hz / group_prescale;
channel_prescale = (group_resolution_hz + expect_resolution_hz / 2) / expect_resolution_hz;
// use the first value found during the search that satisfies the division requirement (highest frequency)
if (channel_prescale > 0 && channel_prescale <= RMT_LL_CHANNEL_CLOCK_MAX_PRESCALE) {
break;
}
}
} else {
group_prescale = periph_src_clk_hz / group->resolution_hz;
channel_prescale = (group->resolution_hz + expect_resolution_hz / 2) / expect_resolution_hz;
}
ESP_RETURN_ON_FALSE(group_prescale > 0 && group_prescale <= RMT_LL_GROUP_CLOCK_MAX_INTEGER_PRESCALE, ESP_ERR_INVALID_ARG, TAG,
"group prescale out of the range");
ESP_RETURN_ON_FALSE(channel_prescale > 0 && channel_prescale <= RMT_LL_CHANNEL_CLOCK_MAX_PRESCALE, ESP_ERR_INVALID_ARG, TAG,
"channel prescale out of the range");
// group prescale is shared by all rmt_channel, only set once. use critical section to avoid race condition.
bool prescale_conflict = false;
group_resolution_hz = periph_src_clk_hz / group_prescale;
portENTER_CRITICAL(&group->spinlock);
if (group->resolution_hz == 0) {
group->resolution_hz = group_resolution_hz;
RMT_CLOCK_SRC_ATOMIC() {
rmt_ll_set_group_clock_src(group->hal.regs, chan->channel_id, group->clk_src, group_prescale, 1, 0);
rmt_ll_enable_group_clock(group->hal.regs, true);
}
} else {
prescale_conflict = (group->resolution_hz != group_resolution_hz);
}
portEXIT_CRITICAL(&group->spinlock);
ESP_RETURN_ON_FALSE(!prescale_conflict, ESP_ERR_INVALID_ARG, TAG,
"group resolution conflict, already is %"PRIu32" but attempt to %"PRIu32"", group->resolution_hz, group_resolution_hz);
ESP_LOGD(TAG, "group (%d) clock resolution:%"PRIu32"Hz", group_id, group->resolution_hz);
*ret_channel_prescale = channel_prescale;
return ESP_OK;
}
#endif // SOC_RMT_CHANNEL_CLK_INDEPENDENT
esp_err_t rmt_select_periph_clock(rmt_channel_handle_t chan, rmt_clock_source_t clk_src, uint32_t expect_channel_resolution)
{
esp_err_t ret = ESP_OK;
rmt_group_t *group = chan->group;
int channel_id = chan->channel_id;
uint32_t periph_src_clk_hz = 0;
bool clock_selection_conflict = false;
// check if we need to update the group clock source, group clock source is shared by all channels
portENTER_CRITICAL(&group->spinlock);
@@ -173,7 +225,7 @@ esp_err_t rmt_select_periph_clock(rmt_channel_handle_t chan, rmt_clock_source_t
clock_selection_conflict = (group->clk_src != clk_src);
}
portEXIT_CRITICAL(&group->spinlock);
ESP_RETURN_ON_FALSE(!clock_selection_conflict, ESP_ERR_INVALID_STATE, TAG,
ESP_RETURN_ON_FALSE(!clock_selection_conflict, ESP_ERR_INVALID_ARG, TAG,
"group clock conflict, already is %d but attempt to %d", group->clk_src, clk_src);
// TODO: [clk_tree] to use a generic clock enable/disable or acquire/release function for all clock source
@@ -185,10 +237,6 @@ esp_err_t rmt_select_periph_clock(rmt_channel_handle_t chan, rmt_clock_source_t
}
#endif // SOC_RMT_SUPPORT_RC_FAST
// get clock source frequency
ESP_RETURN_ON_ERROR(esp_clk_tree_src_get_freq_hz((soc_module_clk_t)clk_src, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &periph_src_clk_hz),
TAG, "get clock source frequency failed");
#if CONFIG_PM_ENABLE
// if DMA is not used, we're using CPU to push the data to the RMT FIFO
// if the CPU frequency goes down, the transfer+encoding scheme could be unstable because CPU can't fill the data in time
@@ -203,19 +251,40 @@ esp_err_t rmt_select_periph_clock(rmt_channel_handle_t chan, rmt_clock_source_t
}
#endif // SOC_RMT_SUPPORT_APB
sprintf(chan->pm_lock_name, "rmt_%d_%d", group->group_id, channel_id); // e.g. rmt_0_0
sprintf(chan->pm_lock_name, "rmt_%d_%d", group->group_id, chan->channel_id); // e.g. rmt_0_0
ret = esp_pm_lock_create(pm_lock_type, 0, chan->pm_lock_name, &chan->pm_lock);
ESP_RETURN_ON_ERROR(ret, TAG, "create pm lock failed");
#endif // CONFIG_PM_ENABLE
esp_clk_tree_enable_src((soc_module_clk_t)clk_src, true);
// no division for group clock source, to achieve highest resolution
uint32_t real_div;
#if SOC_RMT_CHANNEL_CLK_INDEPENDENT
uint32_t periph_src_clk_hz = 0;
// get clock source frequency
ESP_RETURN_ON_ERROR(esp_clk_tree_src_get_freq_hz((soc_module_clk_t)clk_src, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &periph_src_clk_hz),
TAG, "get clock source frequency failed");
RMT_CLOCK_SRC_ATOMIC() {
rmt_ll_set_group_clock_src(group->hal.regs, channel_id, clk_src, 1, 1, 0);
rmt_ll_set_group_clock_src(group->hal.regs, chan->channel_id, clk_src, 1, 1, 0);
rmt_ll_enable_group_clock(group->hal.regs, true);
}
group->resolution_hz = periph_src_clk_hz;
ESP_LOGD(TAG, "group clock resolution:%"PRIu32, group->resolution_hz);
real_div = (group->resolution_hz + expect_channel_resolution / 2) / expect_channel_resolution;
#else
// set division for group clock source, to achieve highest resolution while guaranteeing the channel resolution.
ESP_RETURN_ON_ERROR(s_rmt_set_group_prescale(chan, expect_channel_resolution, &real_div), TAG, "set rmt group prescale failed");
#endif // SOC_RMT_CHANNEL_CLK_INDEPENDENT
if (chan->direction == RMT_CHANNEL_DIRECTION_TX) {
rmt_ll_tx_set_channel_clock_div(group->hal.regs, chan->channel_id, real_div);
} else {
rmt_ll_rx_set_channel_clock_div(group->hal.regs, chan->channel_id, real_div);
}
// resolution lost due to division, calculate the real resolution
chan->resolution_hz = group->resolution_hz / real_div;
if (chan->resolution_hz != expect_channel_resolution) {
ESP_LOGW(TAG, "channel resolution loss, real=%"PRIu32, chan->resolution_hz);
}
return ret;
}
+6 -4
View File
@@ -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
*/
@@ -19,6 +19,7 @@
#include "hal/rmt_hal.h"
#include "hal/dma_types.h"
#include "hal/cache_ll.h"
#include "hal/hal_utils.h"
#include "esp_intr_alloc.h"
#include "esp_heap_caps.h"
#include "esp_clk_tree.h"
@@ -113,7 +114,7 @@ struct rmt_group_t {
portMUX_TYPE spinlock; // to protect per-group register level concurrent access
rmt_hal_context_t hal; // hal layer for each group
rmt_clock_source_t clk_src; // record the group clock source, group clock is shared by all channels
uint32_t resolution_hz; // resolution of group clock
uint32_t resolution_hz; // resolution of group clock. clk_src_hz / prescale = resolution_hz
uint32_t occupy_mask; // a set bit in the mask indicates the channel is not available
rmt_tx_channel_t *tx_channels[SOC_RMT_TX_CANDIDATES_PER_GROUP]; // array of RMT TX channels
rmt_rx_channel_t *rx_channels[SOC_RMT_RX_CANDIDATES_PER_GROUP]; // array of RMT RX channels
@@ -219,17 +220,18 @@ rmt_group_t *rmt_acquire_group_handle(int group_id);
void rmt_release_group_handle(rmt_group_t *group);
/**
* @brief Set clock source for RMT peripheral
* @brief Set clock source and resolution for RMT peripheral
*
* @param chan RMT channel handle
* @param clk_src Clock source
* @param expect_channel_resolution Expected channel resolution
* @return
* - ESP_OK: Set clock source successfully
* - ESP_ERR_NOT_SUPPORTED: Set clock source failed because the clk_src is not supported
* - ESP_ERR_INVALID_STATE: Set clock source failed because the clk_src is different from other RMT channel
* - ESP_FAIL: Set clock source failed because of other error
*/
esp_err_t rmt_select_periph_clock(rmt_channel_handle_t chan, rmt_clock_source_t clk_src);
esp_err_t rmt_select_periph_clock(rmt_channel_handle_t chan, rmt_clock_source_t clk_src, uint32_t expect_channel_resolution);
/**
* @brief Set interrupt priority to RMT group
+4 -12
View File
@@ -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
*/
@@ -276,16 +276,9 @@ esp_err_t rmt_new_rx_channel(const rmt_rx_channel_config_t *config, rmt_channel_
ESP_GOTO_ON_ERROR(ret, err, TAG, "install rx interrupt failed");
}
// select the clock source
ESP_GOTO_ON_ERROR(rmt_select_periph_clock(&rx_channel->base, config->clk_src), err, TAG, "set group clock failed");
// set channel clock resolution, find the divider to get the closest resolution
uint32_t real_div = (group->resolution_hz + config->resolution_hz / 2) / config->resolution_hz;
rmt_ll_rx_set_channel_clock_div(hal->regs, channel_id, real_div);
// resolution loss due to division, calculate the real resolution
rx_channel->base.resolution_hz = group->resolution_hz / real_div;
if (rx_channel->base.resolution_hz != config->resolution_hz) {
ESP_LOGW(TAG, "channel resolution loss, real=%"PRIu32, rx_channel->base.resolution_hz);
}
rx_channel->base.direction = RMT_CHANNEL_DIRECTION_RX;
// select the clock source and set clock resolution
ESP_GOTO_ON_ERROR(rmt_select_periph_clock(&rx_channel->base, config->clk_src, config->resolution_hz), err, TAG, "set clock resolution failed");
rx_channel->filter_clock_resolution_hz = group->resolution_hz;
// On esp32 and esp32s2, the counting clock used by the RX filter always comes from APB clock
@@ -323,7 +316,6 @@ esp_err_t rmt_new_rx_channel(const rmt_rx_channel_config_t *config, rmt_channel_
// initialize other members of rx channel
portMUX_INITIALIZE(&rx_channel->base.spinlock);
atomic_init(&rx_channel->base.fsm, RMT_FSM_INIT);
rx_channel->base.direction = RMT_CHANNEL_DIRECTION_RX;
rx_channel->base.hw_mem_base = &RMTMEM.channels[channel_id + RMT_RX_CHANNEL_OFFSET_IN_GROUP].symbols[0];
// polymorphic methods
rx_channel->base.del = rmt_del_rx_channel;
+4 -12
View File
@@ -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
*/
@@ -331,16 +331,9 @@ esp_err_t rmt_new_tx_channel(const rmt_tx_channel_config_t *config, rmt_channel_
ESP_GOTO_ON_ERROR(rmt_tx_init_dma_link(tx_channel, config), err, TAG, "install tx DMA failed");
}
#endif
// select the clock source
ESP_GOTO_ON_ERROR(rmt_select_periph_clock(&tx_channel->base, config->clk_src), err, TAG, "set group clock failed");
// set channel clock resolution, find the divider to get the closest resolution
uint32_t real_div = (group->resolution_hz + config->resolution_hz / 2) / config->resolution_hz;
rmt_ll_tx_set_channel_clock_div(hal->regs, channel_id, real_div);
// resolution lost due to division, calculate the real resolution
tx_channel->base.resolution_hz = group->resolution_hz / real_div;
if (tx_channel->base.resolution_hz != config->resolution_hz) {
ESP_LOGW(TAG, "channel resolution loss, real=%"PRIu32, tx_channel->base.resolution_hz);
}
tx_channel->base.direction = RMT_CHANNEL_DIRECTION_TX;
// select the clock source and set clock resolution
ESP_GOTO_ON_ERROR(rmt_select_periph_clock(&tx_channel->base, config->clk_src, config->resolution_hz), err, TAG, "set clock resolution failed");
rmt_ll_tx_set_mem_blocks(hal->regs, channel_id, tx_channel->base.mem_block_num);
// set limit threshold, after transmit ping_pong_symbols size, an interrupt event would be generated
@@ -376,7 +369,6 @@ esp_err_t rmt_new_tx_channel(const rmt_tx_channel_config_t *config, rmt_channel_
portMUX_INITIALIZE(&tx_channel->base.spinlock);
atomic_init(&tx_channel->base.fsm, RMT_FSM_INIT);
tx_channel->base.direction = RMT_CHANNEL_DIRECTION_TX;
tx_channel->base.hw_mem_base = &RMTMEM.channels[channel_id + RMT_TX_CHANNEL_OFFSET_IN_GROUP].symbols[0];
// polymorphic methods
tx_channel->base.del = rmt_del_tx_channel;
@@ -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
*/
@@ -155,3 +155,34 @@ TEST_CASE("RMT interrupt priority", "[rmt]")
TEST_ESP_OK(rmt_del_channel(rx_channel));
TEST_ESP_OK(rmt_del_channel(another_rx_channel));
}
#if !SOC_RMT_CHANNEL_CLK_INDEPENDENT
TEST_CASE("rmt multiple channels with different resolution", "[rmt]")
{
rmt_tx_channel_config_t tx_channel_cfg = {
.mem_block_symbols = SOC_RMT_MEM_WORDS_PER_CHANNEL,
.gpio_num = TEST_RMT_GPIO_NUM_A,
.clk_src = RMT_CLK_SRC_DEFAULT,
.resolution_hz = 20 * 1000, // 20KHz
.trans_queue_depth = 1,
};
rmt_channel_handle_t tx_channel = NULL;
rmt_rx_channel_config_t rx_channel_cfg = {
.mem_block_symbols = SOC_RMT_MEM_WORDS_PER_CHANNEL,
.gpio_num = TEST_RMT_GPIO_NUM_B,
.clk_src = RMT_CLK_SRC_DEFAULT,
.resolution_hz = 40 * 1000 * 1000, // 40MHz
};
rmt_channel_handle_t rx_channel = NULL;
TEST_ESP_OK(rmt_new_tx_channel(&tx_channel_cfg, &tx_channel));
TEST_ESP_ERR(ESP_ERR_INVALID_ARG, rmt_new_rx_channel(&rx_channel_cfg, &rx_channel));
rx_channel_cfg.resolution_hz = 1 * 1000 * 1000; // 1MHz
TEST_ESP_OK(rmt_new_rx_channel(&rx_channel_cfg, &rx_channel));
TEST_ESP_OK(rmt_del_channel(tx_channel));
TEST_ESP_OK(rmt_del_channel(rx_channel));
}
#endif //SOC_RMT_CHANNEL_CLK_INDEPENDENT