Skip to content

Commit 9a7585f

Browse files
committed
feat(isotp): add echo example for isotp, verify it's flow control parameters.
1 parent 11e8d9b commit 9a7585f

18 files changed

Lines changed: 933 additions & 0 deletions

‎.github/ISSUE_TEMPLATE/bug-report.yml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ body:
3939
- fmt
4040
- freetype
4141
- iqmath
42+
- isotp
4243
- jsmn
4344
- json_generator
4445
- json_parser

‎.github/workflows/upload_component.yml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ jobs:
5151
fmt;
5252
freetype;
5353
iqmath;
54+
isotp;
5455
jsmn;
5556
json_generator;
5657
json_parser;

‎.idf_build_apps.toml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ manifest_file = [
1313
"esp_serial_slave_link/.build-test-rules.yml",
1414
"expat/.build-test-rules.yml",
1515
"iqmath/.build-test-rules.yml",
16+
"isotp/.build-test-rules.yml",
1617
"jsmn/.build-test-rules.yml",
1718
"json_generator/.build-test-rules.yml",
1819
"json_parser/.build-test-rules.yml",

‎isotp/.build-test-rules.yml‎

Whitespace-only changes.

‎isotp/CMakeLists.txt‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Apply patch to isotp-c submodule
2+
find_package(Git REQUIRED)
3+
4+
execute_process(
5+
COMMAND ${GIT_EXECUTABLE} -C ${CMAKE_CURRENT_SOURCE_DIR}/isotp-c apply --ignore-whitespace ${CMAKE_CURRENT_SOURCE_DIR}/patch/isotp-c.patch
6+
RESULT_VARIABLE GIT_PATCH_RESULT
7+
)
8+
9+
if(NOT GIT_PATCH_RESULT EQUAL "0")
10+
message(WARNING "Failed to apply isotp-c patch! It's possible that the patch is already applied.")
11+
endif()
12+
13+
idf_component_register(
14+
SRCS "esp_isotp/esp_isotp.c" "isotp-c/isotp.c"
15+
INCLUDE_DIRS "esp_isotp" "." "isotp-c"
16+
REQUIRES driver esp_timer esp_driver_twai
17+
)

‎isotp/README.md‎

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# ISO-TP Protocol
2+
3+
![maintenance-status](https://img.shields.io/badge/maintenance-actively--developed-brightgreen.svg)
4+
5+
This component provides an implementation of the ISO 15765-2 (ISO-TP) protocol for the ESP-IDF framework, based on the `isotp-c` library. It allows for sending and receiving large data packets over the CAN bus.
6+
7+
## Features
8+
9+
- Full ISO-TP protocol support (single/multi-frame, flow control)
10+
- ESP-IDF TWAI driver integration
11+
- Large payload handling (up to 4095 bytes, limited by 12-bit length field)
12+
- Linux can-utils compatibility
13+
- QEMU testing support
14+
15+
## Add to project
16+
17+
Packages from this repository are uploaded to [Espressif's component service](https://components.espressif.com/).
18+
You can add them to your project via `idf.py add-dependancy`, e.g.
19+
```
20+
idf.py add-dependency espressif/isotp
21+
```
22+
23+
Alternatively, you can create `idf_component.yml`. More is in [Espressif's documentation](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/tools/idf-component-manager.html).
24+
25+
## Basic Usage
26+
27+
```c
28+
#include "esp_isotp.h"
29+
#include "esp_twai.h"
30+
#include "esp_twai_onchip.h"
31+
32+
// Create TWAI node and ISO-TP configuration
33+
twai_onchip_node_config_t twai_cfg = {
34+
.io_cfg = {.tx = GPIO_NUM_5, .rx = GPIO_NUM_4},
35+
.bit_timing = {.bitrate = 500000},
36+
.tx_queue_depth = 16,
37+
};
38+
twai_node_handle_t twai_node;
39+
twai_new_node_onchip(&twai_cfg, &twai_node);
40+
41+
QueueHandle_t rx_queue = xQueueCreate(16, sizeof(isotp_rx_queue_item_t));
42+
43+
esp_isotp_config_t config = {
44+
.tx_id = 0x7E0, .rx_id = 0x7E8,
45+
.tx_buffer_size = 4096, .rx_buffer_size = 4096,
46+
.twai_node = twai_node, .rx_queue = rx_queue,
47+
};
48+
esp_isotp_handle_t isotp_handle = esp_isotp_new(&config);
49+
50+
// Send/receive data
51+
uint8_t data[] = {0x22, 0xF1, 0x90};
52+
esp_isotp_send(isotp_handle, data, sizeof(data));
53+
54+
esp_isotp_poll(isotp_handle);
55+
uint8_t rx_buffer[4096];
56+
int len = esp_isotp_receive(isotp_handle, rx_buffer, sizeof(rx_buffer));
57+
```
58+
59+
## Configuration
60+
61+
The component uses automatic patch application during build to customize the upstream `isotp-c` library. Current patches:
62+
- **Frame padding disabled**: `ISO_TP_FRAME_PADDING` is commented out to optimize payload usage
63+
- **Response timeout**: Default is 100ms via `ISO_TP_DEFAULT_RESPONSE_TIMEOUT`
64+
65+
## Troubleshooting
66+
67+
- **Multi-instance conflicts**: The underlying `isotp-c` library is designed as a singleton. Avoid creating multiple `esp_isotp` instances simultaneously.
68+
- **Large frame timeouts**: If you experience timeouts with large data frames, check the `ISO_TP_DEFAULT_RESPONSE_TIMEOUT` configuration in `isotp-c/isotp_config.h` (modified via patch during build) and ensure your network has sufficient bandwidth.
69+
- **QEMU test failures**: Ensure the `vcan0` interface is available on your system and that the QEMU path and arguments are correct.
70+
- **Patch application failures**: If you see warnings about patch application during build, the patch may already be applied. This is normal on subsequent builds.
71+
72+
For any technical queries, please open an [issue](https://github.com/espressif/idf-extra-components/issues) on GitHub.

‎isotp/esp_isotp/CMakeLists.txt‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
idf_component_register(SRCS "esp_isotp.c"
2+
INCLUDE_DIRS "."
3+
REQUIRES isotp driver)

‎isotp/esp_isotp/esp_isotp.c‎

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
#include "freertos/FreeRTOS.h"
2+
#include "freertos/queue.h"
3+
#include "esp_log.h"
4+
#include "esp_timer.h"
5+
#include "esp_twai.h"
6+
#include "esp_twai_types.h"
7+
#include "esp_check.h"
8+
#include "esp_isotp.h"
9+
#include <stdarg.h>
10+
#include <stdlib.h>
11+
#include <string.h>
12+
13+
static const char *TAG = "esp_isotp";
14+
static esp_isotp_handle_t s_isotp_handle = NULL;
15+
16+
/**
17+
* @brief TWAI receive callback function
18+
* @note This function runs in ISR context
19+
*/
20+
static IRAM_ATTR bool esp_isotp_rx_callback(twai_node_handle_t handle, const twai_rx_done_event_data_t *edata, void *user_ctx)
21+
{
22+
esp_isotp_handle_t link_handle = (esp_isotp_handle_t) user_ctx;
23+
24+
ESP_RETURN_ON_FALSE(link_handle != NULL, false, TAG, "Invalid link handle");
25+
ESP_RETURN_ON_FALSE(link_handle->rx_queue != NULL, false, TAG, "Invalid RX queue");
26+
27+
BaseType_t higher_priority_task_woken = pdFALSE;
28+
isotp_rx_queue_item_t queue_item = {0};
29+
twai_frame_t rx_frame = {
30+
.buffer = queue_item.data,
31+
.buffer_len = sizeof(queue_item.data),
32+
};
33+
34+
if (twai_node_receive_from_isr(handle, &rx_frame) == ESP_OK) {
35+
if (rx_frame.header.id == link_handle->link.receive_arbitration_id) {
36+
queue_item.header = rx_frame.header;
37+
queue_item.data_len = rx_frame.buffer_len;
38+
xQueueSendFromISR(link_handle->rx_queue, &queue_item, &higher_priority_task_woken);
39+
}
40+
}
41+
return (higher_priority_task_woken == pdTRUE);
42+
}
43+
44+
/**
45+
* @brief Get current timestamp in milliseconds
46+
*/
47+
uint32_t isotp_user_get_ms(void)
48+
{
49+
return (uint32_t)(esp_timer_get_time() / 1000);
50+
}
51+
52+
/**
53+
* @brief Send CAN frame through TWAI driver
54+
*/
55+
int isotp_user_send_can(const uint32_t arbitration_id, const uint8_t *data, const uint8_t size)
56+
{
57+
ESP_RETURN_ON_FALSE(s_isotp_handle != NULL, ISOTP_RET_ERROR, TAG, "Invalid handle");
58+
59+
twai_frame_t tx_msg = {0};
60+
tx_msg.header.id = arbitration_id;
61+
tx_msg.header.ide = false;
62+
tx_msg.header.rtr = false;
63+
tx_msg.buffer = (uint8_t *)data;
64+
tx_msg.buffer_len = size;
65+
66+
esp_err_t ret = twai_node_transmit(s_isotp_handle->twai_node, &tx_msg, 0);
67+
if (ret != ESP_OK) {
68+
ESP_LOGE(TAG, "Failed to send CAN frame: %s", esp_err_to_name(ret));
69+
return ISOTP_RET_ERROR;
70+
}
71+
return ISOTP_RET_OK;
72+
}
73+
74+
/**
75+
* @brief Debug output function for ISO-TP library
76+
*/
77+
void isotp_user_debug(const char *message, ...)
78+
{
79+
va_list args;
80+
va_start(args, message);
81+
esp_log_writev(ESP_LOG_DEBUG, "isotp_c", message, args);
82+
va_end(args);
83+
}
84+
85+
esp_isotp_handle_t esp_isotp_new(const esp_isotp_config_t *config)
86+
{
87+
esp_err_t ret = ESP_OK;
88+
ESP_RETURN_ON_FALSE(config != NULL, NULL, TAG, "Invalid configuration");
89+
ESP_RETURN_ON_FALSE(config->twai_node != NULL, NULL, TAG, "Invalid TWAI node");
90+
91+
// Allocate memory for handle
92+
esp_isotp_handle_t handle = calloc(1, sizeof(struct esp_isotp_link));
93+
ESP_RETURN_ON_FALSE(handle != NULL, NULL, TAG, "Failed to allocate memory for ISO-TP link");
94+
s_isotp_handle = handle;
95+
96+
handle->rx_queue = config->rx_queue;
97+
handle->twai_node = config->twai_node;
98+
99+
handle->tx_buffer = calloc(config->tx_buffer_size, sizeof(uint8_t));
100+
ESP_GOTO_ON_FALSE(handle->tx_buffer, ESP_ERR_NO_MEM, err_tx_buf, TAG, "Failed to allocate memory for TX buffer");
101+
102+
handle->rx_buffer = calloc(config->rx_buffer_size, sizeof(uint8_t));
103+
ESP_GOTO_ON_FALSE(handle->rx_buffer, ESP_ERR_NO_MEM, err_rx_buf, TAG, "Failed to allocate memory for RX buffer");
104+
105+
// Initialize ISO-TP link with static buffers
106+
isotp_init_link(&handle->link, config->tx_id, handle->tx_buffer,
107+
config->tx_buffer_size, handle->rx_buffer, config->rx_buffer_size);
108+
handle->link.receive_arbitration_id = config->rx_id;
109+
110+
// Register TWAI callback
111+
twai_event_callbacks_t cbs = {
112+
.on_rx_done = esp_isotp_rx_callback,
113+
};
114+
ret = twai_node_register_event_callbacks(handle->twai_node, &cbs, handle);
115+
ESP_GOTO_ON_ERROR(ret, err_callbacks, TAG, "Failed to register event callbacks: %s", esp_err_to_name(ret));
116+
117+
// Enable TWAI node
118+
ret = twai_node_enable(handle->twai_node);
119+
ESP_GOTO_ON_ERROR(ret, err_enable, TAG, "Failed to enable TWAI node: %s", esp_err_to_name(ret));
120+
121+
ESP_LOGI(TAG, "New ISO-TP link created, TX: 0x%lX, RX: 0x%lX", config->tx_id, config->rx_id);
122+
return handle;
123+
124+
err_enable:
125+
err_callbacks:
126+
free(handle->rx_buffer);
127+
err_rx_buf:
128+
free(handle->tx_buffer);
129+
err_tx_buf:
130+
free(handle);
131+
return NULL;
132+
}
133+
134+
esp_err_t esp_isotp_poll(esp_isotp_handle_t handle)
135+
{
136+
ESP_RETURN_ON_FALSE(handle != NULL, ESP_ERR_INVALID_ARG, TAG, "Invalid handle");
137+
138+
// Process received queue data
139+
isotp_rx_queue_item_t rx_item;
140+
while (xQueueReceive(handle->rx_queue, &rx_item, 0) == pdTRUE) {
141+
isotp_on_can_message(&handle->link, rx_item.data, rx_item.data_len);
142+
}
143+
144+
// Run ISO-TP state machine
145+
isotp_poll(&handle->link);
146+
147+
return ESP_OK;
148+
}
149+
150+
int esp_isotp_send(esp_isotp_handle_t handle, const uint8_t *data, uint16_t size)
151+
{
152+
ESP_RETURN_ON_FALSE(handle != NULL, ISOTP_RET_ERROR, TAG, "Invalid handle");
153+
ESP_RETURN_ON_FALSE(data != NULL, ISOTP_RET_ERROR, TAG, "Invalid data");
154+
155+
return isotp_send(&handle->link, data, size);
156+
}
157+
158+
int esp_isotp_receive(esp_isotp_handle_t handle, uint8_t *data, uint16_t size)
159+
{
160+
uint16_t out_size = 0;
161+
ESP_RETURN_ON_FALSE(handle != NULL, ISOTP_RET_ERROR, TAG, "Invalid handle");
162+
ESP_RETURN_ON_FALSE(data != NULL, ISOTP_RET_ERROR, TAG, "Invalid data");
163+
164+
int ret = isotp_receive(&handle->link, data, size, &out_size);
165+
if (ret == ISOTP_RET_OK) {
166+
return out_size;
167+
} else if (ret == ISOTP_RET_NO_DATA) {
168+
return 0;
169+
}
170+
return ret;
171+
}
172+
173+
esp_err_t esp_isotp_delete(esp_isotp_handle_t handle)
174+
{
175+
esp_err_t ret = ESP_OK;
176+
ESP_RETURN_ON_FALSE(handle != NULL, ESP_ERR_INVALID_ARG, TAG, "Invalid handle");
177+
178+
// Clear callback
179+
twai_event_callbacks_t cbs = { .on_rx_done = NULL };
180+
ret = twai_node_register_event_callbacks(handle->twai_node, &cbs, NULL);
181+
ESP_GOTO_ON_ERROR(ret, clean, TAG, "Failed to unregister callbacks: %s", esp_err_to_name(ret));
182+
183+
// Disable TWAI node
184+
ret = twai_node_disable(handle->twai_node);
185+
ESP_GOTO_ON_ERROR(ret, clean, TAG, "Failed to disable TWAI node: %s", esp_err_to_name(ret));
186+
187+
clean:
188+
// Free buffers
189+
if (handle->tx_buffer) {
190+
free(handle->tx_buffer);
191+
}
192+
if (handle->rx_buffer) {
193+
free(handle->rx_buffer);
194+
}
195+
free(handle);
196+
return ret;
197+
}

0 commit comments

Comments
 (0)