mirror of
https://github.com/espressif/esp-idf.git
synced 2026-10-03 03:31:41 +03:00
feat(rmt): move the driver to a new component
This commit is contained in:
@@ -0,0 +1,268 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <sys/lock.h>
|
||||
#include "sdkconfig.h"
|
||||
#if CONFIG_RMT_ENABLE_DEBUG_LOG
|
||||
// The local log level must be defined before including esp_log.h
|
||||
// Set the maximum log level for this source file
|
||||
#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
|
||||
#endif
|
||||
#include "esp_log.h"
|
||||
#include "esp_check.h"
|
||||
#include "rmt_private.h"
|
||||
#include "clk_ctrl_os.h"
|
||||
#include "soc/rtc.h"
|
||||
#include "soc/rmt_periph.h"
|
||||
#include "hal/rmt_ll.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "esp_clk_tree.h"
|
||||
#include "esp_private/periph_ctrl.h"
|
||||
|
||||
static const char *TAG = "rmt";
|
||||
|
||||
#if SOC_PERIPH_CLK_CTRL_SHARED
|
||||
#define RMT_CLOCK_SRC_ATOMIC() PERIPH_RCC_ATOMIC()
|
||||
#else
|
||||
#define RMT_CLOCK_SRC_ATOMIC()
|
||||
#endif
|
||||
|
||||
#if !SOC_RCC_IS_INDEPENDENT
|
||||
#define RMT_RCC_ATOMIC() PERIPH_RCC_ATOMIC()
|
||||
#else
|
||||
#define RMT_RCC_ATOMIC()
|
||||
#endif
|
||||
|
||||
typedef struct rmt_platform_t {
|
||||
_lock_t mutex; // platform level mutex lock
|
||||
rmt_group_t *groups[SOC_RMT_GROUPS]; // array of RMT group instances
|
||||
int group_ref_counts[SOC_RMT_GROUPS]; // reference count used to protect group install/uninstall
|
||||
} rmt_platform_t;
|
||||
|
||||
static rmt_platform_t s_platform; // singleton platform
|
||||
|
||||
rmt_group_t *rmt_acquire_group_handle(int group_id)
|
||||
{
|
||||
bool new_group = false;
|
||||
rmt_group_t *group = NULL;
|
||||
|
||||
// prevent install rmt group concurrently
|
||||
_lock_acquire(&s_platform.mutex);
|
||||
if (!s_platform.groups[group_id]) {
|
||||
group = heap_caps_calloc(1, sizeof(rmt_group_t), RMT_MEM_ALLOC_CAPS);
|
||||
if (group) {
|
||||
new_group = true;
|
||||
s_platform.groups[group_id] = group;
|
||||
group->group_id = group_id;
|
||||
group->spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED;
|
||||
// initial occupy_mask: 1111...100...0
|
||||
group->occupy_mask = UINT32_MAX & ~((1 << SOC_RMT_CHANNELS_PER_GROUP) - 1);
|
||||
// group clock won't be configured at this stage, it will be set when allocate the first channel
|
||||
group->clk_src = 0;
|
||||
// group interrupt priority is shared between all channels, it will be set when allocate the first channel
|
||||
group->intr_priority = RMT_GROUP_INTR_PRIORITY_UNINITIALIZED;
|
||||
// enable the bus clock for the RMT peripheral
|
||||
RMT_RCC_ATOMIC() {
|
||||
rmt_ll_enable_bus_clock(group_id, true);
|
||||
rmt_ll_reset_register(group_id);
|
||||
}
|
||||
// hal layer initialize
|
||||
rmt_hal_init(&group->hal);
|
||||
}
|
||||
} else { // group already install
|
||||
group = s_platform.groups[group_id];
|
||||
}
|
||||
if (group) {
|
||||
// someone acquired the group handle means we have a new object that refer to this group
|
||||
s_platform.group_ref_counts[group_id]++;
|
||||
}
|
||||
_lock_release(&s_platform.mutex);
|
||||
|
||||
if (new_group) {
|
||||
ESP_LOGD(TAG, "new group(%d) at %p, occupy=%"PRIx32, group_id, group, group->occupy_mask);
|
||||
}
|
||||
return group;
|
||||
}
|
||||
|
||||
void rmt_release_group_handle(rmt_group_t *group)
|
||||
{
|
||||
int group_id = group->group_id;
|
||||
rmt_clock_source_t clk_src = group->clk_src;
|
||||
bool do_deinitialize = false;
|
||||
rmt_hal_context_t *hal = &group->hal;
|
||||
|
||||
_lock_acquire(&s_platform.mutex);
|
||||
s_platform.group_ref_counts[group_id]--;
|
||||
if (s_platform.group_ref_counts[group_id] == 0) {
|
||||
do_deinitialize = true;
|
||||
s_platform.groups[group_id] = NULL;
|
||||
// disable core clock
|
||||
RMT_CLOCK_SRC_ATOMIC() {
|
||||
rmt_ll_enable_group_clock(hal->regs, false);
|
||||
}
|
||||
// hal layer deinitialize
|
||||
rmt_hal_deinit(hal);
|
||||
// disable bus clock
|
||||
RMT_RCC_ATOMIC() {
|
||||
rmt_ll_enable_bus_clock(group_id, false);
|
||||
}
|
||||
free(group);
|
||||
}
|
||||
_lock_release(&s_platform.mutex);
|
||||
|
||||
switch (clk_src) {
|
||||
#if SOC_RMT_SUPPORT_RC_FAST
|
||||
case RMT_CLK_SRC_RC_FAST:
|
||||
periph_rtc_dig_clk8m_disable();
|
||||
break;
|
||||
#endif // SOC_RMT_SUPPORT_RC_FAST
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (do_deinitialize) {
|
||||
ESP_LOGD(TAG, "del group(%d)", group_id);
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t rmt_select_periph_clock(rmt_channel_handle_t chan, rmt_clock_source_t clk_src)
|
||||
{
|
||||
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);
|
||||
if (group->clk_src == 0) {
|
||||
group->clk_src = clk_src;
|
||||
} else {
|
||||
clock_selection_conflict = (group->clk_src != clk_src);
|
||||
}
|
||||
portEXIT_CRITICAL(&group->spinlock);
|
||||
ESP_RETURN_ON_FALSE(!clock_selection_conflict, ESP_ERR_INVALID_STATE, 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
|
||||
#if SOC_RMT_SUPPORT_RC_FAST
|
||||
if (clk_src == RMT_CLK_SRC_RC_FAST) {
|
||||
// RC_FAST clock is not enabled automatically on start up, we enable it here manually.
|
||||
// Note there's a ref count in the enable/disable function, we must call them in pair in the driver.
|
||||
periph_rtc_dig_clk8m_enable();
|
||||
}
|
||||
#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
|
||||
// so, choose ESP_PM_CPU_FREQ_MAX lock for non-dma mode
|
||||
// otherwise, chose lock type based on the clock source
|
||||
esp_pm_lock_type_t pm_lock_type = chan->dma_chan ? ESP_PM_NO_LIGHT_SLEEP : ESP_PM_CPU_FREQ_MAX;
|
||||
|
||||
#if SOC_RMT_SUPPORT_APB
|
||||
if (clk_src == RMT_CLK_SRC_APB) {
|
||||
// APB clock frequency can be changed during DFS
|
||||
pm_lock_type = ESP_PM_APB_FREQ_MAX;
|
||||
}
|
||||
#endif // SOC_RMT_SUPPORT_APB
|
||||
|
||||
sprintf(chan->pm_lock_name, "rmt_%d_%d", group->group_id, 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
|
||||
|
||||
// no division for group clock source, to achieve highest resolution
|
||||
RMT_CLOCK_SRC_ATOMIC() {
|
||||
rmt_ll_set_group_clock_src(group->hal.regs, 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);
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t rmt_get_channel_id(rmt_channel_handle_t channel, int *ret_id)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(channel && ret_id, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
*ret_id = channel->channel_id;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t rmt_apply_carrier(rmt_channel_handle_t channel, const rmt_carrier_config_t *config)
|
||||
{
|
||||
// specially, we allow config to be NULL, means to disable the carrier submodule
|
||||
ESP_RETURN_ON_FALSE(channel, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
return channel->set_carrier_action(channel, config);
|
||||
}
|
||||
|
||||
esp_err_t rmt_del_channel(rmt_channel_handle_t channel)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(channel, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
gpio_reset_pin(channel->gpio_num);
|
||||
return channel->del(channel);
|
||||
}
|
||||
|
||||
esp_err_t rmt_enable(rmt_channel_handle_t channel)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(channel, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
return channel->enable(channel);
|
||||
}
|
||||
|
||||
esp_err_t rmt_disable(rmt_channel_handle_t channel)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(channel, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
return channel->disable(channel);
|
||||
}
|
||||
|
||||
bool rmt_set_intr_priority_to_group(rmt_group_t *group, int intr_priority)
|
||||
{
|
||||
bool priority_conflict = false;
|
||||
portENTER_CRITICAL(&group->spinlock);
|
||||
if (group->intr_priority == RMT_GROUP_INTR_PRIORITY_UNINITIALIZED) {
|
||||
// intr_priority never allocated, accept user's value unconditionally
|
||||
// intr_priority could only be set once here
|
||||
group->intr_priority = intr_priority;
|
||||
} else {
|
||||
// group intr_priority already specified
|
||||
// If interrupt priority specified before, it CANNOT BE CHANGED until `rmt_release_group_handle()` called
|
||||
// So we have to check if the new priority specified conflicts with the old one
|
||||
if (intr_priority) {
|
||||
// User specified intr_priority, check if conflict or not
|
||||
// Even though the `group->intr_priority` is 0, an intr_priority must have been specified automatically too,
|
||||
// although we do not know it exactly now, so specifying the intr_priority again might also cause conflict.
|
||||
// So no matter if `group->intr_priority` is 0 or not, we have to check.
|
||||
// Value `0` of `group->intr_priority` means "unknown", NOT "unspecified"!
|
||||
if (intr_priority != (group->intr_priority)) {
|
||||
// intr_priority conflicts!
|
||||
priority_conflict = true;
|
||||
}
|
||||
}
|
||||
// else do nothing
|
||||
// user did not specify intr_priority, then keep the old priority
|
||||
// We'll use the `RMT_INTR_ALLOC_FLAG | RMT_ALLOW_INTR_PRIORITY_MASK`, which should always success
|
||||
}
|
||||
// The `group->intr_priority` will not change any longer, even though another task tries to modify it.
|
||||
// So we could exit critical here safely.
|
||||
portEXIT_CRITICAL(&group->spinlock);
|
||||
return priority_conflict;
|
||||
}
|
||||
|
||||
int rmt_get_isr_flags(rmt_group_t *group)
|
||||
{
|
||||
int isr_flags = RMT_INTR_ALLOC_FLAG;
|
||||
if (group->intr_priority) {
|
||||
// Use user-specified priority bit
|
||||
isr_flags |= (1 << (group->intr_priority));
|
||||
} else {
|
||||
// Allow all LOWMED priority bits
|
||||
isr_flags |= RMT_ALLOW_INTR_PRIORITY_MASK;
|
||||
}
|
||||
return isr_flags;
|
||||
}
|
||||
@@ -0,0 +1,304 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/cdefs.h>
|
||||
#include <sys/param.h>
|
||||
#include "sdkconfig.h"
|
||||
#if CONFIG_RMT_ENABLE_DEBUG_LOG
|
||||
// The local log level must be defined before including esp_log.h
|
||||
// Set the maximum log level for this source file
|
||||
#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
|
||||
#endif
|
||||
#include "esp_log.h"
|
||||
#include "esp_check.h"
|
||||
#include "driver/rmt_encoder.h"
|
||||
#include "rmt_private.h"
|
||||
#include "hal/hal_utils.h"
|
||||
|
||||
static const char *TAG = "rmt";
|
||||
|
||||
typedef struct rmt_bytes_encoder_t {
|
||||
rmt_encoder_t base; // encoder base class
|
||||
size_t last_bit_index; // index of the encoding bit position in the encoding byte
|
||||
size_t last_byte_index; // index of the encoding byte in the primary stream
|
||||
rmt_symbol_word_t bit0; // bit zero representing
|
||||
rmt_symbol_word_t bit1; // bit one representing
|
||||
struct {
|
||||
uint32_t msb_first: 1; // encode MSB firstly
|
||||
} flags;
|
||||
} rmt_bytes_encoder_t;
|
||||
|
||||
typedef struct rmt_copy_encoder_t {
|
||||
rmt_encoder_t base; // encoder base class
|
||||
size_t last_symbol_index; // index of symbol position in the primary stream
|
||||
} rmt_copy_encoder_t;
|
||||
|
||||
static esp_err_t rmt_bytes_encoder_reset(rmt_encoder_t *encoder)
|
||||
{
|
||||
rmt_bytes_encoder_t *bytes_encoder = __containerof(encoder, rmt_bytes_encoder_t, base);
|
||||
// reset index to zero
|
||||
bytes_encoder->last_bit_index = 0;
|
||||
bytes_encoder->last_byte_index = 0;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static size_t IRAM_ATTR rmt_encode_bytes(rmt_encoder_t *encoder, rmt_channel_handle_t channel,
|
||||
const void *primary_data, size_t data_size, rmt_encode_state_t *ret_state)
|
||||
{
|
||||
rmt_bytes_encoder_t *bytes_encoder = __containerof(encoder, rmt_bytes_encoder_t, base);
|
||||
rmt_tx_channel_t *tx_chan = __containerof(channel, rmt_tx_channel_t, base);
|
||||
const uint8_t *nd = (const uint8_t *)primary_data;
|
||||
rmt_encode_state_t state = RMT_ENCODING_RESET;
|
||||
rmt_dma_descriptor_t *desc0 = NULL;
|
||||
rmt_dma_descriptor_t *desc1 = NULL;
|
||||
|
||||
size_t byte_index = bytes_encoder->last_byte_index;
|
||||
size_t bit_index = bytes_encoder->last_bit_index;
|
||||
// how many symbols will be generated by the encoder
|
||||
size_t mem_want = (data_size - byte_index - 1) * 8 + (8 - bit_index);
|
||||
// how many symbols we can save for this round
|
||||
size_t mem_have = tx_chan->mem_end - tx_chan->mem_off;
|
||||
// where to put the encoded symbols? DMA buffer or RMT HW memory
|
||||
rmt_symbol_word_t *mem_to_nc = NULL;
|
||||
if (channel->dma_chan) {
|
||||
mem_to_nc = (rmt_symbol_word_t *)RMT_GET_NON_CACHE_ADDR(channel->dma_mem_base);
|
||||
} else {
|
||||
mem_to_nc = channel->hw_mem_base;
|
||||
}
|
||||
// how many symbols will be encoded in this round
|
||||
size_t encode_len = MIN(mem_want, mem_have);
|
||||
bool encoding_truncated = mem_have < mem_want;
|
||||
bool encoding_space_free = mem_have > mem_want;
|
||||
|
||||
if (channel->dma_chan) {
|
||||
// mark the start descriptor
|
||||
if (tx_chan->mem_off < tx_chan->ping_pong_symbols) {
|
||||
desc0 = &tx_chan->dma_nodes_nc[0];
|
||||
} else {
|
||||
desc0 = &tx_chan->dma_nodes_nc[1];
|
||||
}
|
||||
}
|
||||
|
||||
size_t len = encode_len;
|
||||
while (len > 0) {
|
||||
// start from last time truncated encoding
|
||||
uint8_t cur_byte = nd[byte_index];
|
||||
// bit-wise reverse
|
||||
if (bytes_encoder->flags.msb_first) {
|
||||
cur_byte = hal_utils_bitwise_reverse8(cur_byte);
|
||||
}
|
||||
while ((len > 0) && (bit_index < 8)) {
|
||||
if (cur_byte & (1 << bit_index)) {
|
||||
mem_to_nc[tx_chan->mem_off++] = bytes_encoder->bit1;
|
||||
} else {
|
||||
mem_to_nc[tx_chan->mem_off++] = bytes_encoder->bit0;
|
||||
}
|
||||
len--;
|
||||
bit_index++;
|
||||
}
|
||||
if (bit_index >= 8) {
|
||||
byte_index++;
|
||||
bit_index = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (channel->dma_chan) {
|
||||
// mark the end descriptor
|
||||
if (tx_chan->mem_off < tx_chan->ping_pong_symbols) {
|
||||
desc1 = &tx_chan->dma_nodes_nc[0];
|
||||
} else {
|
||||
desc1 = &tx_chan->dma_nodes_nc[1];
|
||||
}
|
||||
|
||||
// cross line, means desc0 has prepared with sufficient data buffer
|
||||
if (desc0 != desc1) {
|
||||
desc0->dw0.length = tx_chan->ping_pong_symbols * sizeof(rmt_symbol_word_t);
|
||||
desc0->dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_DMA;
|
||||
}
|
||||
}
|
||||
|
||||
if (encoding_truncated) {
|
||||
// this encoding has not finished yet, save the truncated position
|
||||
bytes_encoder->last_bit_index = bit_index;
|
||||
bytes_encoder->last_byte_index = byte_index;
|
||||
} else {
|
||||
// reset internal index if encoding session has finished
|
||||
bytes_encoder->last_bit_index = 0;
|
||||
bytes_encoder->last_byte_index = 0;
|
||||
state |= RMT_ENCODING_COMPLETE;
|
||||
}
|
||||
|
||||
if (!encoding_space_free) {
|
||||
// no more free memory, the caller should yield
|
||||
state |= RMT_ENCODING_MEM_FULL;
|
||||
}
|
||||
|
||||
// reset offset pointer when exceeds maximum range
|
||||
if (tx_chan->mem_off >= tx_chan->ping_pong_symbols * 2) {
|
||||
if (channel->dma_chan) {
|
||||
desc1->dw0.length = tx_chan->ping_pong_symbols * sizeof(rmt_symbol_word_t);
|
||||
desc1->dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_DMA;
|
||||
}
|
||||
tx_chan->mem_off = 0;
|
||||
}
|
||||
|
||||
*ret_state = state;
|
||||
return encode_len;
|
||||
}
|
||||
|
||||
static esp_err_t rmt_copy_encoder_reset(rmt_encoder_t *encoder)
|
||||
{
|
||||
rmt_copy_encoder_t *copy_encoder = __containerof(encoder, rmt_copy_encoder_t, base);
|
||||
copy_encoder->last_symbol_index = 0;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static size_t IRAM_ATTR rmt_encode_copy(rmt_encoder_t *encoder, rmt_channel_handle_t channel,
|
||||
const void *primary_data, size_t data_size, rmt_encode_state_t *ret_state)
|
||||
{
|
||||
rmt_copy_encoder_t *copy_encoder = __containerof(encoder, rmt_copy_encoder_t, base);
|
||||
rmt_tx_channel_t *tx_chan = __containerof(channel, rmt_tx_channel_t, base);
|
||||
rmt_symbol_word_t *symbols = (rmt_symbol_word_t *)primary_data;
|
||||
rmt_encode_state_t state = RMT_ENCODING_RESET;
|
||||
rmt_dma_descriptor_t *desc0 = NULL;
|
||||
rmt_dma_descriptor_t *desc1 = NULL;
|
||||
|
||||
size_t symbol_index = copy_encoder->last_symbol_index;
|
||||
// how many symbols will be copied by the encoder
|
||||
size_t mem_want = (data_size / 4 - symbol_index);
|
||||
// how many symbols we can save for this round
|
||||
size_t mem_have = tx_chan->mem_end - tx_chan->mem_off;
|
||||
// where to put the encoded symbols? DMA buffer or RMT HW memory
|
||||
rmt_symbol_word_t *mem_to_nc = NULL;
|
||||
if (channel->dma_chan) {
|
||||
mem_to_nc = (rmt_symbol_word_t *)RMT_GET_NON_CACHE_ADDR(channel->dma_mem_base);
|
||||
} else {
|
||||
mem_to_nc = channel->hw_mem_base;
|
||||
}
|
||||
// how many symbols will be encoded in this round
|
||||
size_t encode_len = MIN(mem_want, mem_have);
|
||||
bool encoding_truncated = mem_have < mem_want;
|
||||
bool encoding_space_free = mem_have > mem_want;
|
||||
|
||||
if (channel->dma_chan) {
|
||||
// mark the start descriptor
|
||||
if (tx_chan->mem_off < tx_chan->ping_pong_symbols) {
|
||||
desc0 = &tx_chan->dma_nodes_nc[0];
|
||||
} else {
|
||||
desc0 = &tx_chan->dma_nodes_nc[1];
|
||||
}
|
||||
}
|
||||
|
||||
size_t len = encode_len;
|
||||
while (len > 0) {
|
||||
mem_to_nc[tx_chan->mem_off++] = symbols[symbol_index++];
|
||||
len--;
|
||||
}
|
||||
|
||||
if (channel->dma_chan) {
|
||||
// mark the end descriptor
|
||||
if (tx_chan->mem_off < tx_chan->ping_pong_symbols) {
|
||||
desc1 = &tx_chan->dma_nodes_nc[0];
|
||||
} else {
|
||||
desc1 = &tx_chan->dma_nodes_nc[1];
|
||||
}
|
||||
|
||||
// cross line, means desc0 has prepared with sufficient data buffer
|
||||
if (desc0 != desc1) {
|
||||
desc0->dw0.length = tx_chan->ping_pong_symbols * sizeof(rmt_symbol_word_t);
|
||||
desc0->dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_DMA;
|
||||
}
|
||||
}
|
||||
|
||||
if (encoding_truncated) {
|
||||
// this encoding has not finished yet, save the truncated position
|
||||
copy_encoder->last_symbol_index = symbol_index;
|
||||
} else {
|
||||
// reset internal index if encoding session has finished
|
||||
copy_encoder->last_symbol_index = 0;
|
||||
state |= RMT_ENCODING_COMPLETE;
|
||||
}
|
||||
|
||||
if (!encoding_space_free) {
|
||||
// no more free memory, the caller should yield
|
||||
state |= RMT_ENCODING_MEM_FULL;
|
||||
}
|
||||
|
||||
// reset offset pointer when exceeds maximum range
|
||||
if (tx_chan->mem_off >= tx_chan->ping_pong_symbols * 2) {
|
||||
if (channel->dma_chan) {
|
||||
desc1->dw0.length = tx_chan->ping_pong_symbols * sizeof(rmt_symbol_word_t);
|
||||
desc1->dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_DMA;
|
||||
}
|
||||
tx_chan->mem_off = 0;
|
||||
}
|
||||
|
||||
*ret_state = state;
|
||||
return encode_len;
|
||||
}
|
||||
|
||||
static esp_err_t rmt_del_bytes_encoder(rmt_encoder_t *encoder)
|
||||
{
|
||||
rmt_bytes_encoder_t *bytes_encoder = __containerof(encoder, rmt_bytes_encoder_t, base);
|
||||
free(bytes_encoder);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t rmt_del_copy_encoder(rmt_encoder_t *encoder)
|
||||
{
|
||||
rmt_copy_encoder_t *copy_encoder = __containerof(encoder, rmt_copy_encoder_t, base);
|
||||
free(copy_encoder);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t rmt_new_bytes_encoder(const rmt_bytes_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
ESP_GOTO_ON_FALSE(config && ret_encoder, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
|
||||
rmt_bytes_encoder_t *encoder = heap_caps_calloc(1, sizeof(rmt_bytes_encoder_t), RMT_MEM_ALLOC_CAPS);
|
||||
ESP_GOTO_ON_FALSE(encoder, ESP_ERR_NO_MEM, err, TAG, "no mem for bytes encoder");
|
||||
encoder->base.encode = rmt_encode_bytes;
|
||||
encoder->base.del = rmt_del_bytes_encoder;
|
||||
encoder->base.reset = rmt_bytes_encoder_reset;
|
||||
encoder->bit0 = config->bit0;
|
||||
encoder->bit1 = config->bit1;
|
||||
encoder->flags.msb_first = config->flags.msb_first;
|
||||
// return general encoder handle
|
||||
*ret_encoder = &encoder->base;
|
||||
ESP_LOGD(TAG, "new bytes encoder @%p", encoder);
|
||||
err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t rmt_new_copy_encoder(const rmt_copy_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
ESP_GOTO_ON_FALSE(config && ret_encoder, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
|
||||
rmt_copy_encoder_t *encoder = heap_caps_calloc(1, sizeof(rmt_copy_encoder_t), RMT_MEM_ALLOC_CAPS);
|
||||
ESP_GOTO_ON_FALSE(encoder, ESP_ERR_NO_MEM, err, TAG, "no mem for copy encoder");
|
||||
encoder->base.encode = rmt_encode_copy;
|
||||
encoder->base.del = rmt_del_copy_encoder;
|
||||
encoder->base.reset = rmt_copy_encoder_reset;
|
||||
// return general encoder handle
|
||||
*ret_encoder = &encoder->base;
|
||||
ESP_LOGD(TAG, "new copy encoder @%p", encoder);
|
||||
err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t rmt_del_encoder(rmt_encoder_handle_t encoder)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(encoder, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
return encoder->del(encoder);
|
||||
}
|
||||
|
||||
esp_err_t rmt_encoder_reset(rmt_encoder_handle_t encoder)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(encoder, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
return encoder->reset(encoder);
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdatomic.h>
|
||||
#include "sdkconfig.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "freertos/idf_additions.h"
|
||||
#include "esp_err.h"
|
||||
#include "soc/soc_caps.h"
|
||||
#include "soc/gdma_channel.h"
|
||||
#include "hal/rmt_types.h"
|
||||
#include "hal/rmt_hal.h"
|
||||
#include "hal/dma_types.h"
|
||||
#include "hal/cache_ll.h"
|
||||
#include "esp_intr_alloc.h"
|
||||
#include "esp_heap_caps.h"
|
||||
#include "esp_pm.h"
|
||||
#include "esp_attr.h"
|
||||
#include "esp_private/gdma.h"
|
||||
#include "driver/rmt_common.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#if CONFIG_RMT_ISR_IRAM_SAFE || CONFIG_RMT_RECV_FUNC_IN_IRAM
|
||||
#define RMT_MEM_ALLOC_CAPS (MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT)
|
||||
#else
|
||||
#define RMT_MEM_ALLOC_CAPS MALLOC_CAP_DEFAULT
|
||||
#endif
|
||||
|
||||
// RMT driver object is per-channel, the interrupt source is shared between channels
|
||||
#if CONFIG_RMT_ISR_IRAM_SAFE
|
||||
#define RMT_INTR_ALLOC_FLAG (ESP_INTR_FLAG_SHARED | ESP_INTR_FLAG_IRAM)
|
||||
#else
|
||||
#define RMT_INTR_ALLOC_FLAG (ESP_INTR_FLAG_SHARED)
|
||||
#endif
|
||||
|
||||
// Hopefully the channel offset won't change in other targets
|
||||
#define RMT_TX_CHANNEL_OFFSET_IN_GROUP 0
|
||||
#define RMT_RX_CHANNEL_OFFSET_IN_GROUP (SOC_RMT_CHANNELS_PER_GROUP - SOC_RMT_TX_CANDIDATES_PER_GROUP)
|
||||
|
||||
#define RMT_ALLOW_INTR_PRIORITY_MASK ESP_INTR_FLAG_LOWMED
|
||||
|
||||
// DMA buffer size must align to `rmt_symbol_word_t`
|
||||
#define RMT_DMA_DESC_BUF_MAX_SIZE (DMA_DESCRIPTOR_BUFFER_MAX_SIZE & ~(sizeof(rmt_symbol_word_t) - 1))
|
||||
|
||||
#define RMT_DMA_NODES_PING_PONG 2 // two nodes ping-pong
|
||||
#define RMT_PM_LOCK_NAME_LEN_MAX 16
|
||||
#define RMT_GROUP_INTR_PRIORITY_UNINITIALIZED (-1)
|
||||
|
||||
// RMT is a slow peripheral, it only supports AHB-GDMA
|
||||
#define RMT_DMA_DESC_ALIGN 4
|
||||
typedef dma_descriptor_align4_t rmt_dma_descriptor_t;
|
||||
|
||||
#ifdef CACHE_LL_L2MEM_NON_CACHE_ADDR
|
||||
#define RMT_GET_NON_CACHE_ADDR(addr) ((addr) ? CACHE_LL_L2MEM_NON_CACHE_ADDR(addr) : 0)
|
||||
#else
|
||||
#define RMT_GET_NON_CACHE_ADDR(addr) (addr)
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
struct {
|
||||
rmt_symbol_word_t symbols[SOC_RMT_MEM_WORDS_PER_CHANNEL];
|
||||
} channels[SOC_RMT_CHANNELS_PER_GROUP];
|
||||
} rmt_block_mem_t;
|
||||
|
||||
// RMTMEM address is declared in <target>.peripherals.ld
|
||||
extern rmt_block_mem_t RMTMEM;
|
||||
|
||||
typedef enum {
|
||||
RMT_CHANNEL_DIRECTION_TX,
|
||||
RMT_CHANNEL_DIRECTION_RX,
|
||||
} 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_t;
|
||||
|
||||
enum {
|
||||
RMT_TX_QUEUE_READY,
|
||||
RMT_TX_QUEUE_PROGRESS,
|
||||
RMT_TX_QUEUE_COMPLETE,
|
||||
RMT_TX_QUEUE_MAX,
|
||||
};
|
||||
|
||||
typedef struct rmt_group_t rmt_group_t;
|
||||
typedef struct rmt_channel_t rmt_channel_t;
|
||||
typedef struct rmt_tx_channel_t rmt_tx_channel_t;
|
||||
typedef struct rmt_rx_channel_t rmt_rx_channel_t;
|
||||
typedef struct rmt_sync_manager_t rmt_sync_manager_t;
|
||||
|
||||
struct rmt_group_t {
|
||||
int group_id; // group ID, index from 0
|
||||
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 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
|
||||
rmt_sync_manager_t *sync_manager; // sync manager, this can be extended into an array if there're more sync controllers in one RMT group
|
||||
int intr_priority; // RMT interrupt priority
|
||||
};
|
||||
|
||||
struct rmt_channel_t {
|
||||
int channel_id; // channel ID, index from 0
|
||||
int gpio_num; // GPIO number used by RMT RX channel
|
||||
uint32_t channel_mask; // mask of the memory blocks that occupied by the channel
|
||||
size_t mem_block_num; // number of occupied RMT memory blocks
|
||||
rmt_group_t *group; // which group the channel belongs to
|
||||
portMUX_TYPE spinlock; // prevent channel resource accessing by user and interrupt concurrently
|
||||
uint32_t resolution_hz; // channel clock resolution
|
||||
intr_handle_t intr; // allocated interrupt handle for each channel
|
||||
_Atomic rmt_fsm_t fsm; // channel life cycle specific FSM
|
||||
rmt_channel_direction_t direction; // channel direction
|
||||
rmt_symbol_word_t *hw_mem_base; // base address of RMT channel hardware memory
|
||||
rmt_symbol_word_t *dma_mem_base; // base address of RMT channel DMA buffer
|
||||
gdma_channel_handle_t dma_chan; // DMA channel
|
||||
esp_pm_lock_handle_t pm_lock; // power management lock
|
||||
#if CONFIG_PM_ENABLE
|
||||
char pm_lock_name[RMT_PM_LOCK_NAME_LEN_MAX]; // pm lock name
|
||||
#endif
|
||||
// RMT channel common interface
|
||||
// The following IO functions will have per-implementation for TX and RX channel
|
||||
esp_err_t (*del)(rmt_channel_t *channel);
|
||||
esp_err_t (*set_carrier_action)(rmt_channel_t *channel, const rmt_carrier_config_t *config);
|
||||
esp_err_t (*enable)(rmt_channel_t *channel);
|
||||
esp_err_t (*disable)(rmt_channel_t *channel);
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
rmt_encoder_handle_t encoder; // encode user payload into RMT symbols
|
||||
const void *payload; // encoder payload
|
||||
size_t payload_bytes; // payload size
|
||||
int loop_count; // transaction can be continued in a loop for specific times
|
||||
int remain_loop_count; // user required loop count may exceed hardware limitation, the driver will transfer them in batches
|
||||
size_t transmitted_symbol_num; // track the number of transmitted symbols
|
||||
struct {
|
||||
uint32_t eot_level : 1; // Set the output level for the "End Of Transmission"
|
||||
uint32_t encoding_done: 1; // Indicate whether the encoding has finished (not the encoding of transmission)
|
||||
} flags;
|
||||
|
||||
} rmt_tx_trans_desc_t;
|
||||
|
||||
struct rmt_tx_channel_t {
|
||||
rmt_channel_t base; // channel base class
|
||||
size_t mem_off; // runtime argument, indicating the next writing position in the RMT hardware memory
|
||||
size_t mem_end; // runtime argument, indicating the end of current writing region
|
||||
size_t ping_pong_symbols; // ping-pong size (half of the RMT channel memory)
|
||||
size_t queue_size; // size of transaction queue
|
||||
size_t num_trans_inflight; // indicates the number of transactions that are undergoing but not recycled to ready_queue
|
||||
QueueHandle_t trans_queues[RMT_TX_QUEUE_MAX]; // transaction queues
|
||||
rmt_tx_trans_desc_t *cur_trans; // points to current transaction
|
||||
void *user_data; // user context
|
||||
rmt_tx_done_callback_t on_trans_done; // callback, invoked on trans done
|
||||
rmt_dma_descriptor_t *dma_nodes; // DMA descriptor nodes
|
||||
rmt_dma_descriptor_t *dma_nodes_nc; // DMA descriptor nodes accessed in non-cached way
|
||||
rmt_tx_trans_desc_t trans_desc_pool[]; // transfer descriptor pool
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
void *buffer; // buffer for saving the received symbols
|
||||
size_t buffer_size; // size of the buffer, in bytes
|
||||
size_t received_symbol_num; // track the number of received symbols
|
||||
size_t copy_dest_off; // tracking offset in the copy destination
|
||||
int dma_desc_index; // tracking the DMA descriptor used by ping-pong
|
||||
struct {
|
||||
uint32_t en_partial_rx: 1; // packet is too long, we need to notify the user to process the data piece by piece, in a ping-pong approach
|
||||
} flags;
|
||||
} rmt_rx_trans_desc_t;
|
||||
|
||||
struct rmt_rx_channel_t {
|
||||
rmt_channel_t base; // channel base class
|
||||
size_t mem_off; // starting offset to fetch the symbols in RMT-MEM
|
||||
size_t ping_pong_symbols; // ping-pong size (half of the RMT channel memory)
|
||||
rmt_rx_done_callback_t on_recv_done; // callback, invoked on receive done
|
||||
void *user_data; // user context
|
||||
rmt_rx_trans_desc_t trans_desc; // transaction description
|
||||
size_t num_dma_nodes; // number of DMA nodes, determined by how big the memory block that user configures
|
||||
rmt_dma_descriptor_t *dma_nodes; // DMA link nodes
|
||||
rmt_dma_descriptor_t *dma_nodes_nc; // DMA descriptor nodes accessed in non-cached way
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Acquire RMT group handle
|
||||
*
|
||||
* @param group_id Group ID
|
||||
* @return RMT group handle
|
||||
*/
|
||||
rmt_group_t *rmt_acquire_group_handle(int group_id);
|
||||
|
||||
/**
|
||||
* @brief Release RMT group handle
|
||||
*
|
||||
* @param group RMT group handle, returned from `rmt_acquire_group_handle`
|
||||
*/
|
||||
void rmt_release_group_handle(rmt_group_t *group);
|
||||
|
||||
/**
|
||||
* @brief Set clock source for RMT peripheral
|
||||
*
|
||||
* @param chan RMT channel handle
|
||||
* @param clk_src Clock source
|
||||
* @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);
|
||||
|
||||
/**
|
||||
* @brief Set interrupt priority to RMT group
|
||||
* @param group RMT group to set interrupt priority to
|
||||
* @param intr_priority User-specified interrupt priority (in num, not bitmask)
|
||||
* @return If the priority conflicts
|
||||
* - true: Interrupt priority conflict with previous specified
|
||||
* - false: Interrupt priority set successfully
|
||||
*/
|
||||
bool rmt_set_intr_priority_to_group(rmt_group_t *group, int intr_priority);
|
||||
|
||||
/**
|
||||
* @brief Get isr_flags to be passed to `esp_intr_alloc_intrstatus()` according to `intr_priority` set in RMT group
|
||||
* @param group RMT group
|
||||
* @return isr_flags
|
||||
*/
|
||||
int rmt_get_isr_flags(rmt_group_t *group);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,807 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/cdefs.h>
|
||||
#include <sys/param.h>
|
||||
#include "sdkconfig.h"
|
||||
#if CONFIG_RMT_ENABLE_DEBUG_LOG
|
||||
// The local log level must be defined before including esp_log.h
|
||||
// Set the maximum log level for this source file
|
||||
#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
|
||||
#endif
|
||||
#include "esp_log.h"
|
||||
#include "esp_check.h"
|
||||
#include "esp_memory_utils.h"
|
||||
#include "esp_rom_gpio.h"
|
||||
#include "soc/rmt_periph.h"
|
||||
#include "soc/rtc.h"
|
||||
#include "hal/rmt_ll.h"
|
||||
#include "hal/cache_hal.h"
|
||||
#include "hal/gpio_hal.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "driver/rmt_rx.h"
|
||||
#include "rmt_private.h"
|
||||
#include "rom/cache.h"
|
||||
|
||||
#define ALIGN_UP(num, align) (((num) + ((align) - 1)) & ~((align) - 1))
|
||||
#define ALIGN_DOWN(num, align) ((num) & ~((align) - 1))
|
||||
|
||||
static const char *TAG = "rmt";
|
||||
|
||||
static esp_err_t rmt_del_rx_channel(rmt_channel_handle_t channel);
|
||||
static esp_err_t rmt_rx_demodulate_carrier(rmt_channel_handle_t channel, const rmt_carrier_config_t *config);
|
||||
static esp_err_t rmt_rx_enable(rmt_channel_handle_t channel);
|
||||
static esp_err_t rmt_rx_disable(rmt_channel_handle_t channel);
|
||||
static void rmt_rx_default_isr(void *args);
|
||||
|
||||
#if SOC_RMT_SUPPORT_DMA
|
||||
static bool rmt_dma_rx_one_block_cb(gdma_channel_handle_t dma_chan, gdma_event_data_t *event_data, void *user_data);
|
||||
|
||||
static void rmt_rx_mount_dma_buffer(rmt_rx_channel_t *rx_chan, const void *buffer, size_t buffer_size, size_t per_block_size, size_t last_block_size)
|
||||
{
|
||||
uint8_t *data = (uint8_t *)buffer;
|
||||
for (int i = 0; i < rx_chan->num_dma_nodes; i++) {
|
||||
rmt_dma_descriptor_t *desc_nc = &rx_chan->dma_nodes_nc[i];
|
||||
desc_nc->buffer = data + i * per_block_size;
|
||||
desc_nc->dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_DMA;
|
||||
desc_nc->dw0.suc_eof = 0;
|
||||
desc_nc->dw0.length = 0;
|
||||
desc_nc->dw0.size = per_block_size;
|
||||
}
|
||||
rx_chan->dma_nodes_nc[rx_chan->num_dma_nodes - 1].dw0.size = last_block_size;
|
||||
}
|
||||
|
||||
static esp_err_t rmt_rx_init_dma_link(rmt_rx_channel_t *rx_channel, const rmt_rx_channel_config_t *config)
|
||||
{
|
||||
gdma_channel_alloc_config_t dma_chan_config = {
|
||||
.direction = GDMA_CHANNEL_DIRECTION_RX,
|
||||
};
|
||||
ESP_RETURN_ON_ERROR(gdma_new_ahb_channel(&dma_chan_config, &rx_channel->base.dma_chan), TAG, "allocate RX DMA channel failed");
|
||||
|
||||
// circular DMA descriptor
|
||||
for (int i = 0; i < rx_channel->num_dma_nodes; i++) {
|
||||
rx_channel->dma_nodes_nc[i].next = &rx_channel->dma_nodes[i + 1];
|
||||
}
|
||||
rx_channel->dma_nodes_nc[rx_channel->num_dma_nodes - 1].next = &rx_channel->dma_nodes[0];
|
||||
|
||||
// register event callbacks
|
||||
gdma_rx_event_callbacks_t cbs = {
|
||||
.on_recv_done = rmt_dma_rx_one_block_cb,
|
||||
};
|
||||
gdma_register_rx_event_callbacks(rx_channel->base.dma_chan, &cbs, rx_channel);
|
||||
return ESP_OK;
|
||||
}
|
||||
#endif // SOC_RMT_SUPPORT_DMA
|
||||
|
||||
static esp_err_t rmt_rx_register_to_group(rmt_rx_channel_t *rx_channel, const rmt_rx_channel_config_t *config)
|
||||
{
|
||||
size_t mem_block_num = 0;
|
||||
// start to search for a free channel
|
||||
// a channel can take up its neighbour's memory block, so the neighbour channel won't work, we should skip these "invaded" ones
|
||||
int channel_scan_start = RMT_RX_CHANNEL_OFFSET_IN_GROUP;
|
||||
int channel_scan_end = RMT_RX_CHANNEL_OFFSET_IN_GROUP + SOC_RMT_RX_CANDIDATES_PER_GROUP;
|
||||
if (config->flags.with_dma) {
|
||||
// for DMA mode, the memory block number is always 1; for non-DMA mode, memory block number is configured by user
|
||||
mem_block_num = 1;
|
||||
// Only the last channel has the DMA capability
|
||||
channel_scan_start = RMT_RX_CHANNEL_OFFSET_IN_GROUP + SOC_RMT_RX_CANDIDATES_PER_GROUP - 1;
|
||||
rx_channel->ping_pong_symbols = 0; // with DMA, we don't need to do ping-pong
|
||||
} else {
|
||||
// one channel can occupy multiple memory blocks
|
||||
mem_block_num = config->mem_block_symbols / SOC_RMT_MEM_WORDS_PER_CHANNEL;
|
||||
if (mem_block_num * SOC_RMT_MEM_WORDS_PER_CHANNEL < config->mem_block_symbols) {
|
||||
mem_block_num++;
|
||||
}
|
||||
rx_channel->ping_pong_symbols = mem_block_num * SOC_RMT_MEM_WORDS_PER_CHANNEL / 2;
|
||||
}
|
||||
rx_channel->base.mem_block_num = mem_block_num;
|
||||
|
||||
// search free channel and then register to the group
|
||||
// memory blocks used by one channel must be continuous
|
||||
uint32_t channel_mask = (1 << mem_block_num) - 1;
|
||||
rmt_group_t *group = NULL;
|
||||
int channel_id = -1;
|
||||
for (int i = 0; i < SOC_RMT_GROUPS; i++) {
|
||||
group = rmt_acquire_group_handle(i);
|
||||
ESP_RETURN_ON_FALSE(group, ESP_ERR_NO_MEM, TAG, "no mem for group (%d)", i);
|
||||
portENTER_CRITICAL(&group->spinlock);
|
||||
for (int j = channel_scan_start; j < channel_scan_end; j++) {
|
||||
if (!(group->occupy_mask & (channel_mask << j))) {
|
||||
group->occupy_mask |= (channel_mask << j);
|
||||
// the channel ID should index from 0
|
||||
channel_id = j - RMT_RX_CHANNEL_OFFSET_IN_GROUP;
|
||||
group->rx_channels[channel_id] = rx_channel;
|
||||
break;
|
||||
}
|
||||
}
|
||||
portEXIT_CRITICAL(&group->spinlock);
|
||||
if (channel_id < 0) {
|
||||
// didn't find a capable channel in the group, don't forget to release the group handle
|
||||
rmt_release_group_handle(group);
|
||||
} else {
|
||||
rx_channel->base.channel_id = channel_id;
|
||||
rx_channel->base.channel_mask = channel_mask;
|
||||
rx_channel->base.group = group;
|
||||
break;
|
||||
}
|
||||
}
|
||||
ESP_RETURN_ON_FALSE(channel_id >= 0, ESP_ERR_NOT_FOUND, TAG, "no free rx channels");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static void rmt_rx_unregister_from_group(rmt_channel_t *channel, rmt_group_t *group)
|
||||
{
|
||||
portENTER_CRITICAL(&group->spinlock);
|
||||
group->rx_channels[channel->channel_id] = NULL;
|
||||
group->occupy_mask &= ~(channel->channel_mask << (channel->channel_id + RMT_RX_CHANNEL_OFFSET_IN_GROUP));
|
||||
portEXIT_CRITICAL(&group->spinlock);
|
||||
// channel has a reference on group, release it now
|
||||
rmt_release_group_handle(group);
|
||||
}
|
||||
|
||||
static esp_err_t rmt_rx_destroy(rmt_rx_channel_t *rx_channel)
|
||||
{
|
||||
if (rx_channel->base.intr) {
|
||||
ESP_RETURN_ON_ERROR(esp_intr_free(rx_channel->base.intr), TAG, "delete interrupt service failed");
|
||||
}
|
||||
if (rx_channel->base.pm_lock) {
|
||||
ESP_RETURN_ON_ERROR(esp_pm_lock_delete(rx_channel->base.pm_lock), TAG, "delete pm_lock failed");
|
||||
}
|
||||
#if SOC_RMT_SUPPORT_DMA
|
||||
if (rx_channel->base.dma_chan) {
|
||||
ESP_RETURN_ON_ERROR(gdma_del_channel(rx_channel->base.dma_chan), TAG, "delete dma channel failed");
|
||||
}
|
||||
#endif // SOC_RMT_SUPPORT_DMA
|
||||
if (rx_channel->base.group) {
|
||||
// de-register channel from RMT group
|
||||
rmt_rx_unregister_from_group(&rx_channel->base, rx_channel->base.group);
|
||||
}
|
||||
if (rx_channel->dma_nodes) {
|
||||
free(rx_channel->dma_nodes);
|
||||
}
|
||||
free(rx_channel);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t rmt_new_rx_channel(const rmt_rx_channel_config_t *config, rmt_channel_handle_t *ret_chan)
|
||||
{
|
||||
#if CONFIG_RMT_ENABLE_DEBUG_LOG
|
||||
esp_log_level_set(TAG, ESP_LOG_DEBUG);
|
||||
#endif
|
||||
esp_err_t ret = ESP_OK;
|
||||
rmt_rx_channel_t *rx_channel = NULL;
|
||||
// Check if priority is valid
|
||||
if (config->intr_priority) {
|
||||
ESP_GOTO_ON_FALSE((config->intr_priority) > 0, ESP_ERR_INVALID_ARG, err, TAG, "invalid interrupt priority:%d", config->intr_priority);
|
||||
ESP_GOTO_ON_FALSE(1 << (config->intr_priority) & RMT_ALLOW_INTR_PRIORITY_MASK, ESP_ERR_INVALID_ARG, err, TAG, "invalid interrupt priority:%d", config->intr_priority);
|
||||
}
|
||||
ESP_GOTO_ON_FALSE(config && ret_chan && config->resolution_hz, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
|
||||
ESP_GOTO_ON_FALSE(GPIO_IS_VALID_GPIO(config->gpio_num), ESP_ERR_INVALID_ARG, err, TAG, "invalid GPIO number");
|
||||
ESP_GOTO_ON_FALSE((config->mem_block_symbols & 0x01) == 0 && config->mem_block_symbols >= SOC_RMT_MEM_WORDS_PER_CHANNEL,
|
||||
ESP_ERR_INVALID_ARG, err, TAG, "mem_block_symbols must be even and at least %d", SOC_RMT_MEM_WORDS_PER_CHANNEL);
|
||||
#if !SOC_RMT_SUPPORT_DMA
|
||||
ESP_GOTO_ON_FALSE(config->flags.with_dma == 0, ESP_ERR_NOT_SUPPORTED, err, TAG, "DMA not supported");
|
||||
#endif // SOC_RMT_SUPPORT_DMA
|
||||
|
||||
// malloc channel memory
|
||||
uint32_t mem_caps = RMT_MEM_ALLOC_CAPS;
|
||||
rx_channel = heap_caps_calloc(1, sizeof(rmt_rx_channel_t), mem_caps);
|
||||
ESP_GOTO_ON_FALSE(rx_channel, ESP_ERR_NO_MEM, err, TAG, "no mem for rx channel");
|
||||
// create DMA descriptor
|
||||
size_t num_dma_nodes = 0;
|
||||
if (config->flags.with_dma) {
|
||||
mem_caps |= MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA;
|
||||
num_dma_nodes = config->mem_block_symbols * sizeof(rmt_symbol_word_t) / RMT_DMA_DESC_BUF_MAX_SIZE + 1;
|
||||
num_dma_nodes = MAX(2, num_dma_nodes); // at least 2 DMA nodes for ping-pong
|
||||
// DMA descriptors must be placed in internal SRAM
|
||||
rx_channel->dma_nodes = heap_caps_aligned_calloc(RMT_DMA_DESC_ALIGN, num_dma_nodes, sizeof(rmt_dma_descriptor_t), mem_caps);
|
||||
ESP_GOTO_ON_FALSE(rx_channel->dma_nodes, ESP_ERR_NO_MEM, err, TAG, "no mem for rx channel DMA nodes");
|
||||
// we will use the non-cached address to manipulate the DMA descriptor, for simplicity
|
||||
rx_channel->dma_nodes_nc = (rmt_dma_descriptor_t *)RMT_GET_NON_CACHE_ADDR(rx_channel->dma_nodes);
|
||||
}
|
||||
rx_channel->num_dma_nodes = num_dma_nodes;
|
||||
// register the channel to group
|
||||
ESP_GOTO_ON_ERROR(rmt_rx_register_to_group(rx_channel, config), err, TAG, "register channel failed");
|
||||
rmt_group_t *group = rx_channel->base.group;
|
||||
rmt_hal_context_t *hal = &group->hal;
|
||||
int channel_id = rx_channel->base.channel_id;
|
||||
int group_id = group->group_id;
|
||||
|
||||
// reset channel, make sure the RX engine is not working, and events are cleared
|
||||
portENTER_CRITICAL(&group->spinlock);
|
||||
rmt_hal_rx_channel_reset(&group->hal, channel_id);
|
||||
portEXIT_CRITICAL(&group->spinlock);
|
||||
|
||||
// When channel receives an end-maker, a DMA in_suc_eof interrupt will be generated
|
||||
// So we don't rely on RMT interrupt any more, GDMA event callback is sufficient
|
||||
if (config->flags.with_dma) {
|
||||
#if SOC_RMT_SUPPORT_DMA
|
||||
ESP_GOTO_ON_ERROR(rmt_rx_init_dma_link(rx_channel, config), err, TAG, "install rx DMA failed");
|
||||
#endif // SOC_RMT_SUPPORT_DMA
|
||||
} else {
|
||||
// RMT interrupt is mandatory if the channel doesn't use DMA
|
||||
// --- install interrupt service
|
||||
// interrupt is mandatory to run basic RMT transactions, so it's not lazy installed in `rmt_tx_register_event_callbacks()`
|
||||
// 1-- Set user specified priority to `group->intr_priority`
|
||||
bool priority_conflict = rmt_set_intr_priority_to_group(group, config->intr_priority);
|
||||
ESP_GOTO_ON_FALSE(!priority_conflict, ESP_ERR_INVALID_ARG, err, TAG, "intr_priority conflict");
|
||||
// 2-- Get interrupt allocation flag
|
||||
int isr_flags = rmt_get_isr_flags(group);
|
||||
// 3-- Allocate interrupt using isr_flag
|
||||
ret = esp_intr_alloc_intrstatus(rmt_periph_signals.groups[group_id].irq, isr_flags,
|
||||
(uint32_t)rmt_ll_get_interrupt_status_reg(hal->regs),
|
||||
RMT_LL_EVENT_RX_MASK(channel_id), rmt_rx_default_isr, rx_channel, &rx_channel->base.intr);
|
||||
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
|
||||
uint32_t real_div = group->resolution_hz / 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);
|
||||
}
|
||||
|
||||
rmt_ll_rx_set_mem_blocks(hal->regs, channel_id, rx_channel->base.mem_block_num);
|
||||
rmt_ll_rx_set_mem_owner(hal->regs, channel_id, RMT_LL_MEM_OWNER_HW);
|
||||
#if SOC_RMT_SUPPORT_RX_PINGPONG
|
||||
rmt_ll_rx_set_limit(hal->regs, channel_id, rx_channel->ping_pong_symbols);
|
||||
// always enable rx wrap, both DMA mode and ping-pong mode rely this feature
|
||||
rmt_ll_rx_enable_wrap(hal->regs, channel_id, true);
|
||||
#endif
|
||||
#if SOC_RMT_SUPPORT_RX_DEMODULATION
|
||||
// disable carrier demodulation by default, can reenable by `rmt_apply_carrier()`
|
||||
rmt_ll_rx_enable_carrier_demodulation(hal->regs, channel_id, false);
|
||||
#endif
|
||||
|
||||
// GPIO Matrix/MUX configuration
|
||||
rx_channel->base.gpio_num = config->gpio_num;
|
||||
gpio_config_t gpio_conf = {
|
||||
.intr_type = GPIO_INTR_DISABLE,
|
||||
// also enable the input path is `io_loop_back` is on, this is useful for debug
|
||||
.mode = GPIO_MODE_INPUT | (config->flags.io_loop_back ? GPIO_MODE_OUTPUT : 0),
|
||||
.pull_down_en = false,
|
||||
.pull_up_en = true,
|
||||
.pin_bit_mask = 1ULL << config->gpio_num,
|
||||
};
|
||||
ESP_GOTO_ON_ERROR(gpio_config(&gpio_conf), err, TAG, "config GPIO failed");
|
||||
esp_rom_gpio_connect_in_signal(config->gpio_num,
|
||||
rmt_periph_signals.groups[group_id].channels[channel_id + RMT_RX_CHANNEL_OFFSET_IN_GROUP].rx_sig,
|
||||
config->flags.invert_in);
|
||||
gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[config->gpio_num], PIN_FUNC_GPIO);
|
||||
|
||||
// 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;
|
||||
rx_channel->base.set_carrier_action = rmt_rx_demodulate_carrier;
|
||||
rx_channel->base.enable = rmt_rx_enable;
|
||||
rx_channel->base.disable = rmt_rx_disable;
|
||||
// return general channel handle
|
||||
*ret_chan = &rx_channel->base;
|
||||
ESP_LOGD(TAG, "new rx channel(%d,%d) at %p, gpio=%d, res=%"PRIu32"Hz, hw_mem_base=%p, ping_pong_size=%d",
|
||||
group_id, channel_id, rx_channel, config->gpio_num, rx_channel->base.resolution_hz,
|
||||
rx_channel->base.hw_mem_base, rx_channel->ping_pong_symbols);
|
||||
return ESP_OK;
|
||||
|
||||
err:
|
||||
if (rx_channel) {
|
||||
rmt_rx_destroy(rx_channel);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static esp_err_t rmt_del_rx_channel(rmt_channel_handle_t channel)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(atomic_load(&channel->fsm) == RMT_FSM_INIT,
|
||||
ESP_ERR_INVALID_STATE, TAG, "channel not in init state");
|
||||
rmt_rx_channel_t *rx_chan = __containerof(channel, rmt_rx_channel_t, base);
|
||||
rmt_group_t *group = channel->group;
|
||||
int group_id = group->group_id;
|
||||
int channel_id = channel->channel_id;
|
||||
ESP_LOGD(TAG, "del rx channel(%d,%d)", group_id, channel_id);
|
||||
// recycle memory resource
|
||||
ESP_RETURN_ON_ERROR(rmt_rx_destroy(rx_chan), TAG, "destroy rx channel failed");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t rmt_rx_register_event_callbacks(rmt_channel_handle_t channel, const rmt_rx_event_callbacks_t *cbs, void *user_data)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(channel && cbs, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
ESP_RETURN_ON_FALSE(channel->direction == RMT_CHANNEL_DIRECTION_RX, ESP_ERR_INVALID_ARG, TAG, "invalid channel direction");
|
||||
rmt_rx_channel_t *rx_chan = __containerof(channel, rmt_rx_channel_t, base);
|
||||
|
||||
#if CONFIG_RMT_ISR_IRAM_SAFE
|
||||
if (cbs->on_recv_done) {
|
||||
ESP_RETURN_ON_FALSE(esp_ptr_in_iram(cbs->on_recv_done), ESP_ERR_INVALID_ARG, TAG, "on_recv_done callback not in IRAM");
|
||||
}
|
||||
if (user_data) {
|
||||
ESP_RETURN_ON_FALSE(esp_ptr_internal(user_data), ESP_ERR_INVALID_ARG, TAG, "user context not in internal RAM");
|
||||
}
|
||||
#endif
|
||||
|
||||
rx_chan->on_recv_done = cbs->on_recv_done;
|
||||
rx_chan->user_data = user_data;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t rmt_receive(rmt_channel_handle_t channel, void *buffer, size_t buffer_size, const rmt_receive_config_t *config)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE_ISR(channel && buffer && buffer_size && config, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
ESP_RETURN_ON_FALSE_ISR(channel->direction == RMT_CHANNEL_DIRECTION_RX, ESP_ERR_INVALID_ARG, TAG, "invalid channel direction");
|
||||
rmt_rx_channel_t *rx_chan = __containerof(channel, rmt_rx_channel_t, base);
|
||||
size_t per_dma_block_size = 0;
|
||||
size_t last_dma_block_size = 0;
|
||||
|
||||
if (channel->dma_chan) {
|
||||
ESP_RETURN_ON_FALSE_ISR(esp_ptr_internal(buffer), ESP_ERR_INVALID_ARG, TAG, "buffer must locate in internal RAM for DMA use");
|
||||
|
||||
#if CONFIG_IDF_TARGET_ESP32P4
|
||||
uint32_t data_cache_line_mask = cache_hal_get_cache_line_size(CACHE_LL_LEVEL_INT_MEM, CACHE_TYPE_DATA) - 1;
|
||||
ESP_RETURN_ON_FALSE_ISR(((uintptr_t)buffer & data_cache_line_mask) == 0, ESP_ERR_INVALID_ARG, TAG, "buffer must be aligned to cache line size");
|
||||
ESP_RETURN_ON_FALSE_ISR((buffer_size & data_cache_line_mask) == 0, ESP_ERR_INVALID_ARG, TAG, "buffer size must be aligned to cache line size");
|
||||
#endif
|
||||
ESP_RETURN_ON_FALSE_ISR(buffer_size <= rx_chan->num_dma_nodes * RMT_DMA_DESC_BUF_MAX_SIZE,
|
||||
ESP_ERR_INVALID_ARG, TAG, "buffer size exceeds DMA capacity");
|
||||
per_dma_block_size = buffer_size / rx_chan->num_dma_nodes;
|
||||
per_dma_block_size = ALIGN_DOWN(per_dma_block_size, sizeof(rmt_symbol_word_t));
|
||||
last_dma_block_size = buffer_size - per_dma_block_size * (rx_chan->num_dma_nodes - 1);
|
||||
ESP_RETURN_ON_FALSE_ISR(last_dma_block_size <= RMT_DMA_DESC_BUF_MAX_SIZE, ESP_ERR_INVALID_ARG, TAG, "buffer size exceeds DMA capacity");
|
||||
}
|
||||
|
||||
rmt_group_t *group = channel->group;
|
||||
rmt_hal_context_t *hal = &group->hal;
|
||||
int channel_id = channel->channel_id;
|
||||
|
||||
uint32_t filter_reg_value = ((uint64_t)group->resolution_hz * config->signal_range_min_ns) / 1000000000UL;
|
||||
uint32_t idle_reg_value = ((uint64_t)channel->resolution_hz * config->signal_range_max_ns) / 1000000000UL;
|
||||
ESP_RETURN_ON_FALSE_ISR(filter_reg_value <= RMT_LL_MAX_FILTER_VALUE, ESP_ERR_INVALID_ARG, TAG, "signal_range_min_ns too big");
|
||||
ESP_RETURN_ON_FALSE_ISR(idle_reg_value <= RMT_LL_MAX_IDLE_VALUE, ESP_ERR_INVALID_ARG, TAG, "signal_range_max_ns too big");
|
||||
|
||||
// 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_ERR_INVALID_STATE, TAG, "channel not in enable state");
|
||||
|
||||
// fill in the transaction descriptor
|
||||
rmt_rx_trans_desc_t *t = &rx_chan->trans_desc;
|
||||
memset(t, 0, sizeof(rmt_rx_trans_desc_t));
|
||||
t->buffer = buffer;
|
||||
t->buffer_size = buffer_size;
|
||||
t->received_symbol_num = 0;
|
||||
t->copy_dest_off = 0;
|
||||
t->dma_desc_index = 0;
|
||||
t->flags.en_partial_rx = config->flags.en_partial_rx;
|
||||
|
||||
if (channel->dma_chan) {
|
||||
#if SOC_RMT_SUPPORT_DMA
|
||||
rmt_rx_mount_dma_buffer(rx_chan, buffer, buffer_size, per_dma_block_size, last_dma_block_size);
|
||||
gdma_reset(channel->dma_chan);
|
||||
gdma_start(channel->dma_chan, (intptr_t)rx_chan->dma_nodes); // note, we must use the cached descriptor address to start the DMA
|
||||
#endif
|
||||
}
|
||||
|
||||
rx_chan->mem_off = 0;
|
||||
portENTER_CRITICAL_SAFE(&channel->spinlock);
|
||||
// reset memory writer offset
|
||||
rmt_ll_rx_reset_pointer(hal->regs, channel_id);
|
||||
rmt_ll_rx_set_mem_owner(hal->regs, channel_id, RMT_LL_MEM_OWNER_HW);
|
||||
// set sampling parameters of incoming signals
|
||||
rmt_ll_rx_set_filter_thres(hal->regs, channel_id, filter_reg_value);
|
||||
rmt_ll_rx_enable_filter(hal->regs, channel_id, config->signal_range_min_ns != 0);
|
||||
rmt_ll_rx_set_idle_thres(hal->regs, channel_id, idle_reg_value);
|
||||
// turn on RMT RX machine
|
||||
rmt_ll_rx_enable(hal->regs, channel_id, true);
|
||||
portEXIT_CRITICAL_SAFE(&channel->spinlock);
|
||||
|
||||
// saying we're in running state, this state will last until the receiving is done
|
||||
// i.e., we will switch back to the enable state in the receive done interrupt handler
|
||||
atomic_store(&channel->fsm, RMT_FSM_RUN);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t rmt_rx_demodulate_carrier(rmt_channel_handle_t channel, const rmt_carrier_config_t *config)
|
||||
{
|
||||
#if !SOC_RMT_SUPPORT_RX_DEMODULATION
|
||||
ESP_RETURN_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, TAG, "rx demodulation not supported");
|
||||
#else
|
||||
rmt_group_t *group = channel->group;
|
||||
rmt_hal_context_t *hal = &group->hal;
|
||||
int group_id = group->group_id;
|
||||
int channel_id = channel->channel_id;
|
||||
uint32_t real_frequency = 0;
|
||||
|
||||
if (config && config->frequency_hz) {
|
||||
// carrier demodulation module works base on channel clock (this is different from TX carrier modulation mode)
|
||||
uint32_t total_ticks = channel->resolution_hz / config->frequency_hz; // Note this division operation will lose precision
|
||||
uint32_t high_ticks = total_ticks * config->duty_cycle;
|
||||
uint32_t low_ticks = total_ticks - high_ticks;
|
||||
|
||||
portENTER_CRITICAL(&channel->spinlock);
|
||||
rmt_ll_rx_set_carrier_level(hal->regs, channel_id, !config->flags.polarity_active_low);
|
||||
rmt_ll_rx_set_carrier_high_low_ticks(hal->regs, channel_id, high_ticks, low_ticks);
|
||||
portEXIT_CRITICAL(&channel->spinlock);
|
||||
// save real carrier frequency
|
||||
real_frequency = channel->resolution_hz / (high_ticks + low_ticks);
|
||||
}
|
||||
|
||||
// enable/disable carrier demodulation
|
||||
portENTER_CRITICAL(&channel->spinlock);
|
||||
rmt_ll_rx_enable_carrier_demodulation(hal->regs, channel_id, real_frequency > 0);
|
||||
portEXIT_CRITICAL(&channel->spinlock);
|
||||
|
||||
if (real_frequency > 0) {
|
||||
ESP_LOGD(TAG, "enable carrier demodulation for channel(%d,%d), freq=%"PRIu32"Hz", group_id, channel_id, real_frequency);
|
||||
} else {
|
||||
ESP_LOGD(TAG, "disable carrier demodulation for channel(%d, %d)", group_id, channel_id);
|
||||
}
|
||||
return ESP_OK;
|
||||
#endif
|
||||
}
|
||||
|
||||
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_ERR_INVALID_STATE, TAG, "channel not in init state");
|
||||
|
||||
rmt_group_t *group = channel->group;
|
||||
rmt_hal_context_t *hal = &group->hal;
|
||||
int channel_id = channel->channel_id;
|
||||
|
||||
// acquire power manager lock
|
||||
if (channel->pm_lock) {
|
||||
esp_pm_lock_acquire(channel->pm_lock);
|
||||
}
|
||||
if (channel->dma_chan) {
|
||||
#if SOC_RMT_SUPPORT_DMA
|
||||
// enable the DMA access mode
|
||||
portENTER_CRITICAL(&channel->spinlock);
|
||||
rmt_ll_rx_enable_dma(hal->regs, channel_id, true);
|
||||
portEXIT_CRITICAL(&channel->spinlock);
|
||||
|
||||
gdma_connect(channel->dma_chan, GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_RMT, 0));
|
||||
#endif // SOC_RMT_SUPPORT_DMA
|
||||
} else {
|
||||
portENTER_CRITICAL(&group->spinlock);
|
||||
rmt_ll_enable_interrupt(hal->regs, RMT_LL_EVENT_RX_MASK(channel_id), true);
|
||||
portEXIT_CRITICAL(&group->spinlock);
|
||||
}
|
||||
|
||||
atomic_store(&channel->fsm, RMT_FSM_ENABLE);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
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)) {
|
||||
valid_state = true;
|
||||
}
|
||||
expected_fsm = RMT_FSM_RUN;
|
||||
if (atomic_compare_exchange_strong(&channel->fsm, &expected_fsm, RMT_FSM_INIT_WAIT)) {
|
||||
valid_state = true;
|
||||
}
|
||||
ESP_RETURN_ON_FALSE(valid_state, ESP_ERR_INVALID_STATE, TAG, "channel not in enable or run state");
|
||||
|
||||
rmt_group_t *group = channel->group;
|
||||
rmt_hal_context_t *hal = &group->hal;
|
||||
int channel_id = channel->channel_id;
|
||||
|
||||
portENTER_CRITICAL(&channel->spinlock);
|
||||
rmt_ll_rx_enable(hal->regs, channel_id, false);
|
||||
portEXIT_CRITICAL(&channel->spinlock);
|
||||
|
||||
if (channel->dma_chan) {
|
||||
#if SOC_RMT_SUPPORT_DMA
|
||||
gdma_stop(channel->dma_chan);
|
||||
gdma_disconnect(channel->dma_chan);
|
||||
portENTER_CRITICAL(&channel->spinlock);
|
||||
rmt_ll_rx_enable_dma(hal->regs, channel_id, false);
|
||||
portEXIT_CRITICAL(&channel->spinlock);
|
||||
#endif
|
||||
} else {
|
||||
portENTER_CRITICAL(&group->spinlock);
|
||||
rmt_ll_enable_interrupt(hal->regs, RMT_LL_EVENT_RX_MASK(channel_id), false);
|
||||
rmt_ll_clear_interrupt_status(hal->regs, RMT_LL_EVENT_RX_MASK(channel_id));
|
||||
portEXIT_CRITICAL(&group->spinlock);
|
||||
}
|
||||
|
||||
// release power manager lock
|
||||
if (channel->pm_lock) {
|
||||
esp_pm_lock_release(channel->pm_lock);
|
||||
}
|
||||
|
||||
// now we can switch the state to init
|
||||
atomic_store(&channel->fsm, RMT_FSM_INIT);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static bool IRAM_ATTR rmt_isr_handle_rx_done(rmt_rx_channel_t *rx_chan)
|
||||
{
|
||||
rmt_channel_t *channel = &rx_chan->base;
|
||||
rmt_group_t *group = channel->group;
|
||||
rmt_hal_context_t *hal = &group->hal;
|
||||
uint32_t channel_id = channel->channel_id;
|
||||
rmt_rx_trans_desc_t *trans_desc = &rx_chan->trans_desc;
|
||||
rmt_rx_done_callback_t cb = rx_chan->on_recv_done;
|
||||
bool need_yield = false;
|
||||
|
||||
rmt_ll_clear_interrupt_status(hal->regs, RMT_LL_EVENT_RX_DONE(channel_id));
|
||||
portENTER_CRITICAL_ISR(&channel->spinlock);
|
||||
// disable the RX engine, it will be enabled again when next time user calls `rmt_receive()`
|
||||
rmt_ll_rx_enable(hal->regs, channel_id, false);
|
||||
portEXIT_CRITICAL_ISR(&channel->spinlock);
|
||||
|
||||
uint32_t offset = rmt_ll_rx_get_memory_writer_offset(hal->regs, channel_id);
|
||||
// sanity check
|
||||
assert(offset >= rx_chan->mem_off);
|
||||
size_t mem_want = (offset - rx_chan->mem_off) * sizeof(rmt_symbol_word_t);
|
||||
size_t mem_have = trans_desc->buffer_size - trans_desc->copy_dest_off;
|
||||
size_t copy_size = mem_want;
|
||||
if (mem_want > mem_have) {
|
||||
if (trans_desc->flags.en_partial_rx) { // check partial receive is enabled or not
|
||||
// notify the user to process the received symbols if the buffer is going to be full
|
||||
if (trans_desc->received_symbol_num) {
|
||||
if (cb) {
|
||||
rmt_rx_done_event_data_t edata = {
|
||||
.received_symbols = trans_desc->buffer,
|
||||
.num_symbols = trans_desc->received_symbol_num,
|
||||
.flags.is_last = false,
|
||||
};
|
||||
if (cb(channel, &edata, rx_chan->user_data)) {
|
||||
need_yield = true;
|
||||
}
|
||||
}
|
||||
trans_desc->copy_dest_off = 0;
|
||||
trans_desc->received_symbol_num = 0;
|
||||
mem_have = trans_desc->buffer_size;
|
||||
|
||||
// even user process the partial received data, the remain buffer may still be insufficient
|
||||
if (mem_want > mem_have) {
|
||||
ESP_DRAM_LOGE(TAG, "user buffer too small, received symbols truncated");
|
||||
copy_size = mem_have;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ESP_DRAM_LOGE(TAG, "user buffer too small, received symbols truncated");
|
||||
copy_size = mem_have;
|
||||
}
|
||||
}
|
||||
|
||||
portENTER_CRITICAL_ISR(&channel->spinlock);
|
||||
rmt_ll_rx_set_mem_owner(hal->regs, channel_id, RMT_LL_MEM_OWNER_SW);
|
||||
// copy the symbols to the user buffer
|
||||
memcpy((uint8_t *)trans_desc->buffer + trans_desc->copy_dest_off, channel->hw_mem_base + rx_chan->mem_off, copy_size);
|
||||
rmt_ll_rx_set_mem_owner(hal->regs, channel_id, RMT_LL_MEM_OWNER_HW);
|
||||
portEXIT_CRITICAL_ISR(&channel->spinlock);
|
||||
|
||||
#if !SOC_RMT_SUPPORT_RX_PINGPONG
|
||||
// for chips doesn't support ping-pong RX, we should check whether the receiver has encountered with a long frame,
|
||||
// whose length is longer than the channel capacity
|
||||
if (rmt_ll_rx_get_interrupt_status_raw(hal->regs, channel_id) & RMT_LL_EVENT_RX_ERROR(channel_id)) {
|
||||
portENTER_CRITICAL_ISR(&channel->spinlock);
|
||||
rmt_ll_rx_reset_pointer(hal->regs, channel_id);
|
||||
portEXIT_CRITICAL_ISR(&channel->spinlock);
|
||||
// this clear operation can only take effect after we copy out the received data and reset the pointer
|
||||
rmt_ll_clear_interrupt_status(hal->regs, RMT_LL_EVENT_RX_ERROR(channel_id));
|
||||
ESP_DRAM_LOGE(TAG, "hw buffer too small, received symbols truncated");
|
||||
}
|
||||
#endif // !SOC_RMT_SUPPORT_RX_PINGPONG
|
||||
|
||||
trans_desc->copy_dest_off += copy_size;
|
||||
trans_desc->received_symbol_num += copy_size / sizeof(rmt_symbol_word_t);
|
||||
// switch back to the enable state, then user can call `rmt_receive` to start a new receive
|
||||
atomic_store(&channel->fsm, RMT_FSM_ENABLE);
|
||||
|
||||
// notify the user that all RMT symbols are received done
|
||||
if (cb) {
|
||||
rmt_rx_done_event_data_t edata = {
|
||||
.received_symbols = trans_desc->buffer,
|
||||
.num_symbols = trans_desc->received_symbol_num,
|
||||
.flags.is_last = true,
|
||||
};
|
||||
if (cb(channel, &edata, rx_chan->user_data)) {
|
||||
need_yield = true;
|
||||
}
|
||||
}
|
||||
return need_yield;
|
||||
}
|
||||
|
||||
#if SOC_RMT_SUPPORT_RX_PINGPONG
|
||||
static bool IRAM_ATTR rmt_isr_handle_rx_threshold(rmt_rx_channel_t *rx_chan)
|
||||
{
|
||||
bool need_yield = false;
|
||||
rmt_channel_t *channel = &rx_chan->base;
|
||||
rmt_group_t *group = channel->group;
|
||||
rmt_hal_context_t *hal = &group->hal;
|
||||
uint32_t channel_id = channel->channel_id;
|
||||
rmt_rx_trans_desc_t *trans_desc = &rx_chan->trans_desc;
|
||||
|
||||
rmt_ll_clear_interrupt_status(hal->regs, RMT_LL_EVENT_RX_THRES(channel_id));
|
||||
|
||||
size_t mem_want = rx_chan->ping_pong_symbols * sizeof(rmt_symbol_word_t);
|
||||
size_t mem_have = trans_desc->buffer_size - trans_desc->copy_dest_off;
|
||||
size_t copy_size = mem_want;
|
||||
if (mem_want > mem_have) {
|
||||
if (trans_desc->flags.en_partial_rx) {
|
||||
// notify the user to process the received symbols if the buffer is going to be full
|
||||
if (trans_desc->received_symbol_num) {
|
||||
rmt_rx_done_callback_t cb = rx_chan->on_recv_done;
|
||||
if (cb) {
|
||||
rmt_rx_done_event_data_t edata = {
|
||||
.received_symbols = trans_desc->buffer,
|
||||
.num_symbols = trans_desc->received_symbol_num,
|
||||
.flags.is_last = false,
|
||||
};
|
||||
if (cb(channel, &edata, rx_chan->user_data)) {
|
||||
need_yield = true;
|
||||
}
|
||||
}
|
||||
trans_desc->copy_dest_off = 0;
|
||||
trans_desc->received_symbol_num = 0;
|
||||
mem_have = trans_desc->buffer_size;
|
||||
|
||||
// even user process the partial received data, the remain buffer size still insufficient
|
||||
if (mem_want > mem_have) {
|
||||
ESP_DRAM_LOGE(TAG, "user buffer too small, received symbols truncated");
|
||||
copy_size = mem_have;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ESP_DRAM_LOGE(TAG, "user buffer too small, received symbols truncated");
|
||||
copy_size = mem_have;
|
||||
}
|
||||
}
|
||||
|
||||
portENTER_CRITICAL_ISR(&channel->spinlock);
|
||||
rmt_ll_rx_set_mem_owner(hal->regs, channel_id, RMT_LL_MEM_OWNER_SW);
|
||||
// copy the symbols to the user buffer
|
||||
memcpy((uint8_t *)trans_desc->buffer + trans_desc->copy_dest_off, channel->hw_mem_base + rx_chan->mem_off, copy_size);
|
||||
rmt_ll_rx_set_mem_owner(hal->regs, channel_id, RMT_LL_MEM_OWNER_HW);
|
||||
portEXIT_CRITICAL_ISR(&channel->spinlock);
|
||||
|
||||
trans_desc->copy_dest_off += copy_size;
|
||||
trans_desc->received_symbol_num += copy_size / sizeof(rmt_symbol_word_t);
|
||||
|
||||
// update the hw memory offset, where stores the next RMT symbols to copy
|
||||
rx_chan->mem_off = rx_chan->ping_pong_symbols - rx_chan->mem_off;
|
||||
|
||||
return need_yield;
|
||||
}
|
||||
#endif // SOC_RMT_SUPPORT_RX_PINGPONG
|
||||
|
||||
static void IRAM_ATTR rmt_rx_default_isr(void *args)
|
||||
{
|
||||
rmt_rx_channel_t *rx_chan = (rmt_rx_channel_t *)args;
|
||||
rmt_channel_t *channel = &rx_chan->base;
|
||||
rmt_group_t *group = channel->group;
|
||||
rmt_hal_context_t *hal = &group->hal;
|
||||
uint32_t channel_id = channel->channel_id;
|
||||
bool need_yield = false;
|
||||
|
||||
uint32_t status = rmt_ll_rx_get_interrupt_status(hal->regs, channel_id);
|
||||
|
||||
#if SOC_RMT_SUPPORT_RX_PINGPONG
|
||||
// RX threshold interrupt
|
||||
if (status & RMT_LL_EVENT_RX_THRES(channel_id)) {
|
||||
if (rmt_isr_handle_rx_threshold(rx_chan)) {
|
||||
need_yield = true;
|
||||
}
|
||||
}
|
||||
#endif // SOC_RMT_SUPPORT_RX_PINGPONG
|
||||
|
||||
// RX end interrupt
|
||||
if (status & RMT_LL_EVENT_RX_DONE(channel_id)) {
|
||||
if (rmt_isr_handle_rx_done(rx_chan)) {
|
||||
need_yield = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (need_yield) {
|
||||
portYIELD_FROM_ISR();
|
||||
}
|
||||
}
|
||||
|
||||
#if SOC_RMT_SUPPORT_DMA
|
||||
static size_t IRAM_ATTR rmt_rx_count_symbols_until_eof(rmt_rx_channel_t *rx_chan, int start_index)
|
||||
{
|
||||
size_t received_bytes = 0;
|
||||
for (int i = 0; i < rx_chan->num_dma_nodes; i++) {
|
||||
received_bytes += rx_chan->dma_nodes_nc[start_index].dw0.length;
|
||||
if (rx_chan->dma_nodes_nc[start_index].dw0.suc_eof) {
|
||||
break;
|
||||
}
|
||||
start_index++;
|
||||
start_index %= rx_chan->num_dma_nodes;
|
||||
}
|
||||
received_bytes = ALIGN_UP(received_bytes, sizeof(rmt_symbol_word_t));
|
||||
return received_bytes / sizeof(rmt_symbol_word_t);
|
||||
}
|
||||
|
||||
static size_t IRAM_ATTR rmt_rx_count_symbols_for_single_block(rmt_rx_channel_t *rx_chan, int desc_index)
|
||||
{
|
||||
size_t received_bytes = rx_chan->dma_nodes_nc[desc_index].dw0.length;
|
||||
received_bytes = ALIGN_UP(received_bytes, sizeof(rmt_symbol_word_t));
|
||||
return received_bytes / sizeof(rmt_symbol_word_t);
|
||||
}
|
||||
|
||||
static bool IRAM_ATTR rmt_dma_rx_one_block_cb(gdma_channel_handle_t dma_chan, gdma_event_data_t *event_data, void *user_data)
|
||||
{
|
||||
bool need_yield = false;
|
||||
rmt_rx_channel_t *rx_chan = (rmt_rx_channel_t *)user_data;
|
||||
rmt_channel_t *channel = &rx_chan->base;
|
||||
rmt_group_t *group = channel->group;
|
||||
rmt_hal_context_t *hal = &group->hal;
|
||||
rmt_rx_trans_desc_t *trans_desc = &rx_chan->trans_desc;
|
||||
uint32_t channel_id = channel->channel_id;
|
||||
|
||||
#if CONFIG_IDF_TARGET_ESP32P4
|
||||
int invalidate_map = CACHE_MAP_L1_DCACHE;
|
||||
if (esp_ptr_external_ram((const void *)trans_desc->buffer)) {
|
||||
invalidate_map |= CACHE_MAP_L2_CACHE;
|
||||
}
|
||||
Cache_Invalidate_Addr(invalidate_map, (uint32_t)trans_desc->buffer, trans_desc->buffer_size);
|
||||
#endif
|
||||
|
||||
if (event_data->flags.normal_eof) {
|
||||
// if the DMA received an EOF, it means the RMT peripheral has received an "end marker"
|
||||
portENTER_CRITICAL_ISR(&channel->spinlock);
|
||||
// disable the RX engine, it will be enabled again in the next `rmt_receive()`
|
||||
rmt_ll_rx_enable(hal->regs, channel_id, false);
|
||||
portEXIT_CRITICAL_ISR(&channel->spinlock);
|
||||
|
||||
// switch back to the enable state, then user can call `rmt_receive` to start a new receive
|
||||
atomic_store(&channel->fsm, RMT_FSM_ENABLE);
|
||||
|
||||
if (rx_chan->on_recv_done) {
|
||||
int recycle_start_index = trans_desc->dma_desc_index;
|
||||
rmt_rx_done_event_data_t edata = {
|
||||
.received_symbols = rx_chan->dma_nodes_nc[recycle_start_index].buffer,
|
||||
.num_symbols = rmt_rx_count_symbols_until_eof(rx_chan, recycle_start_index),
|
||||
.flags.is_last = true,
|
||||
};
|
||||
if (rx_chan->on_recv_done(channel, &edata, rx_chan->user_data)) {
|
||||
need_yield = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// it's a partial receive done event
|
||||
if (trans_desc->flags.en_partial_rx) {
|
||||
if (rx_chan->on_recv_done) {
|
||||
size_t dma_desc_index = trans_desc->dma_desc_index;
|
||||
rmt_rx_done_event_data_t edata = {
|
||||
.received_symbols = rx_chan->dma_nodes_nc[dma_desc_index].buffer,
|
||||
.num_symbols = rmt_rx_count_symbols_for_single_block(rx_chan, dma_desc_index),
|
||||
.flags.is_last = false,
|
||||
};
|
||||
if (rx_chan->on_recv_done(channel, &edata, rx_chan->user_data)) {
|
||||
need_yield = true;
|
||||
}
|
||||
|
||||
dma_desc_index++;
|
||||
trans_desc->dma_desc_index = dma_desc_index % rx_chan->num_dma_nodes;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return need_yield;
|
||||
}
|
||||
|
||||
#endif // SOC_RMT_SUPPORT_DMA
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user