Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions esp_isotp/examples/echo/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# 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)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
set(COMPONENTS main)

project(esp_isotp_echo_example)
55 changes: 55 additions & 0 deletions esp_isotp/examples/echo/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# ISO-TP Echo Example

Simple ISO-TP echo service: receives data and sends it back. Supports single-frame and multi-frame transfers.

## How to Use Example

### Hardware Required

* An ESP32 development board
* A transceiver (e.g., TJA1050)
* An USB cable for power supply and programming

### Configuration

Use `idf.py menuconfig` to configure the example:

- **ISO-TP Echo Configuration → TWAI Basic Configuration**:
- TX GPIO Number (default: GPIO 5)
- RX GPIO Number (default: GPIO 4)
- TWAI Bitrate (default: 500000)

- **ISO-TP Echo Configuration → ISO-TP Configuration**:
- TX/RX Message IDs (default: 0x7E0/0x7E8)
- Buffer sizes

Connect the ESP32 to a CAN transceiver and the CAN bus.

### Build and Flash

Run `idf.py -p PORT flash monitor` to build, flash and monitor the project.

(To exit the serial monitor, type ``Ctrl-]``.)

See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects.

### Example Output

Once the application is running, you will see the following output:

```
I (xxx) isotp_echo: ISO-TP Echo Demo started
I (xxx) isotp_echo: ISO-TP echo example's TX ID: 0x7E0, RX ID: 0x7E8
```

To test the echo functionality, you can use a tool like `can-utils` on a Linux machine connected to the same CAN bus:

```bash
# Send a message and wait for the echo (using default IDs from Kconfig)
candump -tA -e -c -a vcan0 &
(isotprecv -s 0x7E0 -d 0x7E8 vcan0 | hexdump -C) & (echo 11 22 33 44 55 66 DE AD BE EF | isotpsend -s 0x7E0 -d 0x7E8 vcan0)
```

## Troubleshooting

For any technical queries, please open an [issue](https://github.com/espressif/idf-extra-components/issues) on GitHub. We will get back to you soon.
42 changes: 42 additions & 0 deletions esp_isotp/examples/echo/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Unlicense OR CC0-1.0

import pytest
import subprocess
import logging


@pytest.fixture(autouse=True, scope='session')
def setup_vcan_interface():
"""Ensure vcan0 interface is available for QEMU CAN testing."""
created_interface = False

try:
# Check if vcan0 exists
result = subprocess.run(['ip', 'link', 'show', 'vcan0'],
capture_output=True, text=True, check=False)
if result.returncode != 0:
logging.info("Creating vcan0 interface...")
# Try creating vcan0 interface
subprocess.run(['sudo', 'ip', 'link', 'add', 'dev', 'vcan0', 'type', 'vcan'],
capture_output=True, text=True, check=False)
created_interface = True

# Ensure it's up
subprocess.run(['sudo', 'ip', 'link', 'set', 'up', 'vcan0'],
capture_output=True, text=True, check=False)
logging.info("vcan0 interface ready")

except Exception as e:
logging.warning(f"Could not setup vcan0: {e}. QEMU will handle CAN interface setup.")

yield # Test execution happens here

# Cleanup: Remove vcan0 interface if we created it
if created_interface:
try:
subprocess.run(['sudo', 'ip', 'link', 'delete', 'vcan0'],
capture_output=True, text=True, check=False)
logging.info("vcan0 interface cleaned up")
except Exception as e:
logging.warning(f"Could not cleanup vcan0: {e}")
2 changes: 2 additions & 0 deletions esp_isotp/examples/echo/main/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
idf_component_register(SRCS "isotp_echo_main.c"
INCLUDE_DIRS ".")
91 changes: 91 additions & 0 deletions esp_isotp/examples/echo/main/Kconfig.projbuild
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
menu "ISO-TP Echo Configuration"
menu "TWAI Basic Configuration"
config EXAMPLE_TX_GPIO_NUM
int "TX GPIO Number"
default 5
range 0 48
help
GPIO pin number for TWAI transmission.

config EXAMPLE_RX_GPIO_NUM
int "RX GPIO Number"
default 4
range 0 48
help
GPIO pin number for TWAI reception.

config EXAMPLE_BITRATE
int "TWAI Bitrate"
default 500000
range 25000 1000000
help
TWAI bitrate in bits per second.
Common values: 125000, 250000, 500000, 1000000

config EXAMPLE_TWAI_TX_QUEUE_DEPTH
int "TWAI TX Queue Length"
default 16
range 1 64
help
Length of the TWAI transmit queue.

endmenu

menu "ISO-TP Configuration"
config EXAMPLE_ISOTP_TX_ID
hex "TX Message ID"
default 0x7E8
help
TWAI ID for transmitting ISO-TP messages.

config EXAMPLE_ISOTP_RX_ID
hex "RX Message ID"
default 0x7E0
help
TWAI ID for receiving ISO-TP messages.

config EXAMPLE_ISOTP_TX_BUFFER_SIZE
int "ISO-TP TX Buffer Size"
default 4096
range 256 8192
help
Size of the ISO-TP transmission buffer.

config EXAMPLE_ISOTP_RX_BUFFER_SIZE
int "ISO-TP RX Buffer Size"
default 4096
range 256 8192
help
Size of the ISO-TP reception buffer.

config EXAMPLE_ISOTP_TX_FRAME_POOL_SIZE
int "ISO-TP TX Frame Pool Size"
default 8
range 1 32
help
Number of TX frames in the ISO-TP transmission pool.
endmenu

menu "Task Configuration"
config EXAMPLE_ECHO_TASK_STACK_SIZE
int "Task Stack Size"
default 4096
range 2048 32768
help
Stack size for the echo task.

config EXAMPLE_ECHO_TASK_PRIORITY
int "Task Priority"
default 10
range 1 25
help
FreeRTOS priority for the echo task.

config EXAMPLE_ECHO_POLL_DELAY_MS
int "Poll Interval (ms)"
default 1
range 1 100
help
Delay between consecutive ISO-TP protocol polls.
endmenu
endmenu
4 changes: 4 additions & 0 deletions esp_isotp/examples/echo/main/idf_component.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
## IDF Component Manager Manifest File
dependencies:
esp_isotp:
override_path: '../../../'
130 changes: 130 additions & 0 deletions esp_isotp/examples/echo/main/isotp_echo_main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "esp_check.h"
#include "esp_twai.h"
#include "esp_twai_onchip.h"
#include "esp_isotp.h"

static const char *TAG = "isotp_echo";

// Global variables for cleanup
static esp_isotp_handle_t g_isotp_handle = NULL;
static twai_node_handle_t g_twai_node = NULL;
static esp_err_t isotp_echo_init(void);
static esp_err_t isotp_echo_deinit(void);

static void on_tx_done(esp_isotp_handle_t handle, uint32_t tx_size, void *user_arg)
{
ESP_EARLY_LOGI(TAG, "TX complete: %lu bytes", (unsigned long)tx_size);
}

static void on_rx_done(esp_isotp_handle_t handle, const uint8_t *data, uint32_t size, void *user_arg)
{
ESP_EARLY_LOGI(TAG, "RX complete: %lu bytes, echoing back...", (unsigned long)size);

// Check handle validity using ESP-IDF standard macro for ISR context
ESP_RETURN_VOID_ON_FALSE_ISR(handle, TAG, "Echo send failed: invalid handle");

// Echo back the received data immediately (ISR-safe API)
esp_err_t err = esp_isotp_send(handle, data, size);
if (unlikely(err != ESP_OK && err != ESP_ERR_NOT_FINISHED)) {
ESP_EARLY_LOGE(TAG, "Echo send failed: %s", esp_err_to_name(err));
}

}

void app_main(void)
{
ESP_LOGI(TAG, "ISO-TP Echo Demo started");

// Initialize the ISO-TP echo example
ESP_ERROR_CHECK(isotp_echo_init());

// Main task will just sleep, the echo_task handles the ISO-TP communication
while (1) {
vTaskDelay(pdMS_TO_TICKS(10000));
}

// Deinitialize the ISO-TP echo example
isotp_echo_deinit();
}

static void echo_task(void *arg)
{
esp_isotp_handle_t isotp_handle = (esp_isotp_handle_t)arg;

ESP_LOGI(TAG, "ISO-TP Echo task started");

while (1) {
// Poll ISO-TP protocol state machine (timeouts, consecutive frames, etc.)
ESP_ERROR_CHECK(esp_isotp_poll(isotp_handle));

// Small delay to ensure accurate STmin timing and prevent 100% CPU usage
vTaskDelay(pdMS_TO_TICKS(CONFIG_EXAMPLE_ECHO_POLL_DELAY_MS));
Copy link

Copilot AI Sep 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The configuration symbol CONFIG_EXAMPLE_ECHO_POLL_DELAY_MS is used but not defined in the provided Kconfig file. This should reference CONFIG_EXAMPLE_ECHO_POLL_DELAY_MS which is defined in Kconfig.projbuild.

Copilot uses AI. Check for mistakes.

}

ESP_LOGI(TAG, "ISO-TP Echo task finished");
vTaskDelete(NULL);
}

static esp_err_t isotp_echo_init(void)
{
twai_onchip_node_config_t twai_cfg = {
.io_cfg = {
.tx = CONFIG_EXAMPLE_TX_GPIO_NUM,
.rx = CONFIG_EXAMPLE_RX_GPIO_NUM,
},
.bit_timing.bitrate = CONFIG_EXAMPLE_BITRATE,
.tx_queue_depth = CONFIG_EXAMPLE_TWAI_TX_QUEUE_DEPTH,
.intr_priority = 0,
};

ESP_ERROR_CHECK(twai_new_node_onchip(&twai_cfg, &g_twai_node));

esp_isotp_config_t isotp_cfg = {
.tx_id = CONFIG_EXAMPLE_ISOTP_TX_ID,
.rx_id = CONFIG_EXAMPLE_ISOTP_RX_ID,
.tx_buffer_size = CONFIG_EXAMPLE_ISOTP_TX_BUFFER_SIZE,
.rx_buffer_size = CONFIG_EXAMPLE_ISOTP_RX_BUFFER_SIZE,
.tx_frame_pool_size = CONFIG_EXAMPLE_ISOTP_TX_FRAME_POOL_SIZE,
.rx_callback = on_rx_done,
.tx_callback = on_tx_done,
.callback_arg = NULL,
};

ESP_ERROR_CHECK(esp_isotp_new_transport(g_twai_node, &isotp_cfg, &g_isotp_handle));

// Create echo task
BaseType_t task_ret = xTaskCreate(echo_task, "isotp_echo", CONFIG_EXAMPLE_ECHO_TASK_STACK_SIZE,
g_isotp_handle, CONFIG_EXAMPLE_ECHO_TASK_PRIORITY, NULL);
ESP_RETURN_ON_FALSE(task_ret == pdPASS, ESP_FAIL, TAG, "Failed to create echo task");

ESP_LOGI(TAG, "ISO-TP echo example's TX ID: 0x%X, RX ID: 0x%X",
CONFIG_EXAMPLE_ISOTP_TX_ID, CONFIG_EXAMPLE_ISOTP_RX_ID);

return ESP_OK;
}

static esp_err_t isotp_echo_deinit(void)
{
ESP_RETURN_ON_FALSE(g_isotp_handle != NULL, ESP_OK, TAG, "ISO-TP echo example is not initialized");

esp_isotp_delete(g_isotp_handle);
g_isotp_handle = NULL;

if (g_twai_node) {
twai_node_delete(g_twai_node);
g_twai_node = NULL;
}

ESP_LOGI(TAG, "ISO-TP echo example deinitialized");
return ESP_OK;
}
Loading
Loading