mirror of
https://github.com/espressif/esp-idf.git
synced 2025-09-18 07:47:18 +00:00
feat(driver_twai): update and simplify network example using new driver
This commit is contained in:
@@ -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:
|
||||
|
@@ -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()`
|
||||
|
107
examples/peripherals/twai/twai_network/pytest_twai_network.py
Normal file
107
examples/peripherals/twai/twai_network/pytest_twai_network.py
Normal 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')
|
@@ -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')
|
@@ -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)
|
@@ -0,0 +1,5 @@
|
||||
idf_component_register(
|
||||
SRCS "twai_listen_only.c"
|
||||
REQUIRES esp_driver_twai
|
||||
INCLUDE_DIRS "."
|
||||
)
|
@@ -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
|
@@ -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));
|
||||
}
|
@@ -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)
|
@@ -1,3 +0,0 @@
|
||||
idf_component_register(SRCS "twai_network_example_listen_only_main.c"
|
||||
REQUIRES driver
|
||||
INCLUDE_DIRS ".")
|
@@ -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
|
@@ -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);
|
||||
}
|
@@ -1,3 +0,0 @@
|
||||
idf_component_register(SRCS "twai_network_example_master_main.c"
|
||||
REQUIRES driver
|
||||
INCLUDE_DIRS ".")
|
@@ -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
|
@@ -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);
|
||||
}
|
@@ -1,3 +0,0 @@
|
||||
idf_component_register(SRCS "twai_network_example_slave_main.c"
|
||||
REQUIRES driver
|
||||
INCLUDE_DIRS ".")
|
@@ -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
|
@@ -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);
|
||||
}
|
@@ -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)
|
@@ -0,0 +1,5 @@
|
||||
idf_component_register(
|
||||
SRCS "twai_sender.c"
|
||||
REQUIRES esp_driver_twai esp_timer
|
||||
INCLUDE_DIRS "."
|
||||
)
|
@@ -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
|
@@ -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 *) ×tamp,
|
||||
.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));
|
||||
}
|
Reference in New Issue
Block a user