feat(parlio_rx): add parlio rx examples
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
@@ -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:
|
||||
|
||||

|
||||
|
||||
#### Internal Signals That Simulated by GPIOs
|
||||
|
||||

|
||||
|
||||
#### External I2S Philips Signals with I2S Decoder
|
||||
|
||||

|
||||
|
||||
## 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.
|
||||
|
||||
+12
@@ -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)
|
||||
+288
@@ -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;
|
||||
}
|
||||
+66
@@ -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
|
||||
+76
@@ -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()
|
||||
+128
@@ -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()
|
||||
+147
@@ -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;
|
||||
}
|
||||
+147
@@ -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
|
||||
+74
@@ -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
|
||||
+31
@@ -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);
|
||||
}
|
||||
+41
@@ -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);
|
||||
}
|
||||
+84
@@ -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 |
@@ -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 ".")
|
||||
@@ -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
|
||||
@@ -0,0 +1,3 @@
|
||||
dependencies:
|
||||
protocol_examples_common:
|
||||
path: ${IDF_PATH}/examples/common_components/protocol_examples_common
|
||||
+132
@@ -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);
|
||||
}
|
||||
@@ -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,
|
||||
|
@@ -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
|
||||
Reference in New Issue
Block a user