feat(driver_twai): update and simplify network example using new driver

This commit is contained in:
wanckl
2025-07-02 20:26:13 +08:00
parent 959557d00a
commit fe8f0f08f3
25 changed files with 581 additions and 1010 deletions

View File

@@ -511,12 +511,12 @@ examples/peripherals/twai/twai_error_recovery:
examples/peripherals/twai/twai_network:
disable:
- if: SOC_TWAI_SUPPORTED != 1 or SOC_TWAI_SUPPORT_FD == 1
reason: This example not support FD
- if: SOC_TWAI_SUPPORTED != 1
disable_test:
- if: 1 == 1
temporary: true
reason: Test is flakey, TODO IDF-2939
- if: IDF_TARGET == "esp32"
reason: esp32c5,esp32 test has been disabled, because C5 twai don't support listen only in network test, see errata issue 5
depends_components:
- esp_driver_twai
examples/peripherals/twai/twai_utils:
disable:

View File

@@ -1,189 +1,163 @@
| Supported Targets | ESP32 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-H21 | ESP32-P4 | ESP32-S2 | ESP32-S3 |
| ----------------- | ----- | -------- | -------- | -------- | --------- | -------- | -------- | -------- |
| Supported Targets | ESP32 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-H2 | ESP32-H21 | ESP32-P4 | ESP32-S2 | ESP32-S3 |
| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | -------- | -------- |
# TWAI Network Example
(See the README.md file in the upper level 'examples' directory for more information about examples.)
This example demonstrates TWAI (Two-Wire Automotive Interface) network communication using the ESP-IDF TWAI driver. It consists of two programs that showcase different aspects of TWAI bus communication.
This example demonstrates how to use the TWAI driver to program a target (ESP32, ESP32-S2, ESP32-S3 or ESP32-C3) as a TWAI node, and have the two nodes (Network Master and Network Slave) communicate on a TWAI network. The Listen Only node is optional and acts as a network monitor meaning that it only receives messages and does not influence the bus in any way (i.e. doesn't not acknowledge or send error frames).
## Overview
Note that concept of master/slave in this example refers to which node initiates
and stops the transfer of a stream of data messages.
### Programs
## How to use example
- **twai_sender**: Sends periodic heartbeat messages and large data packets
- **twai_listen_only**: Monitors specific message ID using a filter
### Hardware Required
### Key Features
This example requires at least two targets (e.g., an ESP32 or ESP32-S2) to act as the Network Master and Network Slave. The third target (Listen Only) is optional. Each target must be connected to an external transceiver (e.g., a SN65HVD23X transceiver). The transceivers must then be interconnected to form a TWAI network.
- Event-driven message handling with callbacks
- Message filtering using acceptance filters in listen-only mode
- Single/Burst data transmission and reception
- Real-time bus error and node status reporting
The following diagram illustrates an example network:
## Hardware Setup
```text
---------- ---------- --------------
| Master | | Slave | | Listen Only |
| | | | | |
| TX RX | | TX RX | | TX RX |
---------- ---------- --------------
| | | | | |
| | | | | |
---------- ---------- ----------
| D R | | D R | | D R |
| | | | | |
| VP230 | | VP230 | | VP230 |
| | | | | |
| H L | | H L | | H L |
---------- ---------- ----------
| | | | | |
| | | | | |
|--x------|-----x------|-----x------|--| H
| | |
|---------x------------x------------x--| L
### Wiring
```
For multi-device testing:
1. Connect TWAI transceivers to each ESP32xx
2. Wire TWAI_H and TWAI_L between all devices using line topology
3. Add 120Ω termination resistors at both ends of the bus
Note: If you don't have an external transceiver, you can still run the [TWAI Self Test example](../twai_self_test/README.md)
For single-device testing, enable self_test mode in the sender.
### Configure the project
### GPIO Configuration
For each node in the TWAI network (i.e., Master, Slave, Listen Only)...
The GPIO pins can be configured using menuconfig:
* Set the target of the build (where `{IDF_TARGET}` stands for the target chip such as `esp32` or `esp32s2`).
* Then run `menuconfig` to configure the example.
```sh
idf.py set-target {IDF_TARGET}
```bash
idf.py menuconfig
```
* Under `Example Configuration`, configure the pin assignments using the options `TX GPIO Number` and `RX GPIO Number` according to how the target was connected to the transceiver. By default, `TX GPIO Number` and `RX GPIO Number` are set to the following values:
* On the ESP32, `TX GPIO Number` and `RX GPIO Number` default to `21` and `22` respectively
* On other chips, `TX GPIO Number` and `RX GPIO Number` default to `0` and `2` respectively
Navigate to: `Example Configuration` → Configure the following:
- **TWAI TX GPIO Num**: GPIO pin for TWAI TX
- **TWAI RX GPIO Num**: GPIO pin for TWAI RX
### Build and Flash
For each node, build the project and flash it to the board, then run monitor tool to view serial output:
```sh
idf.py -p PORT flash monitor
**Default configuration:**
```c
#define TWAI_TX_GPIO GPIO_NUM_4
#define TWAI_RX_GPIO GPIO_NUM_5
#define TWAI_BITRATE 1000000 // 1 Mbps
```
(Replace PORT with the name of the serial port to use.)
## Message Types
(To exit the serial monitor, type ``Ctrl-]``.)
| ID | Type | Frequency | Size | Description |
|----|------|-----------|------|-------------|
| 0x7FF | Heartbeat | 1 Hz | 8 bytes | Timestamp data |
| 0x100 | Data | Every 10s | 1000 bytes | Test data (125 frames) |
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
## Building and Running
## Example Output
### Build each program (sender, listen_only):
Network Master
**Enter the sub-application directory and run:**
```text
I (345) TWAI Master: Driver installed
I (345) TWAI Master: Driver started
I (345) TWAI Master: Transmitting ping
I (3105) TWAI Master: Transmitted start command
I (3105) TWAI Master: Received data value 339
...
I (5545) TWAI Master: Received data value 584
I (5545) TWAI Master: Transmitted stop command
I (5595) TWAI Master: Driver stopped
I (6595) TWAI Master: Driver started
I (6595) TWAI Master: Transmitting ping
I (7095) TWAI Master: Transmitted start command
I (7095) TWAI Master: Received data value 738
...
I (9535) TWAI Master: Received data value 983
I (9535) TWAI Master: Transmitted stop command
I (9585) TWAI Master: Driver stopped
I (10585) TWAI Master: Driver started
I (10585) TWAI Master: Transmitting ping
I (11085) TWAI Master: Transmitted start command
I (11085) TWAI Master: Received data value 1137
...
I (13525) TWAI Master: Received data value 1382
I (13525) TWAI Master: Transmitted stop command
I (13575) TWAI Master: Driver stopped
I (14575) TWAI Master: Driver uninstalled
```bash
idf.py set-target esp32 build flash monitor
```
Network Slave
## Expected Output
```text
Slave starting in 3
Slave starting in 2
Slave starting in 1
I (6322) TWAI Slave: Driver installed
I (6322) TWAI Slave: Driver started
I (6462) TWAI Slave: Transmitted ping response
I (6712) TWAI Slave: Start transmitting data
I (6712) TWAI Slave: Transmitted data value 339
...
I (9162) TWAI Slave: Transmitted data value 584
I (9212) TWAI Slave: Transmitted stop response
I (9312) TWAI Slave: Driver stopped
I (10312) TWAI Slave: Driver started
I (10452) TWAI Slave: Transmitted ping response
I (10702) TWAI Slave: Start transmitting data
I (10702) TWAI Slave: Transmitted data value 738
...
I (13152) TWAI Slave: Transmitted data value 983
I (13202) TWAI Slave: Transmitted stop response
I (13302) TWAI Slave: Driver stopped
I (14302) TWAI Slave: Driver started
I (14442) TWAI Slave: Transmitted ping response
I (14692) TWAI Slave: Start transmitting data
I (14692) TWAI Slave: Transmitted data value 1137
...
I (17142) TWAI Slave: Transmitted data value 1382
I (17192) TWAI Slave: Transmitted stop response
I (17292) TWAI Slave: Driver stopped
I (18292) TWAI Slave: Driver uninstalled
### Sender
```
===================TWAI Sender Example Starting...===================
I (xxx) twai_sender: TWAI Sender started successfully
I (xxx) twai_sender: Sending messages on IDs: 0x100 (data), 0x7FF (heartbeat)
I (xxx) twai_sender: Sending heartbeat message: 1234567890
I (xxx) twai_sender: Sending packet of 1000 bytes in 125 frames
```
Network Listen Only
```text
I (326) TWAI Listen Only: Driver installed
I (326) TWAI Listen Only: Driver started
I (366) TWAI Listen Only: Received master ping
...
I (1866) TWAI Listen Only: Received master ping
I (1866) TWAI Listen Only: Received slave ping response
I (2116) TWAI Listen Only: Received master start command
I (2116) TWAI Listen Only: Received data value 329
...
I (4566) TWAI Listen Only: Received data value 574
I (4566) TWAI Listen Only: Received master stop command
I (4606) TWAI Listen Only: Received slave stop response
I (5606) TWAI Listen Only: Received master ping
I (5856) TWAI Listen Only: Received master ping
I (5856) TWAI Listen Only: Received slave ping response
I (6106) TWAI Listen Only: Received master start command
I (6106) TWAI Listen Only: Received data value 728
...
I (8556) TWAI Listen Only: Received data value 973
I (8556) TWAI Listen Only: Received master stop command
I (8596) TWAI Listen Only: Received slave stop response
I (9596) TWAI Listen Only: Received master ping
I (9846) TWAI Listen Only: Received master ping
I (9846) TWAI Listen Only: Received slave ping response
I (10096) TWAI Listen Only: Received master start command
I (10096) TWAI Listen Only: Received data value 1127
...
I (12546) TWAI Listen Only: Received data value 1372
I (12546) TWAI Listen Only: Received master stop command
I (12586) TWAI Listen Only: Received slave stop response
I (12586) TWAI Listen Only: Driver stopped
I (12586) TWAI Listen Only: Driver uninstalled
### Listen-Only Monitor
```
===================TWAI Listen Only Example Starting...===================
I (xxx) twai_listen: Buffer initialized: 200 slots for burst data
I (xxx) twai_listen: TWAI node created
I (xxx) twai_listen: Filter enabled for ID: 0x100 Mask: 0x7F0
I (xxx) twai_listen: TWAI start listening...
I (xxx) twai_listen: RX: 100 [8] 0 0 0 0 0 0 0 0
I (xxx) twai_listen: RX: 100 [8] 1 1 1 1 1 1 1 1
```
## Example Breakdown
## Implementation Details
The communication between the Network Master and Network Slave execute the following steps over multiple iterations:
### Message Buffering
1. Both master and slave go through install and start their TWAI drivers independently.
2. The master repeatedly sends **PING** messages until it receives a **PING_RESP** (ping response message) from the slave. The slave will only send a **PING_RESP** message when it receives a **PING** message from the master.
3. Once the master has received the **PING_RESP** from the slave, it will send a **START_CMD** message to the slave.
4. Upon receiving the **START_CMD** message, the slave will start transmitting **DATA** messages until the master sends a **STOP_CMD**. The master will send the **STOP_CMD** after receiving N **DATA** messages from the slave (N = 50 by default).
5. When the slave receives the **STOP_CMD**, it will confirm that it has stopped by sending a **STOP_RESP** message to the master.
Each program uses a buffer pool to handle incoming messages efficiently:
- **Sender**: Small buffer for transmission completion tracking
- **Listen-Only**: 100-slot buffer for monitoring filtered traffic
### Operating Modes
- **Normal Mode** (Sender): Participates in bus communication, sends ACK frames
- **Listen-Only Mode** (Monitor): Receives filtered messages without transmitting anything
### Message Filtering
The listen-only monitor uses hardware acceptance filters to receive only specific message IDs:
```c
twai_mask_filter_config_t data_filter = {
.id = TWAI_DATA_ID,
.mask = 0x7F0, // Match high 7 bits of the ID, ignore low 4 bits
.is_ext = false, // Receive only standard ID
};
```
### Error Handling
- Bus error logging and status monitoring
## Configuration
### Customizing Buffer Sizes
Adjust buffer sizes in each program as needed:
```c
#define POLL_DEPTH 200 // Listen-only buffer size
```
### Changing Message IDs
Update the message ID definitions:
```c
#define TWAI_DATA_ID 0x100
#define TWAI_HEARTBEAT_ID 0x7FF
```
## Use Cases
This example is suitable for:
- Learning TWAI bus communication
- Testing TWAI network setups
- Developing custom TWAI protocols
- Bus monitoring and debugging
- Prototyping automotive communication systems
## Troubleshooting
### No Communication
- Check GPIO pin connections
- Verify bitrate settings match between devices
- Ensure proper bus termination
### Buffer Overflows
- Increase buffer size (`POLL_DEPTH`)
- Reduce bus message transmission rate
- Optimize message processing code
### Bus Errors
- Check physical bus wiring
- Verify termination resistors (120Ω at each end)
- Monitor error counters with `twai_node_get_info()`

View File

@@ -0,0 +1,107 @@
# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: CC0-1.0
import os.path
import subprocess
import pytest
from can import Bus
from can import Message
from pytest_embedded_idf import IdfDut
from pytest_embedded_idf.utils import soc_filtered_targets
TWAI_SUPPORTED_TARGETS = soc_filtered_targets('SOC_TWAI_SUPPORTED == 1')
# Socket CAN fixture
@pytest.fixture(name='socket_can')
def fixture_create_socket_can() -> Bus:
start_command = 'sudo ip link set can0 up type can bitrate 1000000'
stop_command = 'sudo ip link set can0 down'
try:
subprocess.run(start_command, shell=True, capture_output=True, text=True)
except Exception as e:
print(f'Open bus Error: {e}')
bus = Bus(interface='socketcan', channel='can0', bitrate=1000000)
yield bus # test invoked here
bus.shutdown()
subprocess.run(stop_command, shell=True, capture_output=True, text=True)
# Generate minimal combinations that each target appears in each app
def generate_target_combinations(target_list: list, count: int = 2) -> list:
combinations = []
num_targets = len(target_list)
for round_num in range(num_targets):
selected_targets = [target_list[(round_num + i) % num_targets] for i in range(count)]
combinations.append('|'.join(selected_targets))
return combinations
@pytest.mark.twai_std
@pytest.mark.parametrize('count', [2], indirect=True)
@pytest.mark.timeout(120)
@pytest.mark.parametrize(
'app_path,target',
[
(
f'{os.path.join(os.path.dirname(__file__), "twai_listen_only")}|'
f'{os.path.join(os.path.dirname(__file__), "twai_sender")}',
target_combo,
)
for target_combo in generate_target_combinations(TWAI_SUPPORTED_TARGETS)
],
indirect=True,
)
@pytest.mark.temp_skip_ci(
targets=['esp32c5,esp32'], reason="C5 twai don't support listen only in network test, see errata issue 5"
)
def test_twai_network_multi(dut: tuple[IdfDut, IdfDut], socket_can: Bus) -> None:
"""
Test TWAI network communication between two nodes:
- dut[0]: listener (first chip) - uses twai_listen_only
- dut[1]: sender (second chip) - uses twai_sender
"""
# Print chip information for debugging
print(f'===> Pytest testing with chips: {dut[0].app.target} (listener), {dut[1].app.target} (sender)')
# Initialize listener node first
dut[0].expect('===================TWAI Listen Only Example Starting...===================')
dut[0].expect('TWAI start listening...')
# Initialize sender node and start communication
dut[1].expect('===================TWAI Sender Example Starting...===================')
dut[1].expect('TWAI Sender started successfully')
# Verify communication is working
# Wait for sender to send messages
dut[1].expect('Sending heartbeat message:', timeout=10)
# Check that listener is receiving data
dut[0].expect('RX:', timeout=15) # Listener should see filtered messages
# Check if socket receive any messages
socket_rcv_cnt = 0
for i in range(100):
msg = socket_can.recv(timeout=1)
if msg is not None:
socket_rcv_cnt += 1
print(f'Socket receive {socket_rcv_cnt} messages')
assert socket_rcv_cnt > 50, 'Socket NO messages'
# Wait a bit more to ensure stable communication
dut[1].expect('Sending packet of', timeout=10)
dut[0].expect('RX:', timeout=10)
# Check if esp32 receive messages from usb can
message = Message(
arbitration_id=0x10A,
is_extended_id=False,
data=b'Hi ESP32',
)
print('USB CAN Send:', message)
socket_can.send(message, timeout=0.2)
dut[0].expect_exact('RX: 10a [8] 48 69 20 45 53 50 33 32', timeout=10) # ASCII: Hi ESP32
print('===> TWAI network communication test completed successfully')

View File

@@ -1,90 +0,0 @@
# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: CC0-1.0
import os.path
from threading import Thread
from typing import Tuple
import pytest
from pytest_embedded import Dut
# Define tuple of strings to expect for each DUT.
master_expect = ('TWAI Master: Driver installed', 'TWAI Master: Driver uninstalled')
slave_expect = ('TWAI Slave: Driver installed', 'TWAI Slave: Driver uninstalled')
listen_only_expect = (
'TWAI Listen Only: Driver installed',
'TWAI Listen Only: Driver uninstalled',
)
def dut_thread_callback(**kwargs) -> None: # type: ignore
# Parse keyword arguments
dut = kwargs['dut'] # Get DUT from kwargs
expected = kwargs['expected']
result = kwargs[
'result'
] # Get result[out] from kwargs. MUST be of mutable type e.g. list
# Must reset again as flashing during start_app will reset multiple times, causing unexpected results
dut.reset()
for string in expected:
dut.expect(string, 20)
# Mark thread has run to completion without any exceptions
result[0] = True
@pytest.mark.skip(reason="there's not a good approach to sync multiple DUTs")
@pytest.mark.twai_network
@pytest.mark.parametrize(
'count, app_path',
[
(
3,
f'{os.path.join(os.path.dirname(__file__), "twai_network_master")}|'
f'{os.path.join(os.path.dirname(__file__), "twai_network_slave")}|'
f'{os.path.join(os.path.dirname(__file__), "twai_network_listen_only")}',
),
],
indirect=True,
)
def test_twai_network_example(dut: Tuple[Dut, Dut, Dut]) -> None:
dut_master = dut[0]
dut_slave = dut[1]
dut_listen_only = dut[2]
# Create dict of keyword arguments for each dut
results = [[False], [False], [False]]
master_kwargs = {'dut': dut_master, 'result': results[0], 'expected': master_expect}
slave_kwargs = {'dut': dut_slave, 'result': results[1], 'expected': slave_expect}
listen_only_kwargs = {
'dut': dut_listen_only,
'result': results[2],
'expected': listen_only_expect,
}
# Create thread for each dut
dut_master_thread = Thread(
target=dut_thread_callback, name='Master Thread', kwargs=master_kwargs
)
dut_slave_thread = Thread(
target=dut_thread_callback, name='Slave Thread', kwargs=slave_kwargs
)
dut_listen_only_thread = Thread(
target=dut_thread_callback, name='Listen Only Thread', kwargs=listen_only_kwargs
)
# Start each thread
dut_listen_only_thread.start()
dut_master_thread.start()
dut_slave_thread.start()
# Wait for threads to complete
dut_listen_only_thread.join()
dut_master_thread.join()
dut_slave_thread.join()
# check each thread ran to completion
for result in results:
if result[0] is not True:
raise Exception('One or more threads did not run successfully')

View File

@@ -5,4 +5,4 @@ cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
idf_build_set_property(MINIMAL_BUILD ON)
project(twai_network_slave)
project(twai_listen_only)

View File

@@ -0,0 +1,5 @@
idf_component_register(
SRCS "twai_listen_only.c"
REQUIRES esp_driver_twai
INCLUDE_DIRS "."
)

View File

@@ -0,0 +1,17 @@
menu "Example Configuration"
config EXAMPLE_TWAI_TX_GPIO
int "TWAI TX GPIO Num"
range 0 SOC_GPIO_OUT_RANGE_MAX
default 4
help
GPIO number for TWAI TX to transceiver.
config EXAMPLE_TWAI_RX_GPIO
int "TWAI RX GPIO Num"
range 0 SOC_GPIO_IN_RANGE_MAX
default 5
help
GPIO number for TWAI RX from transceiver.
endmenu

View File

@@ -0,0 +1,146 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "esp_log.h"
#include "esp_twai.h"
#include "esp_twai_onchip.h"
#define TWAI_LISTENER_TX_GPIO -1 // Listen only node doesn't need TX pin
#define TWAI_LISTENER_RX_GPIO CONFIG_EXAMPLE_TWAI_RX_GPIO
#define TWAI_BITRATE 1000000
// Message IDs (must match sender)
#define TWAI_DATA_ID 0x100
// Buffer for burst data handling
#define POLL_DEPTH 200
static const char *TAG = "twai_listen";
typedef struct {
twai_frame_t frame;
uint8_t data[TWAI_FRAME_MAX_LEN];
} twai_listener_data_t;
typedef struct {
twai_node_handle_t node_hdl;
twai_listener_data_t *rx_pool;
SemaphoreHandle_t free_pool_semaphore;
SemaphoreHandle_t rx_result_semaphore;
int write_idx;
int read_idx;
} twai_listener_ctx_t;
// Error callback
static bool IRAM_ATTR twai_listener_on_error_callback(twai_node_handle_t handle, const twai_error_event_data_t *edata, void *user_ctx)
{
ESP_EARLY_LOGW(TAG, "bus error: 0x%x", edata->err_flags.val);
return false;
}
// Node state
static bool IRAM_ATTR twai_listener_on_state_change_callback(twai_node_handle_t handle, const twai_state_change_event_data_t *edata, void *user_ctx)
{
const char *twai_state_name[] = {"error_active", "error_warning", "error_passive", "bus_off"};
ESP_EARLY_LOGI(TAG, "state changed: %s -> %s", twai_state_name[edata->old_sta], twai_state_name[edata->new_sta]);
return false;
}
// TWAI receive callback - store data and signal
static bool IRAM_ATTR twai_listener_rx_callback(twai_node_handle_t handle, const twai_rx_done_event_data_t *edata, void *user_ctx)
{
BaseType_t woken;
twai_listener_ctx_t *ctx = (twai_listener_ctx_t *)user_ctx;
if (xSemaphoreTakeFromISR(ctx->free_pool_semaphore, &woken) != pdTRUE) {
ESP_EARLY_LOGI(TAG, "Pool full, dropping frame");
return (woken == pdTRUE);
}
if (twai_node_receive_from_isr(handle, &ctx->rx_pool[ctx->write_idx].frame) == ESP_OK) {
ctx->write_idx = (ctx->write_idx + 1) % POLL_DEPTH;
xSemaphoreGiveFromISR(ctx->rx_result_semaphore, &woken);
}
return (woken == pdTRUE);
}
void app_main(void)
{
printf("===================TWAI Listen Only Example Starting...===================\n");
// Create semaphore for receive notification
twai_listener_ctx_t twai_listener_ctx = {0};
twai_listener_ctx.free_pool_semaphore = xSemaphoreCreateCounting(POLL_DEPTH, POLL_DEPTH);
twai_listener_ctx.rx_result_semaphore = xSemaphoreCreateCounting(POLL_DEPTH, 0);
assert(twai_listener_ctx.free_pool_semaphore != NULL);
assert(twai_listener_ctx.rx_result_semaphore != NULL);
twai_listener_ctx.rx_pool = calloc(POLL_DEPTH, sizeof(twai_listener_data_t));
assert(twai_listener_ctx.rx_pool != NULL);
for (int i = 0; i < POLL_DEPTH; i++) {
twai_listener_ctx.rx_pool[i].frame.buffer = twai_listener_ctx.rx_pool[i].data;
twai_listener_ctx.rx_pool[i].frame.buffer_len = sizeof(twai_listener_ctx.rx_pool[i].data);
}
ESP_LOGI(TAG, "Buffer initialized: %d slots for burst data", POLL_DEPTH);
// Configure TWAI node
twai_onchip_node_config_t node_config = {
.io_cfg = {
.tx = TWAI_LISTENER_TX_GPIO,
.rx = TWAI_LISTENER_RX_GPIO,
.quanta_clk_out = -1,
.bus_off_indicator = -1,
},
.bit_timing.bitrate = TWAI_BITRATE,
.flags.enable_listen_only = true,
};
// Create TWAI node
ESP_ERROR_CHECK(twai_new_node_onchip(&node_config, &twai_listener_ctx.node_hdl));
ESP_LOGI(TAG, "TWAI node created");
// Configure acceptance filter
twai_mask_filter_config_t data_filter = {
.id = TWAI_DATA_ID,
.mask = 0x7F0, // Match high 7 bits of the ID, ignore low 4 bits
.is_ext = false, // Receive only standard ID
};
ESP_ERROR_CHECK(twai_node_config_mask_filter(twai_listener_ctx.node_hdl, 0, &data_filter));
ESP_LOGI(TAG, "Filter enabled for ID: 0x%03X Mask: 0x%03X", data_filter.id, data_filter.mask);
// Register callbacks
twai_event_callbacks_t callbacks = {
.on_rx_done = twai_listener_rx_callback,
.on_error = twai_listener_on_error_callback,
.on_state_change = twai_listener_on_state_change_callback,
};
ESP_ERROR_CHECK(twai_node_register_event_callbacks(twai_listener_ctx.node_hdl, &callbacks, &twai_listener_ctx));
// Enable TWAI node
ESP_ERROR_CHECK(twai_node_enable(twai_listener_ctx.node_hdl));
ESP_LOGI(TAG, "TWAI start listening...");
// Main loop - process all buffered data when signaled
while (1) {
if (xSemaphoreTake(twai_listener_ctx.rx_result_semaphore, portMAX_DELAY) == pdTRUE) {
twai_frame_t *frame = &twai_listener_ctx.rx_pool[twai_listener_ctx.read_idx].frame;
ESP_LOGI(TAG, "RX: %x [%d] %x %x %x %x %x %x %x %x", \
frame->header.id, frame->header.dlc, frame->buffer[0], frame->buffer[1], frame->buffer[2], frame->buffer[3], frame->buffer[4], frame->buffer[5], frame->buffer[6], frame->buffer[7]);
twai_listener_ctx.read_idx = (twai_listener_ctx.read_idx + 1) % POLL_DEPTH;
xSemaphoreGive(twai_listener_ctx.free_pool_semaphore);
}
}
// Cleanup
vSemaphoreDelete(twai_listener_ctx.rx_result_semaphore);
vSemaphoreDelete(twai_listener_ctx.free_pool_semaphore);
free(twai_listener_ctx.rx_pool);
ESP_ERROR_CHECK(twai_node_disable(twai_listener_ctx.node_hdl));
ESP_ERROR_CHECK(twai_node_delete(twai_listener_ctx.node_hdl));
}

View File

@@ -1,8 +0,0 @@
# The following 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)
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
idf_build_set_property(MINIMAL_BUILD ON)
project(twai_network_listen_only)

View File

@@ -1,3 +0,0 @@
idf_component_register(SRCS "twai_network_example_listen_only_main.c"
REQUIRES driver
INCLUDE_DIRS ".")

View File

@@ -1,19 +0,0 @@
menu "Example Configuration"
config EXAMPLE_TX_GPIO_NUM
int "TX GPIO number"
default 21 if IDF_TARGET_ESP32
default 0
help
This option selects the GPIO pin used for the TX signal. Connect the
TX signal to your transceiver.
config EXAMPLE_RX_GPIO_NUM
int "RX GPIO number"
default 22 if IDF_TARGET_ESP32
default 2
help
This option selects the GPIO pin used for the RX signal. Connect the
RX signal to your transceiver.
endmenu

View File

@@ -1,121 +0,0 @@
/*
* SPDX-FileCopyrightText: 2010-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
/*
* The following example demonstrates a Listen Only node in a TWAI network. The
* Listen Only node will not take part in any TWAI bus activity (no acknowledgments
* and no error frames). This example will execute multiple iterations, with each
* iteration the Listen Only node will do the following:
* 1) Listen for ping and ping response
* 2) Listen for start command
* 3) Listen for data messages
* 4) Listen for stop and stop response
*/
#include <stdio.h>
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "freertos/semphr.h"
#include "esp_err.h"
#include "esp_log.h"
#include "driver/twai.h"
/* --------------------- Definitions and static variables ------------------ */
//Example Configuration
#define NO_OF_ITERS 3
#define RX_TASK_PRIO 9
#define TX_GPIO_NUM CONFIG_EXAMPLE_TX_GPIO_NUM
#define RX_GPIO_NUM CONFIG_EXAMPLE_RX_GPIO_NUM
#define EXAMPLE_TAG "TWAI Listen Only"
#define ID_MASTER_STOP_CMD 0x0A0
#define ID_MASTER_START_CMD 0x0A1
#define ID_MASTER_PING 0x0A2
#define ID_SLAVE_STOP_RESP 0x0B0
#define ID_SLAVE_DATA 0x0B1
#define ID_SLAVE_PING_RESP 0x0B2
static const twai_filter_config_t f_config = TWAI_FILTER_CONFIG_ACCEPT_ALL();
static const twai_timing_config_t t_config = TWAI_TIMING_CONFIG_25KBITS();
//Set TX queue length to 0 due to listen only mode
static const twai_general_config_t g_config = {.mode = TWAI_MODE_LISTEN_ONLY,
.tx_io = TX_GPIO_NUM, .rx_io = RX_GPIO_NUM,
.clkout_io = TWAI_IO_UNUSED, .bus_off_io = TWAI_IO_UNUSED,
.tx_queue_len = 0, .rx_queue_len = 5,
.alerts_enabled = TWAI_ALERT_NONE,
.clkout_divider = 0
};
static SemaphoreHandle_t rx_sem;
/* --------------------------- Tasks and Functions -------------------------- */
static void twai_receive_task(void *arg)
{
xSemaphoreTake(rx_sem, portMAX_DELAY);
bool start_cmd = false;
bool stop_resp = false;
uint32_t iterations = 0;
while (iterations < NO_OF_ITERS) {
twai_message_t rx_msg;
twai_receive(&rx_msg, portMAX_DELAY);
if (rx_msg.identifier == ID_MASTER_PING) {
ESP_LOGI(EXAMPLE_TAG, "Received master ping");
} else if (rx_msg.identifier == ID_SLAVE_PING_RESP) {
ESP_LOGI(EXAMPLE_TAG, "Received slave ping response");
} else if (rx_msg.identifier == ID_MASTER_START_CMD) {
ESP_LOGI(EXAMPLE_TAG, "Received master start command");
start_cmd = true;
} else if (rx_msg.identifier == ID_SLAVE_DATA) {
uint32_t data = 0;
for (int i = 0; i < rx_msg.data_length_code; i++) {
data |= (rx_msg.data[i] << (i * 8));
}
ESP_LOGI(EXAMPLE_TAG, "Received data value %"PRIu32, data);
} else if (rx_msg.identifier == ID_MASTER_STOP_CMD) {
ESP_LOGI(EXAMPLE_TAG, "Received master stop command");
} else if (rx_msg.identifier == ID_SLAVE_STOP_RESP) {
ESP_LOGI(EXAMPLE_TAG, "Received slave stop response");
stop_resp = true;
}
if (start_cmd && stop_resp) {
//Each iteration is complete after a start command and stop response is received
iterations++;
start_cmd = 0;
stop_resp = 0;
}
}
xSemaphoreGive(rx_sem);
vTaskDelete(NULL);
}
void app_main(void)
{
rx_sem = xSemaphoreCreateBinary();
xTaskCreatePinnedToCore(twai_receive_task, "TWAI_rx", 4096, NULL, RX_TASK_PRIO, NULL, tskNO_AFFINITY);
//Install and start TWAI driver
ESP_ERROR_CHECK(twai_driver_install(&g_config, &t_config, &f_config));
ESP_LOGI(EXAMPLE_TAG, "Driver installed");
ESP_ERROR_CHECK(twai_start());
ESP_LOGI(EXAMPLE_TAG, "Driver started");
xSemaphoreGive(rx_sem); //Start RX task
vTaskDelay(pdMS_TO_TICKS(100));
xSemaphoreTake(rx_sem, portMAX_DELAY); //Wait for RX task to complete
//Stop and uninstall TWAI driver
ESP_ERROR_CHECK(twai_stop());
ESP_LOGI(EXAMPLE_TAG, "Driver stopped");
ESP_ERROR_CHECK(twai_driver_uninstall());
ESP_LOGI(EXAMPLE_TAG, "Driver uninstalled");
//Cleanup
vSemaphoreDelete(rx_sem);
}

View File

@@ -1,3 +0,0 @@
idf_component_register(SRCS "twai_network_example_master_main.c"
REQUIRES driver
INCLUDE_DIRS ".")

View File

@@ -1,19 +0,0 @@
menu "Example Configuration"
config EXAMPLE_TX_GPIO_NUM
int "TX GPIO number"
default 21 if IDF_TARGET_ESP32
default 0
help
This option selects the GPIO pin used for the TX signal. Connect the
TX signal to your transceiver.
config EXAMPLE_RX_GPIO_NUM
int "RX GPIO number"
default 22 if IDF_TARGET_ESP32
default 2
help
This option selects the GPIO pin used for the RX signal. Connect the
RX signal to your transceiver.
endmenu

View File

@@ -1,266 +0,0 @@
/*
* SPDX-FileCopyrightText: 2010-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
/*
* The following example demonstrates a master node in a TWAI network. The master
* node is responsible for initiating and stopping the transfer of data messages.
* The example will execute multiple iterations, with each iteration the master
* node will do the following:
* 1) Start the TWAI driver
* 2) Repeatedly send ping messages until a ping response from slave is received
* 3) Send start command to slave and receive data messages from slave
* 4) Send stop command to slave and wait for stop response from slave
* 5) Stop the TWAI driver
*/
#include <stdio.h>
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "freertos/semphr.h"
#include "esp_err.h"
#include "esp_log.h"
#include "driver/twai.h"
/* --------------------- Definitions and static variables ------------------ */
//Example Configuration
#define PING_PERIOD_MS 250
#define NO_OF_DATA_MSGS 50
#define NO_OF_ITERS 3
#define ITER_DELAY_MS 1000
#define RX_TASK_PRIO 8
#define TX_TASK_PRIO 9
#define CTRL_TSK_PRIO 10
#define TX_GPIO_NUM CONFIG_EXAMPLE_TX_GPIO_NUM
#define RX_GPIO_NUM CONFIG_EXAMPLE_RX_GPIO_NUM
#define EXAMPLE_TAG "TWAI Master"
#define ID_MASTER_STOP_CMD 0x0A0
#define ID_MASTER_START_CMD 0x0A1
#define ID_MASTER_PING 0x0A2
#define ID_SLAVE_STOP_RESP 0x0B0
#define ID_SLAVE_DATA 0x0B1
#define ID_SLAVE_PING_RESP 0x0B2
typedef enum {
TX_SEND_PINGS,
TX_SEND_START_CMD,
TX_SEND_STOP_CMD,
TX_TASK_EXIT,
} tx_task_action_t;
typedef enum {
RX_RECEIVE_PING_RESP,
RX_RECEIVE_DATA,
RX_RECEIVE_STOP_RESP,
RX_TASK_EXIT,
} rx_task_action_t;
static const twai_timing_config_t t_config = TWAI_TIMING_CONFIG_25KBITS();
static const twai_filter_config_t f_config = TWAI_FILTER_CONFIG_ACCEPT_ALL();
static const twai_general_config_t g_config = TWAI_GENERAL_CONFIG_DEFAULT(TX_GPIO_NUM, RX_GPIO_NUM, TWAI_MODE_NORMAL);
static const twai_message_t ping_message = {
// Message type and format settings
.extd = 0, // Standard Format message (11-bit ID)
.rtr = 0, // Send a data frame
.ss = 1, // Is single shot (won't retry on error or NACK)
.self = 0, // Not a self reception request
.dlc_non_comp = 0, // DLC is less than 8
// Message ID and payload
.identifier = ID_MASTER_PING,
.data_length_code = 0,
.data = {0},
};
static const twai_message_t start_message = {
// Message type and format settings
.extd = 0, // Standard Format message (11-bit ID)
.rtr = 0, // Send a data frame
.ss = 0, // Not single shot
.self = 0, // Not a self reception request
.dlc_non_comp = 0, // DLC is less than 8
// Message ID and payload
.identifier = ID_MASTER_START_CMD,
.data_length_code = 0,
.data = {0},
};
static const twai_message_t stop_message = {
// Message type and format settings
.extd = 0, // Standard Format message (11-bit ID)
.rtr = 0, // Send a data frame
.ss = 0, // Not single shot
.self = 0, // Not a self reception request
.dlc_non_comp = 0, // DLC is less than 8
// Message ID and payload
.identifier = ID_MASTER_STOP_CMD,
.data_length_code = 0,
.data = {0},
};
static QueueHandle_t tx_task_queue;
static QueueHandle_t rx_task_queue;
static SemaphoreHandle_t stop_ping_sem;
static SemaphoreHandle_t ctrl_task_sem;
static SemaphoreHandle_t done_sem;
/* --------------------------- Tasks and Functions -------------------------- */
static void twai_receive_task(void *arg)
{
while (1) {
rx_task_action_t action;
xQueueReceive(rx_task_queue, &action, portMAX_DELAY);
if (action == RX_RECEIVE_PING_RESP) {
//Listen for ping response from slave
while (1) {
twai_message_t rx_msg;
twai_receive(&rx_msg, portMAX_DELAY);
if (rx_msg.identifier == ID_SLAVE_PING_RESP) {
xSemaphoreGive(stop_ping_sem);
xSemaphoreGive(ctrl_task_sem);
break;
}
}
} else if (action == RX_RECEIVE_DATA) {
//Receive data messages from slave
uint32_t data_msgs_rec = 0;
while (data_msgs_rec < NO_OF_DATA_MSGS) {
twai_message_t rx_msg;
twai_receive(&rx_msg, portMAX_DELAY);
if (rx_msg.identifier == ID_SLAVE_DATA) {
uint32_t data = 0;
for (int i = 0; i < rx_msg.data_length_code; i++) {
data |= (rx_msg.data[i] << (i * 8));
}
ESP_LOGI(EXAMPLE_TAG, "Received data value %"PRIu32, data);
data_msgs_rec ++;
}
}
xSemaphoreGive(ctrl_task_sem);
} else if (action == RX_RECEIVE_STOP_RESP) {
//Listen for stop response from slave
while (1) {
twai_message_t rx_msg;
twai_receive(&rx_msg, portMAX_DELAY);
if (rx_msg.identifier == ID_SLAVE_STOP_RESP) {
xSemaphoreGive(ctrl_task_sem);
break;
}
}
} else if (action == RX_TASK_EXIT) {
break;
}
}
vTaskDelete(NULL);
}
static void twai_transmit_task(void *arg)
{
while (1) {
tx_task_action_t action;
xQueueReceive(tx_task_queue, &action, portMAX_DELAY);
if (action == TX_SEND_PINGS) {
//Repeatedly transmit pings
ESP_LOGI(EXAMPLE_TAG, "Transmitting ping");
while (xSemaphoreTake(stop_ping_sem, 0) != pdTRUE) {
twai_transmit(&ping_message, portMAX_DELAY);
vTaskDelay(pdMS_TO_TICKS(PING_PERIOD_MS));
}
} else if (action == TX_SEND_START_CMD) {
//Transmit start command to slave
twai_transmit(&start_message, portMAX_DELAY);
ESP_LOGI(EXAMPLE_TAG, "Transmitted start command");
} else if (action == TX_SEND_STOP_CMD) {
//Transmit stop command to slave
twai_transmit(&stop_message, portMAX_DELAY);
ESP_LOGI(EXAMPLE_TAG, "Transmitted stop command");
} else if (action == TX_TASK_EXIT) {
break;
}
}
vTaskDelete(NULL);
}
static void twai_control_task(void *arg)
{
xSemaphoreTake(ctrl_task_sem, portMAX_DELAY);
tx_task_action_t tx_action;
rx_task_action_t rx_action;
for (int iter = 0; iter < NO_OF_ITERS; iter++) {
ESP_ERROR_CHECK(twai_start());
ESP_LOGI(EXAMPLE_TAG, "Driver started");
//Start transmitting pings, and listen for ping response
tx_action = TX_SEND_PINGS;
rx_action = RX_RECEIVE_PING_RESP;
xQueueSend(tx_task_queue, &tx_action, portMAX_DELAY);
xQueueSend(rx_task_queue, &rx_action, portMAX_DELAY);
//Send Start command to slave, and receive data messages
xSemaphoreTake(ctrl_task_sem, portMAX_DELAY);
tx_action = TX_SEND_START_CMD;
rx_action = RX_RECEIVE_DATA;
xQueueSend(tx_task_queue, &tx_action, portMAX_DELAY);
xQueueSend(rx_task_queue, &rx_action, portMAX_DELAY);
//Send Stop command to slave when enough data messages have been received. Wait for stop response
xSemaphoreTake(ctrl_task_sem, portMAX_DELAY);
tx_action = TX_SEND_STOP_CMD;
rx_action = RX_RECEIVE_STOP_RESP;
xQueueSend(tx_task_queue, &tx_action, portMAX_DELAY);
xQueueSend(rx_task_queue, &rx_action, portMAX_DELAY);
xSemaphoreTake(ctrl_task_sem, portMAX_DELAY);
ESP_ERROR_CHECK(twai_stop());
ESP_LOGI(EXAMPLE_TAG, "Driver stopped");
vTaskDelay(pdMS_TO_TICKS(ITER_DELAY_MS));
}
//Stop TX and RX tasks
tx_action = TX_TASK_EXIT;
rx_action = RX_TASK_EXIT;
xQueueSend(tx_task_queue, &tx_action, portMAX_DELAY);
xQueueSend(rx_task_queue, &rx_action, portMAX_DELAY);
//Delete Control task
xSemaphoreGive(done_sem);
vTaskDelete(NULL);
}
void app_main(void)
{
//Create tasks, queues, and semaphores
rx_task_queue = xQueueCreate(1, sizeof(rx_task_action_t));
tx_task_queue = xQueueCreate(1, sizeof(tx_task_action_t));
ctrl_task_sem = xSemaphoreCreateBinary();
stop_ping_sem = xSemaphoreCreateBinary();
done_sem = xSemaphoreCreateBinary();
xTaskCreatePinnedToCore(twai_receive_task, "TWAI_rx", 4096, NULL, RX_TASK_PRIO, NULL, tskNO_AFFINITY);
xTaskCreatePinnedToCore(twai_transmit_task, "TWAI_tx", 4096, NULL, TX_TASK_PRIO, NULL, tskNO_AFFINITY);
xTaskCreatePinnedToCore(twai_control_task, "TWAI_ctrl", 4096, NULL, CTRL_TSK_PRIO, NULL, tskNO_AFFINITY);
//Install TWAI driver
ESP_ERROR_CHECK(twai_driver_install(&g_config, &t_config, &f_config));
ESP_LOGI(EXAMPLE_TAG, "Driver installed");
xSemaphoreGive(ctrl_task_sem); //Start control task
xSemaphoreTake(done_sem, portMAX_DELAY); //Wait for completion
//Uninstall TWAI driver
ESP_ERROR_CHECK(twai_driver_uninstall());
ESP_LOGI(EXAMPLE_TAG, "Driver uninstalled");
//Cleanup
vQueueDelete(rx_task_queue);
vQueueDelete(tx_task_queue);
vSemaphoreDelete(ctrl_task_sem);
vSemaphoreDelete(stop_ping_sem);
vSemaphoreDelete(done_sem);
}

View File

@@ -1,3 +0,0 @@
idf_component_register(SRCS "twai_network_example_slave_main.c"
REQUIRES driver
INCLUDE_DIRS ".")

View File

@@ -1,19 +0,0 @@
menu "Example Configuration"
config EXAMPLE_TX_GPIO_NUM
int "TX GPIO number"
default 21 if IDF_TARGET_ESP32
default 0
help
This option selects the GPIO pin used for the TX signal. Connect the
TX signal to your transceiver.
config EXAMPLE_RX_GPIO_NUM
int "RX GPIO number"
default 22 if IDF_TARGET_ESP32
default 2
help
This option selects the GPIO pin used for the RX signal. Connect the
RX signal to your transceiver.
endmenu

View File

@@ -1,293 +0,0 @@
/*
* SPDX-FileCopyrightText: 2010-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
/*
* The following example demonstrates a slave node in a TWAI network. The slave
* node is responsible for sending data messages to the master. The example will
* execute multiple iterations, with each iteration the slave node will do the
* following:
* 1) Start the TWAI driver
* 2) Listen for ping messages from master, and send ping response
* 3) Listen for start command from master
* 4) Send data messages to master and listen for stop command
* 5) Send stop response to master
* 6) Stop the TWAI driver
*/
#include <stdio.h>
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "freertos/semphr.h"
#include "esp_err.h"
#include "esp_log.h"
#include "driver/twai.h"
/* --------------------- Definitions and static variables ------------------ */
//Example Configuration
#define DATA_PERIOD_MS 50
#define NO_OF_ITERS 3
#define ITER_DELAY_MS 1000
#define RX_TASK_PRIO 8 //Receiving task priority
#define TX_TASK_PRIO 9 //Sending task priority
#define CTRL_TSK_PRIO 10 //Control task priority
#define TX_GPIO_NUM CONFIG_EXAMPLE_TX_GPIO_NUM
#define RX_GPIO_NUM CONFIG_EXAMPLE_RX_GPIO_NUM
#define EXAMPLE_TAG "TWAI Slave"
#define ID_MASTER_STOP_CMD 0x0A0
#define ID_MASTER_START_CMD 0x0A1
#define ID_MASTER_PING 0x0A2
#define ID_SLAVE_STOP_RESP 0x0B0
#define ID_SLAVE_DATA 0x0B1
#define ID_SLAVE_PING_RESP 0x0B2
typedef enum {
TX_SEND_PING_RESP,
TX_SEND_DATA,
TX_SEND_STOP_RESP,
TX_TASK_EXIT,
} tx_task_action_t;
typedef enum {
RX_RECEIVE_PING,
RX_RECEIVE_START_CMD,
RX_RECEIVE_STOP_CMD,
RX_TASK_EXIT,
} rx_task_action_t;
static const twai_general_config_t g_config = TWAI_GENERAL_CONFIG_DEFAULT(TX_GPIO_NUM, RX_GPIO_NUM, TWAI_MODE_NORMAL);
static const twai_timing_config_t t_config = TWAI_TIMING_CONFIG_25KBITS();
static const twai_filter_config_t f_config = TWAI_FILTER_CONFIG_ACCEPT_ALL();
static const twai_message_t ping_resp = {
// Message type and format settings
.extd = 0, // Standard Format message (11-bit ID)
.rtr = 0, // Send a data frame
.ss = 0, // Not single shot
.self = 0, // Not a self reception request
.dlc_non_comp = 0, // DLC is less than 8
// Message ID and payload
.identifier = ID_SLAVE_PING_RESP,
.data_length_code = 0,
.data = {0},
};
static const twai_message_t stop_resp = {
// Message type and format settings
.extd = 0, // Standard Format message (11-bit ID)
.rtr = 0, // Send a data frame
.ss = 0, // Not single shot
.self = 0, // Not a self reception request
.dlc_non_comp = 0, // DLC is less than 8
// Message ID and payload
.identifier = ID_SLAVE_STOP_RESP,
.data_length_code = 0,
.data = {0},
};
// Data bytes of data message will be initialized in the transmit task
static twai_message_t data_message = {
// Message type and format settings
.extd = 0, // Standard Format message (11-bit ID)
.rtr = 0, // Send a data frame
.ss = 0, // Not single shot
.self = 0, // Not a self reception request
.dlc_non_comp = 0, // DLC is less than 8
// Message ID and payload
.identifier = ID_SLAVE_DATA,
.data_length_code = 4,
.data = {1, 2, 3, 4},
};
static QueueHandle_t tx_task_queue;
static QueueHandle_t rx_task_queue;
static SemaphoreHandle_t ctrl_task_sem;
static SemaphoreHandle_t stop_data_sem;
static SemaphoreHandle_t done_sem;
/* --------------------------- Tasks and Functions -------------------------- */
static void twai_receive_task(void *arg)
{
while (1) {
rx_task_action_t action;
xQueueReceive(rx_task_queue, &action, portMAX_DELAY);
if (action == RX_RECEIVE_PING) {
//Listen for pings from master
twai_message_t rx_msg;
while (1) {
twai_receive(&rx_msg, portMAX_DELAY);
if (rx_msg.identifier == ID_MASTER_PING) {
xSemaphoreGive(ctrl_task_sem);
break;
}
}
} else if (action == RX_RECEIVE_START_CMD) {
//Listen for start command from master
twai_message_t rx_msg;
while (1) {
twai_receive(&rx_msg, portMAX_DELAY);
if (rx_msg.identifier == ID_MASTER_START_CMD) {
xSemaphoreGive(ctrl_task_sem);
break;
}
}
} else if (action == RX_RECEIVE_STOP_CMD) {
//Listen for stop command from master
twai_message_t rx_msg;
while (1) {
twai_receive(&rx_msg, portMAX_DELAY);
if (rx_msg.identifier == ID_MASTER_STOP_CMD) {
xSemaphoreGive(stop_data_sem);
xSemaphoreGive(ctrl_task_sem);
break;
}
}
} else if (action == RX_TASK_EXIT) {
break;
}
}
vTaskDelete(NULL);
}
static void twai_transmit_task(void *arg)
{
while (1) {
tx_task_action_t action;
xQueueReceive(tx_task_queue, &action, portMAX_DELAY);
if (action == TX_SEND_PING_RESP) {
//Transmit ping response to master
twai_transmit(&ping_resp, portMAX_DELAY);
ESP_LOGI(EXAMPLE_TAG, "Transmitted ping response");
xSemaphoreGive(ctrl_task_sem);
} else if (action == TX_SEND_DATA) {
//Transmit data messages until stop command is received
ESP_LOGI(EXAMPLE_TAG, "Start transmitting data");
while (1) {
//FreeRTOS tick count used to simulate sensor data
uint32_t sensor_data = xTaskGetTickCount();
for (int i = 0; i < 4; i++) {
data_message.data[i] = (sensor_data >> (i * 8)) & 0xFF;
}
twai_transmit(&data_message, portMAX_DELAY);
ESP_LOGI(EXAMPLE_TAG, "Transmitted data value %"PRIu32, sensor_data);
vTaskDelay(pdMS_TO_TICKS(DATA_PERIOD_MS));
if (xSemaphoreTake(stop_data_sem, 0) == pdTRUE) {
break;
}
}
} else if (action == TX_SEND_STOP_RESP) {
//Transmit stop response to master
twai_transmit(&stop_resp, portMAX_DELAY);
ESP_LOGI(EXAMPLE_TAG, "Transmitted stop response");
xSemaphoreGive(ctrl_task_sem);
} else if (action == TX_TASK_EXIT) {
break;
}
}
vTaskDelete(NULL);
}
static void twai_control_task(void *arg)
{
xSemaphoreTake(ctrl_task_sem, portMAX_DELAY);
tx_task_action_t tx_action;
rx_task_action_t rx_action;
for (int iter = 0; iter < NO_OF_ITERS; iter++) {
ESP_ERROR_CHECK(twai_start());
ESP_LOGI(EXAMPLE_TAG, "Driver started");
//Listen of pings from master
rx_action = RX_RECEIVE_PING;
xQueueSend(rx_task_queue, &rx_action, portMAX_DELAY);
xSemaphoreTake(ctrl_task_sem, portMAX_DELAY);
//Send ping response
tx_action = TX_SEND_PING_RESP;
xQueueSend(tx_task_queue, &tx_action, portMAX_DELAY);
xSemaphoreTake(ctrl_task_sem, portMAX_DELAY);
//Listen for start command
rx_action = RX_RECEIVE_START_CMD;
xQueueSend(rx_task_queue, &rx_action, portMAX_DELAY);
xSemaphoreTake(ctrl_task_sem, portMAX_DELAY);
//Start sending data messages and listen for stop command
tx_action = TX_SEND_DATA;
rx_action = RX_RECEIVE_STOP_CMD;
xQueueSend(tx_task_queue, &tx_action, portMAX_DELAY);
xQueueSend(rx_task_queue, &rx_action, portMAX_DELAY);
xSemaphoreTake(ctrl_task_sem, portMAX_DELAY);
//Send stop response
tx_action = TX_SEND_STOP_RESP;
xQueueSend(tx_task_queue, &tx_action, portMAX_DELAY);
xSemaphoreTake(ctrl_task_sem, portMAX_DELAY);
//Wait for bus to become free
twai_status_info_t status_info;
twai_get_status_info(&status_info);
while (status_info.msgs_to_tx > 0) {
vTaskDelay(pdMS_TO_TICKS(100));
twai_get_status_info(&status_info);
}
ESP_ERROR_CHECK(twai_stop());
ESP_LOGI(EXAMPLE_TAG, "Driver stopped");
vTaskDelay(pdMS_TO_TICKS(ITER_DELAY_MS));
}
//Stop TX and RX tasks
tx_action = TX_TASK_EXIT;
rx_action = RX_TASK_EXIT;
xQueueSend(tx_task_queue, &tx_action, portMAX_DELAY);
xQueueSend(rx_task_queue, &rx_action, portMAX_DELAY);
//Delete Control task
xSemaphoreGive(done_sem);
vTaskDelete(NULL);
}
void app_main(void)
{
//Add short delay to allow master it to initialize first
for (int i = 3; i > 0; i--) {
printf("Slave starting in %d\n", i);
vTaskDelay(pdMS_TO_TICKS(1000));
}
//Create semaphores and tasks
tx_task_queue = xQueueCreate(1, sizeof(tx_task_action_t));
rx_task_queue = xQueueCreate(1, sizeof(rx_task_action_t));
ctrl_task_sem = xSemaphoreCreateBinary();
stop_data_sem = xSemaphoreCreateBinary();
done_sem = xSemaphoreCreateBinary();
xTaskCreatePinnedToCore(twai_receive_task, "TWAI_rx", 4096, NULL, RX_TASK_PRIO, NULL, tskNO_AFFINITY);
xTaskCreatePinnedToCore(twai_transmit_task, "TWAI_tx", 4096, NULL, TX_TASK_PRIO, NULL, tskNO_AFFINITY);
xTaskCreatePinnedToCore(twai_control_task, "TWAI_ctrl", 4096, NULL, CTRL_TSK_PRIO, NULL, tskNO_AFFINITY);
//Install TWAI driver, trigger tasks to start
ESP_ERROR_CHECK(twai_driver_install(&g_config, &t_config, &f_config));
ESP_LOGI(EXAMPLE_TAG, "Driver installed");
xSemaphoreGive(ctrl_task_sem); //Start Control task
xSemaphoreTake(done_sem, portMAX_DELAY); //Wait for tasks to complete
//Uninstall TWAI driver
ESP_ERROR_CHECK(twai_driver_uninstall());
ESP_LOGI(EXAMPLE_TAG, "Driver uninstalled");
//Cleanup
vSemaphoreDelete(ctrl_task_sem);
vSemaphoreDelete(stop_data_sem);
vSemaphoreDelete(done_sem);
vQueueDelete(tx_task_queue);
vQueueDelete(rx_task_queue);
}

View File

@@ -5,4 +5,4 @@ cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
idf_build_set_property(MINIMAL_BUILD ON)
project(twai_network_master)
project(twai_sender)

View File

@@ -0,0 +1,5 @@
idf_component_register(
SRCS "twai_sender.c"
REQUIRES esp_driver_twai esp_timer
INCLUDE_DIRS "."
)

View File

@@ -0,0 +1,17 @@
menu "Example Configuration"
config EXAMPLE_TWAI_TX_GPIO
int "TWAI TX GPIO Num"
range 0 SOC_GPIO_OUT_RANGE_MAX
default 4
help
GPIO number for TWAI TX to transceiver.
config EXAMPLE_TWAI_RX_GPIO
int "TWAI RX GPIO Num"
range 0 SOC_GPIO_IN_RANGE_MAX
default 5
help
GPIO number for TWAI RX from transceiver.
endmenu

View File

@@ -0,0 +1,139 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <string.h>
#include <sys/param.h>
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "esp_log.h"
#include "esp_timer.h"
#include "esp_twai.h"
#include "esp_twai_onchip.h"
#define TWAI_SENDER_TX_GPIO CONFIG_EXAMPLE_TWAI_TX_GPIO
#define TWAI_SENDER_RX_GPIO CONFIG_EXAMPLE_TWAI_RX_GPIO
#define TWAI_QUEUE_DEPTH 10
#define TWAI_BITRATE 1000000
// Message IDs
#define TWAI_DATA_ID 0x100
#define TWAI_HEARTBEAT_ID 0x7FF
#define TWAI_DATA_LEN 1000
static const char *TAG = "twai_sender";
typedef struct {
twai_frame_t frame;
uint8_t data[TWAI_FRAME_MAX_LEN];
} twai_sender_data_t;
// Transmission completion callback
static IRAM_ATTR bool twai_sender_tx_done_callback(twai_node_handle_t handle, const twai_tx_done_event_data_t *edata, void *user_ctx)
{
BaseType_t woken;
SemaphoreHandle_t *tx_semaphore = (SemaphoreHandle_t *)user_ctx;
if (!edata->is_tx_success) {
ESP_EARLY_LOGW(TAG, "Failed to transmit message, ID: 0x%X", edata->done_tx_frame->header.id);
}
xSemaphoreGiveFromISR(*tx_semaphore, &woken);
return (woken == pdTRUE);
}
// Bus error callback
static IRAM_ATTR bool twai_sender_on_error_callback(twai_node_handle_t handle, const twai_error_event_data_t *edata, void *user_ctx)
{
ESP_EARLY_LOGW(TAG, "TWAI node error: 0x%x", edata->err_flags.val);
return false; // No task wake required
}
void app_main(void)
{
twai_node_handle_t sender_node = NULL;
SemaphoreHandle_t tx_semaphore = NULL;
printf("===================TWAI Sender Example Starting...===================\n");
// Configure TWAI node
twai_onchip_node_config_t node_config = {
.io_cfg = {
.tx = TWAI_SENDER_TX_GPIO,
.rx = TWAI_SENDER_RX_GPIO,
.quanta_clk_out = -1,
.bus_off_indicator = -1,
},
.bit_timing = {
.bitrate = TWAI_BITRATE,
},
.fail_retry_cnt = 3,
.tx_queue_depth = TWAI_QUEUE_DEPTH,
};
// Create TWAI node
ESP_ERROR_CHECK(twai_new_node_onchip(&node_config, &sender_node));
// Register transmission completion callback
twai_event_callbacks_t callbacks = {
.on_tx_done = twai_sender_tx_done_callback,
.on_error = twai_sender_on_error_callback,
};
ESP_ERROR_CHECK(twai_node_register_event_callbacks(sender_node, &callbacks, &tx_semaphore));
// Enable TWAI node
ESP_ERROR_CHECK(twai_node_enable(sender_node));
// Create semaphore for transmission completion
tx_semaphore = xSemaphoreCreateCounting(howmany(TWAI_DATA_LEN, TWAI_FRAME_MAX_LEN), 0);
assert(tx_semaphore != NULL);
ESP_LOGI(TAG, "TWAI Sender started successfully");
ESP_LOGI(TAG, "Sending messages with IDs: 0x%03X (data), 0x%03X (heartbeat)", TWAI_DATA_ID, TWAI_HEARTBEAT_ID);
while (1) {
// Send heartbeat message
uint64_t timestamp = esp_timer_get_time();
twai_frame_t tx_frame = {
.header.id = TWAI_HEARTBEAT_ID,
.buffer = (uint8_t *) &timestamp,
.buffer_len = sizeof(timestamp),
};
ESP_ERROR_CHECK(twai_node_transmit(sender_node, &tx_frame, 500));
ESP_LOGI(TAG, "Sending heartbeat message: %lld", timestamp);
xSemaphoreTake(tx_semaphore, portMAX_DELAY);
// Send burst data messages every 10 seconds
if ((timestamp / 1000000) % 10 == 0) {
int num_frames = howmany(TWAI_DATA_LEN, TWAI_FRAME_MAX_LEN);
twai_sender_data_t *data = (twai_sender_data_t *)calloc(num_frames, sizeof(twai_sender_data_t));
assert(data != NULL);
ESP_LOGI(TAG, "Sending packet of %d bytes in %d frames", TWAI_DATA_LEN, num_frames);
for (int i = 0; i < num_frames; i++) {
data[i].frame.header.id = TWAI_DATA_ID;
data[i].frame.buffer = data[i].data;
data[i].frame.buffer_len = TWAI_FRAME_MAX_LEN;
memset(data[i].data, i, TWAI_FRAME_MAX_LEN);
ESP_ERROR_CHECK(twai_node_transmit(sender_node, &data[i].frame, 500));
}
// Frames mounted, wait for all frames to be transmitted
for (int i = 0; i < num_frames; i++) {
xSemaphoreTake(tx_semaphore, portMAX_DELAY);
}
free(data);
}
vTaskDelay(pdMS_TO_TICKS(1000));
twai_node_status_t status;
twai_node_get_info(sender_node, &status, NULL);
if (status.state == TWAI_ERROR_BUS_OFF) {
ESP_LOGW(TAG, "Bus-off detected");
return;
}
}
vSemaphoreDelete(tx_semaphore);
ESP_ERROR_CHECK(twai_node_disable(sender_node));
ESP_ERROR_CHECK(twai_node_delete(sender_node));
}