feat(parlio_rx): add parlio rx examples

This commit is contained in:
laokaiyao 2023-10-07 14:34:21 +08:00
parent 95839eb1c6
commit db7e90fef9
44 changed files with 2017 additions and 518 deletions

View File

@ -24,10 +24,10 @@ typedef struct {
size_t max_recv_size; /*!< Maximum receive size in one transaction, in bytes. This decides the number of DMA nodes will be used for each transaction */
size_t data_width; /*!< Parallel IO data width, can set to 1/2/4/8/..., but can't be greater than PARLIO_RX_UNIT_MAX_DATA_WIDTH */
parlio_clock_source_t clk_src; /*!< Parallel IO clock source */
uint32_t clk_freq_hz; /*!< The source clock frequency for external when the clock source is selected as PARLIO_CLK_SRC_EXTERNAL;
The expected output clock frequency when the clock source is from internal */
gpio_num_t clk_gpio_num; /*!< The input or output clock pin. If the clock source is input from external,
the clock gpio must be set, otherwise set `-1` can disable the clock output if not needed */
uint32_t ext_clk_freq_hz; /*!< The external source clock frequency. Only be valid when select PARLIO_CLK_SRC_EXTERNAL as clock source */
uint32_t exp_clk_freq_hz; /*!< The expected sample/bit clock frequency, which is divided from the internal or external clock regarding the clock source */
gpio_num_t clk_in_gpio_num; /*!< The the external clock input pin. Only be valid when select PARLIO_CLK_SRC_EXTERNAL as clock source. Set to -1 if not needed */
gpio_num_t clk_out_gpio_num; /*!< The sample/bit clock output pin. Set to -1 if not needed */
gpio_num_t valid_gpio_num; /*!< GPIO number of the valid signal. The signal on this pin is used to indicate whether the data on the data lines are valid.
Only takes effect when using level or pulse delimiter, set to `-1` if only use the soft delimiter */
gpio_num_t data_gpio_nums[PARLIO_RX_UNIT_MAX_DATA_WIDTH]; /*!< Parallel IO data GPIO numbers, set to `-1` if it's not used,
@ -35,14 +35,14 @@ typedef struct {
struct {
uint32_t free_clk : 1; /*!< Whether the input external clock is a free-running clock. A free-running clock will always keep running (e.g. I2S bclk),
a non-free-running clock will start when there are data transporting and stop when the bus idle (e.g. SPI).
This flag only takes effect when the clock source is selected as PARLIO_CLK_SRC_EXTERNAL */
This flag only takes effect when select PARLIO_CLK_SRC_EXTERNAL as clock source */
uint32_t clk_gate_en : 1; /*!< Enable RX clock gating, only available when the clock direction is output(not supported on ESP32-C6)
the output clock will be controlled by the valid gpio,
i.e. high level of valid gpio to enable the clock output, low to disable */
uint32_t io_loop_back: 1; /*!< For debug/test, the signal output from the GPIO will be fed to the input path as well */
uint32_t io_no_init: 1; /*!< Set to skip initializing the GPIO, but only attach the pralio rx signals to those GPIOs via IO Matrix.
So that the signals that have attached to those GPIO won't be overwritten. Mainly used for self communication or self monitoring */
} flags;
} flags; /*!< RX driver flags */
} parlio_rx_unit_config_t;
/**
@ -50,7 +50,11 @@ typedef struct {
*
* @param[in] config Parallel IO RX unit configuration
* @param[out] ret_unit Returned Parallel IO RX unit handle
* @return esp_err_t
* @return
* - ESP_ERR_INVALID_ARG Invalid arguments in the parameter list or the rx unit configuration
* - ESP_ERR_NOT_FOUND No available rx unit found
* - ESP_ERR_NO_MEM No enough memory for the rx unit resources
* - ESP_OK Success to allocate the rx unit
*/
esp_err_t parlio_new_rx_unit(const parlio_rx_unit_config_t *config, parlio_rx_unit_handle_t *ret_unit);
@ -58,7 +62,10 @@ esp_err_t parlio_new_rx_unit(const parlio_rx_unit_config_t *config, parlio_rx_un
* @brief Delete a Parallel IO RX unit
*
* @param[in] rx_unit Parallel IO RX unit handle that created by `parlio_new_rx_unit`
* @return esp_err_t
* @return
* - ESP_ERR_INVALID_ARG rx_unit is NULL
* - ESP_ERR_INVALID_STATE The rx unit is enabled, can't delete an enabled rx unit
* - ESP_OK Success to delete the rx unit
*/
esp_err_t parlio_del_rx_unit(parlio_rx_unit_handle_t rx_unit);
@ -79,19 +86,24 @@ typedef struct {
uint32_t timeout_ticks; /*!< The number of source clock ticks to trigger timeout interrupt. Set 0 to disable the receive timeout interrupt
The timeout counter starts when the valid/enable signal is invalid/disabled. */
struct {
uint32_t active_level: 1; /*!< Which level indicates the validation of the transmitting data */
uint32_t active_low_en: 1; /*!< Set true to set the valid signal active when the level is low,
otherwise, the valid signal becomes active when its level is high */
} flags; /*!< Extra flags */
} parlio_rx_level_delimiter_config_t;
/**
* @brief Create a level delimiter
*
* @note This function only allocate the software resources, the hardware configurations
* will lazy installed while the transaction that using this delimiter start processing
* @note The enable signal must be aligned with the valid data.
* @note There're at most `SOC_PARLIO_RX_UNIT_MAX_DATA_WIDTH - 1` IO pins left for RXD
*
* @param[in] config Level delimiter configuration
* @param[out] ret_delimiter Returned delimiter handle
* @return esp_err_t
* @return
* - ESP_ERR_INVALID_ARG Invalid arguments in the parameter list or the level delimiter configuration
* - ESP_ERR_NO_MEM No enough memory for the level delimiter resources
* - ESP_OK Success to allocate the level delimiter
*/
esp_err_t parlio_new_rx_level_delimiter(const parlio_rx_level_delimiter_config_t *config,
parlio_rx_delimiter_handle_t *ret_delimiter);
@ -124,12 +136,16 @@ typedef struct {
/**
* @brief Create a pulse delimiter
*
* @note This function only allocate the software resources, the hardware configurations
* will lazy installed while the transaction that using this delimiter start processing
* @note There're at most `SOC_PARLIO_RX_UNIT_MAX_DATA_WIDTH - 1` IO pins left for RXD
*
* @param[in] config Pulse delimiter configuration
* @param[out] ret_delimiter Returned delimiter handle
* @return esp_err_t
* @return
* - ESP_ERR_INVALID_ARG Invalid arguments in the parameter list or the pulse delimiter configuration
* - ESP_ERR_NO_MEM No enough memory for the pulse delimiter resources
* - ESP_OK Success to allocate the pulse delimiter
*/
esp_err_t parlio_new_rx_pulse_delimiter(const parlio_rx_pulse_delimiter_config_t *config,
parlio_rx_delimiter_handle_t *ret_delimiter);
@ -144,16 +160,20 @@ typedef struct {
uint32_t eof_data_len; /*!< Set the data length to trigger the End Of Frame (EOF, i.e. transaction done)
interrupt, if the data length is set to `0`, that mean the EOF will only triggers
when the end pulse detected, please ensure there is an end pulse for a frame and
`has_end_pulse` flag is set */
`parlio_rx_pulse_delimiter_config_t::has_end_pulse` flag is set */
uint32_t timeout_ticks; /*!< The number of APB clock ticks to trigger timeout interrupt. Set 0 to disable the receive timeout interrupt */
} parlio_rx_soft_delimiter_config_t;
/**
* @brief Create a pulse delimiter
*
* @note This function only allocate the software resources, the hardware configurations
* will lazy installed while the transaction that using this delimiter start processing
* @param[in] config Soft delimiter configuration
* @param[out] ret_delimiter Returned delimiter handle
* @return esp_err_t
* @return
* - ESP_ERR_INVALID_ARG Invalid arguments in the parameter list or the soft delimiter configuration
* - ESP_ERR_NO_MEM No enough memory for the soft delimiter resources
* - ESP_OK Success to allocate the soft delimiter
*/
esp_err_t parlio_new_rx_soft_delimiter(const parlio_rx_soft_delimiter_config_t *config,
parlio_rx_delimiter_handle_t *ret_delimiter);
@ -165,31 +185,22 @@ esp_err_t parlio_new_rx_soft_delimiter(const parlio_rx_soft_delimiter_config_t *
* @param[in] rx_unit Parallel IO RX unit handle that created by `parlio_new_rx_unit`
* @param[in] delimiter Delimiter handle
* @param[in] start_stop Set true to start, set false to stop
* @return esp_err_t
* @return
* - ESP_ERR_INVALID_ARG Invalid arguments in the parameter list or not soft delimiter
* - ESP_ERR_INVALID_STATE The rx unit not enabled
* - ESP_OK Success to start or stop the soft delimiter
*/
esp_err_t parlio_rx_soft_delimiter_start_stop(parlio_rx_unit_handle_t rx_unit, parlio_rx_delimiter_handle_t delimiter, bool start_stop);
/**
* @brief Generic Selection of the delimiter create functions
* @note This is a helper function for creating the delimiter generically,
* input different type of configuration to create a corresponding delimiter
*
* @param[in] config The configuration of the delimiter, whose type decides the delimiter type that created,
* Can be `parlio_rx_level_delimiter_config_t`, `parlio_rx_pulse_delimiter_config_t` or `parlio_rx_soft_delimiter_config_t`
* @param[out] ret_delimiter Returned delimiter handle
*/
#define parlio_new_rx_delimiter(config, ret_delimiter) _Generic((config), \
parlio_rx_level_delimiter_config_t* : parlio_new_rx_level_delimiter, \
parlio_rx_pulse_delimiter_config_t* : parlio_new_rx_pulse_delimiter, \
parlio_rx_soft_delimiter_config_t* : parlio_new_rx_soft_delimiter, \
default: parlio_new_rx_soft_delimiter) (config, ret_delimiter)
/**
* @brief Delete the delimiter
* @note To delete the delimiter safely, please delete it after disable all the RX units
*
* @param[in] delimiter Delimiter handle
* @return esp_err_t
* @return
* - ESP_ERR_INVALID_ARG The input delimiter is NULL
* - ESP_ERR_INVALID_STATE The delimiter is on receiving
* - ESP_OK Success to delete the delimiter
*/
esp_err_t parlio_del_rx_delimiter(parlio_rx_delimiter_handle_t delimiter);
@ -200,15 +211,21 @@ esp_err_t parlio_del_rx_delimiter(parlio_rx_delimiter_handle_t delimiter);
* @param[in] reset_queue Whether to reset the receiving queue.
* If set to false, the legacy receive transactions in the queue are still available,
* If set to true, the legacy receive transactions in the queue are dropped.
* @return esp_err_t
* @return
* - ESP_ERR_INVALID_ARG The input rx_unit is NULL
* - ESP_ERR_INVALID_STATE The rx unit has been enabled
* - ESP_OK Success to enable the rx unit
*/
esp_err_t parlio_rx_unit_enable(parlio_rx_unit_handle_t rx_unit, bool reset_queue);
/**
* @brief Disable the Parallel IO TX unit
* @brief Disable the Parallel IO RX unit
*
* @param[in] rx_unit Parallel IO RX unit handle that created by `parlio_new_rx_unit`
* @return esp_err_t
* @return
* - ESP_ERR_INVALID_ARG The input rx_unit is NULL
* - ESP_ERR_INVALID_STATE The rx unit has been disabled
* - ESP_OK Success to disable the rx unit
*/
esp_err_t parlio_rx_unit_disable(parlio_rx_unit_handle_t rx_unit);
@ -216,22 +233,24 @@ esp_err_t parlio_rx_unit_disable(parlio_rx_unit_handle_t rx_unit);
* @brief Configuration of a receive transaction
*/
typedef struct {
parlio_rx_delimiter_handle_t delimiter; /*!< The delimiter of this receiving transaction */
parlio_rx_delimiter_handle_t delimiter; /*!< The delimiter of this receiving transaction */
struct {
uint32_t is_infinite: 1; /*!< Whether this is an infinite transaction that supposed to receive continuously */
uint32_t indirect_mount: 1;/*!< This flag only take effect when `is_infinite` is enabled.
* Enable this flag, the DMA descriptor will mount to an internal DMA buffer instead,
uint32_t partial_rx_en: 1; /*!< Whether this is an infinite transaction that supposed to receive continuously and partially */
uint32_t indirect_mount: 1; /*!< This flag only take effect when `partial_rx_en` is enabled.
* Enable this flag, an INTERNAL DMA buffer will be mounted to the DMA descriptor instead,
* The data will be copy to the payload in every interrupt. So that to guarantee the payload buffer
* is valid during the `on_receive_done` callback.
* Either `is_infinite` or `indirect_mount` is disabled,
* the finite payload will be mounted to the DMA descriptor directly.
* By default, the receive payload will be mounted to the DMA descriptor directly,
* Either `partial_rx_en` or `indirect_mount` is disabled,
* the user given finite payload will be mounted to the DMA descriptor directly.
* By default, the user given receive payload will be mounted to the DMA descriptor directly.
*/
} flags; /*!< Extra flags */
} parlio_receive_config_t;
/**
* @brief Receive data by Parallel IO RX unit
* @note This is a non-blocking and asynchronous function. To block or realize synchronous receive,
* please call `parlio_rx_unit_wait_all_done` after this function
* @note The receive transaction will start immediately when there is not other transaction on receiving,
* Otherwise it will be sent to the transaction queue to wait for the bus.
*
@ -239,7 +258,12 @@ typedef struct {
* @param[in] payload The payload buffer pointer
* @param[in] payload_size The size of the payload buffer, in bytes.
* @param[in] recv_cfg The configuration of this receive transaction
* @return esp_err_t
* @return
* - ESP_ERR_INVALID_ARG Invalid arguments in the parameter list or the receive configuration
* - ESP_ERR_NO_MEM No memory for the internal DMA buffer (only when parlio_receive_config_t::indirect_mount enabled)
* - ESP_ERR_INVALID_STATE Transaction queue is full, failed to queue the current transaction.
* Or the internal buffer is under using by an infinite transaction, can't allocate a new one
* - ESP_OK Success to queue the current receiving transaction
*/
esp_err_t parlio_rx_unit_receive(parlio_rx_unit_handle_t rx_unit,
void *payload,
@ -248,10 +272,16 @@ esp_err_t parlio_rx_unit_receive(parlio_rx_unit_handle_t rx_unit,
/**
* @brief Wait for all pending RX transactions done
* @note This function will block until all receiving transactions done or timeout.
* When timeout occurs, either the timeout limitation too short for all transactions done,
* or the peripheral got stuck and no more interrupts trigger (e.g., external clock stopped).
*
* @param[in] rx_unit Parallel IO RX unit handle that created by `parlio_new_rx_unit`
* @param[in] timeout_ms Timeout in milliseconds, `-1` means to wait forever (software timeout)
* @return esp_err_t
* @return
* - ESP_ERR_INVALID_ARG The input rx_unit is NULL
* - ESP_ERR_TIMEOUT Wait for all transactions done timeout
* - ESP_OK All transaction done
*/
esp_err_t parlio_rx_unit_wait_all_done(parlio_rx_unit_handle_t rx_unit, int timeout_ms);
@ -261,7 +291,6 @@ esp_err_t parlio_rx_unit_wait_all_done(parlio_rx_unit_handle_t rx_unit, int time
typedef struct {
parlio_rx_delimiter_handle_t delimiter; /*!< The current delimiter of this receiving event */
void *data; /*!< The data buffer address that just finished receiving */
size_t size; /*!< The total size of the data buffer , in byte */
size_t recv_bytes; /*!< The number of received bytes in the data buffer */
} parlio_rx_event_data_t;
@ -291,9 +320,12 @@ typedef struct {
* @brief Register event callbacks for Parallel IO RX unit
*
* @param[in] rx_unit Parallel IO RX unit handle that created by `parlio_new_rx_unit`
* @param[in] cbs Callbacks
* @param[in] cbs Callback group, set callback to NULL to deregister the corresponding callback (callback group pointer shouldn't be NULL)
* @param[in] user_data User specified data that will be transported to the callbacks
* @return esp_err_t
* @return
* - ESP_ERR_INVALID_ARG The input rx_unit is NULL
* - ESP_ERR_INVALID_STATE The rx unit has been enabled, callback should be registered before enabling the unit
* - ESP_OK Success to register the callbacks
*/
esp_err_t parlio_rx_unit_register_event_callbacks(parlio_rx_unit_handle_t rx_unit, const parlio_rx_event_callbacks_t *cbs, void *user_data);

View File

@ -99,12 +99,23 @@ esp_err_t parlio_register_unit_to_group(parlio_unit_base_handle_t unit)
parlio_unit_base_handle_t *group_unit = NULL;
ESP_RETURN_ON_FALSE(group, ESP_ERR_NO_MEM, TAG, "no memory for group (%d)", i);
portENTER_CRITICAL(&group->spinlock);
for (int j = 0; j < SOC_PARLIO_RX_UNITS_PER_GROUP; j++) {
group_unit = (unit->dir == PARLIO_DIR_TX) ? &group->tx_units[j] : &group->rx_units[j];
if (*group_unit == NULL) {
*group_unit = unit;
unit_id = j;
break;
if (unit->dir == PARLIO_DIR_TX) {
for (int j = 0; j < SOC_PARLIO_TX_UNITS_PER_GROUP; j++) {
group_unit = &group->tx_units[j];
if (*group_unit == NULL) {
*group_unit = unit;
unit_id = j;
break;
}
}
} else {
for (int j = 0; j < SOC_PARLIO_RX_UNITS_PER_GROUP; j++) {
group_unit = &group->rx_units[j];
if (*group_unit == NULL) {
*group_unit = unit;
unit_id = j;
break;
}
}
}
portEXIT_CRITICAL(&group->spinlock);
@ -125,16 +136,15 @@ esp_err_t parlio_register_unit_to_group(parlio_unit_base_handle_t unit)
void parlio_unregister_unit_from_group(parlio_unit_base_handle_t unit)
{
if (unit) {
parlio_group_t *group = unit->group;
portENTER_CRITICAL(&group->spinlock);
if (unit->dir == PARLIO_DIR_TX) {
group->tx_units[unit->unit_id] = NULL;
} else {
group->rx_units[unit->unit_id] = NULL;
}
portEXIT_CRITICAL(&group->spinlock);
/* the rx unit has a reference of the group, release it now */
parlio_release_group_handle(group);
assert(unit);
parlio_group_t *group = unit->group;
portENTER_CRITICAL(&group->spinlock);
if (unit->dir == PARLIO_DIR_TX) {
group->tx_units[unit->unit_id] = NULL;
} else {
group->rx_units[unit->unit_id] = NULL;
}
portEXIT_CRITICAL(&group->spinlock);
/* the parlio unit has a reference of the group, release it now */
parlio_release_group_handle(group);
}

View File

@ -118,12 +118,22 @@ struct parlio_unit_t {
parlio_group_t *group; // group handle
};
parlio_group_t *parlio_acquire_group_handle(int group_id);
void parlio_release_group_handle(parlio_group_t *group);
/**
* @brief Register the rx or tx unit to the parlio group
*
* @param[in] unit The TX/RX unit base handle
* @return
* - ESP_ERR_NO_MEM No memory for the unit
* - ESP_ERR_NOT_FOUND No available unit found on this group
* - ESP_OK Success to register the unit on the group
*/
esp_err_t parlio_register_unit_to_group(parlio_unit_base_handle_t unit);
/**
* @brief Unregister the rx or tx unit from the parlio group
*
* @param[in] unit The TX/RX unit base handle
*/
void parlio_unregister_unit_from_group(parlio_unit_base_handle_t unit);
#ifdef __cplusplus

View File

@ -6,7 +6,6 @@
#include <stdlib.h>
#include <string.h>
#include <stdatomic.h>
#include <sys/cdefs.h>
#include <sys/param.h>
#include "sdkconfig.h"
@ -27,6 +26,7 @@
#include "hal/parlio_ll.h"
#include "hal/gpio_hal.h"
#include "hal/dma_types.h"
#include "hal/hal_utils.h"
#include "driver/gpio.h"
#include "driver/parlio_rx.h"
#include "parlio_private.h"
@ -57,12 +57,10 @@ typedef struct {
*/
typedef struct parlio_rx_unit_t {
/* Unit general Resources */
int unit_id; /*!< unit id */
parlio_dir_t dir; /*!< unit direction */
parlio_group_t *group; /*!< group handle */
struct parlio_unit_t base; /*!< base unit */
parlio_clock_source_t clk_src; /*!< clock source of the unit */
parlio_rx_unit_config_t cfg; /*!< basic configuration of the rx unit */
bool is_enabled; /*!< State flag that indicates whether the unit is enabled */
volatile bool is_enabled; /*!< State flag that indicates whether the unit is enabled */
/* Mutex Lock */
SemaphoreHandle_t mutex; /*!< Mutex lock for concurrence safety,
* which should be acquired and released in a same function */
@ -118,7 +116,7 @@ typedef struct parlio_rx_delimiter_t {
uint32_t eof_data_len; /*!< The length of the data to trigger the eof interrupt */
uint32_t timeout_ticks; /*!< The ticks of source clock that can trigger hardware timeout */
struct {
uint32_t active_level: 1; /*!< Which level indicates the validation of the transmitting data */
uint32_t active_low_en: 1; /*!< Whether the transmitting data validate when the valid signal at low level */
uint32_t start_bit_included: 1; /*!< Whether data bit is included in the start pulse */
uint32_t end_bit_included: 1; /*!< Whether data bit is included in the end pulse, only valid when `has_end_pulse` is true */
uint32_t has_end_pulse: 1; /*!< Whether there's an end pulse to terminate the transaction,
@ -127,13 +125,16 @@ typedef struct parlio_rx_delimiter_t {
} flags;
} parlio_rx_delimiter_t;
static portMUX_TYPE s_rx_spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED;
static IRAM_ATTR size_t s_parlio_mount_transaction_buffer(parlio_rx_unit_handle_t rx_unit, parlio_rx_transaction_t *trans)
{
dma_descriptor_t *p_desc = rx_unit->dma_descs;
/* Update the current transaction to the next one, and declare the delimiter is under using of the rx unit */
memcpy(&rx_unit->curr_trans, trans, sizeof(parlio_rx_transaction_t));
portENTER_CRITICAL_SAFE(&s_rx_spinlock);
trans->delimiter->under_using = true;
portEXIT_CRITICAL_SAFE(&s_rx_spinlock);
uint32_t desc_num = trans->size / DMA_DESCRIPTOR_BUFFER_MAX_SIZE_4B_ALIGNED;
uint32_t remain_num = trans->size % DMA_DESCRIPTOR_BUFFER_MAX_SIZE_4B_ALIGNED;
@ -177,7 +178,7 @@ static IRAM_ATTR size_t s_parlio_mount_transaction_buffer(parlio_rx_unit_handle_
static IRAM_ATTR void s_parlio_set_delimiter_config(parlio_rx_unit_handle_t rx_unit, parlio_rx_delimiter_handle_t deli)
{
parlio_hal_context_t *hal = &(rx_unit->group->hal);
parlio_hal_context_t *hal = &(rx_unit->base.group->hal);
/* Set the clock sampling edge and the bit order */
parlio_ll_rx_set_sample_clock_edge(hal->regs, deli->sample_edge);
@ -187,7 +188,7 @@ static IRAM_ATTR void s_parlio_set_delimiter_config(parlio_rx_unit_handle_t rx_u
switch (deli->mode) {
case PARLIO_RX_LEVEL_MODE:
/* Select the level receive mode */
parlio_ll_rx_set_level_recv_mode(hal->regs, deli->flags.active_level);
parlio_ll_rx_set_level_recv_mode(hal->regs, deli->flags.active_low_en);
parlio_ll_rx_treat_data_line_as_en(hal->regs, deli->valid_sig_line_id);
break;
case PARLIO_RX_PULSE_MODE:
@ -235,8 +236,8 @@ static IRAM_ATTR void s_parlio_set_delimiter_config(parlio_rx_unit_handle_t rx_u
static esp_err_t s_parlio_rx_unit_set_gpio(parlio_rx_unit_handle_t rx_unit, const parlio_rx_unit_config_t *config)
{
int group_id = rx_unit->group->group_id;
int unit_id = rx_unit->unit_id;
int group_id = rx_unit->base.group->group_id;
int unit_id = rx_unit->base.unit_id;
/* Default GPIO configuration */
gpio_config_t gpio_conf = {
.intr_type = GPIO_INTR_DISABLE,
@ -246,24 +247,24 @@ static esp_err_t s_parlio_rx_unit_set_gpio(parlio_rx_unit_handle_t rx_unit, cons
/* When the source clock comes from external, enable the gpio input direction and connect to the clock input signal */
if (config->clk_src == PARLIO_CLK_SRC_EXTERNAL) {
ESP_RETURN_ON_FALSE(config->clk_gpio_num >= 0, ESP_ERR_INVALID_ARG, TAG, "clk_gpio_num must be set while the clock input from external");
ESP_RETURN_ON_FALSE(config->clk_in_gpio_num >= 0, ESP_ERR_INVALID_ARG, TAG, "clk_in_gpio_num must be set while the clock input from external");
/* Connect the clock in signal to the GPIO matrix if it is set */
if (!config->flags.io_no_init) {
gpio_conf.mode = config->flags.io_loop_back ? GPIO_MODE_INPUT_OUTPUT : GPIO_MODE_INPUT;
gpio_conf.pin_bit_mask = BIT64(config->clk_gpio_num);
gpio_conf.pin_bit_mask = BIT64(config->clk_in_gpio_num);
ESP_RETURN_ON_ERROR(gpio_config(&gpio_conf), TAG, "config clk in GPIO failed");
}
esp_rom_gpio_connect_in_signal(config->clk_gpio_num,
esp_rom_gpio_connect_in_signal(config->clk_in_gpio_num,
parlio_periph_signals.groups[group_id].rx_units[unit_id].clk_in_sig, false);
}
/* When the source clock comes from internal and supported to output the internal clock,
* enable the gpio output direction and connect to the clock output signal */
else if (config->clk_gpio_num >= 0) {
if (config->clk_out_gpio_num >= 0) {
#if SOC_PARLIO_RX_CLK_SUPPORT_OUTPUT
gpio_conf.mode = config->flags.io_loop_back ? GPIO_MODE_INPUT_OUTPUT : GPIO_MODE_OUTPUT;
gpio_conf.pin_bit_mask = BIT64(config->clk_gpio_num);
gpio_conf.pin_bit_mask = BIT64(config->clk_out_gpio_num);
ESP_RETURN_ON_ERROR(gpio_config(&gpio_conf), TAG, "config clk out GPIO failed");
esp_rom_gpio_connect_out_signal(config->clk_gpio_num,
esp_rom_gpio_connect_out_signal(config->clk_out_gpio_num,
parlio_periph_signals.groups[group_id].rx_units[unit_id].clk_out_sig, false, false);
#else
ESP_RETURN_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, TAG, "this target not support to output the clock");
@ -318,7 +319,6 @@ static IRAM_ATTR bool s_parlio_rx_default_eof_callback(gdma_channel_handle_t dma
/* If received a normal EOF, it's a receive done event on parlio RX */
if (rx_unit->cbs.on_receive_done) {
evt_data.data = rx_unit->usr_recv_buf;
evt_data.size = rx_unit->curr_trans.size;
evt_data.recv_bytes = rx_unit->curr_trans.recv_bytes;
need_yield |= rx_unit->cbs.on_receive_done(rx_unit, &evt_data, rx_unit->user_data);
}
@ -328,27 +328,29 @@ static IRAM_ATTR bool s_parlio_rx_default_eof_callback(gdma_channel_handle_t dma
/* For infinite transactions, reset the receiving bytes when the transaction is done */
rx_unit->curr_trans.recv_bytes = 0;
} else {
parlio_rx_transaction_t next_trans;
parlio_rx_transaction_t next_trans = {};
/* The current transaction finished, try to get the next transaction from the transaction queue */
if (xQueueReceiveFromISR(rx_unit->trans_que, &next_trans, &high_task_woken) == pdTRUE) {
if (rx_unit->cfg.flags.free_clk) {
parlio_ll_rx_enable_clock(rx_unit->group->hal.regs, false);
parlio_ll_rx_enable_clock(rx_unit->base.group->hal.regs, false);
}
/* If the delimiter of the next transaction is not same as the current one, need to re-config the hardware */
if (next_trans.delimiter != rx_unit->curr_trans.delimiter) {
s_parlio_set_delimiter_config(rx_unit, next_trans.delimiter);
}
/* Mount the new transaction buffer and start the new transaction */
s_parlio_mount_transaction_buffer(rx_unit, &rx_unit->curr_trans);
s_parlio_mount_transaction_buffer(rx_unit, &next_trans);
gdma_start(rx_unit->dma_chan, (intptr_t)rx_unit->dma_descs);
if (rx_unit->cfg.flags.free_clk) {
parlio_ll_rx_start(rx_unit->group->hal.regs, true);
parlio_ll_rx_enable_clock(rx_unit->group->hal.regs, true);
parlio_ll_rx_start(rx_unit->base.group->hal.regs, true);
parlio_ll_rx_enable_clock(rx_unit->base.group->hal.regs, true);
}
} else {
} else if (rx_unit->curr_trans.delimiter) { // Add condition in case the curr_trans has been cleared in the last timeout isr
/* No more transaction pending to receive, clear the current transaction */
portENTER_CRITICAL_ISR(&s_rx_spinlock);
rx_unit->curr_trans.delimiter->under_using = false;
memset(&rx_unit->curr_trans, 0, sizeof(parlio_rx_transaction_t));
portEXIT_CRITICAL_ISR(&s_rx_spinlock);
need_yield |= high_task_woken == pdTRUE;
xSemaphoreGiveFromISR(rx_unit->trans_sem, &high_task_woken);
}
@ -362,13 +364,16 @@ static IRAM_ATTR bool s_parlio_rx_default_desc_done_callback(gdma_channel_handle
{
parlio_rx_unit_handle_t rx_unit = (parlio_rx_unit_handle_t )user_data;
bool need_yield = false;
/* No need to process the data if error EOF (i.e. timeout) happened */
if (event_data->flags.abnormal_eof) {
return false;
}
/* Get the finished descriptor from the current descriptor */
dma_descriptor_t *finished_desc = rx_unit->curr_desc;
parlio_rx_event_data_t evt_data = {
.delimiter = rx_unit->curr_trans.delimiter,
// TODO: The current descriptor is not able to access when error EOF occur
.data = finished_desc->buffer,
.size = finished_desc->dw0.size,
.recv_bytes = finished_desc->dw0.length,
};
if (rx_unit->cbs.on_partial_receive) {
@ -378,7 +383,9 @@ static IRAM_ATTR bool s_parlio_rx_default_desc_done_callback(gdma_channel_handle
if (rx_unit->curr_trans.flags.infinite && rx_unit->curr_trans.flags.indirect_mount) {
memcpy(rx_unit->usr_recv_buf + rx_unit->curr_trans.recv_bytes, evt_data.data, evt_data.recv_bytes);
} else {
portENTER_CRITICAL_ISR(&s_rx_spinlock);
rx_unit->curr_trans.delimiter->under_using = false;
portEXIT_CRITICAL_ISR(&s_rx_spinlock);
}
/* Update received bytes */
if (rx_unit->curr_trans.recv_bytes >= rx_unit->curr_trans.size) {
@ -395,7 +402,7 @@ static esp_err_t s_parlio_rx_create_dma_descriptors(parlio_rx_unit_handle_t rx_u
{
ESP_RETURN_ON_FALSE(rx_unit, ESP_ERR_INVALID_ARG, TAG, "invalid param");
uint32_t desc_num =max_recv_size / DMA_DESCRIPTOR_BUFFER_MAX_SIZE_4B_ALIGNED + 1;
uint32_t desc_num = max_recv_size / DMA_DESCRIPTOR_BUFFER_MAX_SIZE_4B_ALIGNED + 1;
/* set at least 2 descriptors */
if (desc_num < 2) {
desc_num = 4;
@ -439,25 +446,43 @@ static esp_err_t s_parlio_rx_unit_init_dma(parlio_rx_unit_handle_t rx_unit)
static esp_err_t s_parlio_select_periph_clock(parlio_rx_unit_handle_t rx_unit, const parlio_rx_unit_config_t *config)
{
parlio_hal_context_t *hal = &rx_unit->group->hal;
parlio_hal_context_t *hal = &rx_unit->base.group->hal;
parlio_clock_source_t clk_src = config->clk_src;
uint32_t periph_src_clk_hz = 0;
uint32_t div = 1;
uint32_t src_freq_hz = 0;
uint32_t exp_freq_hz = 0;
hal_utils_clk_div_t clk_div = {
.integer = 1,
};
/* if the source clock is input from the GPIO, then we're in the slave mode */
if (clk_src != PARLIO_CLK_SRC_EXTERNAL) {
ESP_RETURN_ON_FALSE(config->clk_freq_hz, ESP_ERR_INVALID_ARG, TAG, "clock frequency not set");
ESP_RETURN_ON_FALSE(config->exp_clk_freq_hz, ESP_ERR_INVALID_ARG, TAG, "output clock frequency not set");
exp_freq_hz = config->exp_clk_freq_hz;
/* get the internal clock source frequency */
esp_clk_tree_src_get_freq_hz(clk_src, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &periph_src_clk_hz);
/* set clock division, round up */
div = (periph_src_clk_hz + config->clk_freq_hz - 1) / config->clk_freq_hz;
esp_clk_tree_src_get_freq_hz(clk_src, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &src_freq_hz);
} else {
periph_src_clk_hz = config->clk_freq_hz;
ESP_RETURN_ON_FALSE(config->ext_clk_freq_hz, ESP_ERR_INVALID_ARG, TAG, "input clock frequency not set");
exp_freq_hz = config->exp_clk_freq_hz > 0 ? config->exp_clk_freq_hz : config->ext_clk_freq_hz;
src_freq_hz = config->ext_clk_freq_hz;
}
/* set clock division, round up */
hal_utils_clk_info_t clk_info = {
.src_freq_hz = src_freq_hz,
.exp_freq_hz = exp_freq_hz,
.max_integ = PARLIO_LL_RX_MAX_CLK_INT_DIV,
.min_integ = 1,
.round_opt = HAL_DIV_ROUND,
};
#if PARLIO_LL_RX_MAX_CLK_FRACT_DIV
clk_info.max_fract = PARLIO_LL_RX_MAX_CLK_FRACT_DIV;
rx_unit->cfg.exp_clk_freq_hz = hal_utils_calc_clk_div_frac_accurate(&clk_info, &clk_div);
#else
rx_unit->cfg.exp_clk_freq_hz = hal_utils_calc_clk_div_integer(&clk_info, &clk_div.integer);
#endif
#if CONFIG_PM_ENABLE
if (clk_src != PARLIO_CLK_SRC_EXTERNAL) {
/* XTAL and PLL clock source will be turned off in light sleep, so we need to create a NO_LIGHT_SLEEP lock */
sprintf(rx_unit->pm_lock_name, "parlio_rx_%d_%d", rx_unit->group->group_id, rx_unit->unit_id); // e.g. parlio_rx_0_0
sprintf(rx_unit->pm_lock_name, "parlio_rx_%d_%d", rx_unit->base.group->group_id, rx_unit->base.unit_id); // e.g. parlio_rx_0_0
esp_err_t ret = esp_pm_lock_create(ESP_PM_NO_LIGHT_SLEEP, 0, rx_unit->pm_lock_name, &rx_unit->pm_lock);
ESP_RETURN_ON_ERROR(ret, TAG, "create NO_LIGHT_SLEEP lock failed");
}
@ -465,14 +490,13 @@ static esp_err_t s_parlio_select_periph_clock(parlio_rx_unit_handle_t rx_unit, c
/* Set clock configuration */
parlio_ll_rx_set_clock_source(hal->regs, clk_src);
parlio_ll_rx_set_clock_div(hal->regs, div);
parlio_ll_rx_set_clock_div(hal->regs, &clk_div);
rx_unit->clk_src = clk_src;
rx_unit->cfg.clk_freq_hz = periph_src_clk_hz / div;
/* warning if precision lost due to division */
if ((clk_src != PARLIO_CLK_SRC_EXTERNAL) &&
(config->clk_freq_hz != rx_unit->cfg.clk_freq_hz )) {
ESP_LOGW(TAG, "precision loss, real output frequency: %"PRIu32, rx_unit->cfg.clk_freq_hz );
(config->exp_clk_freq_hz != rx_unit->cfg.exp_clk_freq_hz )) {
ESP_LOGW(TAG, "precision loss, real output frequency: %"PRIu32, rx_unit->cfg.exp_clk_freq_hz );
}
return ESP_OK;
@ -510,7 +534,7 @@ static esp_err_t s_parlio_destroy_rx_unit(parlio_rx_unit_handle_t rx_unit)
free(rx_unit->dma_buf);
}
/* Unregister the RX unit from the PARLIO group */
if (rx_unit->group) {
if (rx_unit->base.group) {
parlio_unregister_unit_from_group((parlio_unit_base_handle_t)rx_unit);
}
/* Free the RX unit */
@ -527,6 +551,8 @@ esp_err_t parlio_new_rx_unit(const parlio_rx_unit_config_t *config, parlio_rx_un
/* Check the data width to be the the power of 2 */
ESP_RETURN_ON_FALSE(__builtin_popcount(config->data_width) == 1, ESP_ERR_INVALID_ARG, TAG,
"data line number should be the power of 2 without counting valid signal");
ESP_RETURN_ON_FALSE(config->data_width <= (int)PARLIO_RX_UNIT_MAX_DATA_WIDTH, ESP_ERR_INVALID_ARG, TAG,
"data line number should be within %d", (int)PARLIO_RX_UNIT_MAX_DATA_WIDTH);
esp_err_t ret = ESP_OK;
parlio_rx_unit_handle_t unit = NULL;
@ -534,11 +560,12 @@ esp_err_t parlio_new_rx_unit(const parlio_rx_unit_config_t *config, parlio_rx_un
/* Allocate unit memory */
unit = heap_caps_calloc(1, sizeof(parlio_rx_unit_t), PARLIO_MEM_ALLOC_CAPS);
ESP_GOTO_ON_FALSE(unit, ESP_ERR_NO_MEM, err, TAG, "no memory for rx unit");
unit->dir = PARLIO_DIR_RX;
unit->base.dir = PARLIO_DIR_RX;
unit->is_enabled = false;
/* Initialize mutex lock */
unit->mutex = xSemaphoreCreateMutexWithCaps(PARLIO_MEM_ALLOC_CAPS);
ESP_GOTO_ON_FALSE(unit->mutex, ESP_ERR_NO_MEM, err, TAG, "no memory for mutex semaphore");
/* Create transaction binary semaphore */
unit->trans_sem = xSemaphoreCreateBinaryWithCaps(PARLIO_MEM_ALLOC_CAPS);
ESP_GOTO_ON_FALSE(unit->trans_sem, ESP_ERR_NO_MEM, err, TAG, "no memory for transaction semaphore");
@ -559,7 +586,7 @@ esp_err_t parlio_new_rx_unit(const parlio_rx_unit_config_t *config, parlio_rx_un
unit->cfg.flags.free_clk = 1;
}
parlio_group_t *group = unit->group;
parlio_group_t *group = unit->base.group;
parlio_hal_context_t *hal = &group->hal;
/* Initialize GPIO */
ESP_GOTO_ON_ERROR(s_parlio_rx_unit_set_gpio(unit, config), err, TAG, "failed to set GPIO");
@ -576,13 +603,17 @@ esp_err_t parlio_new_rx_unit(const parlio_rx_unit_config_t *config, parlio_rx_un
parlio_ll_rx_set_bus_width(hal->regs, config->data_width);
#if SOC_PARLIO_RX_CLK_SUPPORT_GATING
parlio_ll_rx_enable_clock_gating(hal->regs, config->flags.clk_gate_en);
#else
if (config->flags.clk_gate_en) {
ESP_LOGW(TAG, "The current target does not support clock gating");
}
#endif // SOC_PARLIO_RX_CLK_SUPPORT_GATING
/* return RX unit handle */
*ret_unit = unit;
ESP_LOGD(TAG, "new rx unit(%d,%d) at %p, trans_queue_depth=%zu",
group->group_id, unit->unit_id, (void *)unit, unit->cfg.trans_queue_depth);
group->group_id, unit->base.unit_id, (void *)unit, unit->cfg.trans_queue_depth);
return ESP_OK;
err:
@ -595,21 +626,24 @@ err:
esp_err_t parlio_del_rx_unit(parlio_rx_unit_handle_t rx_unit)
{
ESP_RETURN_ON_FALSE(rx_unit, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
// Not necessary to take the semaphore while checking the flag because it's going to be deleted
ESP_RETURN_ON_FALSE(!rx_unit->is_enabled, ESP_ERR_INVALID_STATE, TAG, "the unit has not disabled");
ESP_LOGD(TAG, "del rx unit (%d, %d)", rx_unit->group->group_id, rx_unit->unit_id);
ESP_LOGD(TAG, "del rx unit (%d, %d)", rx_unit->base.group->group_id, rx_unit->base.unit_id);
return s_parlio_destroy_rx_unit(rx_unit);
}
esp_err_t parlio_rx_unit_enable(parlio_rx_unit_handle_t rx_unit, bool reset_queue)
{
ESP_RETURN_ON_FALSE(rx_unit, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
ESP_RETURN_ON_FALSE(!rx_unit->is_enabled, ESP_ERR_INVALID_STATE, TAG, "the unit has enabled or running");
rx_unit->is_enabled = true;
parlio_hal_context_t *hal = &rx_unit->group->hal;
esp_err_t ret = ESP_OK;
parlio_hal_context_t *hal = &rx_unit->base.group->hal;
xSemaphoreTake(rx_unit->mutex, portMAX_DELAY);
ESP_GOTO_ON_FALSE(!rx_unit->is_enabled, ESP_ERR_INVALID_STATE, err, TAG, "the unit has enabled or running");
rx_unit->is_enabled = true;
/* Acquire the power management lock incase */
if (rx_unit->pm_lock) {
esp_pm_lock_acquire(rx_unit->pm_lock);
@ -623,43 +657,50 @@ esp_err_t parlio_rx_unit_enable(parlio_rx_unit_handle_t rx_unit, bool reset_queu
}
/* Check if we need to start a pending transaction */
parlio_rx_transaction_t trans;
parlio_rx_transaction_t trans = {};
if (reset_queue) {
xQueueReset(rx_unit->trans_que);
xSemaphoreGive(rx_unit->trans_sem);
} else if (xQueueReceive(rx_unit->trans_que, &trans, 0) == pdTRUE) {
xSemaphoreTake(rx_unit->trans_sem, 0);
// The semaphore always supposed to be taken successfully
assert(xSemaphoreTake(rx_unit->trans_sem, 0) == pdTRUE);
if (rx_unit->cfg.flags.free_clk) {
parlio_ll_rx_enable_clock(hal->regs, false);
}
s_parlio_set_delimiter_config(rx_unit, trans.delimiter);
s_parlio_mount_transaction_buffer(rx_unit, &rx_unit->curr_trans);
s_parlio_mount_transaction_buffer(rx_unit, &trans);
gdma_start(rx_unit->dma_chan, (intptr_t)rx_unit->curr_desc);
if (rx_unit->cfg.flags.free_clk) {
parlio_ll_rx_start(hal->regs, true);
parlio_ll_rx_enable_clock(hal->regs, true);
}
}
err:
xSemaphoreGive(rx_unit->mutex);
return ESP_OK;
return ret;
}
esp_err_t parlio_rx_unit_disable(parlio_rx_unit_handle_t rx_unit)
{
ESP_RETURN_ON_FALSE(rx_unit, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
ESP_RETURN_ON_FALSE(rx_unit->is_enabled, ESP_ERR_INVALID_STATE, TAG, "the unit has disabled");
rx_unit->is_enabled = false;
/* stop the RX engine */
parlio_hal_context_t *hal = &rx_unit->group->hal;
esp_err_t ret = ESP_OK;
parlio_hal_context_t *hal = &rx_unit->base.group->hal;
xSemaphoreTake(rx_unit->mutex, portMAX_DELAY);
ESP_GOTO_ON_FALSE(rx_unit->is_enabled, ESP_ERR_INVALID_STATE, err, TAG, "the unit has disabled");
rx_unit->is_enabled = false;
/* stop the RX engine */
gdma_stop(rx_unit->dma_chan);
parlio_ll_rx_enable_clock(hal->regs, false);
parlio_ll_rx_start(hal->regs, false);
if (rx_unit->curr_trans.delimiter) {
portENTER_CRITICAL(&s_rx_spinlock);
rx_unit->curr_trans.delimiter->under_using = false;
portEXIT_CRITICAL(&s_rx_spinlock);
}
xSemaphoreGive(rx_unit->trans_sem);
/* For continuous receiving, free the temporary buffer and stop the DMA */
if (rx_unit->dma_buf) {
@ -672,9 +713,10 @@ esp_err_t parlio_rx_unit_disable(parlio_rx_unit_handle_t rx_unit)
}
/* Erase the current transaction */
memset(&rx_unit->curr_trans, 0, sizeof(parlio_rx_transaction_t));
err:
xSemaphoreGive(rx_unit->mutex);
return ESP_OK;
return ret;
}
esp_err_t parlio_new_rx_level_delimiter(const parlio_rx_level_delimiter_config_t *config,
@ -698,7 +740,7 @@ esp_err_t parlio_new_rx_level_delimiter(const parlio_rx_level_delimiter_config_t
delimiter->bit_pack_order = config->bit_pack_order;
delimiter->eof_data_len = config->eof_data_len;
delimiter->timeout_ticks = config->timeout_ticks;
delimiter->flags.active_level = config->flags.active_level;
delimiter->flags.active_low_en = config->flags.active_low_en;
*ret_delimiter = delimiter;
@ -773,13 +815,16 @@ esp_err_t parlio_rx_soft_delimiter_start_stop(parlio_rx_unit_handle_t rx_unit, p
{
ESP_RETURN_ON_FALSE(rx_unit && delimiter, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
ESP_RETURN_ON_FALSE(delimiter->mode == PARLIO_RX_SOFT_MODE, ESP_ERR_INVALID_ARG, TAG, "The delimiter is not soft delimiter");
ESP_RETURN_ON_FALSE(rx_unit->is_enabled, ESP_ERR_INVALID_STATE, TAG, "the unit has not enabled");
esp_err_t ret = ESP_OK;
xSemaphoreTake(rx_unit->mutex, portMAX_DELAY);
parlio_hal_context_t *hal = &(rx_unit->group->hal);
ESP_GOTO_ON_FALSE(rx_unit->is_enabled, ESP_ERR_INVALID_STATE, err, TAG, "the unit has not enabled");
parlio_hal_context_t *hal = &(rx_unit->base.group->hal);
parlio_ll_rx_start_soft_recv(hal->regs, start_stop);
err:
xSemaphoreGive(rx_unit->mutex);
return ESP_OK;
return ret;
}
esp_err_t parlio_del_rx_delimiter(parlio_rx_delimiter_handle_t delimiter)
@ -792,20 +837,25 @@ esp_err_t parlio_del_rx_delimiter(parlio_rx_delimiter_handle_t delimiter)
static esp_err_t s_parlio_rx_unit_do_transaction(parlio_rx_unit_handle_t rx_unit, parlio_rx_transaction_t *trans)
{
if (!rx_unit->curr_trans.delimiter) {
xSemaphoreTake(rx_unit->trans_sem, 0);
bool is_stopped = false;
/* Get whether DMA stopped atomically */
portENTER_CRITICAL_ISR(&s_rx_spinlock);
is_stopped = rx_unit->curr_trans.delimiter == NULL;
portEXIT_CRITICAL_ISR(&s_rx_spinlock);
if (is_stopped) {
if (rx_unit->cfg.flags.free_clk) {
parlio_ll_rx_enable_clock(rx_unit->group->hal.regs, false);
parlio_ll_rx_enable_clock(rx_unit->base.group->hal.regs, false);
}
if (trans->delimiter != rx_unit->curr_trans.delimiter) {
s_parlio_set_delimiter_config(rx_unit, trans->delimiter);
}
s_parlio_mount_transaction_buffer(rx_unit, trans);
// Take semaphore without block time here, only indicate there are transactions on receiving
xSemaphoreTake(rx_unit->trans_sem, 0);
gdma_start(rx_unit->dma_chan, (intptr_t)rx_unit->curr_desc);
if (rx_unit->cfg.flags.free_clk) {
printf("enable start\n");
parlio_ll_rx_start(rx_unit->group->hal.regs, true);
parlio_ll_rx_enable_clock(rx_unit->group->hal.regs, true);
parlio_ll_rx_start(rx_unit->base.group->hal.regs, true);
parlio_ll_rx_enable_clock(rx_unit->base.group->hal.regs, true);
}
} else { // Otherwise send to the queue
/* Send the transaction to the queue */
@ -837,14 +887,15 @@ esp_err_t parlio_rx_unit_receive(parlio_rx_unit_handle_t rx_unit,
ESP_RETURN_ON_FALSE(recv_cfg->delimiter->valid_sig_line_id >= rx_unit->cfg.data_width,
ESP_ERR_INVALID_ARG, TAG, "the valid_sig_line_id of this delimiter is conflict with rx unit data width");
/* Assign the signal here to ensure iram safe */
recv_cfg->delimiter->valid_sig = parlio_periph_signals.groups[rx_unit->group->group_id].
rx_units[rx_unit->unit_id].
recv_cfg->delimiter->valid_sig = parlio_periph_signals.groups[rx_unit->base.group->group_id].
rx_units[rx_unit->base.unit_id].
data_sigs[recv_cfg->delimiter->valid_sig_line_id];
}
void *p_buffer = payload;
/* Create the internal DMA buffer for the infinite transaction if indirect_mount is set */
if (recv_cfg->flags.is_infinite && recv_cfg->flags.indirect_mount) {
if (recv_cfg->flags.partial_rx_en && recv_cfg->flags.indirect_mount) {
ESP_RETURN_ON_FALSE(!rx_unit->dma_buf, ESP_ERR_INVALID_STATE, TAG, "infinite transaction is using the internal DMA buffer");
/* Allocate the internal DMA buffer to store the data temporary */
rx_unit->dma_buf = heap_caps_calloc(1, payload_size, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA);
ESP_RETURN_ON_FALSE(rx_unit->dma_buf, ESP_ERR_NO_MEM, TAG, "No memory for the internal DMA buffer");
@ -858,7 +909,7 @@ esp_err_t parlio_rx_unit_receive(parlio_rx_unit_handle_t rx_unit,
.payload = p_buffer,
.size = payload_size,
.recv_bytes = 0,
.flags.infinite = recv_cfg->flags.is_infinite,
.flags.infinite = recv_cfg->flags.partial_rx_en,
.flags.indirect_mount = recv_cfg->flags.indirect_mount,
};
rx_unit->usr_recv_buf = payload;
@ -873,29 +924,38 @@ esp_err_t parlio_rx_unit_wait_all_done(parlio_rx_unit_handle_t rx_unit, int time
{
ESP_RETURN_ON_FALSE(rx_unit, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
xSemaphoreTake(rx_unit->mutex, portMAX_DELAY);
TickType_t ticks = timeout_ms < 0 ? portMAX_DELAY : pdMS_TO_TICKS(timeout_ms);
/* Waiting for the all transaction done signal */
if (xSemaphoreTake(rx_unit->trans_sem, ticks) == pdFALSE) {
xSemaphoreGive(rx_unit->mutex);
return ESP_ERR_TIMEOUT;
}
/* Waiting for the all transactions done */
ESP_RETURN_ON_FALSE(xSemaphoreTake(rx_unit->trans_sem, ticks) == pdTRUE, ESP_ERR_TIMEOUT, TAG, "wait all transactions done timeout");
/* Put back the signal */
xSemaphoreGive(rx_unit->trans_sem);
xSemaphoreGive(rx_unit->mutex);
return ESP_OK;
}
esp_err_t parlio_rx_unit_register_event_callbacks(parlio_rx_unit_handle_t rx_unit, const parlio_rx_event_callbacks_t *cbs, void *user_data)
{
ESP_RETURN_ON_FALSE(rx_unit, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
ESP_RETURN_ON_FALSE(!rx_unit->is_enabled, ESP_ERR_INVALID_STATE, TAG, "the unit has enabled or running");
esp_err_t ret = ESP_OK;
ESP_RETURN_ON_FALSE(rx_unit, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
ESP_RETURN_ON_FALSE(cbs, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
#if CONFIG_PARLIO_ISR_IRAM_SAFE
ESP_RETURN_ON_FALSE(!cbs->on_partial_receive || esp_ptr_in_iram(cbs->on_partial_receive), ESP_ERR_INVALID_ARG,
TAG, "on_partial_receive not in IRAM");
ESP_RETURN_ON_FALSE(!cbs->on_receive_done || esp_ptr_in_iram(cbs->on_receive_done), ESP_ERR_INVALID_ARG,
TAG, "on_receive_done not in IRAM");
ESP_RETURN_ON_FALSE(!cbs->on_timeout || esp_ptr_in_iram(cbs->on_timeout), ESP_ERR_INVALID_ARG,
TAG, "on_timeout not in IRAM");
ESP_RETURN_ON_FALSE(!user_data || esp_ptr_internal(user_data), ESP_ERR_INVALID_ARG,
TAG, "user_data not in internal RAM");
#endif
xSemaphoreTake(rx_unit->mutex, portMAX_DELAY);
ESP_GOTO_ON_FALSE(!rx_unit->is_enabled, ESP_ERR_INVALID_STATE, err, TAG, "the unit has enabled or running");
memcpy(&rx_unit->cbs, cbs, sizeof(parlio_rx_event_callbacks_t));
rx_unit->user_data = user_data;
err:
xSemaphoreGive(rx_unit->mutex);
return ESP_OK;
return ret;
}

View File

@ -45,9 +45,7 @@ typedef struct {
} parlio_tx_trans_desc_t;
typedef struct parlio_tx_unit_t {
int unit_id; // unit id
parlio_dir_t dir;
parlio_group_t *group; // group handle
struct parlio_unit_t base; // base unit
size_t data_width; // data width
intr_handle_t intr; // allocated interrupt handle
esp_pm_lock_handle_t pm_lock; // power management lock
@ -120,9 +118,9 @@ static esp_err_t parlio_destroy_tx_unit(parlio_tx_unit_t *tx_unit)
vQueueDeleteWithCaps(tx_unit->trans_queues[i]);
}
}
if (tx_unit->group) {
if (tx_unit->base.group) {
// de-register from group
parlio_unregister_unit_from_group((parlio_unit_base_handle_t)tx_unit);
parlio_unregister_unit_from_group((parlio_unit_base_handle_t)&(tx_unit->base));
}
free(tx_unit->dma_nodes);
free(tx_unit);
@ -131,8 +129,8 @@ static esp_err_t parlio_destroy_tx_unit(parlio_tx_unit_t *tx_unit)
static esp_err_t parlio_tx_unit_configure_gpio(parlio_tx_unit_t *tx_unit, const parlio_tx_unit_config_t *config)
{
int group_id = tx_unit->group->group_id;
int unit_id = tx_unit->unit_id;
int group_id = tx_unit->base.group->group_id;
int unit_id = tx_unit->base.unit_id;
gpio_config_t gpio_conf = {
.intr_type = GPIO_INTR_DISABLE,
.mode = config->flags.io_loop_back ? GPIO_MODE_INPUT_OUTPUT : GPIO_MODE_OUTPUT,
@ -195,7 +193,7 @@ static esp_err_t parlio_tx_unit_init_dma(parlio_tx_unit_t *tx_unit)
static esp_err_t parlio_select_periph_clock(parlio_tx_unit_t *tx_unit, const parlio_tx_unit_config_t *config)
{
parlio_hal_context_t *hal = &tx_unit->group->hal;
parlio_hal_context_t *hal = &tx_unit->base.group->hal;
parlio_clock_source_t clk_src = config->clk_in_gpio_num >= 0 ? PARLIO_CLK_SRC_EXTERNAL : config->clk_src;
uint32_t periph_src_clk_hz = 0;
// if the source clock is input from the GPIO, then we're in the slave mode
@ -210,7 +208,7 @@ static esp_err_t parlio_select_periph_clock(parlio_tx_unit_t *tx_unit, const par
#if CONFIG_PM_ENABLE
if (clk_src != PARLIO_CLK_SRC_EXTERNAL) {
// XTAL and PLL clock source will be turned off in light sleep, so we need to create a NO_LIGHT_SLEEP lock
sprintf(tx_unit->pm_lock_name, "parlio_tx_%d_%d", tx_unit->group->group_id, tx_unit->unit_id); // e.g. parlio_tx_0_0
sprintf(tx_unit->pm_lock_name, "parlio_tx_%d_%d", tx_unit->base.group->group_id, tx_unit->base.unit_id); // e.g. parlio_tx_0_0
esp_err_t ret = esp_pm_lock_create(ESP_PM_NO_LIGHT_SLEEP, 0, tx_unit->pm_lock_name, &tx_unit->pm_lock);
ESP_RETURN_ON_ERROR(ret, TAG, "create NO_LIGHT_SLEEP lock failed");
}
@ -280,14 +278,14 @@ esp_err_t parlio_new_tx_unit(const parlio_tx_unit_config_t *config, parlio_tx_un
unit->dma_nodes[i].next = (i == dma_nodes_num - 1) ? NULL : &(unit->dma_nodes[i+1]);
}
unit->max_transfer_bits = config->max_transfer_size * 8;
unit->dir = PARLIO_DIR_TX;
unit->base.dir = PARLIO_DIR_TX;
unit->data_width = data_width;
//create transaction queue
ESP_GOTO_ON_ERROR(parlio_tx_create_trans_queue(unit, config), err, TAG, "create transaction queue failed");
// register the unit to a group
ESP_GOTO_ON_ERROR(parlio_register_unit_to_group((parlio_unit_base_handle_t)unit), err, TAG, "register unit to group failed");
parlio_group_t *group = unit->group;
ESP_GOTO_ON_ERROR(parlio_register_unit_to_group((parlio_unit_base_handle_t)&(unit->base)), err, TAG, "register unit to group failed");
parlio_group_t *group = unit->base.group;
parlio_hal_context_t *hal = &group->hal;
// select the clock source
ESP_GOTO_ON_ERROR(parlio_select_periph_clock(unit, config), err, TAG, "set clock source failed");
@ -346,7 +344,7 @@ esp_err_t parlio_new_tx_unit(const parlio_tx_unit_config_t *config, parlio_tx_un
// return TX unit handle
*ret_unit = unit;
ESP_LOGD(TAG, "new tx unit(%d,%d) at %p, out clk=%"PRIu32"Hz, queue_depth=%zu, idle_mask=%"PRIx32,
group->group_id, unit->unit_id, unit, unit->out_clk_freq_hz, unit->queue_depth, unit->idle_value_mask);
group->group_id, unit->base.unit_id, unit, unit->out_clk_freq_hz, unit->queue_depth, unit->idle_value_mask);
return ESP_OK;
err:
@ -360,7 +358,7 @@ esp_err_t parlio_del_tx_unit(parlio_tx_unit_handle_t unit)
{
ESP_RETURN_ON_FALSE(unit, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
ESP_RETURN_ON_FALSE(atomic_load(&unit->fsm) == PARLIO_TX_FSM_INIT, ESP_ERR_INVALID_STATE, TAG, "unit not in init state");
ESP_LOGD(TAG, "del tx unit(%d,%d)", unit->group->group_id, unit->unit_id);
ESP_LOGD(TAG, "del tx unit(%d,%d)", unit->base.group->group_id, unit->base.unit_id);
return parlio_destroy_tx_unit(unit);
}
@ -425,7 +423,7 @@ esp_err_t parlio_tx_unit_register_event_callbacks(parlio_tx_unit_handle_t tx_uni
static void IRAM_ATTR parlio_tx_do_transaction(parlio_tx_unit_t *tx_unit, parlio_tx_trans_desc_t *t)
{
parlio_hal_context_t *hal = &tx_unit->group->hal;
parlio_hal_context_t *hal = &tx_unit->base.group->hal;
tx_unit->cur_trans = t;
@ -458,7 +456,7 @@ esp_err_t parlio_tx_unit_enable(parlio_tx_unit_handle_t tx_unit)
if (tx_unit->pm_lock) {
esp_pm_lock_acquire(tx_unit->pm_lock);
}
parlio_hal_context_t *hal = &tx_unit->group->hal;
parlio_hal_context_t *hal = &tx_unit->base.group->hal;
parlio_ll_enable_interrupt(hal->regs, PARLIO_LL_EVENT_TX_EOF, true);
atomic_store(&tx_unit->fsm, PARLIO_TX_FSM_ENABLE);
} else {
@ -506,7 +504,7 @@ esp_err_t parlio_tx_unit_disable(parlio_tx_unit_handle_t tx_unit)
ESP_RETURN_ON_FALSE(valid_state, ESP_ERR_INVALID_STATE, TAG, "unit can't be disabled in state %d", expected_fsm);
// stop the TX engine
parlio_hal_context_t *hal = &tx_unit->group->hal;
parlio_hal_context_t *hal = &tx_unit->base.group->hal;
gdma_stop(tx_unit->dma_chan);
parlio_ll_tx_start(hal->regs, false);
parlio_ll_enable_interrupt(hal->regs, PARLIO_LL_EVENT_TX_EOF, false);
@ -569,7 +567,7 @@ esp_err_t parlio_tx_unit_transmit(parlio_tx_unit_handle_t tx_unit, const void *p
static void IRAM_ATTR parlio_tx_default_isr(void *args)
{
parlio_tx_unit_t *tx_unit = (parlio_tx_unit_t *)args;
parlio_group_t *group = tx_unit->group;
parlio_group_t *group = tx_unit->base.group;
parlio_hal_context_t *hal = &group->hal;
BaseType_t high_task_woken = pdFALSE;
bool need_yield = false;

View File

@ -10,5 +10,5 @@ endif()
# In order for the cases defined by `TEST_CASE` to be linked into the final elf,
# the component can be registered as WHOLE_ARCHIVE
idf_component_register(SRCS ${srcs}
PRIV_REQUIRES unity driver soc
PRIV_REQUIRES unity driver
WHOLE_ARCHIVE)

View File

@ -32,8 +32,10 @@
.max_recv_size = 10 * 1024, \
.data_width = 1, \
.clk_src = _clk_src, \
.clk_freq_hz = _clk_freq, \
.clk_gpio_num = _clk_src == PARLIO_CLK_SRC_EXTERNAL ? TEST_CLK_GPIO : -1, \
.ext_clk_freq_hz = _clk_src == PARLIO_CLK_SRC_EXTERNAL ? _clk_freq : 0, \
.clk_in_gpio_num = _clk_src == PARLIO_CLK_SRC_EXTERNAL ? TEST_CLK_GPIO : -1, \
.exp_clk_freq_hz = _clk_freq, \
.clk_out_gpio_num = -1, \
.valid_gpio_num = TEST_VALID_GPIO, \
.data_gpio_nums = { \
[0] = TEST_DATA0_GPIO, \
@ -48,52 +50,6 @@
#define TEST_TASK_DATA_READY_BIT 0x01
#define TEST_TASK_FINISHED_BIT 0x02
TEST_CASE("parallel_rx_unit_install_uninstall", "[parlio_rx]")
{
printf("install rx units exhaustively\r\n");
parlio_rx_unit_handle_t units[SOC_PARLIO_GROUPS * SOC_PARLIO_RX_UNITS_PER_GROUP];
int k = 0;
parlio_rx_unit_config_t config = TEST_DEFAULT_UNIT_CONFIG(PARLIO_CLK_SRC_DEFAULT, 1000000);
for (int i = 0; i < SOC_PARLIO_GROUPS; i++) {
for (int j = 0; j < SOC_PARLIO_RX_UNITS_PER_GROUP; j++) {
TEST_ESP_OK(parlio_new_rx_unit(&config, &units[k++]));
}
}
TEST_ESP_ERR(ESP_ERR_NOT_FOUND, parlio_new_rx_unit(&config, &units[0]));
for (int i = 0; i < k; i++) {
TEST_ESP_OK(parlio_del_rx_unit(units[i]));
}
// clock from external
config.clk_src = PARLIO_CLK_SRC_EXTERNAL;
// clock gpio must be set when the clock is input from external
TEST_ESP_ERR(ESP_ERR_INVALID_ARG, parlio_new_rx_unit(&config, &units[0]));
// clock from internal
config.clk_src = PARLIO_CLK_SRC_DEFAULT;
config.clk_gpio_num = TEST_CLK_GPIO;
#if SOC_PARLIO_RX_CLK_SUPPORT_OUTPUT
TEST_ESP_OK(parlio_new_rx_unit(&config, &units[0]));
TEST_ESP_OK(parlio_del_rx_unit(units[0]));
#else
// failed because of not support output the clock to a gpio
TEST_ESP_ERR(ESP_ERR_NOT_SUPPORTED, parlio_new_rx_unit(&config, &units[0]));
config.clk_gpio_num = -1;
#endif
config.data_width = 3;
// data width should be power of 2
TEST_ESP_ERR(ESP_ERR_INVALID_ARG, parlio_new_rx_unit(&config, &units[0]));
config.data_width = 4;
TEST_ESP_OK(parlio_new_rx_unit(&config, &units[0]));
TEST_ESP_OK(parlio_rx_unit_enable(units[0], true));
// delete unit before it's disabled is not allowed
TEST_ESP_ERR(ESP_ERR_INVALID_STATE, parlio_del_rx_unit(units[0]));
TEST_ESP_OK(parlio_rx_unit_disable(units[0]));
TEST_ESP_OK(parlio_del_rx_unit(units[0]));
}
typedef struct {
uint32_t partial_recv_cnt;
uint32_t recv_done_cnt;
@ -124,107 +80,6 @@ static bool test_parlio_rx_timeout_callback(parlio_rx_unit_handle_t rx_unit, con
return false;
}
#define TEST_PAYLOAD_SIZE 5000
// This test case uses soft delimiter
TEST_CASE("parallel_rx_unit_receive_transaction_test", "[parlio_rx]")
{
parlio_rx_unit_handle_t rx_unit = NULL;
parlio_rx_delimiter_handle_t deli = NULL;
parlio_rx_delimiter_handle_t timeout_deli = NULL;
parlio_rx_unit_config_t config = TEST_DEFAULT_UNIT_CONFIG(PARLIO_CLK_SRC_DEFAULT, 1000000);
TEST_ESP_OK(parlio_new_rx_unit(&config, &rx_unit));
parlio_rx_soft_delimiter_config_t sft_deli_cfg = {
.sample_edge = PARLIO_SAMPLE_EDGE_POS,
.eof_data_len = TEST_PAYLOAD_SIZE,
.timeout_ticks = 0,
};
TEST_ESP_OK(parlio_new_rx_delimiter(&sft_deli_cfg, &deli));
parlio_rx_level_delimiter_config_t lvl_deli_cfg = {
.valid_sig_line_id = TEST_VALID_SIG,
.sample_edge = PARLIO_SAMPLE_EDGE_POS,
.bit_pack_order = PARLIO_BIT_PACK_ORDER_MSB,
.eof_data_len = TEST_PAYLOAD_SIZE,
.timeout_ticks = 5,
.flags = {
.active_level = 1,
},
};
TEST_ESP_OK(parlio_new_rx_delimiter(&lvl_deli_cfg, &timeout_deli));
parlio_rx_event_callbacks_t cbs = {
.on_partial_receive = test_parlio_rx_partial_recv_callback,
.on_receive_done = test_parlio_rx_done_callback,
.on_timeout = test_parlio_rx_timeout_callback,
};
test_data_t test_data = {
.partial_recv_cnt = 0,
.recv_done_cnt = 0,
};
TEST_ESP_OK(parlio_rx_unit_register_event_callbacks(rx_unit, &cbs, &test_data));
TEST_ESP_OK(parlio_rx_unit_enable(rx_unit, true));
parlio_receive_config_t recv_config = {
.delimiter = deli,
.flags.is_infinite = false,
};
uint8_t *payload = heap_caps_calloc(1, TEST_PAYLOAD_SIZE, TEST_PARLIO_MEM_ALLOC_CAPS);
TEST_ASSERT(payload);
printf("Testing one normal transaction...\n");
TEST_ESP_OK(parlio_rx_soft_delimiter_start_stop(rx_unit, deli, true));
TEST_ESP_OK(parlio_rx_unit_receive(rx_unit, payload, TEST_PAYLOAD_SIZE, &recv_config));
TEST_ESP_OK(parlio_rx_unit_wait_all_done(rx_unit, 5000));
TEST_ESP_OK(parlio_rx_soft_delimiter_start_stop(rx_unit, deli, false));
TEST_ASSERT_EQUAL_UINT32(2, test_data.partial_recv_cnt);
TEST_ASSERT_EQUAL_UINT32(1, test_data.recv_done_cnt);
memset(&test_data, 0, sizeof(test_data_t));
printf("Testing normal transactions in queue...\n");
TEST_ESP_OK(parlio_rx_soft_delimiter_start_stop(rx_unit, deli, true));
// push 5 repeated transactions to the queue
for (int i = 0; i < 5; i++) {
TEST_ESP_OK(parlio_rx_unit_receive(rx_unit, payload, TEST_PAYLOAD_SIZE, &recv_config));
}
TEST_ESP_OK(parlio_rx_unit_wait_all_done(rx_unit, 5000));
TEST_ESP_OK(parlio_rx_soft_delimiter_start_stop(rx_unit, deli, false));
TEST_ASSERT_EQUAL_UINT32(10, test_data.partial_recv_cnt);
TEST_ASSERT_EQUAL_UINT32(5, test_data.recv_done_cnt);
memset(&test_data, 0, sizeof(test_data_t));
printf("Testing the infinite transaction...\n");
recv_config.flags.is_infinite = true;
TEST_ESP_OK(parlio_rx_soft_delimiter_start_stop(rx_unit, deli, true));
TEST_ESP_OK(parlio_rx_unit_receive(rx_unit, payload, TEST_PAYLOAD_SIZE, &recv_config));
// Won't receive done semaphore in infinite transaction
TEST_ESP_ERR(ESP_ERR_TIMEOUT, parlio_rx_unit_wait_all_done(rx_unit, 500));
TEST_ESP_OK(parlio_rx_soft_delimiter_start_stop(rx_unit, deli, false));
TEST_ASSERT_GREATER_THAN(6, test_data.partial_recv_cnt);
TEST_ASSERT_GREATER_THAN(3, test_data.recv_done_cnt);
memset(&test_data, 0, sizeof(test_data_t));
// printf("Testing the timeout callback...\n");
// recv_config.flags.is_infinite = false;
// recv_config.delimiter = timeout_deli;
// // push 5 repeated transactions to the queue
// for (int i = 0; i < 5; i++) {
// TEST_ESP_OK(parlio_rx_unit_receive(rx_unit, payload, TEST_PAYLOAD_SIZE, &recv_config));
// gpio_set_level(TEST_VALID_GPIO, 1);
// vTaskDelay(pdMS_TO_TICKS(100));
// gpio_set_level(TEST_VALID_GPIO, 0);
// vTaskDelay(pdMS_TO_TICKS(50));
// }
// TEST_ASSERT_TRUE(test_data.timeout_cnt);
TEST_ESP_OK(parlio_rx_unit_disable(rx_unit));
TEST_ESP_OK(parlio_del_rx_delimiter(deli));
TEST_ESP_OK(parlio_del_rx_delimiter(timeout_deli));
TEST_ESP_OK(parlio_del_rx_unit(rx_unit));
free(payload);
};
static void connect_signal_internally(uint32_t gpio, uint32_t sigo, uint32_t sigi)
{
gpio_config_t gpio_conf = {
@ -407,15 +262,12 @@ static void level_delimiter_sender_task_spi(void *args)
}
}
static bool test_delimiter(parlio_rx_delimiter_handle_t deli, void (*sender_task_thread)(void *args))
static bool test_delimiter(parlio_rx_delimiter_handle_t deli, bool free_running_clk, void (*sender_task_thread)(void *args))
{
parlio_rx_unit_handle_t rx_unit = NULL;
parlio_rx_unit_config_t config = TEST_DEFAULT_UNIT_CONFIG(PARLIO_CLK_SRC_EXTERNAL, 1000000);
if (sender_task_thread == pulse_delimiter_sender_task_i2s) {
// I2S offers free-running clock
config.flags.free_clk = 1;
}
config.flags.free_clk = free_running_clk;
TEST_ESP_OK(parlio_new_rx_unit(&config, &rx_unit));
TEST_ESP_OK(parlio_rx_unit_enable(rx_unit, true));
@ -431,7 +283,7 @@ static bool test_delimiter(parlio_rx_delimiter_handle_t deli, void (*sender_task
parlio_receive_config_t recv_config = {
.delimiter = deli,
.flags.is_infinite = false,
.flags.partial_rx_en = false,
};
uint8_t recv_buff[TEST_EOF_DATA_LEN];
bool is_success = false;
@ -479,12 +331,12 @@ TEST_CASE("parallel_rx_unit_level_delimiter_test_via_spi", "[parlio_rx]")
.eof_data_len = TEST_EOF_DATA_LEN,
.timeout_ticks = 0,
.flags = {
.active_level = 1,
.active_low_en = 0,
},
};
parlio_rx_delimiter_handle_t deli = NULL;
TEST_ESP_OK(parlio_new_rx_delimiter(&lvl_deli_cfg, &deli));
bool is_success = test_delimiter(deli, level_delimiter_sender_task_spi);
TEST_ESP_OK(parlio_new_rx_level_delimiter(&lvl_deli_cfg, &deli));
bool is_success = test_delimiter(deli, false, level_delimiter_sender_task_spi);
TEST_ESP_OK(parlio_del_rx_delimiter(deli));
TEST_ASSERT(is_success);
}
@ -506,8 +358,200 @@ TEST_CASE("parallel_rx_unit_pulse_delimiter_test_via_i2s", "[parlio_rx]")
},
};
parlio_rx_delimiter_handle_t deli = NULL;
TEST_ESP_OK(parlio_new_rx_delimiter(&pls_deli_cfg, &deli));
bool is_success = test_delimiter(deli, pulse_delimiter_sender_task_i2s);
TEST_ESP_OK(parlio_new_rx_pulse_delimiter(&pls_deli_cfg, &deli));
bool is_success = test_delimiter(deli, true, pulse_delimiter_sender_task_i2s);
TEST_ESP_OK(parlio_del_rx_delimiter(deli));
TEST_ASSERT(is_success);
}
TEST_CASE("parallel_rx_unit_install_uninstall", "[parlio_rx]")
{
printf("install rx units exhaustively\r\n");
parlio_rx_unit_handle_t units[SOC_PARLIO_GROUPS * SOC_PARLIO_RX_UNITS_PER_GROUP];
int k = 0;
parlio_rx_unit_config_t config = TEST_DEFAULT_UNIT_CONFIG(PARLIO_CLK_SRC_DEFAULT, 1000000);
for (int i = 0; i < SOC_PARLIO_GROUPS; i++) {
for (int j = 0; j < SOC_PARLIO_RX_UNITS_PER_GROUP; j++) {
TEST_ESP_OK(parlio_new_rx_unit(&config, &units[k++]));
}
}
TEST_ESP_ERR(ESP_ERR_NOT_FOUND, parlio_new_rx_unit(&config, &units[0]));
for (int i = 0; i < k; i++) {
TEST_ESP_OK(parlio_del_rx_unit(units[i]));
}
// clock from external
config.clk_src = PARLIO_CLK_SRC_EXTERNAL;
// clock gpio must be set when the clock is input from external
TEST_ESP_ERR(ESP_ERR_INVALID_ARG, parlio_new_rx_unit(&config, &units[0]));
// clock from internal
config.clk_src = PARLIO_CLK_SRC_DEFAULT;
config.clk_out_gpio_num = TEST_CLK_GPIO;
#if SOC_PARLIO_RX_CLK_SUPPORT_OUTPUT
TEST_ESP_OK(parlio_new_rx_unit(&config, &units[0]));
TEST_ESP_OK(parlio_del_rx_unit(units[0]));
#else
// failed because of not support output the clock to a gpio
TEST_ESP_ERR(ESP_ERR_NOT_SUPPORTED, parlio_new_rx_unit(&config, &units[0]));
config.clk_out_gpio_num = -1;
#endif
config.data_width = 3;
// data width should be power of 2
TEST_ESP_ERR(ESP_ERR_INVALID_ARG, parlio_new_rx_unit(&config, &units[0]));
config.data_width = 4;
TEST_ESP_OK(parlio_new_rx_unit(&config, &units[0]));
TEST_ESP_OK(parlio_rx_unit_enable(units[0], true));
// delete unit before it's disabled is not allowed
TEST_ESP_ERR(ESP_ERR_INVALID_STATE, parlio_del_rx_unit(units[0]));
TEST_ESP_OK(parlio_rx_unit_disable(units[0]));
TEST_ESP_OK(parlio_del_rx_unit(units[0]));
}
#define TEST_PAYLOAD_SIZE 5000
// This test case uses soft delimiter
TEST_CASE("parallel_rx_unit_receive_transaction_test", "[parlio_rx]")
{
parlio_rx_unit_handle_t rx_unit = NULL;
parlio_rx_delimiter_handle_t deli = NULL;
parlio_rx_unit_config_t config = TEST_DEFAULT_UNIT_CONFIG(PARLIO_CLK_SRC_DEFAULT, 1000000);
config.flags.free_clk = 1;
TEST_ESP_OK(parlio_new_rx_unit(&config, &rx_unit));
parlio_rx_soft_delimiter_config_t sft_deli_cfg = {
.sample_edge = PARLIO_SAMPLE_EDGE_POS,
.eof_data_len = TEST_PAYLOAD_SIZE,
.timeout_ticks = 0,
};
TEST_ESP_OK(parlio_new_rx_soft_delimiter(&sft_deli_cfg, &deli));
parlio_rx_event_callbacks_t cbs = {
.on_partial_receive = test_parlio_rx_partial_recv_callback,
.on_receive_done = test_parlio_rx_done_callback,
};
test_data_t test_data = {
.partial_recv_cnt = 0,
.recv_done_cnt = 0,
};
TEST_ESP_OK(parlio_rx_unit_register_event_callbacks(rx_unit, &cbs, &test_data));
TEST_ESP_OK(parlio_rx_unit_enable(rx_unit, true));
parlio_receive_config_t recv_config = {
.delimiter = deli,
.flags.partial_rx_en = false,
};
uint8_t *payload = heap_caps_calloc(1, TEST_PAYLOAD_SIZE, TEST_PARLIO_MEM_ALLOC_CAPS);
TEST_ASSERT(payload);
printf("Testing one normal transaction...\n");
TEST_ESP_OK(parlio_rx_soft_delimiter_start_stop(rx_unit, deli, true));
TEST_ESP_OK(parlio_rx_unit_receive(rx_unit, payload, TEST_PAYLOAD_SIZE, &recv_config));
TEST_ESP_OK(parlio_rx_unit_wait_all_done(rx_unit, 5000));
TEST_ESP_OK(parlio_rx_soft_delimiter_start_stop(rx_unit, deli, false));
TEST_ASSERT_EQUAL_UINT32(2, test_data.partial_recv_cnt);
TEST_ASSERT_EQUAL_UINT32(1, test_data.recv_done_cnt);
memset(&test_data, 0, sizeof(test_data_t));
printf("Testing normal transactions in queue...\n");
TEST_ESP_OK(parlio_rx_soft_delimiter_start_stop(rx_unit, deli, true));
// push 5 repeated transactions to the queue
for (int i = 0; i < 5; i++) {
TEST_ESP_OK(parlio_rx_unit_receive(rx_unit, payload, TEST_PAYLOAD_SIZE, &recv_config));
}
TEST_ESP_OK(parlio_rx_unit_wait_all_done(rx_unit, 5000));
TEST_ESP_OK(parlio_rx_soft_delimiter_start_stop(rx_unit, deli, false));
TEST_ASSERT_EQUAL_UINT32(10, test_data.partial_recv_cnt);
TEST_ASSERT_EQUAL_UINT32(5, test_data.recv_done_cnt);
memset(&test_data, 0, sizeof(test_data_t));
printf("Testing resume transactions in queue after enabling...\n");
TEST_ESP_OK(parlio_rx_soft_delimiter_start_stop(rx_unit, deli, true));
// push 5 repeated transactions to the queue
for (int i = 0; i < 10; i++) {
TEST_ESP_OK(parlio_rx_unit_receive(rx_unit, payload, TEST_PAYLOAD_SIZE, &recv_config));
}
TEST_ESP_OK(parlio_rx_unit_disable(rx_unit));
memset(&test_data, 0, sizeof(test_data_t));
TEST_ESP_OK(parlio_rx_unit_enable(rx_unit, false));
TEST_ESP_OK(parlio_rx_unit_wait_all_done(rx_unit, 5000));
TEST_ESP_OK(parlio_rx_soft_delimiter_start_stop(rx_unit, deli, false));
TEST_ASSERT_GREATER_THAN(2, test_data.partial_recv_cnt);
TEST_ASSERT_GREATER_THAN(1, test_data.recv_done_cnt);
memset(&test_data, 0, sizeof(test_data_t));
printf("Testing the infinite transaction...\n");
recv_config.flags.partial_rx_en = true;
TEST_ESP_OK(parlio_rx_soft_delimiter_start_stop(rx_unit, deli, true));
TEST_ESP_OK(parlio_rx_unit_receive(rx_unit, payload, TEST_PAYLOAD_SIZE, &recv_config));
// Won't receive done semaphore in infinite transaction
TEST_ESP_ERR(ESP_ERR_TIMEOUT, parlio_rx_unit_wait_all_done(rx_unit, 500));
TEST_ESP_OK(parlio_rx_soft_delimiter_start_stop(rx_unit, deli, false));
TEST_ASSERT_GREATER_THAN(6, test_data.partial_recv_cnt);
TEST_ASSERT_GREATER_THAN(3, test_data.recv_done_cnt);
memset(&test_data, 0, sizeof(test_data_t));
TEST_ESP_OK(parlio_rx_unit_disable(rx_unit));
TEST_ESP_OK(parlio_del_rx_delimiter(deli));
TEST_ESP_OK(parlio_del_rx_unit(rx_unit));
free(payload);
};
TEST_CASE("parallel_rx_unit_receive_timeout_test", "[parlio_rx]")
{
parlio_rx_unit_handle_t rx_unit = NULL;
parlio_rx_delimiter_handle_t timeout_deli = NULL;
parlio_rx_unit_config_t config = TEST_DEFAULT_UNIT_CONFIG(PARLIO_CLK_SRC_DEFAULT, 1000000);
config.flags.free_clk = 1;
config.flags.clk_gate_en = 1;
TEST_ESP_OK(parlio_new_rx_unit(&config, &rx_unit));
parlio_rx_level_delimiter_config_t lvl_deli_cfg = {
.valid_sig_line_id = TEST_VALID_SIG,
.sample_edge = PARLIO_SAMPLE_EDGE_POS,
.bit_pack_order = PARLIO_BIT_PACK_ORDER_MSB,
.eof_data_len = TEST_PAYLOAD_SIZE,
.timeout_ticks = 400,
.flags = {
.active_low_en = 0,
},
};
TEST_ESP_OK(parlio_new_rx_level_delimiter(&lvl_deli_cfg, &timeout_deli));
parlio_rx_event_callbacks_t cbs = {
.on_timeout = test_parlio_rx_timeout_callback,
};
test_data_t test_data = {
.timeout_cnt = 0,
};
TEST_ESP_OK(parlio_rx_unit_register_event_callbacks(rx_unit, &cbs, &test_data));
TEST_ESP_OK(parlio_rx_unit_enable(rx_unit, true));
parlio_receive_config_t recv_config = {
.delimiter = timeout_deli,
.flags.partial_rx_en = false,
};
uint8_t *payload = heap_caps_calloc(1, TEST_PAYLOAD_SIZE, TEST_PARLIO_MEM_ALLOC_CAPS);
TEST_ASSERT(payload);
printf("Testing the timeout callback...\n");
// push 5 repeated transactions to the queue
for (int i = 0; i < 5; i++) {
TEST_ESP_OK(parlio_rx_unit_receive(rx_unit, payload, TEST_PAYLOAD_SIZE, &recv_config));
gpio_set_level(TEST_VALID_GPIO, 1);
vTaskDelay(pdMS_TO_TICKS(10));
gpio_set_level(TEST_VALID_GPIO, 0);
vTaskDelay(pdMS_TO_TICKS(5));
printf("Transaction %d finished\n", i);
}
TEST_ASSERT_TRUE(test_data.timeout_cnt);
TEST_ESP_OK(parlio_rx_unit_disable(rx_unit));
TEST_ESP_OK(parlio_del_rx_delimiter(timeout_deli));
TEST_ESP_OK(parlio_del_rx_unit(rx_unit));
free(payload);
}

View File

@ -17,4 +17,4 @@ from pytest_embedded import Dut
indirect=True,
)
def test_parlio(dut: Dut) -> None:
dut.run_all_single_board_cases(reset=True)
dut.run_all_single_board_cases()

View File

@ -101,33 +101,6 @@ static inline void parlio_ll_rx_set_clock_source(parl_io_dev_t *dev, parlio_cloc
PCR.parl_clk_rx_conf.parl_clk_rx_sel = clk_sel;
}
/**
* @brief Get the clock source for the RX unit
*
* @param dev Parallel IO register base address
* @return
* parlio_clock_source_t RX core clock source
*/
static inline parlio_clock_source_t parlio_ll_rx_get_clock_source(parl_io_dev_t *dev)
{
(void)dev;
uint32_t clk_sel = PCR.parl_clk_rx_conf.parl_clk_rx_sel;
switch (clk_sel) {
case 0:
return PARLIO_CLK_SRC_XTAL;
case 1:
return PARLIO_CLK_SRC_PLL_F240M;
case 2:
return PARLIO_CLK_SRC_RC_FAST;
case 3:
return PARLIO_CLK_SRC_EXTERNAL;
default: // unsupported clock source
HAL_ASSERT(false);
break;
}
return PARLIO_CLK_SRC_DEFAULT;
}
/**
* @brief Set the clock divider for the RX unit
*
@ -209,13 +182,13 @@ static inline void parlio_ll_rx_set_recv_bit_len(parl_io_dev_t *dev, uint32_t bi
* @brief Set the sub mode of the level controlled receive mode
*
* @param dev Parallel IO register base address
* @param active_level Level of the external enable signal, true for active high, false for active low
* @param active_low_en Level of the external enable signal, true for active low, false for active high
*/
__attribute__((always_inline))
static inline void parlio_ll_rx_set_level_recv_mode(parl_io_dev_t *dev, bool active_level)
static inline void parlio_ll_rx_set_level_recv_mode(parl_io_dev_t *dev, bool active_low_en)
{
dev->rx_cfg0.rx_smp_mode_sel = 0;
dev->rx_cfg0.rx_level_submode_sel = !active_level; // 0: active low, 1: active high
dev->rx_cfg0.rx_level_submode_sel = active_low_en;
}
/**
@ -425,33 +398,6 @@ static inline void parlio_ll_tx_set_clock_source(parl_io_dev_t *dev, parlio_cloc
PCR.parl_clk_tx_conf.parl_clk_tx_sel = clk_sel;
}
/**
* @brief Get the clock source for the TX unit
*
* @param dev Parallel IO register base address
* @return
* parlio_clock_source_t TX core clock source
*/
static inline parlio_clock_source_t parlio_ll_tx_get_clock_source(parl_io_dev_t *dev)
{
(void)dev;
uint32_t clk_sel = PCR.parl_clk_tx_conf.parl_clk_tx_sel;
switch (clk_sel) {
case 0:
return PARLIO_CLK_SRC_XTAL;
case 1:
return PARLIO_CLK_SRC_PLL_F240M;
case 2:
return PARLIO_CLK_SRC_RC_FAST;
case 3:
return PARLIO_CLK_SRC_EXTERNAL;
default: // unsupported clock source
HAL_ASSERT(false);
break;
}
return PARLIO_CLK_SRC_DEFAULT;
}
/**
* @brief Set the clock divider for the TX unit
*

View File

@ -103,33 +103,6 @@ static inline void parlio_ll_rx_set_clock_source(parl_io_dev_t *dev, parlio_cloc
PCR.parl_clk_rx_conf.parl_clk_rx_sel = clk_sel;
}
/**
* @brief Get the clock source for the RX unit
*
* @param dev Parallel IO register base address
* @return
* parlio_clock_source_t RX core clock source
*/
static inline parlio_clock_source_t parlio_ll_rx_get_clock_source(parl_io_dev_t *dev)
{
(void)dev;
uint32_t clk_sel = PCR.parl_clk_rx_conf.parl_clk_rx_sel;
switch (clk_sel) {
case 0:
return PARLIO_CLK_SRC_XTAL;
case 1:
return PARLIO_CLK_SRC_PLL_F96M;
case 2:
return PARLIO_CLK_SRC_RC_FAST;
case 3:
return PARLIO_CLK_SRC_EXTERNAL;
default: // unsupported clock source
HAL_ASSERT(false);
break;
}
return PARLIO_CLK_SRC_DEFAULT;
}
/**
* @brief Set the clock divider for the RX unit
*
@ -210,13 +183,13 @@ static inline void parlio_ll_rx_set_recv_bit_len(parl_io_dev_t *dev, uint32_t bi
* @brief Set the sub mode of the level controlled receive mode
*
* @param dev Parallel IO register base address
* @param active_level Level of the external enable signal, true for active high, false for active low
* @param active_low_en Level of the external enable signal, true for active low, false for active high
*/
__attribute__((always_inline))
static inline void parlio_ll_rx_set_level_recv_mode(parl_io_dev_t *dev, bool active_level)
static inline void parlio_ll_rx_set_level_recv_mode(parl_io_dev_t *dev, bool active_low_en)
{
dev->rx_mode_cfg.rx_smp_mode_sel = 0;
dev->rx_mode_cfg.rx_ext_en_inv = !active_level; // 0: active low, 1: active high
dev->rx_mode_cfg.rx_ext_en_inv = active_low_en;
}
/**
@ -432,33 +405,6 @@ static inline void parlio_ll_tx_set_clock_source(parl_io_dev_t *dev, parlio_cloc
PCR.parl_clk_tx_conf.parl_clk_tx_sel = clk_sel;
}
/**
* @brief Get the clock source for the TX unit
*
* @param dev Parallel IO register base address
* @return
* parlio_clock_source_t TX core clock source
*/
static inline parlio_clock_source_t parlio_ll_tx_get_clock_source(parl_io_dev_t *dev)
{
(void)dev;
uint32_t clk_sel = PCR.parl_clk_tx_conf.parl_clk_tx_sel;
switch (clk_sel) {
case 0:
return PARLIO_CLK_SRC_XTAL;
case 1:
return PARLIO_CLK_SRC_PLL_F96M;
case 2:
return PARLIO_CLK_SRC_RC_FAST;
case 3:
return PARLIO_CLK_SRC_EXTERNAL;
default: // unsupported clock source
HAL_ASSERT(false);
break;
}
return PARLIO_CLK_SRC_DEFAULT;
}
/**
* @brief Set the clock divider for the TX unit
*

View File

@ -126,38 +126,6 @@ static inline void parlio_ll_rx_set_clock_source(parl_io_dev_t *dev, parlio_ll_c
/// the critical section needs to declare the __DECLARE_RCC_ATOMIC_ENV variable in advance
#define parlio_ll_rx_set_clock_source(...) (void)__DECLARE_RCC_ATOMIC_ENV; parlio_ll_rx_set_clock_source(__VA_ARGS__)
/**
* @brief Get the clock source for the RX unit
*
* @param dev Parallel IO register base address
* @return
* parlio_clock_source_t RX core clock source
*/
static inline parlio_ll_clock_source_t parlio_ll_rx_get_clock_source(parl_io_dev_t *dev)
{
(void)dev;
uint32_t clk_sel = HP_SYS_CLKRST.peri_clk_ctrl117.reg_parlio_rx_clk_src_sel;
switch (clk_sel) {
case 0:
return PARLIO_LL_CLK_SRC_XTAL;
case 1:
return PARLIO_LL_CLK_SRC_RC_FAST;
case 2:
return PARLIO_LL_CLK_SRC_PLL_F160M;
case 3:
return PARLIO_LL_CLK_SRC_PAD;
default: // unsupported clock source
HAL_ASSERT(false);
break;
}
return PARLIO_LL_CLK_SRC_XTAL;
}
/// use a macro to wrap the function, force the caller to use it in a critical section
/// the critical section needs to declare the __DECLARE_RCC_ATOMIC_ENV variable in advance
#define parlio_ll_rx_get_clock_source(...) (void)__DECLARE_RCC_ATOMIC_ENV; parlio_ll_rx_get_clock_source(__VA_ARGS__)
/**
* @brief Set the clock divider for the RX unit
*
@ -481,37 +449,6 @@ static inline void parlio_ll_tx_set_clock_source(parl_io_dev_t *dev, parlio_ll_c
/// the critical section needs to declare the __DECLARE_RCC_ATOMIC_ENV variable in advance
#define parlio_ll_tx_set_clock_source(...) (void)__DECLARE_RCC_ATOMIC_ENV; parlio_ll_tx_set_clock_source(__VA_ARGS__)
/**
* @brief Get the clock source for the TX unit
*
* @param dev Parallel IO register base address
* @return
* parlio_clock_source_t TX core clock source
*/
static inline parlio_ll_clock_source_t parlio_ll_tx_get_clock_source(parl_io_dev_t *dev)
{
(void)dev;
uint32_t clk_sel = HP_SYS_CLKRST.peri_clk_ctrl118.reg_parlio_tx_clk_src_sel;
switch (clk_sel) {
case 0:
return PARLIO_LL_CLK_SRC_XTAL;
case 1:
return PARLIO_LL_CLK_SRC_RC_FAST;
case 2:
return PARLIO_LL_CLK_SRC_PLL_F160M;
case 3:
return PARLIO_LL_CLK_SRC_PAD;
default: // unsupported clock source
HAL_ASSERT(false);
break;
}
return PARLIO_LL_CLK_SRC_XTAL;
}
/// use a macro to wrap the function, force the caller to use it in a critical section
/// the critical section needs to declare the __DECLARE_RCC_ATOMIC_ENV variable in advance
#define parlio_ll_tx_get_clock_source(...) (void)__DECLARE_RCC_ATOMIC_ENV; parlio_ll_tx_get_clock_source(__VA_ARGS__)
/**
* @brief Set the clock divider for the TX unit
*

View File

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@ -49,11 +49,6 @@ typedef enum {
PARLIO_BIT_PACK_ORDER_MSB, /*!< Bit pack order: MSB */
} parlio_bit_pack_order_t;
typedef enum {
PARIO_CLK_TYPE_FREE,
PARIO_CLK_TYPE_NON_FREE,
} parlio_clock_type_t;
#if SOC_PARLIO_SUPPORTED
/**
* @brief Parallel IO clock source

View File

@ -8,10 +8,6 @@ The Parallel IO peripheral is a general purpose parallel interface that can be u
The TX and RX driver of Parallel IO peripheral are designed separately, you can include ``driver/parlio_tx.h`` or ``driver/parlio_rx.h`` to use any of them.
Parlio RX Driver
----------------
Application Examples
--------------------

View File

@ -218,7 +218,13 @@ examples/peripherals/parlio:
temporary: true
reason: lack of runner
examples/peripherals/parlio/simple_rgb_led_matrix:
examples/peripherals/parlio/parlio_rx:
disable:
- if: SOC_PARLIO_SUPPORTED != 1 or IDF_TARGET == "esp32p4"
temporary: true
reason: not support esp32p4 yet (IDF-7471)
examples/peripherals/parlio/parlio_tx/simple_rgb_led_matrix:
disable:
- if: SOC_PARLIO_SUPPORTED != 1 or SOC_DEDICATED_GPIO_SUPPORTED != 1
disable_test:

View File

@ -0,0 +1,8 @@
# For more information about build system see
# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html
# The following five lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(logic_analyzer)

View File

@ -0,0 +1,229 @@
| Supported Targets | ESP32-C6 | ESP32-H2 |
| ----------------- | -------- | -------- |
# Logic Analyzer Example
(See the README.md file in the upper level 'examples' directory for more information about examples.)
This example demonstrates how to implement a logic analyzer via Parallel IO RX peripheral. The main implementation is extracted as `esp_probe` component for portability.
Parallel IO is a peripheral that can sample data on multiple GPIOs in parallel at a high rate, which in its basic form is exactly the functionality of a logic analyzer.
This example uses the "software delimiter mode" of the Parallel IO RX driver, so that the Parallel IO RX peripheral can be driven by the internal clock and keep sampling continuously.
## Example Overview
Under the default configuration, this example will probe the signals on several GPIOs, meanwhile these GPIOs will generate some signals by GPIO driver. The probed raw data will be stored temporary on the heap. When the allocated heap is run out or the probing time expires, the probing will stop and output the stored raw data to the output stream.
## How to Use Example
### Step 0: Requirement
#### Hardware
* A development board which has the parlio peripheral integrated, for supported chips see the table at the top
* A USB cable for power supply and programming
* Network connection if using TCP output stream
#### Extra Software on PC
* [PulseView](https://sigrok.org/wiki/Downloads) for visualizing the captured signal
### Step 1: Configure The Project
Run `idf.py menuconfig` under this example director to configure the example.
1. The `esp_probe` component can probe either internal signals or external signals, choose this configuration to select:
* "Select signal source": to select where the signals come from.
- "Probing the internal signals": the signals come from the chip itself (i.e., internal). The output signals on the GPIO will be feed back to the Parallel IO RX via GPIO matrix. This example only simulates some signals by GPIO driver, you can also modify the example code to probe the signals that produced by other peripherals (like I2C, I2S, SPI, etc).
- "Probing the External signals": the signals come from external source that connected to the probing GPIOs.
2. The `esp_probe` component support Flash or TCP stream to output the raw data, choose this configuration to select:
* "Select ESP probe dump stream": to select the output stream
- "Dump data into FLASH": the data will be dumped into the Flash, it requires you to allocate a FAT partition in the partition table. In this example, there is a `storage` partition for saving the probed raw data. Then we can use `esptool`to read this partition on the host side. Normally we read the whole `storage` partition, because we don't know the exact start address that the raw data saved.
- "Dump data to the host using TCP": the data will be dumped to the host directly via the TCP stream. Target should support connecting to the network via WiFi or Ethernet.
3. To change the probing GPIOs, modify the `s_probe_gpio` array in the `logic_analyzer_example_main.c`
#### Flash Stream
If we choose to dump into flash, please make sure the data partition label we set in the menuconfig is same as what we set in the `partition.csv`
* "The label of the data storage partition": sets the partition label that specified in `partition.csv` file. Defaults to `storage`.
#### TCP Stream
If we choose to dump via TCP, please specify the WiFi or Ethernet configurations in the menu "Example Connection Configuration", and then specify the TCP server information in "ESP probe configurations > Select ESP probe dump stream" menu:
* "TCP server IP address": the IP address that TCP server bound
* "TCP server port": the port the TCP server is listening on (default 8888)
### Step 2: Build And Flash
(If you're using TCP stream, please go to Step 3 first)
Build the project and flash it to the board, then run monitor tool to view serial output:
```bash
idf.py -p PORT build flash monitor
```
And then waiting until the main task finished
```
I (2625) main_task: Returned from app_main()
```
(To exit the serial monitor, type ``Ctrl-]``.)
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
### Step 3: Convert Raw Data into VCD Format
Before conversion, we need to know how many channels in the raw data. In this example, we probed 4 GPIOs that set in `s_probe_gpio`, so we need to specify the channel number for the conversion.
#### Flash Stream
1. Exit the serial monitor and use `esptool` to read the flash partition to the host:
For the default partition in this example:
```bash
parttool.py -p <serial_port> -b 406800 -f partitions.csv read_partition -h -n storage --output probe_raw.dat
```
2. Convert the raw data into VCD format:
(Run the following command under the example directory)
```bash
python components/esp_probe/host/vcd_dumper.py -n 4 -i probe_raw.dat -o probe_vcd_data.vcd
```
* `-n`: the channel number that probed
* `-i`: the the input raw data file
* `-o`: the output vcd file name (optional)
Then you can get the VCD file on the host.
**Note:** This example converted the whole partition into VCD file for demonstration, you can also adopt some methods to extract the saved file in this partitions like:
- Download the whole partition into the host and mount it as FS on host by Loop Device or RAM Disk;
- Download the whole partition into the host and parse the FAT Root Directory and find the data offset of the file `/esp_probe/probe_raw.dat`;
#### TCP Stream
1. Start the TCP server on the host:
(Run the following command under the example directory)
```bash
python components/esp_probe/host/tcp_server -n 4 -p 8888 -o probe_vcd_data.vcd
```
* `-n`: the channel number that probed
* `-p`: the TCP port (default 8888)
* `-o`: the output vcd file name (optional)
Then you can see the TCP server is on, log printed like:
```
TCP listening at 192.168.1.106:8888
```
2. Jump back to the Step 2 to flash and monitor the target
3. If the TCP client connected successfully, there will be data received, you can see the log like:
```
Client 192.168.1.183:49626 joined
data received 1440 bytes
data received 1440 bytes
data received 2880 bytes
...
Client 192.168.1.183:49626 left
```
Then you can get the VCD data on the host.
### Step 4: View The Signals on PulseView
Open PulseView and import the file `probe_vcd_data.vcd` with `Value Change Dump data` option:
![pulseview_image](./img/pulseview.jpeg)
#### Internal Signals That Simulated by GPIOs
![internal_gpio_signals](./img/gpio_sig.jpeg)
#### External I2S Philips Signals with I2S Decoder
![external_i2s_signals](./img/i2s_sig.jpeg)
## Further More Introduction on `esp_probe`
### Data Mode
#### Stream Mode
Stream mode will output the raw data via the output stream without storing. Stream mode can be selected by the helper macro `ESP_PROBE_DEFAULT_STREAM_CONFIG` (the probe GPIOs need to be set additionally).
* Advantage is that don't need extra storage to store the raw data.
* Disadvantage is that, due to the received data must finish sending before the new data receives, the sample rate is limited by the output stream bandwidth. Normally it can only achieve a low sample rate to avoid data lost.
#### Buffer Mode (Recommended)
Buffer mode store the raw data on the heap storage temporary before sending them. Buffer mode can be selected by the helper macro `ESP_PROBE_DEFAULT_BUFFER_CONFIG` (the probe GPIOs need to be set additionally).
* Advantage is that, it can support a high sample rate.
* Disadvantage is that, the sampling time is limited by the available heap size. Normally the available heap size is only several hundreds KB, which can only hold raw data for several ms when sampling with a high rate. But buffer mode is still recommended as it is more useful in most of cases.
#### Buffer Stream Mode
Buffer Stream mode store the raw data on the heap storage temporary but the storage will be used as Ping-Pong buffer to send the data in the storage. Buffer Stream mode can be selected by the helper macro `ESP_PROBE_DEFAULT_BUFFER_STREAM_CONFIG` (the probe GPIOs need to be set additionally).
* Advantage is that, the sampling time is not limited. Comparing to the Stream mode, it can reach a relative higher sample rate.
* Disadvantage is that, we still need to guarantee the output stream can catch the new data generation, otherwise data will be still be dropped.
### Output Streams
Currently `esp_probe` supports `Flash` and `TCP` stream.
#### Flash Stream
Requires a built-in Flash or external Flash and a data partition in FAT format.
* Advantages:
1. Support larger storage comparing to the heap;
2. ESP modules always have Flash on it, and no additional WiFi environment required;
* Disadvantages:
1. Low bandwidth. About several hundreds KB/s supported (e.g. 300+ KB/s on ESP32-H2);
2. Have to use `esptool` to read the raw data from the partition and then dump it into VCD format additionally;
3. This example convert the whole storage partition into VCD format, which includes some unrelated data. Or you have to get the file from the partition in some ways additionally.
#### TCP Stream
Requires WiFi or Ethernet supported on the target and accessible network.
* Advantages:
1. High bandwidth. Nearly 30 MB/s throughput supported, which allows a higher sample rate. For example, the sample rate can reach `25MB/s` on C6 with `Buffer Mode`, and 700+ KB/s with `Stream mode` without data dropped;
2. The TCP server can dump the raw data into VCD format directly without other operations;
* Disadvantages:
1. WiFi or Ethernet is required on the target;
2. Larger image size;
## Troubleshooting
* The TCP server will sometimes miss the client disconnection (i.e. no `Client 192.168.1.183:56208 left` log).
- Just quit the progress by `Ctrl + C` and wait for a while util the port is released
For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon.

View File

@ -0,0 +1,12 @@
set(src "hw_impl/esp_probe_impl_parlio.c" "esp_probe.c"
"stream/flash_fatfs.c" "stream/file_stream.c")
if(CONFIG_SOC_WIFI_SUPPORTED OR CONFIG_SOC_EMAC_SUPPORTED)
list(APPEND src "stream/tcp_stream.c")
endif()
idf_component_register(SRCS ${src}
INCLUDE_DIRS "./include"
PRIV_INCLUDE_DIRS "."
PRIV_REQUIRES driver hal fatfs esp_partition esp_netif
lwip)

View File

@ -0,0 +1,288 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "freertos/idf_additions.h"
#include "sdkconfig.h"
#include "esp_probe_private.h"
#include "esp_probe.h"
#include "esp_check.h"
#include "esp_cpu.h"
static const char *TAG = "esp_probe";
#define GET_US_BY_CCOUNT(t) ((float)(t)/CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ)
static void s_esp_probe_flush_task(void *args)
{
esp_probe_handle_t handle = (esp_probe_handle_t)args;
esp_probe_flush_data_t flush_data;
// Suspend if not start probing yet
if (!handle->flags.is_started) {
vTaskSuspend(NULL);
}
while (1) {
if (xQueueReceive(handle->flush_que, &flush_data, portMAX_DELAY) == pdTRUE) {
if (flush_data.size) {
fwrite(flush_data.data, flush_data.size, 1, handle->out_stream);
}
}
// If probe is stopped and no data to flush, suspend the task
if (!handle->flags.is_started && uxQueueSpacesAvailable(handle->flush_que) == 2) {
vTaskSuspend(NULL);
}
}
}
static void s_esp_probe_push_flush_queue(esp_probe_handle_t handle, void *data, uint32_t size, bool reset_ptr)
{
esp_probe_flush_data_t flush_data = {
.data = data,
.size = size,
};
TickType_t timeout_tick = 0;
if (!size) {
// If no data to flush but only inform the flush task to suspend
// then wait until data flushed
timeout_tick = portMAX_DELAY;
}
if (xQueueSend(handle->flush_que, &flush_data, timeout_tick) != pdTRUE) {
esp_probe_flush_data_t dummy;
xQueueReceive(handle->flush_que, &dummy, 0);
xQueueSend(handle->flush_que, &flush_data, 0);
ESP_LOGW(TAG, "data dropped");
}
if (reset_ptr) {
handle->w_ptr = 0;
handle->f_ptr = 0;
} else {
handle->f_ptr = handle->w_ptr; // Update the flush pointer to the current write pointer
}
}
static void s_esp_probe_dump_task(void *args)
{
esp_probe_handle_t handle = (esp_probe_handle_t)args;
esp_probe_recv_data_t recv_data;
bool need_suspend = false;
uint32_t byte_dump = 0;
// Suspend if not start probing yet
if (!handle->flags.is_started) {
vTaskSuspend(NULL);
}
while (1) {
// Receive the data that from data receive callback
if (xQueueReceive(handle->recv_que, &recv_data, portMAX_DELAY) == pdTRUE) {
if (handle->buf_size) { // Buffer mode, adopt ping-pong buffer strategy to flush data
if (unlikely(handle->w_ptr + recv_data.size > handle->max_dump_size)) {
// If the received in buffer has exceeded the max_dump_size but not reached the buffer size
// Only dump the rest piece of data
byte_dump = handle->max_dump_size - handle->w_ptr;
} else {
// If the rest buffer is not sufficient for the received data
// Flush the second half of the buffer
if (handle->w_ptr + recv_data.size > handle->buf_size) {
s_esp_probe_push_flush_queue(handle, handle->buffer + handle->f_ptr,
handle->w_ptr - handle->f_ptr, true);
}
byte_dump = recv_data.size;
}
memcpy(handle->buffer + handle->w_ptr, recv_data.data, byte_dump);
handle->w_ptr += byte_dump;
// If the last flush pointer is 0 and the stored data has exceed the half of the buffer
// Flush the first half of the buffer
// Here minus some extra bytes to trigger the flush more timely
if (!handle->f_ptr && handle->w_ptr >= handle->buf_size / 2 - 32) {
s_esp_probe_push_flush_queue(handle, handle->buffer, handle->w_ptr, false);
}
} else { // Stream mode, write directly
if (handle->dump_data_size + recv_data.size > handle->max_dump_size) {
byte_dump = handle->max_dump_size - handle->dump_data_size;
} else {
byte_dump = recv_data.size;
}
fwrite(recv_data.data, byte_dump, 1, handle->out_stream);
}
// Update the dump data size statistic
handle->dump_data_size += byte_dump;
}
uint32_t exceed_bytes = 0;
// If reach the max dump size, suspend the task
if (handle->dump_data_size >= handle->max_dump_size) {
ESP_LOGW(TAG, "Dump data size reached the max dump size");
need_suspend = true;
exceed_bytes = handle->dump_data_size - handle->max_dump_size;
}
// If probe is stopped, suspend the task
need_suspend |= !handle->flags.is_started;
if (need_suspend) {
// Flush the legacy data before suspend
if (handle->buf_size) {
s_esp_probe_push_flush_queue(handle, handle->buffer + handle->f_ptr,
handle->w_ptr - handle->f_ptr - exceed_bytes, true);
}
vTaskSuspend(NULL);
}
}
}
static void s_esp_probe_destroy_object(esp_probe_handle_t handle)
{
if (handle) {
if (handle->buffer) {
free(handle->buffer);
handle->buffer = NULL;
}
if (handle->recv_que) {
vQueueDeleteWithCaps(handle->recv_que);
handle->recv_que = NULL;
}
if (handle->dump_task) {
vTaskDelete(handle->dump_task);
handle->dump_task = NULL;
}
if (handle->flush_que) {
vQueueDeleteWithCaps(handle->flush_que);
handle->flush_que = NULL;
}
if (handle->flush_task) {
vTaskDelete(handle->flush_task);
handle->flush_task = NULL;
}
free(handle);
}
}
esp_err_t esp_new_probe(esp_probe_config_t *config, esp_probe_handle_t* ret_handle)
{
ESP_RETURN_ON_FALSE(config && ret_handle, ESP_ERR_INVALID_ARG, TAG, "input parameter is NULL");
// Get the max channel id and check whether any gpio specified
int max_chan_id = ESP_PROBE_MAX_CHANNEL_NUM - 1;
while (config->probe_gpio[max_chan_id] < 0) {
max_chan_id--;
ESP_RETURN_ON_FALSE(max_chan_id > 0, ESP_ERR_INVALID_ARG, TAG, "no gpio to probe");
}
// Create the probe object
esp_err_t ret = ESP_OK;
esp_probe_handle_t probe = heap_caps_calloc(1, sizeof(struct esp_probe_t), ESP_PROBE_ALLOC_CAPS);
ESP_RETURN_ON_FALSE(probe, ESP_ERR_NO_MEM, TAG, "no memory for probe object");
probe->max_dump_size = config->max_dump_size_kb ? (config->max_dump_size_kb << 10) : UINT32_MAX;
// Allocate the storage buffer if the data should be cached
if (config->storage_depth_kb) {
if (config->storage_depth_kb < 8) {
ESP_LOGW(TAG, "the storage depth is at least 8 KB, set to 8 KB instead");
probe->buf_size = 8192;
} else {
probe->buf_size = config->storage_depth_kb * 1024;
}
probe->buffer = calloc(1, probe->buf_size);
ESP_GOTO_ON_FALSE(probe->buffer, ESP_ERR_NO_MEM, err, TAG, "no memory for data storage");
// Create flush task
xTaskCreate(s_esp_probe_flush_task, "esp_probe_flush_task", 4096, probe,
(UBaseType_t)config->dump_task_priority, &(probe->flush_task));
// Create the flush queue
probe->flush_que = xQueueCreateWithCaps(2, sizeof(esp_probe_flush_data_t), MALLOC_CAP_DEFAULT);
ESP_GOTO_ON_FALSE(probe->flush_que, ESP_ERR_NO_MEM, err, TAG, "no memory for flush queue");
}
// Create the receive queue
probe->recv_que = xQueueCreateWithCaps(ESP_PROBE_DEFAULT_Q_DEPTH, sizeof(esp_probe_recv_data_t), ESP_PROBE_ALLOC_CAPS);
ESP_GOTO_ON_FALSE(probe->recv_que, ESP_ERR_NO_MEM, err, TAG, "no memory for receive queue");
// Create the dump task and suspend it
xTaskCreate(s_esp_probe_dump_task, "esp_probe_dump_task", 4096, probe,
(UBaseType_t)config->dump_task_priority, &(probe->dump_task));
// Initialize the hardware peripheral
ESP_GOTO_ON_ERROR(esp_probe_priv_init_hardware(probe, config, max_chan_id), err, TAG, "fail to init the hardware peripheral");
*ret_handle = probe;
return ret;
err:
s_esp_probe_destroy_object(probe);
probe = NULL;
return ret;
}
esp_err_t esp_del_probe(esp_probe_handle_t handle)
{
ESP_RETURN_ON_FALSE(handle, ESP_ERR_INVALID_ARG, TAG, "input parameter is NULL");
ESP_RETURN_ON_FALSE(!handle->flags.is_started, ESP_ERR_INVALID_STATE, TAG, "probe is still running");
esp_probe_priv_deinit_hardware(handle);
s_esp_probe_destroy_object(handle);
return ESP_OK;
}
esp_err_t esp_probe_start(esp_probe_handle_t handle, FILE *out_stream)
{
ESP_RETURN_ON_FALSE(handle, ESP_ERR_INVALID_ARG, TAG, "input parameter is NULL");
ESP_RETURN_ON_FALSE(!handle->flags.is_started, ESP_ERR_INVALID_STATE, TAG, "probe is still running");
handle->flags.is_started = true;
esp_err_t ret = ESP_OK;
ESP_GOTO_ON_ERROR(esp_probe_priv_enable_hardware(handle), err, TAG, "enable the hardware failed");
out_stream = out_stream ? out_stream : stdout;
if (handle->out_stream != out_stream) {
handle->out_stream = out_stream;
// io stream changed, reset timestamp and dump data size
handle->dump_data_size = 0;
}
if (handle->buf_size) {
// Reset the queue before resume the task
xQueueReset(handle->flush_que);
vTaskResume(handle->flush_task);
}
// Reset the queue before resume the task
xQueueReset(handle->recv_que);
vTaskResume(handle->dump_task);
return ret;
err:
handle->flags.is_started = false;
return ret;
}
esp_err_t esp_probe_stop(esp_probe_handle_t handle, uint32_t *dump_data_size)
{
ESP_RETURN_ON_FALSE(handle, ESP_ERR_INVALID_ARG, TAG, "input parameter is NULL");
if (!handle->flags.is_started) {
return ESP_OK;
}
// Set start flag to false
handle->flags.is_started = false;
// Waiting for the dump task be suspended after detecting the start flag is cleared
while (eTaskGetState(handle->dump_task) != eSuspended) {
vTaskDelay(1);
}
ESP_RETURN_ON_ERROR(esp_probe_priv_disable_hardware(handle), TAG, "disable the hardware failed");
// Waiting for the flush task to flush the legacy data
if (handle->buf_size) {
// Push an empty data to inform the flush task to suspend
s_esp_probe_push_flush_queue(handle, NULL, 0, true);
while (eTaskGetState(handle->flush_task) != eSuspended) {
vTaskDelay(1);
}
}
if (dump_data_size) {
*dump_data_size = handle->dump_data_size;
}
return ESP_OK;
}

View File

@ -0,0 +1,66 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "stdint.h"
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "esp_heap_caps.h"
#include "esp_probe.h"
#ifdef __cplusplus
extern "C" {
#endif
#define ESP_PROBE_DEFAULT_Q_DEPTH 8
#define ESP_PROBE_DEFAULT_MAX_RECV_SIZE (ESP_PROBE_DEFAULT_Q_DEPTH * 4092)
#if CONFIG_PARLIO_ISR_IRAM_SAFE
#define ESP_PROBE_ALLOC_CAPS (MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT)
#else
#define ESP_PROBE_ALLOC_CAPS MALLOC_CAP_DEFAULT
#endif
struct esp_probe_t {
uint32_t sample_width; /*!< sample width, i.e., enabled probe channel nums */
uint32_t sample_rate_hz; /*!< sample rate in Hz */
FILE *out_stream; /*!< Output stream */
TaskHandle_t dump_task; /*!< Task handle of the raw data dump task */
TaskHandle_t flush_task; /*!< Task handle of the raw data flush task, only created in buffer mode */
int dump_task_priority; /*!< Task priority */
uint32_t max_dump_size; /*!< Max dump size */
uint32_t dump_data_size; /*!< Dump data size */
QueueHandle_t recv_que; /*!< Receive data queue */
QueueHandle_t flush_que; /*!< Flush data queue */
uint8_t *buffer; /*!< The storage buffer for dump data */
uint32_t buf_size; /*!< The storage buffer size/depth */
uint32_t w_ptr; /*!< The pointer of the current write offset of the buffer */
uint32_t f_ptr; /*!< The pointer of the current flush offset of the buffer */
struct {
volatile uint32_t is_started: 1; /*!< Is the sampling started */
} flags;
};
typedef struct {
void *data; /*!< The pointer of the received raw data buffer */
uint32_t size; /*!< The size of the received raw data buffer */
} esp_probe_recv_data_t;
typedef esp_probe_recv_data_t esp_probe_flush_data_t;
esp_err_t esp_probe_priv_init_hardware(esp_probe_handle_t handle, esp_probe_config_t *config, int max_chan_id);
esp_err_t esp_probe_priv_deinit_hardware(esp_probe_handle_t handle);
esp_err_t esp_probe_priv_enable_hardware(esp_probe_handle_t handle);
esp_err_t esp_probe_priv_disable_hardware(esp_probe_handle_t handle);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,76 @@
# SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
import argparse
import socket
from io import TextIOWrapper
from typing import Any
from vcd_dumper import VCDDumper
def _get_local_host_ip() -> Any:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(('8.8.8.8', 80))
return s.getsockname()[0]
def _tcp_server(port:int, chan_num:int, vcd_out_io:TextIOWrapper) -> None:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
host = _get_local_host_ip()
s.bind((host, port))
s.listen(5)
print(f'TCP listening at {host}:{port}')
try:
while True:
c, addr = s.accept()
print(f'Client {addr[0]}:{addr[1]} joined')
vcd = VCDDumper()
vcd.open_new_vcd_file(chan_num, vcd_out_io)
while True:
recv_data = c.recv(16384)
if recv_data == b'':
break
print(f'data received {len(recv_data)} bytes')
vcd.dump_samples(recv_data)
print(f'Client {addr[0]}:{addr[1]} left')
vcd.close_vcd_file()
c.close()
finally:
vcd.close_vcd_file()
c.close()
s.shutdown(socket.SHUT_RDWR)
s.close()
print('TCP server closed')
def tcp_server_main() -> None:
# Args parser
parser = argparse.ArgumentParser(description='Dump raw data to VCD format')
parser.add_argument('-n', '--chan-num', type=int, help='The channel number that probed')
parser.add_argument('-p', '--port', type=int, help='The TCP port', default=8888)
parser.add_argument('-o', '--output-file', type=argparse.FileType('w'), help='The output vcd file name (optional)', default=None)
# Parse args
args = parser.parse_args()
chan_num = args.chan_num
port = args.port
vcd_out_io = args.output_file
if chan_num <= 0:
raise ValueError('Invalid channel number')
# Get the actual probe channel number (round up to the nearest number that is power of 2)
p = 0
while (1 << p) < chan_num:
p = p + 1
chan_num = 1 << p
_tcp_server(port, chan_num, vcd_out_io)
if __name__ == '__main__':
tcp_server_main()

View File

@ -0,0 +1,128 @@
# SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
# This dumper script run on the host after get the probed raw data on the host
# Usage: python vcd_dumper.py -n <probe_channel_number> -f <path_to_raw_data_file>
import argparse
import time
from io import FileIO, TextIOWrapper
class VCDDumper():
def __init__(self) -> None:
pass
def open_new_vcd_file(self, chan_num: int, vcd_out_io:TextIOWrapper) -> None:
self.ts = 0
self.odd_samples = 0
self.prev = 0
self.chan_num = chan_num
self.step = 1 if chan_num <= 8 else 2
# vcd file name
if vcd_out_io:
self.vcd = vcd_out_io
else:
dump_file = 'probe_{}.vcd'.format(time.strftime('%m-%d_%H-%M-%S'))
self.vcd = open(dump_file, 'w', encoding='utf-8')
# Write VCD header
self.vcd.write('$date today $end\n'
'$timescale 1 ns $end\n'
'$scope module logic_analyzer $end\n')
for i in range(chan_num):
self.vcd.write(f'$var wire 1 {chr(33 + i)} chan_{i} $end\n')
self.vcd.write('$upscope $end\n'
'$enddefinitions $end\n'
'$dumpvars\n')
for i in range(chan_num):
self.vcd.write(f'0{chr(33 + i)}\n')
self.vcd.write('$end\n')
def close_vcd_file(self) -> None:
if self.vcd:
self.vcd.close()
def _dump_one_sample_to_vcd(self, prev:int, curr:int) -> None:
change_mask = prev ^ curr
if change_mask == 0:
return
self.vcd.write(f'#{self.ts}\n')
i = 0
while change_mask != 0:
if change_mask & 1:
self.vcd.write(f'{(curr >> i) & 1}{chr(33 + i)}\n')
change_mask >>= 1
i += 1
def dump_samples(self, raw_data:bytes) -> None:
# Parse the raw data and write VCD body
byte_cnt = 0
# If last time is one byte left, load it
if self.odd_samples:
samples = self.odd_samples
self.odd_samples = 0
byte_cnt = 1
else:
samples = 0
# Loop the raw data and parse byte by byte
for byte in raw_data:
byte_cnt += 1
if byte_cnt % self.step == 0:
samples = (samples & 0xff00) | byte
self.odd_samples = 0
else:
samples = byte << 8
self.odd_samples = samples
continue
samples = byte
# Loop the samples in one byte
for _ in range((8 * self.step) // self.chan_num):
curr = samples & ((2 ** self.chan_num) - 1)
self._dump_one_sample_to_vcd(self.prev, curr)
samples >>= self.chan_num
self.prev = curr
self.ts += 1
def dump_samples_from_file(self, raw_in_io:FileIO) -> None:
data_bytes = raw_in_io.read()
self.dump_samples(data_bytes)
def dump_binary_raw_data_to_vcd(chan_num:int, raw_in_io:FileIO, vcd_out_io:TextIOWrapper) -> None:
vcd = VCDDumper()
vcd.open_new_vcd_file(chan_num, vcd_out_io)
vcd.dump_samples_from_file(raw_in_io)
vcd.close_vcd_file()
print('Converted successfully!')
def dump_vcd_main() -> None:
# Args parser
parser = argparse.ArgumentParser(description='Dump raw data to VCD format')
parser.add_argument('-n', '--chan-num', type=int, help='The channel number that probed')
parser.add_argument('-i', '--input-file', type=argparse.FileType('rb'), help='The the input raw data file')
parser.add_argument('-o', '--output-file', type=argparse.FileType('w'), help='The output vcd file name (optional)', default=None)
# Parse args
args = parser.parse_args()
chan_num = args.chan_num
raw_in_io = args.input_file
vcd_out_io = args.output_file
if chan_num <= 0:
raise ValueError('Invalid channel number')
# Get the actual probe channel number (round up to the nearest number that is power of 2)
p = 0
while (1 << p) < chan_num:
p = p + 1
chan_num = 1 << p
# Convert the data to the VCD format and save it into the file
dump_binary_raw_data_to_vcd(chan_num, raw_in_io, vcd_out_io)
if __name__ == '__main__':
dump_vcd_main()

View File

@ -0,0 +1,147 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <inttypes.h>
#include <string.h>
#include "sdkconfig.h"
#include "driver/parlio_rx.h"
#include "esp_clk_tree.h"
#include "esp_heap_caps.h"
#include "esp_check.h"
#include "esp_probe_private.h"
typedef struct {
parlio_rx_unit_handle_t rx_unit;
parlio_rx_delimiter_handle_t deli;
uint8_t *payload;
} esp_probe_impl_pralio_t;
static const char *TAG = "esp_probe_impl";
static esp_probe_impl_pralio_t *s_ephi = NULL;
static bool s_esp_probe_on_recv_callback(parlio_rx_unit_handle_t rx_unit, const parlio_rx_event_data_t *edata, void *user_data)
{
esp_probe_handle_t handle = (esp_probe_handle_t)user_data;
esp_probe_recv_data_t recv_data = {
.data = edata->data,
.size = edata->recv_bytes,
};
BaseType_t need_yield;
xQueueSendFromISR(handle->recv_que, &recv_data, &need_yield);
return need_yield == pdTRUE;
}
esp_err_t esp_probe_priv_init_hardware(esp_probe_handle_t handle, esp_probe_config_t *config, int max_chan_id)
{
ESP_RETURN_ON_FALSE(!s_ephi, ESP_ERR_INVALID_STATE, TAG, "parlio rx has initialized");
ESP_RETURN_ON_FALSE(max_chan_id <= PARLIO_RX_UNIT_MAX_DATA_WIDTH, ESP_ERR_NOT_SUPPORTED, TAG,
"The target can only support upto %d channels", (int)PARLIO_RX_UNIT_MAX_DATA_WIDTH);
esp_err_t ret = ESP_OK;
s_ephi = calloc(1, sizeof(esp_probe_impl_pralio_t));
ESP_RETURN_ON_FALSE(s_ephi, ESP_ERR_NO_MEM, TAG, "no memory for the esp probe hardware implementation");
s_ephi->payload = heap_caps_calloc(1, ESP_PROBE_DEFAULT_MAX_RECV_SIZE, ESP_PROBE_ALLOC_CAPS);
ESP_GOTO_ON_FALSE(s_ephi->payload, ESP_ERR_NO_MEM, err, TAG, "no memory for payload");
// Get the channel number, the channel number can only be the power of 2
uint32_t sample_width = 1;
while (sample_width < max_chan_id) {
sample_width <<= 1;
}
handle->sample_width = sample_width;
// Get the sample rate
uint32_t src_freq_hz = 0;
esp_clk_tree_src_get_freq_hz(PARLIO_CLK_SRC_DEFAULT, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &src_freq_hz);
uint32_t sample_rate_hz = config->sample_rate_hz ? config->sample_rate_hz : src_freq_hz;
if (sample_rate_hz > src_freq_hz) {
ESP_LOGW(TAG, "sample rate exceed the max value, limited to %"PRIu32, src_freq_hz);
sample_rate_hz = src_freq_hz;
}
handle->sample_rate_hz = sample_rate_hz;
// Allocate the parlio rx instance
parlio_rx_unit_config_t parlio_rx_cfg = {
.trans_queue_depth = ESP_PROBE_DEFAULT_Q_DEPTH,
.max_recv_size = ESP_PROBE_DEFAULT_MAX_RECV_SIZE,
.data_width = sample_width,
.clk_src = PARLIO_CLK_SRC_DEFAULT,
.ext_clk_freq_hz = 0, // Use the internal clock, no external clock needed
.clk_in_gpio_num = GPIO_NUM_NC, // Use the internal clock, no external clock input gpio needed
.exp_clk_freq_hz = sample_rate_hz, // Set expected clock frequency (i.e., sample rate)
.clk_out_gpio_num = GPIO_NUM_NC, // Use the internal clock for sampling and does not need to output
.valid_gpio_num = GPIO_NUM_NC, // Does not need valid gpio, all data gpio are used as sampling channel
.flags = {
.clk_gate_en = false,
.io_loop_back = true,
.io_no_init = true,
}
};
memcpy(parlio_rx_cfg.data_gpio_nums, config->probe_gpio, PARLIO_RX_UNIT_MAX_DATA_WIDTH * sizeof(gpio_num_t));
ESP_GOTO_ON_ERROR(parlio_new_rx_unit(&parlio_rx_cfg, &s_ephi->rx_unit), err, TAG, "Failed to allocate the parlio rx unit");
// Allocate the software delimiter
parlio_rx_soft_delimiter_config_t sft_deli_cfg = {
.sample_edge = PARLIO_SAMPLE_EDGE_POS,
.eof_data_len = 0xFFFF > ESP_PROBE_DEFAULT_MAX_RECV_SIZE ? ESP_PROBE_DEFAULT_MAX_RECV_SIZE : 0xFFFF,
.timeout_ticks = 0,
};
ESP_GOTO_ON_ERROR(parlio_new_rx_soft_delimiter(&sft_deli_cfg, &s_ephi->deli), err, TAG, "Failed to allocate the delimiter");
// Register the data receive callback
parlio_rx_event_callbacks_t cbs = {
.on_partial_receive = s_esp_probe_on_recv_callback,
};
ESP_GOTO_ON_ERROR(parlio_rx_unit_register_event_callbacks(s_ephi->rx_unit, &cbs, handle), err, TAG, "Failed to register the receive callback");
return ESP_OK;
err:
esp_probe_priv_deinit_hardware(handle);
return ret;
}
esp_err_t esp_probe_priv_deinit_hardware(esp_probe_handle_t handle)
{
(void)handle;
if (s_ephi) {
if (s_ephi->deli) {
ESP_RETURN_ON_ERROR(parlio_del_rx_delimiter(s_ephi->deli), TAG, "Failed to delete the parlio rx delimiter");
s_ephi->deli = NULL;
}
if (s_ephi->rx_unit) {
ESP_RETURN_ON_ERROR(parlio_del_rx_unit(s_ephi->rx_unit), TAG, "Failed to delete the parlio rx unit");
s_ephi->rx_unit = NULL;
}
if (s_ephi->payload) {
free(s_ephi->payload);
s_ephi->payload = NULL;
}
free(s_ephi);
s_ephi = NULL;
}
return ESP_OK;
}
esp_err_t esp_probe_priv_enable_hardware(esp_probe_handle_t handle)
{
(void)handle;
ESP_RETURN_ON_ERROR(parlio_rx_unit_enable(s_ephi->rx_unit, true), TAG, "Failed to enable the parlio rx unit");
ESP_RETURN_ON_ERROR(parlio_rx_soft_delimiter_start_stop(s_ephi->rx_unit, s_ephi->deli, true), TAG, "Failed to start the soft delimiter");
parlio_receive_config_t recv_cfg = {
.delimiter = s_ephi->deli,
.flags.partial_rx_en = true, // Infinite receiving, use callback to get received data
};
ESP_RETURN_ON_ERROR(parlio_rx_unit_receive(s_ephi->rx_unit, s_ephi->payload, ESP_PROBE_DEFAULT_MAX_RECV_SIZE, &recv_cfg), TAG, "Failed to receive data");
return ESP_OK;
}
esp_err_t esp_probe_priv_disable_hardware(esp_probe_handle_t handle)
{
(void)handle;
ESP_RETURN_ON_ERROR(parlio_rx_soft_delimiter_start_stop(s_ephi->rx_unit, s_ephi->deli, false), TAG, "Failed to stop the soft delimiter");
ESP_RETURN_ON_ERROR(parlio_rx_unit_disable(s_ephi->rx_unit), TAG, "Failed to disable the parlio rx unit");
return ESP_OK;
}

View File

@ -0,0 +1,147 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdio.h>
#include "driver/parlio_rx.h"
#ifdef __cplusplus
extern "C" {
#endif
#define ESP_PROBE_MAX_CHANNEL_NUM 16 /*!< Max supported probe channel number.
Note that not all targets can reach the max channel, for example, ESP32-H2 has only 8 channels */
#define ESP_PROBE_MAX_SAMPLE_RATE 0 /*!< The maximum sample rates are different among different targets,
Set 0 to adopt the maximum sample rate of the current target */
/**
* @brief ESP Probe stream mode default configuration
* @note The stream mode relies heavily on the bandwidth of the output stream
* Stream mode can only achieve a relatively low sample rate to guarantee no samples are dropped,
* But it can keep sampling continuously for a long time
*/
#define ESP_PROBE_DEFAULT_STREAM_CONFIG(rate_hz) { \
.sample_rate_hz = rate_hz, \
.dump_task_priority = 6, \
.storage_depth_kb = 0, \
.max_dump_size_kb = 0, \
.probe_gpio = { \
[0 ... ESP_PROBE_MAX_CHANNEL_NUM - 1] = -1, \
}, \
}
/**
* @brief ESP Probe buffer mode default configuration
* @note The buffer mode will store the data onto heap storage first,
* and output all of them to the stream in once.
* @note The probe will stop if run out of the given storage on heap.
* So that to guarantee a reliable data storing during the sampling period.
* However, the disadvantage is that, the probing time is limited by the available heap size,
* the higher the sample rate is, the shorter time the sampling last.
*/
#define ESP_PROBE_DEFAULT_BUFFER_CONFIG(rate_hz, depth_kb) { \
.sample_rate_hz = rate_hz, \
.dump_task_priority = 6, \
.storage_depth_kb = depth_kb, \
.max_dump_size_kb = depth_kb, \
.probe_gpio = { \
[0 ... ESP_PROBE_MAX_CHANNEL_NUM - 1] = -1, \
}, \
}
/**
* @brief ESP Probe buffer stream mode default configuration
* @note The buffer stream mode will store the data onto heap storage first,
* and output them to the stream every half of the storage
* @note Buffer stream mode means the probe will loop to use the given storage on heap.
* So that we can also sample the data continuously for a long time.
* However, while the data outputting rate can't catch the data producing rate,
* data will still be dropped.
* The difference comparing to the stream mode is that, it sends a larger block in every output,
* i.e., it can hold more reliable samples for a time
*/
#define ESP_PROBE_DEFAULT_BUFFER_STREAM_CONFIG(rate_hz, depth_kb) { \
.sample_rate_hz = rate_hz, \
.dump_task_priority = 6, \
.storage_depth_kb = depth_kb, \
.max_dump_size_kb = 0, \
.probe_gpio = { \
[0 ... ESP_PROBE_MAX_CHANNEL_NUM - 1] = -1, \
}, \
}
typedef struct {
uint32_t sample_rate_hz; /*!< The sample rate of the probe, unit: Hz, set '0' to use the max sample rate */
int probe_gpio[ESP_PROBE_MAX_CHANNEL_NUM]; /*!< The GPIO of each probe channel, please set '-1' for unused channels,
some targets like ESP32-H2 only support up to 8 channels */
uint32_t storage_depth_kb; /*!< The max heap storage depth for probed data (unit: kilobytes).
0: the probed data won't be stored but output in realtime, i.e. stream mode;
others: the probed data will be stored in a ping-pong buffer,
The ping-pong buffer will be flushed alternately, i.e. buffer mode.
ATTENTION: storage depth at lease be 8 KB, otherwise there is no difference with stream mode. */
uint32_t max_dump_size_kb; /*!< The max dump size for the limited storage (like flash/ram) (unit: kilobytes),
The probe will stop if the dump data size reach this value.
set 0 for no dump size limitation (like dumping to the host via UART/USB/Network),
set equals to `storage_depth_kb` to guarantee no sample lost in a short time */
uint32_t dump_task_priority; /*!< The priority of the dump task, which used for dumping the probed data via the out stream */
} esp_probe_config_t;
typedef struct esp_probe_t *esp_probe_handle_t; /*!< The handle of probe instance */
/**
* @brief Allocate a new probe instance
*
* @param[in] config The probe configuration
* @param[out] ret_handle The returned probe handle
* @return
* - ESP_OK Success to allocate the probe instance
* - ESP_NO_MEM No memory for probe instance
* - ESP_ERR_INVALID_ARG NULL pointer or invalid configuration
* - ESP_ERR_NOT_SUPPORTED Exceed the max supported channels
*/
esp_err_t esp_new_probe(esp_probe_config_t *config, esp_probe_handle_t* ret_handle);
/**
* @brief Delete the probe instance
*
* @param[in] handle The probe handle
* @return
* - ESP_OK Success to delete the probe instance
* - ESP_ERR_INVALID_ARG NULL pointer
* - ESP_ERR_INVALID_STATE The probe has not stopped yet
*/
esp_err_t esp_del_probe(esp_probe_handle_t handle);
/**
* @brief Start sampling on the probe channels
*
* @param[in] handle The probe handle
* @param[in] out_stream The output stream for the VCD data, set NULL will use 'stdout' by default
* @return
* - ESP_OK Success to start the sampling on the probe channels
* - ESP_ERR_INVALID_ARG NULL pointer
* - ESP_ERR_INVALID_STATE The probe has already started
*/
esp_err_t esp_probe_start(esp_probe_handle_t handle, FILE *out_stream);
/**
* @brief Stop sampling on the probe channels
* @note If the 'esp_probe_config_t::storage_depth_kb' is set (i.e. buffer mode),
* The data in the buffer will be popped to the out stream after this function is called
* @note This function is
*
* @param[in] handle The probe handle
* @param[out] dump_data_size The dumped data size, can be NULL if not needed
* @return
* - ESP_OK Success to stop the sampling on the probe channels
* - Others Failed to stop the sampling
*/
esp_err_t esp_probe_stop(esp_probe_handle_t handle, uint32_t *dump_data_size);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,74 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdio.h>
#include <stdint.h>
#include "soc/soc_caps.h"
#ifdef __cplusplus
extern "C" {
#endif
#if SOC_WIFI_SUPPORTED || SOC_EMAC_SUPPORTED
/**
* @brief Open the output stream to the host via TCP
*
* @param[in] host_ip The host IP address
* @param[in] port The host port
* @return
* - The the output stream pointer
*/
FILE* esp_probe_open_tcp_stream(const char *host_ip, int port);
/**
* @brief Close the output stream to the host
*
* @param[in] f The the output stream pointer
*/
void esp_probe_close_tcp_stream(FILE *f);
#endif // SOC_WIFI_SUPPORTED || SOC_EMAC_SUPPORTED
/**
* @brief Initialize the FAT partition in SPIFlash
*
* @param[in] mount_point The mount point
* @param[in] partition_label The label of the partition that used for saving the raw probed data
* @param[out] size The size of this partition, can be NULL if not needed
* @return
* - ESP_ERR_NOT_FOUND: Not found the FAT partition with this label
* - ESP_OK: Initialize FAT partition success
* - others: Initialize failed due to other reasons
*/
esp_err_t esp_probe_init_spiflash_fatfs(const char *mount_point, const char *partition_label, uint32_t *size);
/**
* @brief Deinitialize the FAT partition in SPIFlash
*
* @param[in] mount_point The mount point
*/
void esp_probe_deinit_spiflash_fatfs(const char *mount_point);
/**
* @brief Open the output stream to the FAT partition in flash
*
* @param[in] file_path The file path to open
* @return
* - The the output stream pointer
*/
FILE* esp_probe_open_file_stream(const char *file_path);
/**
* @brief Close the output stream to the FAT partition in flash
*
* @param[in] f The the output stream pointer
*/
void esp_probe_close_file_stream(FILE *f);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,31 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include "esp_check.h"
const static char *TAG = "file_stream";
FILE* esp_probe_open_file_stream(const char *file_path)
{
esp_err_t ret = ESP_FAIL;
// Create new raw data file
FILE *f = fopen(file_path, "wb");
ESP_GOTO_ON_FALSE(f, ESP_FAIL, err, TAG, "Failed to open file, error %s", strerror(errno));
ESP_LOGI(TAG, "Stream opened");
return f;
err:
ESP_LOGE(TAG, "Open Flash stream failed with error code %s", esp_err_to_name(ret));
return NULL;
}
void esp_probe_close_file_stream(FILE *f)
{
fclose(f);
}

View File

@ -0,0 +1,41 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "esp_vfs_fat.h"
#include "esp_partition.h"
#include "esp_check.h"
static const char *TAG = "flash_fat";
static wl_handle_t s_wlh = WL_INVALID_HANDLE;
esp_err_t esp_probe_init_spiflash_fatfs(const char *mount_point, const char *partition_label, uint32_t *size)
{
// Get the partition info
const esp_partition_t *part = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, partition_label);
ESP_RETURN_ON_FALSE(part, ESP_ERR_NOT_FOUND, TAG, "Failed to find the partition with label %s", partition_label);
ESP_LOGI(TAG, "Probe data partition base addr: 0x%lx size: 0x%lx", part->address, part->size);
if (size) {
*size = (uint32_t)part->size;
}
// Format and mount the partition of storage
esp_vfs_fat_mount_config_t mount_config = {
.max_files = 4,
.format_if_mount_failed = true,
.allocation_unit_size = CONFIG_WL_SECTOR_SIZE,
.disk_status_check_enable = false
};
ESP_RETURN_ON_ERROR(esp_vfs_fat_spiflash_mount_rw_wl(mount_point, partition_label, &mount_config, &s_wlh),
TAG, "Failed to mount FATFS");
ESP_LOGI(TAG, "flash FATFS mounted");
return ESP_OK;
}
void esp_probe_deinit_spiflash_fatfs(const char *mount_point)
{
esp_vfs_fat_spiflash_unmount_rw_wl(mount_point, s_wlh);
}

View File

@ -0,0 +1,84 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdint.h>
#include <sys/param.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "lwip/sockets.h"
#include "esp_check.h"
#define EXAMPLE_ESP_MAXIMUM_RETRY 5
static const char *TAG = "tcp_stream";
static int s_tcp_client_connect(const char *host_ip, int port)
{
int addr_family = 0;
int ip_protocol = 0;
struct sockaddr_in dest_addr = {};
dest_addr.sin_addr.s_addr = inet_addr(host_ip);
dest_addr.sin_family = AF_INET;
dest_addr.sin_port = htons(port);
addr_family = AF_INET;
ip_protocol = IPPROTO_IP;
int sock = socket(addr_family, SOCK_STREAM, ip_protocol);
if (sock < 0) {
ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);
goto _exit;
}
ESP_LOGI(TAG, "Socket created, connecting to %s:%d", host_ip, port);
int err = connect(sock, (struct sockaddr *)&dest_addr, sizeof(struct sockaddr_in6));
if (err != 0) {
ESP_LOGE(TAG, "Socket unable to connect: errno %d", errno);
goto _exit;
}
ESP_LOGI(TAG, "Successfully connected");
return sock;
_exit:
if (sock > 1) {
close(sock);
}
return -1;
}
FILE* esp_probe_open_tcp_stream(const char *host_ip, int port)
{
esp_err_t ret = ESP_FAIL;
// Create a TCP socket
int sock = s_tcp_client_connect(host_ip, port);
if (sock < 0) {
goto err;
}
// LWIP socket has been registered to the VFS, associate the TCP socket with the file descriptor for output stream
FILE *f = fdopen(sock, "wb");
ESP_GOTO_ON_FALSE(f, ESP_FAIL, err, TAG, "open tcp stream failed, error %s", strerror(errno));
return f;
err:
ESP_LOGE(TAG, "Open TCP stream failed with error code %s", esp_err_to_name(ret));
return NULL;
}
void esp_probe_close_tcp_stream(FILE *f)
{
// Get the socket by the file descriptor
int sock = fileno(f);
// Close the file stream
fclose(f);
// Shutdown explicitly to inform the server no further data will be sent
shutdown(sock, SHUT_RDWR);
// Close the socket
close(sock);
ESP_LOGI(TAG, "TCP stream closed");
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

View File

@ -0,0 +1,3 @@
idf_component_register(SRCS "logic_analyzer_example_main.c"
PRIV_REQUIRES esp_probe driver nvs_flash esp_netif protocol_examples_common
INCLUDE_DIRS ".")

View File

@ -0,0 +1,39 @@
menu "ESP probe configurations"
choice EXAMPLE_SIGNAL_SRC
prompt "Select signal source"
default EXAMPLE_INTERNAL_SIGNAL
config EXAMPLE_INTERNAL_SIGNAL
bool "Probing the internal signals"
config EXAMPLE_EXTERNAL_SIGNAL
bool "Probing the External signals"
endchoice
choice EXAMPLE_STREAM
prompt "Select ESP probe dump stream"
default EXAMPLE_FLASH_STREAM if !SOC_WIFI_SUPPORTED && !SOC_EMAC_SUPPORTED
default EXAMPLE_TCP_STREAM if SOC_WIFI_SUPPORTED || SOC_EMAC_SUPPORTED
help
Select the dump stream for the sampled data
config EXAMPLE_FLASH_STREAM
bool "Dump data into FLASH"
config EXAMPLE_TCP_STREAM
depends on SOC_WIFI_SUPPORTED || SOC_EMAC_SUPPORTED
bool "Dump data to the host using TCP"
endchoice
config EXAMPLE_HOST_IP_ADDR
depends on EXAMPLE_TCP_STREAM
default "192.168.1.100"
string "TCP server IP address"
config EXAMPLE_HOST_PORT
depends on EXAMPLE_TCP_STREAM
default 8888
int "TCP server port"
config EXAMPLE_PARTITION_LABEL
depends on EXAMPLE_FLASH_STREAM
default "storage"
string "The label of the data storage partition"
endmenu

View File

@ -0,0 +1,3 @@
dependencies:
protocol_examples_common:
path: ${IDF_PATH}/examples/common_components/protocol_examples_common

View File

@ -0,0 +1,132 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
#include <stdio.h>
#include <string.h>
#include <inttypes.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "esp_check.h"
#include "esp_probe.h"
#include "esp_probe_streams.h"
// Alias of the Kconfig options
#if CONFIG_EXAMPLE_TCP_STREAM
#include "nvs_flash.h"
#include "protocol_examples_common.h"
#include "esp_event.h"
#define EXAMPLE_HOST_IP_ADDR CONFIG_EXAMPLE_HOST_IP_ADDR // Host IP (string)
#define EXAMPLE_HOST_PORT CONFIG_EXAMPLE_HOST_PORT // Host port (int)
#elif CONFIG_EXAMPLE_FLASH_STREAM
#define EXAMPLE_MOUNT_POINT "/esp_probe"
#define EXAMPLE_DATA_FILE_PATH EXAMPLE_MOUNT_POINT"/probe_raw.dat"
#define EXAMPLE_PARTITION_LABEL CONFIG_EXAMPLE_PARTITION_LABEL // Flash partition label (string, see 'partitions.csv')
#endif
#define EXAMPLE_SAMPLE_RATE_HZ (8 * 1000 * 1000)
#define EXAMPLE_STORAGE_DEPTH_KB 128
// GPIOs to probe
const static int s_probe_gpio[] = {2, 3, 4, 5};
const static char *TAG = "example";
static esp_probe_handle_t s_probe = NULL;
#if CONFIG_EXAMPLE_INTERNAL_SIGNAL
void example_init_gpio_to_generate_internal_signal(void)
{
gpio_config_t gpio_cfg = {
.pin_bit_mask = BIT(s_probe_gpio[0]) |
BIT(s_probe_gpio[1]) |
BIT(s_probe_gpio[2]) |
BIT(s_probe_gpio[3]),
.mode = GPIO_MODE_INPUT_OUTPUT,
};
gpio_config(&gpio_cfg);
}
#endif
void example_probe_function(void)
{
#if CONFIG_EXAMPLE_INTERNAL_SIGNAL
// Simulate the internal signals
// Pulses might not spread averagely because the thread might be interrupted
for (int cnt = 0; cnt < 100; cnt++) {
for (int i = 0; i < 4; i++) {
gpio_set_level(s_probe_gpio[i], cnt & BIT(i));
}
vTaskDelay(pdMS_TO_TICKS(1));
}
#else // CONFIG_EXAMPLE_EXTERNAL_SIGNAL
// Probing the external signal here
vTaskDelay(pdMS_TO_TICKS(100));
#endif // CONFIG_EXAMPLE_INTERNAL_SIGNAL
}
FILE *example_probe_init(void)
{
FILE *f = NULL;
// Create dump stream
#if CONFIG_EXAMPLE_TCP_STREAM
ESP_ERROR_CHECK(nvs_flash_init());
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
ESP_ERROR_CHECK(example_connect());
f = esp_probe_open_tcp_stream(EXAMPLE_HOST_IP_ADDR, EXAMPLE_HOST_PORT);
#elif CONFIG_EXAMPLE_FLASH_STREAM
ESP_ERROR_CHECK(esp_probe_init_spiflash_fatfs(EXAMPLE_MOUNT_POINT, EXAMPLE_PARTITION_LABEL, NULL));
f = esp_probe_open_file_stream(EXAMPLE_DATA_FILE_PATH);
#endif
assert(f);
// Configure and allocate the ESP probe
esp_probe_config_t config = ESP_PROBE_DEFAULT_BUFFER_CONFIG(EXAMPLE_SAMPLE_RATE_HZ, EXAMPLE_STORAGE_DEPTH_KB);
// Set the GPIOs to be probed
memcpy(&config.probe_gpio, &s_probe_gpio, sizeof(s_probe_gpio));
ESP_ERROR_CHECK(esp_new_probe(&config, &s_probe));
return f;
}
void example_probe_signals(FILE *f, void (*probe_func)(void))
{
uint32_t dump_data_size = 0;
// Probe the signals during the function, the data will be dumped via the out stream
ESP_ERROR_CHECK(esp_probe_start(s_probe, f));
probe_func();
ESP_ERROR_CHECK(esp_probe_stop(s_probe, &dump_data_size));
ESP_LOGI(TAG, "Probe finished! %"PRIu32" (0x%"PRIx32") bytes dumped\n", dump_data_size, dump_data_size);
}
void example_probe_deinit(FILE *f)
{
// Delete the probe instance and free the resources
ESP_ERROR_CHECK(esp_del_probe(s_probe));
s_probe = NULL;
// Close the output stream
#if CONFIG_EXAMPLE_TCP_STREAM
esp_probe_close_tcp_stream(f);
example_disconnect();
#elif CONFIG_EXAMPLE_FLASH_STREAM
esp_probe_close_file_stream(f);
esp_probe_deinit_spiflash_fatfs(EXAMPLE_PARTITION_LABEL);
#endif
}
void app_main(void)
{
#if CONFIG_EXAMPLE_INTERNAL_SIGNAL
// If choose to probe the internal signals via IO MUX, init GPIOs for simulating the internal signals
example_init_gpio_to_generate_internal_signal();
#endif
// Initialize the probe, the probe needs an output stream to dump the raw data
FILE *f = example_probe_init();
// Use the probe as decorator, probe the signals that generated during this function
example_probe_signals(f, example_probe_function);
// Deinitialize the probe, close the output stream
example_probe_deinit(f);
}

View File

@ -0,0 +1,5 @@
# Name, Type, SubType, Offset, Size, Flags
# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
nvs, data, nvs, 0x9000, 0x6000,
factory, 0, 0, 0x10000, 1M,
storage, data, fat, , 528K,
1 # Name, Type, SubType, Offset, Size, Flags
2 # Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
3 nvs, data, nvs, 0x9000, 0x6000,
4 factory, 0, 0, 0x10000, 1M,
5 storage, data, fat, , 528K,

View File

@ -0,0 +1,6 @@
CONFIG_ESP_TASK_WDT_EN=n
CONFIG_FREERTOS_HZ=1000
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
CONFIG_FATFS_LFN_STACK=y
CONFIG_VFS_SUPPORT_IO=y