Skip to content

Commit 77032d6

Browse files
committed
feat(isotp): add echo example for isotp.
1 parent 2aee564 commit 77032d6

File tree

10 files changed

+588
-0
lines changed

10 files changed

+588
-0
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# For more information about build system see
2+
# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html
3+
# The following five lines of boilerplate have to be in your project's
4+
# CMakeLists in this exact order for cmake to work correctly
5+
cmake_minimum_required(VERSION 3.16)
6+
7+
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
8+
9+
project(esp_isotp_echo_example)

esp_isotp/examples/echo/README.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# ISO-TP Echo Example
2+
3+
Simple ISO-TP echo service: receives data and sends it back. Supports single-frame and multi-frame transfers.
4+
5+
## How to Use Example
6+
7+
### Hardware Required
8+
9+
* An ESP32 development board
10+
* A transceiver (e.g., TJA1050)
11+
* An USB cable for power supply and programming
12+
13+
### Configuration
14+
15+
Use `idf.py menuconfig` to configure the example:
16+
17+
- **ISO-TP Echo Configuration → TWAI Basic Configuration**:
18+
- TX GPIO Number (default: GPIO 5)
19+
- RX GPIO Number (default: GPIO 4)
20+
- TWAI Bitrate (default: 500000)
21+
22+
- **ISO-TP Echo Configuration → ISO-TP Configuration**:
23+
- TX/RX Message IDs (default: 0x7E0/0x7E8)
24+
- Buffer sizes
25+
26+
Connect the ESP32 to a CAN transceiver and the CAN bus.
27+
28+
### Build and Flash
29+
30+
Run `idf.py -p PORT flash monitor` to build, flash and monitor the project.
31+
32+
(To exit the serial monitor, type ``Ctrl-]``.)
33+
34+
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.
35+
36+
### Example Output
37+
38+
Once the application is running, you will see the following output:
39+
40+
```
41+
I (xxx) isotp_echo: ISO-TP Echo Demo started
42+
I (xxx) isotp_echo: ISO-TP echo example's TX ID: 0x7E0, RX ID: 0x7E8
43+
```
44+
45+
To test the echo functionality, you can use a tool like `can-utils` on a Linux machine connected to the same CAN bus:
46+
47+
```bash
48+
# Send a message and wait for the echo (using default IDs from Kconfig)
49+
candump -tA -e -c -a vcan0 &
50+
(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)
51+
```
52+
53+
## Troubleshooting
54+
55+
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.
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
2+
# SPDX-License-Identifier: Unlicense OR CC0-1.0
3+
4+
import pytest
5+
import subprocess
6+
import logging
7+
8+
9+
@pytest.fixture(autouse=True, scope='session')
10+
def setup_vcan_interface():
11+
"""Ensure vcan0 interface is available for QEMU CAN testing."""
12+
created_interface = False
13+
14+
try:
15+
# Check if vcan0 exists
16+
result = subprocess.run(['ip', 'link', 'show', 'vcan0'],
17+
capture_output=True, text=True, check=False)
18+
if result.returncode != 0:
19+
logging.info("Creating vcan0 interface...")
20+
# Try creating vcan0 interface
21+
subprocess.run(['sudo', 'ip', 'link', 'add', 'dev', 'vcan0', 'type', 'vcan'],
22+
capture_output=True, text=True, check=False)
23+
created_interface = True
24+
25+
# Ensure it's up
26+
subprocess.run(['sudo', 'ip', 'link', 'set', 'up', 'vcan0'],
27+
capture_output=True, text=True, check=False)
28+
logging.info("vcan0 interface ready")
29+
30+
except Exception as e:
31+
logging.warning(f"Could not setup vcan0: {e}. QEMU will handle CAN interface setup.")
32+
33+
yield # Test execution happens here
34+
35+
# Cleanup: Remove vcan0 interface if we created it
36+
if created_interface:
37+
try:
38+
subprocess.run(['sudo', 'ip', 'link', 'delete', 'vcan0'],
39+
capture_output=True, text=True, check=False)
40+
logging.info("vcan0 interface cleaned up")
41+
except Exception as e:
42+
logging.warning(f"Could not cleanup vcan0: {e}")
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
idf_component_register(SRCS "isotp_echo_main.c"
2+
INCLUDE_DIRS ".")
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
menu "ISO-TP Echo Configuration"
2+
menu "TWAI Basic Configuration"
3+
config EXAMPLE_TX_GPIO_NUM
4+
int "TX GPIO Number"
5+
default 5
6+
range 0 48
7+
help
8+
GPIO pin number for TWAI transmission.
9+
10+
config EXAMPLE_RX_GPIO_NUM
11+
int "RX GPIO Number"
12+
default 4
13+
range 0 48
14+
help
15+
GPIO pin number for TWAI reception.
16+
17+
config EXAMPLE_BITRATE
18+
int "TWAI Bitrate"
19+
default 500000
20+
range 25000 1000000
21+
help
22+
TWAI bitrate in bits per second.
23+
Common values: 125000, 250000, 500000, 1000000
24+
25+
config EXAMPLE_TWAI_TX_QUEUE_DEPTH
26+
int "TWAI TX Queue Length"
27+
default 16
28+
range 1 64
29+
help
30+
Length of the TWAI transmit queue.
31+
32+
endmenu
33+
34+
menu "ISO-TP Configuration"
35+
config EXAMPLE_ISOTP_TX_ID
36+
hex "TX Message ID"
37+
default 0x7E8
38+
help
39+
TWAI ID for transmitting ISO-TP messages.
40+
41+
config EXAMPLE_ISOTP_RX_ID
42+
hex "RX Message ID"
43+
default 0x7E0
44+
help
45+
TWAI ID for receiving ISO-TP messages.
46+
47+
config EXAMPLE_ISOTP_TX_BUFFER_SIZE
48+
int "ISO-TP TX Buffer Size"
49+
default 4096
50+
range 256 8192
51+
help
52+
Size of the ISO-TP transmission buffer.
53+
54+
config EXAMPLE_ISOTP_RX_BUFFER_SIZE
55+
int "ISO-TP RX Buffer Size"
56+
default 4096
57+
range 256 8192
58+
help
59+
Size of the ISO-TP reception buffer.
60+
61+
config EXAMPLE_ISOTP_TX_FRAME_POOL_SIZE
62+
int "ISO-TP TX Frame Pool Size"
63+
default 8
64+
range 1 32
65+
help
66+
Number of TX frames in the ISO-TP transmission pool.
67+
endmenu
68+
69+
menu "Task Configuration"
70+
config EXAMPLE_ECHO_TASK_STACK_SIZE
71+
int "Task Stack Size"
72+
default 4096
73+
range 2048 32768
74+
help
75+
Stack size for the echo task.
76+
77+
config EXAMPLE_ECHO_TASK_PRIORITY
78+
int "Task Priority"
79+
default 10
80+
range 1 25
81+
help
82+
FreeRTOS priority for the echo task.
83+
84+
config EXAMPLE_ECHO_POLL_DELAY_MS
85+
int "Poll Interval (ms)"
86+
default 1
87+
range 1 100
88+
help
89+
Delay between consecutive ISO-TP protocol polls.
90+
endmenu
91+
endmenu
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
## IDF Component Manager Manifest File
2+
dependencies:
3+
esp_isotp:
4+
override_path: '../../../'
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
3+
*
4+
* SPDX-License-Identifier: Unlicense OR CC0-1.0
5+
*/
6+
7+
#include <stdio.h>
8+
#include "freertos/FreeRTOS.h"
9+
#include "freertos/task.h"
10+
#include "esp_log.h"
11+
#include "esp_check.h"
12+
#include "esp_twai.h"
13+
#include "esp_twai_onchip.h"
14+
#include "esp_isotp.h"
15+
16+
static const char *TAG = "isotp_echo";
17+
18+
// Global variables for cleanup
19+
static esp_isotp_handle_t g_isotp_handle = NULL;
20+
static twai_node_handle_t g_twai_node = NULL;
21+
static esp_err_t isotp_echo_init(void);
22+
static esp_err_t isotp_echo_deinit(void);
23+
24+
static void on_tx_done(esp_isotp_handle_t handle, uint32_t tx_size, void *user_arg)
25+
{
26+
ESP_EARLY_LOGI(TAG, "TX complete: %lu bytes", (unsigned long)tx_size);
27+
}
28+
29+
static void on_rx_done(esp_isotp_handle_t handle, const uint8_t *data, uint32_t size, void *user_arg)
30+
{
31+
ESP_EARLY_LOGI(TAG, "RX complete: %lu bytes, echoing back...", (unsigned long)size);
32+
33+
// Check handle validity using ESP-IDF standard macro for ISR context
34+
ESP_RETURN_VOID_ON_FALSE_ISR(handle, TAG, "Echo send failed: invalid handle");
35+
36+
// Echo back the received data immediately (ISR-safe API)
37+
esp_err_t err = esp_isotp_send(handle, data, size);
38+
if (unlikely(err != ESP_OK && err != ESP_ERR_NOT_FINISHED)) {
39+
ESP_EARLY_LOGE(TAG, "Echo send failed: %s", esp_err_to_name(err));
40+
}
41+
42+
}
43+
44+
void app_main(void)
45+
{
46+
ESP_LOGI(TAG, "ISO-TP Echo Demo started");
47+
48+
// Initialize the ISO-TP echo example
49+
ESP_ERROR_CHECK(isotp_echo_init());
50+
51+
// Main task will just sleep, the echo_task handles the ISO-TP communication
52+
while (1) {
53+
vTaskDelay(pdMS_TO_TICKS(10000));
54+
}
55+
56+
// Deinitialize the ISO-TP echo example
57+
isotp_echo_deinit();
58+
}
59+
60+
static void echo_task(void *arg)
61+
{
62+
esp_isotp_handle_t isotp_handle = (esp_isotp_handle_t)arg;
63+
64+
ESP_LOGI(TAG, "ISO-TP Echo task started");
65+
66+
while (1) {
67+
// Poll ISO-TP protocol state machine (timeouts, consecutive frames, etc.)
68+
ESP_ERROR_CHECK(esp_isotp_poll(isotp_handle));
69+
70+
// Small delay to ensure accurate STmin timing and prevent 100% CPU usage
71+
vTaskDelay(pdMS_TO_TICKS(CONFIG_EXAMPLE_ECHO_POLL_DELAY_MS));
72+
}
73+
74+
ESP_LOGI(TAG, "ISO-TP Echo task finished");
75+
vTaskDelete(NULL);
76+
}
77+
78+
static esp_err_t isotp_echo_init(void)
79+
{
80+
twai_onchip_node_config_t twai_cfg = {
81+
.io_cfg = {
82+
.tx = CONFIG_EXAMPLE_TX_GPIO_NUM,
83+
.rx = CONFIG_EXAMPLE_RX_GPIO_NUM,
84+
},
85+
.bit_timing.bitrate = CONFIG_EXAMPLE_BITRATE,
86+
.tx_queue_depth = CONFIG_EXAMPLE_TWAI_TX_QUEUE_DEPTH,
87+
.intr_priority = 0,
88+
};
89+
90+
ESP_ERROR_CHECK(twai_new_node_onchip(&twai_cfg, &g_twai_node));
91+
92+
esp_isotp_config_t isotp_cfg = {
93+
.tx_id = CONFIG_EXAMPLE_ISOTP_TX_ID,
94+
.rx_id = CONFIG_EXAMPLE_ISOTP_RX_ID,
95+
.tx_buffer_size = CONFIG_EXAMPLE_ISOTP_TX_BUFFER_SIZE,
96+
.rx_buffer_size = CONFIG_EXAMPLE_ISOTP_RX_BUFFER_SIZE,
97+
.tx_frame_pool_size = CONFIG_EXAMPLE_ISOTP_TX_FRAME_POOL_SIZE,
98+
.rx_callback = on_rx_done,
99+
.tx_callback = on_tx_done,
100+
.callback_arg = NULL,
101+
};
102+
103+
ESP_ERROR_CHECK(esp_isotp_new_transport(g_twai_node, &isotp_cfg, &g_isotp_handle));
104+
105+
// Create echo task
106+
BaseType_t task_ret = xTaskCreate(echo_task, "isotp_echo", CONFIG_EXAMPLE_ECHO_TASK_STACK_SIZE,
107+
g_isotp_handle, CONFIG_EXAMPLE_ECHO_TASK_PRIORITY, NULL);
108+
ESP_RETURN_ON_FALSE(task_ret == pdPASS, ESP_FAIL, TAG, "Failed to create echo task");
109+
110+
ESP_LOGI(TAG, "ISO-TP echo example's TX ID: 0x%X, RX ID: 0x%X",
111+
CONFIG_EXAMPLE_ISOTP_TX_ID, CONFIG_EXAMPLE_ISOTP_RX_ID);
112+
113+
return ESP_OK;
114+
}
115+
116+
static esp_err_t isotp_echo_deinit(void)
117+
{
118+
ESP_RETURN_ON_FALSE(g_isotp_handle != NULL, ESP_OK, TAG, "ISO-TP echo example is not initialized");
119+
120+
esp_isotp_delete(g_isotp_handle);
121+
g_isotp_handle = NULL;
122+
123+
if (g_twai_node) {
124+
twai_node_delete(g_twai_node);
125+
g_twai_node = NULL;
126+
}
127+
128+
ESP_LOGI(TAG, "ISO-TP echo example deinitialized");
129+
return ESP_OK;
130+
}

0 commit comments

Comments
 (0)