feat(rmt): callback function can support partial receive

This commit is contained in:
morris
2023-11-02 18:51:33 +08:00
parent 05e6ccbba7
commit add4749d15
8 changed files with 316 additions and 95 deletions

View File

@@ -22,7 +22,8 @@ extern "C" {
* The variables used in the function should be in the SRAM as well.
*/
typedef struct {
rmt_rx_done_callback_t on_recv_done; /*!< Event callback, invoked when one RMT channel receiving transaction completes */
rmt_rx_done_callback_t on_recv_done; /*!< Event callback, invoked when the RMT channel reception is finished
or partial data is received */
} rmt_rx_event_callbacks_t;
/**
@@ -35,13 +36,13 @@ typedef struct {
size_t mem_block_symbols; /*!< Size of memory block, in number of `rmt_symbol_word_t`, must be an even.
In the DMA mode, this field controls the DMA buffer size, it can be set to a large value (e.g. 1024);
In the normal mode, this field controls the number of RMT memory block that will be used by the channel. */
int intr_priority; /*!< RMT interrupt priority,
if set to 0, the driver will try to allocate an interrupt with a relative low priority (1,2,3) */
struct {
uint32_t invert_in: 1; /*!< Whether to invert the incoming RMT channel signal */
uint32_t with_dma: 1; /*!< If set, the driver will allocate an RMT channel with DMA capability */
uint32_t io_loop_back: 1; /*!< For debug/test, the signal output from the GPIO will be fed to the input path as well */
} flags; /*!< RX channel config flags */
int intr_priority; /*!< RMT interrupt priority,
if set to 0, the driver will try to allocate an interrupt with a relative low priority (1,2,3) */
} rmt_rx_channel_config_t;
/**
@@ -50,6 +51,12 @@ typedef struct {
typedef struct {
uint32_t signal_range_min_ns; /*!< A pulse whose width is smaller than this threshold will be treated as glitch and ignored */
uint32_t signal_range_max_ns; /*!< RMT will stop receiving if one symbol level has kept more than `signal_range_max_ns` */
/// Receive specific flags
struct extra_flags {
uint32_t en_partial_rx: 1; /*!< Set this flag if the incoming data is very long, and the driver can only receive the data piece by piece,
because the user buffer is not sufficient to save all the data. */
} flags; /*!< Receive specific config flags */
} rmt_receive_config_t;
/**

View File

@@ -56,6 +56,9 @@ typedef bool (*rmt_tx_done_callback_t)(rmt_channel_handle_t tx_chan, const rmt_t
typedef struct {
rmt_symbol_word_t *received_symbols; /*!< Point to the received RMT symbols */
size_t num_symbols; /*!< The number of received RMT symbols */
struct {
uint32_t is_last: 1; /*!< Indicating if the current received data are the last part of the transaction */
} flags; /*!< Extra flags */
} rmt_rx_done_event_data_t;
/**

View File

@@ -176,6 +176,10 @@ typedef struct {
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 {

View File

@@ -28,7 +28,8 @@
#include "rmt_private.h"
#include "rom/cache.h"
#define ALIGN_UP(num, align) (((num) + ((align) - 1)) & ~((align) - 1))
#define ALIGN_UP(num, align) (((num) + ((align) - 1)) & ~((align) - 1))
#define ALIGN_DOWN(num, align) ((num) & ~((align) - 1))
static const char *TAG = "rmt";
@@ -39,36 +40,20 @@ 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_eof_cb(gdma_channel_handle_t dma_chan, gdma_event_data_t *event_data, void *user_data);
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_dma_descriptor_t *desc_array, rmt_dma_descriptor_t *desc_array_nc, size_t array_size, const void *buffer, size_t buffer_size)
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)
{
size_t prepared_length = 0;
uint8_t *data = (uint8_t *)buffer;
int dma_node_i = 0;
rmt_dma_descriptor_t *desc = NULL;
while (buffer_size > RMT_DMA_DESC_BUF_MAX_SIZE) {
desc = &desc_array_nc[dma_node_i];
desc->dw0.suc_eof = 0;
desc->dw0.size = RMT_DMA_DESC_BUF_MAX_SIZE;
desc->dw0.length = 0;
desc->dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_DMA;
desc->buffer = &data[prepared_length];
desc->next = &desc_array[dma_node_i + 1]; // note, we must use the cache address for the "next" pointer
prepared_length += RMT_DMA_DESC_BUF_MAX_SIZE;
buffer_size -= RMT_DMA_DESC_BUF_MAX_SIZE;
dma_node_i++;
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;
}
if (buffer_size) {
desc = &desc_array_nc[dma_node_i];
desc->dw0.suc_eof = 0;
desc->dw0.size = buffer_size;
desc->dw0.length = 0;
desc->dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_DMA;
desc->buffer = &data[prepared_length];
prepared_length += buffer_size;
}
desc->next = NULL; // one-off DMA chain
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)
@@ -77,8 +62,16 @@ static esp_err_t rmt_rx_init_dma_link(rmt_rx_channel_t *rx_channel, const rmt_rx
.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_eof = rmt_dma_rx_eof_cb,
.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;
@@ -204,6 +197,7 @@ esp_err_t rmt_new_rx_channel(const rmt_rx_channel_config_t *config, rmt_channel_
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");
@@ -347,6 +341,8 @@ esp_err_t rmt_receive(rmt_channel_handle_t channel, void *buffer, size_t buffer_
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");
@@ -356,11 +352,14 @@ esp_err_t rmt_receive(rmt_channel_handle_t channel, void *buffer, size_t buffer_
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
}
if (channel->dma_chan) {
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;
@@ -377,14 +376,17 @@ esp_err_t rmt_receive(rmt_channel_handle_t channel, void *buffer, size_t buffer_
// 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->dma_nodes, rx_chan->dma_nodes_nc, rx_chan->num_dma_nodes, buffer, buffer_size);
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
@@ -531,16 +533,6 @@ static esp_err_t rmt_rx_disable(rmt_channel_handle_t channel)
return ESP_OK;
}
static size_t IRAM_ATTR rmt_copy_symbols(rmt_symbol_word_t *symbol_stream, size_t symbol_num, void *buffer, size_t offset, size_t buffer_size)
{
size_t mem_want = symbol_num * sizeof(rmt_symbol_word_t);
size_t mem_have = buffer_size - offset;
size_t copy_size = MIN(mem_want, mem_have);
// do memory copy
memcpy(buffer + offset, symbol_stream, copy_size);
return copy_size;
}
static bool IRAM_ATTR rmt_isr_handle_rx_done(rmt_rx_channel_t *rx_chan)
{
rmt_channel_t *channel = &rx_chan->base;
@@ -548,21 +540,55 @@ static bool IRAM_ATTR rmt_isr_handle_rx_done(rmt_rx_channel_t *rx_chan)
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 user space
size_t stream_symbols = offset - rx_chan->mem_off;
size_t copy_size = rmt_copy_symbols(channel->hw_mem_base + rx_chan->mem_off, stream_symbols,
trans_desc->buffer, trans_desc->copy_dest_off, trans_desc->buffer_size);
// 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);
@@ -579,22 +605,19 @@ static bool IRAM_ATTR rmt_isr_handle_rx_done(rmt_rx_channel_t *rx_chan)
}
#endif // !SOC_RMT_SUPPORT_RX_PINGPONG
// check whether all symbols are copied
if (copy_size != stream_symbols * sizeof(rmt_symbol_word_t)) {
ESP_DRAM_LOGE(TAG, "user buffer too small, received symbols truncated");
}
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 with receive RMT symbols
if (rx_chan->on_recv_done) {
// 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 (rx_chan->on_recv_done(channel, &edata, rx_chan->user_data)) {
if (cb(channel, &edata, rx_chan->user_data)) {
need_yield = true;
}
}
@@ -604,6 +627,7 @@ static bool IRAM_ATTR rmt_isr_handle_rx_done(rmt_rx_channel_t *rx_chan)
#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;
@@ -612,24 +636,54 @@ static bool IRAM_ATTR rmt_isr_handle_rx_threshold(rmt_rx_channel_t *rx_chan)
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 user space
size_t copy_size = rmt_copy_symbols(channel->hw_mem_base + rx_chan->mem_off, rx_chan->ping_pong_symbols,
trans_desc->buffer, trans_desc->copy_dest_off, trans_desc->buffer_size);
// 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);
// check whether all symbols are copied
if (copy_size != rx_chan->ping_pong_symbols * sizeof(rmt_symbol_word_t)) {
ESP_DRAM_LOGE(TAG, "received symbols truncated");
}
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 false;
return need_yield;
}
#endif // SOC_RMT_SUPPORT_RX_PINGPONG
@@ -666,18 +720,29 @@ static void IRAM_ATTR rmt_rx_default_isr(void *args)
}
#if SOC_RMT_SUPPORT_DMA
static size_t IRAM_ATTR rmt_rx_get_received_symbol_num_from_dma(rmt_dma_descriptor_t *desc_nc)
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;
while (desc_nc) {
received_bytes += desc_nc->dw0.length;
desc_nc = (rmt_dma_descriptor_t *)RMT_GET_NON_CACHE_ADDR(desc_nc->next);
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 bool IRAM_ATTR rmt_dma_rx_eof_cb(gdma_channel_handle_t dma_chan, gdma_event_data_t *event_data, void *user_data)
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;
@@ -687,11 +752,6 @@ static bool IRAM_ATTR rmt_dma_rx_eof_cb(gdma_channel_handle_t dma_chan, gdma_eve
rmt_rx_trans_desc_t *trans_desc = &rx_chan->trans_desc;
uint32_t channel_id = channel->channel_id;
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);
#if CONFIG_IDF_TARGET_ESP32P4
int invalidate_map = CACHE_MAP_L1_DCACHE;
if (esp_ptr_external_ram((const void *)trans_desc->buffer)) {
@@ -700,19 +760,48 @@ static bool IRAM_ATTR rmt_dma_rx_eof_cb(gdma_channel_handle_t dma_chan, gdma_eve
Cache_Invalidate_Addr(invalidate_map, (uint32_t)trans_desc->buffer, trans_desc->buffer_size);
#endif
// 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 (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);
if (rx_chan->on_recv_done) {
rmt_rx_done_event_data_t edata = {
.received_symbols = trans_desc->buffer,
.num_symbols = rmt_rx_get_received_symbol_num_from_dma(rx_chan->dma_nodes_nc),
};
if (rx_chan->on_recv_done(channel, &edata, rx_chan->user_data)) {
need_yield = true;
// 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

View File

@@ -108,13 +108,13 @@ typedef struct {
size_t received_symbol_num;
rmt_receive_config_t rx_config;
rmt_symbol_word_t remote_codes[128];
} test_nec_rx_user_data_t;
} test_rx_user_data_t;
IRAM_ATTR
static bool test_rmt_rx_done_callback(rmt_channel_handle_t channel, const rmt_rx_done_event_data_t *edata, void *user_data)
{
BaseType_t high_task_wakeup = pdFALSE;
test_nec_rx_user_data_t *test_user_data = (test_nec_rx_user_data_t *)user_data;
test_rx_user_data_t *test_user_data = (test_rx_user_data_t *)user_data;
test_user_data->received_symbol_num += edata->num_symbols;
// should receive one RMT symbol at a time
if (edata->num_symbols == 1) {
@@ -142,13 +142,13 @@ static void test_rmt_rx_iram_safe(size_t mem_block_symbols, bool with_dma, rmt_c
TEST_ESP_OK(rmt_new_rx_channel(&rx_channel_cfg, &rx_channel));
// initialize the GPIO level to low
TEST_ESP_OK(gpio_set_level(0, 0));
TEST_ESP_OK(gpio_set_level(TEST_RMT_GPIO_NUM_A, 0));
printf("register rx event callbacks\r\n");
rmt_rx_event_callbacks_t cbs = {
.on_recv_done = test_rmt_rx_done_callback,
};
test_nec_rx_user_data_t test_user_data = {
test_rx_user_data_t test_user_data = {
.task_to_notify = xTaskGetCurrentTaskHandle(),
.received_symbol_num = 0,
.rx_config = {
@@ -165,7 +165,7 @@ static void test_rmt_rx_iram_safe(size_t mem_block_symbols, bool with_dma, rmt_c
TEST_ESP_OK(rmt_receive(rx_channel, test_user_data.remote_codes, sizeof(test_user_data.remote_codes), &test_user_data.rx_config));
// disable the flash cache, and simulate input signal by GPIO
unity_utils_run_cache_disable_stub(test_simulate_input_post_cache_disable, 0);
unity_utils_run_cache_disable_stub(test_simulate_input_post_cache_disable, TEST_RMT_GPIO_NUM_A);
TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(1000)));
TEST_ASSERT_EQUAL(TEST_RMT_SYMBOLS, test_user_data.received_symbol_num);

View File

@@ -11,6 +11,7 @@
#include "unity.h"
#include "driver/rmt_tx.h"
#include "driver/rmt_rx.h"
#include "driver/gpio.h"
#include "soc/soc_caps.h"
#include "test_util_rmt_encoders.h"
#include "test_board.h"
@@ -45,7 +46,7 @@ static void test_rmt_rx_nec_carrier(size_t mem_block_symbols, bool with_dma, rmt
{
uint32_t const test_rx_buffer_symbols = 128;
rmt_symbol_word_t *remote_codes = heap_caps_aligned_calloc(64, test_rx_buffer_symbols, sizeof(rmt_symbol_word_t),
MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA);
MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA);
TEST_ASSERT_NOT_NULL(remote_codes);
rmt_rx_channel_config_t rx_channel_cfg = {
@@ -201,3 +202,108 @@ TEST_CASE("rmt rx nec with carrier", "[rmt]")
test_rmt_rx_nec_carrier(128, true, RMT_CLK_SRC_DEFAULT);
#endif
}
#if SOC_RMT_SUPPORT_RX_PINGPONG
#define TEST_RMT_SYMBOLS 10000 // a very long frame, contains 10000 symbols
static void pwm_bit_bang(int gpio_num)
{
for (int i = 0; i < TEST_RMT_SYMBOLS; i++) {
gpio_set_level(gpio_num, 1);
esp_rom_delay_us(50);
gpio_set_level(gpio_num, 0);
esp_rom_delay_us(50);
}
}
typedef struct {
TaskHandle_t task_to_notify;
size_t received_symbol_num;
} test_rx_user_data_t;
TEST_RMT_CALLBACK_ATTR
static bool test_rmt_partial_receive_done(rmt_channel_handle_t channel, const rmt_rx_done_event_data_t *edata, void *user_data)
{
BaseType_t high_task_wakeup = pdFALSE;
test_rx_user_data_t *test_user_data = (test_rx_user_data_t *)user_data;
test_user_data->received_symbol_num += edata->num_symbols;
// when receive done, notify the task to check the received data
if (edata->flags.is_last) {
vTaskNotifyGiveFromISR(test_user_data->task_to_notify, &high_task_wakeup);
}
return high_task_wakeup == pdTRUE;
}
static void test_rmt_partial_receive(size_t mem_block_symbols, bool with_dma, rmt_clock_source_t clk_src)
{
uint32_t const test_rx_buffer_symbols = 128; // the user buffer is small, it can't hold all the received symbols
rmt_symbol_word_t *receive_user_buf = heap_caps_aligned_calloc(64, test_rx_buffer_symbols, sizeof(rmt_symbol_word_t),
MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA);
TEST_ASSERT_NOT_NULL(receive_user_buf);
rmt_rx_channel_config_t rx_channel_cfg = {
.clk_src = clk_src,
.resolution_hz = 1000000, // 1MHz, 1 tick = 1us
.mem_block_symbols = mem_block_symbols,
.gpio_num = TEST_RMT_GPIO_NUM_A,
.flags.with_dma = with_dma,
.flags.io_loop_back = true, // the GPIO will act like a loopback
};
printf("install rx channel\r\n");
rmt_channel_handle_t rx_channel = NULL;
TEST_ESP_OK(rmt_new_rx_channel(&rx_channel_cfg, &rx_channel));
// initialize the GPIO level to low
TEST_ESP_OK(gpio_set_level(TEST_RMT_GPIO_NUM_A, 0));
printf("register rx event callbacks\r\n");
rmt_rx_event_callbacks_t cbs = {
.on_recv_done = test_rmt_partial_receive_done,
};
test_rx_user_data_t test_user_data = {
.task_to_notify = xTaskGetCurrentTaskHandle(),
.received_symbol_num = 0,
};
TEST_ESP_OK(rmt_rx_register_event_callbacks(rx_channel, &cbs, &test_user_data));
printf("enable rx channel\r\n");
TEST_ESP_OK(rmt_enable(rx_channel));
rmt_receive_config_t rx_config = {
.signal_range_min_ns = 1250,
.signal_range_max_ns = 12000000,
.flags.en_partial_rx = true, // enable partial receive
};
// ready to receive
TEST_ESP_OK(rmt_receive(rx_channel, receive_user_buf, test_rx_buffer_symbols * sizeof(rmt_symbol_word_t), &rx_config));
// simulate input signal by GPIO
pwm_bit_bang(TEST_RMT_GPIO_NUM_A);
TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(2000)));
printf("received %zu symbols\r\n", test_user_data.received_symbol_num);
TEST_ASSERT_EQUAL(TEST_RMT_SYMBOLS, test_user_data.received_symbol_num);
// verify the received data
for (int i = 0; i < 10; i++) {
printf("{%d:%d},{%d:%d}\r\n", receive_user_buf[i].level0, receive_user_buf[i].duration0, receive_user_buf[i].level1, receive_user_buf[i].duration1);
TEST_ASSERT_EQUAL(1, receive_user_buf[i].level0);
TEST_ASSERT_INT_WITHIN(20, 50, receive_user_buf[i].duration0);
TEST_ASSERT_EQUAL(0, receive_user_buf[i].level1);
TEST_ASSERT_INT_WITHIN(20, 50, receive_user_buf[i].duration1);
}
printf("disable rx channels\r\n");
TEST_ESP_OK(rmt_disable(rx_channel));
printf("delete channels and encoder\r\n");
TEST_ESP_OK(rmt_del_channel(rx_channel));
free(receive_user_buf);
}
TEST_CASE("rmt rx long frame partially", "[rmt]")
{
test_rmt_partial_receive(SOC_RMT_MEM_WORDS_PER_CHANNEL, false, RMT_CLK_SRC_DEFAULT);
#if SOC_RMT_SUPPORT_DMA
test_rmt_partial_receive(256, true, RMT_CLK_SRC_DEFAULT);
#endif
}
#endif // SOC_RMT_SUPPORT_RX_PINGPONG

View File

@@ -203,9 +203,13 @@ The RX channel-supported event callbacks are listed in the :cpp:type:`rmt_rx_eve
- :cpp:member:`rmt_rx_event_callbacks_t::on_recv_done` sets a callback function for "receive-done" event. The function prototype is declared in :cpp:type:`rmt_rx_done_callback_t`.
.. note::
The "receive-done" is not equivalent to "receive-finished". This callback can also be called at a "partial-receive-done" time, for many times during one receive transaction.
Users can save their own context in :cpp:func:`rmt_tx_register_event_callbacks` and :cpp:func:`rmt_rx_register_event_callbacks` as well, via the parameter ``user_data``. The user data is directly passed to each callback function.
In the callback function, users can fetch the event-specific data that is filled by the driver in the ``edata``. Note that the ``edata`` pointer is only valid during the callback.
In the callback function, users can fetch the event-specific data that is filled by the driver in the ``edata``. Note that the ``edata`` pointer is **only** valid during the callback, please do not try to save this pointer and use that outside of the callback function.
The TX-done event data is defined in :cpp:type:`rmt_tx_done_event_data_t`:
@@ -213,8 +217,9 @@ The TX-done event data is defined in :cpp:type:`rmt_tx_done_event_data_t`:
The RX-complete event data is defined in :cpp:type:`rmt_rx_done_event_data_t`:
- :cpp:member:`rmt_rx_done_event_data_t::received_symbols` points to the received RMT symbols. These symbols are saved in the ``buffer`` parameter of the :cpp:func:`rmt_receive` function. Users should not free this receive buffer before the callback returns.
- :cpp:member:`rmt_rx_done_event_data_t::received_symbols` points to the received RMT symbols. These symbols are saved in the ``buffer`` parameter of the :cpp:func:`rmt_receive` function. Users should not free this receive buffer before the callback returns. If you also enabled the partial receive feature, then the user buffer will be used as a "second level buffer", where its content can be overwritten by data comes in afterwards. In this case, you should copy the received data to another place if you want to keep it or process it later.
- :cpp:member:`rmt_rx_done_event_data_t::num_symbols` indicates the number of received RMT symbols. This value is not larger than the ``buffer_size`` parameter of :cpp:func:`rmt_receive` function. If the ``buffer_size`` is not sufficient to accommodate all the received RMT symbols, the driver only keeps the maximum number of symbols that the buffer can hold, and excess symbols are discarded or ignored.
- :cpp:member:`rmt_rx_done_event_data_t::is_last` indicates whether the current received buffer is the last one in the transaction. This is useful when you enable the partial reception feature by :cpp:member:`rmt_receive_config_t::extra_flags::en_partial_rx`.
.. _rmt-enable-and-disable-channel:
@@ -326,6 +331,7 @@ As also discussed in the :ref:`rmt-enable-and-disable-channel`, calling :cpp:fun
- :cpp:member:`rmt_receive_config_t::signal_range_min_ns` specifies the minimal valid pulse duration in either high or low logic levels. A pulse width that is smaller than this value is treated as a glitch, and ignored by the hardware.
- :cpp:member:`rmt_receive_config_t::signal_range_max_ns` specifies the maximum valid pulse duration in either high or low logic levels. A pulse width that is bigger than this value is treated as **Stop Signal**, and the receiver generates receive-complete event immediately.
- If the incoming packet is long, that they cannot be stored in the user buffer at once, you can enable the partial reception feature by setting :cpp:member:`rmt_receive_config_t::extra_flags::en_partial_rx` to ``true``. In this case, the driver invokes :cpp:member:`rmt_rx_event_callbacks_t::on_recv_done` callback multiple times during one transaction, when the user buffer is **almost full**. You can check the value of :cpp:member::`rmt_rx_done_event_data_t::is_last` to know if the transaction is about to finish.
The RMT receiver starts the RX machine after the user calls :cpp:func:`rmt_receive` with the provided configuration above. Note that, this configuration is transaction specific, which means, to start a new round of reception, the user needs to set the :cpp:type:`rmt_receive_config_t` again. The receiver saves the incoming signals into its internal memory block or DMA buffer, in the format of :cpp:type:`rmt_symbol_word_t`.
@@ -337,7 +343,7 @@ The RMT receiver starts the RX machine after the user calls :cpp:func:`rmt_recei
Due to the limited size of the memory block, the RMT receiver can only save short frames whose length is not longer than the memory block capacity. Long frames are truncated by the hardware, and the driver reports an error message: ``hw buffer too small, received symbols truncated``.
The copy destination should be provided in the ``buffer`` parameter of :cpp:func:`rmt_receive` function. If this buffer overlfows due to an insufficient buffer size, the receiver can continue to work, but overflowed symbols are dropped and the following error message is reported: ``user buffer too small, received symbols truncated``. Please take care of the lifecycle of the ``buffer`` parameter, ensuring that the buffer is not recycled before the receiver is finished or stopped.
The copy destination should be provided in the ``buffer`` parameter of :cpp:func:`rmt_receive` function. If this buffer overflows due to an insufficient buffer size, the receiver can continue to work, but overflowed symbols are dropped and the following error message is reported: ``user buffer too small, received symbols truncated``. Please take care of the lifecycle of the ``buffer`` parameter, ensuring that the buffer is not recycled before the receiver is finished or stopped.
The receiver is stopped by the driver when it finishes working, i.e., receive a signal whose duration is bigger than :cpp:member:`rmt_receive_config_t::signal_range_max_ns`. The user needs to call :cpp:func:`rmt_receive` again to restart the receiver, if necessary. The user can get the received data in the :cpp:member:`rmt_rx_event_callbacks_t::on_recv_done` callback. See also :ref:`rmt-register-event-callbacks` for more information.

View File

@@ -205,6 +205,10 @@ RMT 发射器可以生成载波信号,并将其调制到消息信号上。载
也可使用参数 ``user_data``,在 :cpp:func:`rmt_tx_register_event_callbacks` 和 :cpp:func:`rmt_rx_register_event_callbacks` 中保存自定义上下文。用户数据将直接传递给每个回调函数。
.. note::
"receive-done" 不等同于 "receive-finished". 这个回调函数也可以在 "partial-receive-done" 时间发生的时候被调用。
在回调函数中可以获取驱动程序在 ``edata`` 中填充的特定事件数据。注意,``edata`` 指针仅在回调的持续时间内有效。
有关 TX 完成事件数据的定义,请参阅 :cpp:type:`rmt_tx_done_event_data_t`
@@ -213,8 +217,9 @@ RMT 发射器可以生成载波信号,并将其调制到消息信号上。载
有关 RX 完成事件数据的定义,请参阅 :cpp:type:`rmt_rx_done_event_data_t`
- :cpp:member:`rmt_rx_done_event_data_t::received_symbols` 指向接收到的 RMT 符号,这些符号存储在 :cpp:func:`rmt_receive` 函数的 ``buffer`` 参数中,在回调函数返回前不应释放此接收缓冲区。
- :cpp:member:`rmt_rx_done_event_data_t::received_symbols` 指向接收到的 RMT 符号,这些符号存储在 :cpp:func:`rmt_receive` 函数的 ``buffer`` 参数中,在回调函数返回前不应释放此接收缓冲区。如果你还启用了部分接收的功能,则这个用户缓冲区会被用作“二级缓冲区”,其中的内容可以被随后传入的数据覆盖。在这种情况下,如果你想要保存或者稍后处理一些数据,则需要将接收到的数据复制到其他位置。
- :cpp:member:`rmt_rx_done_event_data_t::num_symbols` 表示接收到的 RMT 符号数量,该值不会超过 :cpp:func:`rmt_receive` 函数的 ``buffer_size`` 参数。如果 ``buffer_size`` 不足以容纳所有接收到的 RMT 符号,驱动程序将只保存缓冲区能够容纳的最大数量的符号,并丢弃或忽略多余的符号。
- :cpp:member:`rmt_rx_done_event_data_t::is_last` 指示收到的数据包是否是当前的接收任务中的最后一个。这个标志在你使能 :cpp:member:`rmt_receive_config_t::extra_flags::en_partial_rx` 部分接收功能时非常有用。
.. _rmt-enable-and-disable-channel:
@@ -326,6 +331,7 @@ RMT 是一种特殊的通信外设,无法像 SPI 和 I2C 那样发送原始字
- :cpp:member:`rmt_receive_config_t::signal_range_min_ns` 指定高电平或低电平有效脉冲的最小持续时间。如果脉冲宽度小于指定值,硬件会将其视作干扰信号并忽略。
- :cpp:member:`rmt_receive_config_t::signal_range_max_ns` 指定高电平或低电平有效脉冲的最大持续时间。如果脉冲宽度大于指定值,接收器会将其视作 **停止信号**,并立即生成接收完成事件。
- 如果传入的数据包很长,无法一次性保存在用户缓冲区中,可以通过将 :cpp:member:`rmt_receive_config_t::extra_flags::en_partial_rx` 设置为 ``true`` 来开启部分接收功能。在这种情况下,当用户缓冲区快满的时候,驱动会多次调用 :cpp:member:`rmt_rx_event_callbacks_t::on_recv_done` 回调函数来通知用户去处理已经收到的数据。你可以检查 :cpp:member::`rmt_rx_done_event_data_t::is_last` 的值来了解当前事务是否已经结束。
根据以上配置调用 :cpp:func:`rmt_receive`RMT 接收器会启动 RX 机制。注意,以上配置均针对特定事务存在,也就是说,要开启新一轮的接收时,需要再次设置 :cpp:type:`rmt_receive_config_t` 选项。接收器会将传入信号以 :cpp:type:`rmt_symbol_word_t` 的格式保存在内部内存块或 DMA 缓冲区中。
@@ -337,7 +343,7 @@ RMT 是一种特殊的通信外设,无法像 SPI 和 I2C 那样发送原始字
由于内存块大小有限RMT 接收器只能保存长度不超过内存块容量的短帧。硬件会将长帧截断,并由驱动程序报错:``hw buffer too small, received symbols truncated``
应在 :cpp:func:`rmt_receive` 函数的 ``buffer`` 参数中提供复制目标。如果由于缓冲区大小不足而导致缓冲区溢出,接收器仍可继续工作,但会丢弃溢出的符号,并报告此错误信息:``user buffer too small, received symbols truncated``。请注意 ``buffer`` 参数的生命周期,确保在接收器完成或停止工作前不会回收缓冲区。
应在 :cpp:func:`rmt_receive` 函数的 ``buffer`` 参数中提供复制目标。如果由于缓冲区大小不足而导致缓冲区溢出,接收器仍可继续工作,但会丢弃溢出的符号,并报告此错误信息: ``user buffer too small, received symbols truncated``。请注意 ``buffer`` 参数的生命周期,确保在接收器完成或停止工作前不会回收缓冲区。
当接收器完成工作,即接收到持续时间大于 :cpp:member:`rmt_receive_config_t::signal_range_max_ns` 的信号时,驱动程序将停止接收器。如有需要,应再次调用 :cpp:func:`rmt_receive` 重新启动接收器。在 :cpp:member:`rmt_rx_event_callbacks_t::on_recv_done` 的回调中可以获取接收到的数据。要获取更多有关详情,请参阅 :ref:`rmt-register-event-callbacks`
@@ -573,7 +579,7 @@ Kconfig 选项
- :ref:`CONFIG_RMT_ISR_IRAM_SAFE` 控制默认 ISR 处理程序能否在禁用 cache 的情况下工作。详情请参阅 :ref:`rmt-iram-safe`
- :ref:`CONFIG_RMT_ENABLE_DEBUG_LOG` 用于启用调试日志输出,启用此选项将增加固件的二进制文件大小。
- :ref:`CONFIG_RMT_RECV_FUNC_IN_IRAM` 用于控制 RMT 接收函数被链接到系统存的哪个位置IRAM 还是 Flash。详情请参阅 :ref:`rmt-iram-safe`
- :ref:`CONFIG_RMT_RECV_FUNC_IN_IRAM` 用于控制 RMT 接收函数被链接到系统存的哪个位置IRAM 还是 Flash。详情请参阅 :ref:`rmt-iram-safe`
应用示例
--------------------