test(driver_twai): test app rename and add cache safe test

This commit is contained in:
wanckl
2025-04-30 10:38:59 +08:00
parent 8a60934c4f
commit 00c78197df
26 changed files with 686 additions and 365 deletions
@@ -0,0 +1,21 @@
# This is the project CMakeLists.txt file for the test subproject
cmake_minimum_required(VERSION 3.16)
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
set(COMPONENTS main)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(twai_test)
if(CONFIG_COMPILER_DUMP_RTL_FILES)
add_custom_target(check_test_app_sections ALL
COMMAND ${PYTHON} $ENV{IDF_PATH}/tools/ci/check_callgraph.py
--rtl-dirs ${CMAKE_BINARY_DIR}/esp-idf/driver/,${CMAKE_BINARY_DIR}/esp-idf/hal/
--elf-file ${CMAKE_BINARY_DIR}/twai_test.elf
find-refs
--from-sections=.iram0.text
--to-sections=.flash.text,.flash.rodata
--exit-code
DEPENDS ${elf}
)
endif()
@@ -0,0 +1,8 @@
| Supported Targets | ESP32 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-P4 | ESP32-S2 | ESP32-S3 |
| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | -------- |
# Enable Socket CAN Device with bitrate 250Kbps
```bash
sudo ip link set can0 up type can bitrate 250000 restart-ms 100
```
@@ -0,0 +1,9 @@
set(srcs "test_app_main.c"
"test_twai_loop_back.c"
"test_twai_interactive.c")
# In order for the cases defined by `TEST_CASE` to be linked into the final elf,
# the component can be registered as WHOLE_ARCHIVE
idf_component_register(SRCS ${srcs}
PRIV_REQUIRES unity driver
WHOLE_ARCHIVE)
@@ -0,0 +1,39 @@
/*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "unity.h"
#include "unity_test_utils.h"
#include "esp_heap_caps.h"
#include "esp_newlib.h"
// Some resources are lazy allocated in the TWAI driver, the threshold is left for that case
#define TEST_MEMORY_LEAK_THRESHOLD (200)
void setUp(void)
{
unity_utils_record_free_mem();
}
void tearDown(void)
{
esp_reent_cleanup(); //clean up some of the newlib's lazy allocations
unity_utils_evaluate_leaks_direct(TEST_MEMORY_LEAK_THRESHOLD);
}
void app_main(void)
{
// _______ ___ ___ _____ _
// |_ _\ \ / / \ |_ _| |_ _|__ ___| |_
// | | \ \ /\ / / _ \ | | | |/ _ \/ __| __|
// | | \ V V / ___ \ | | | | __/\__ \ |_
// |_| \_/\_/_/ \_\___| |_|\___||___/\__|
printf(" _______ ___ ___ _____ _\n");
printf("|_ _\\ \\ / / \\ |_ _| |_ _|__ ___| |_\n");
printf(" | | \\ \\ /\\ / / _ \\ | | | |/ _ \\/ __| __|\n");
printf(" | | \\ V V / ___ \\ | | | | __/\\__ \\ |_\n");
printf(" |_| \\_/\\_/_/ \\_\\___| |_|\\___||___/\\__|\n");
unity_run_menu();
}
@@ -0,0 +1,92 @@
/*
* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <inttypes.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_attr.h"
#include "unity.h"
#include "unity_test_utils.h"
#include "driver/twai.h"
#include "soc/soc_caps.h"
#include "esp_log.h"
#define TEST_TWAI_TX_PIN 4
#define TEST_TWAI_RX_PIN 5
#if CONFIG_TWAI_ISR_IN_IRAM
static void IRAM_ATTR test_delay_post_cache_disable(void *args)
{
esp_rom_delay_us(1000);
}
#endif
TEST_CASE("twai_listen_only", "[twai]")
{
twai_timing_config_t t_config = TWAI_TIMING_CONFIG_250KBITS();
twai_filter_config_t f_config = TWAI_FILTER_CONFIG_ACCEPT_ALL();
twai_general_config_t g_config = TWAI_GENERAL_CONFIG_DEFAULT(TEST_TWAI_TX_PIN, TEST_TWAI_RX_PIN, TWAI_MODE_LISTEN_ONLY);
#if CONFIG_TWAI_ISR_IN_IRAM
g_config.intr_flags |= ESP_INTR_FLAG_IRAM;
#endif
// listen only mode doesn't need a tx queue
g_config.tx_queue_len = 0;
TEST_ESP_OK(twai_driver_install(&g_config, &t_config, &f_config));
TEST_ESP_OK(twai_start());
#if CONFIG_TWAI_ISR_IN_IRAM
printf("disable flash cache and check if we can still receive the frame\n");
for (int i = 0; i < 100; i++) {
unity_utils_run_cache_disable_stub(test_delay_post_cache_disable, NULL);
}
#endif
uint8_t expected_data[8] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88};
twai_message_t rx_msg;
TEST_ESP_OK(twai_receive(&rx_msg, portMAX_DELAY));
TEST_ASSERT_EQUAL(0x123, rx_msg.identifier);
ESP_LOG_BUFFER_HEX("rx", rx_msg.data, rx_msg.data_length_code);
TEST_ASSERT_EQUAL_HEX8_ARRAY(expected_data, rx_msg.data, rx_msg.data_length_code);
TEST_ESP_OK(twai_stop());
TEST_ESP_OK(twai_driver_uninstall());
}
TEST_CASE("twai_remote_request", "[twai]")
{
twai_handle_t bus_handle;
twai_timing_config_t t_config = TWAI_TIMING_CONFIG_250KBITS();
twai_filter_config_t f_config = TWAI_FILTER_CONFIG_ACCEPT_ALL();
twai_general_config_t g_config = TWAI_GENERAL_CONFIG_DEFAULT(TEST_TWAI_TX_PIN, TEST_TWAI_RX_PIN, TWAI_MODE_NORMAL);
#if CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32P4
g_config.controller_id = 1;
#endif
TEST_ESP_OK(twai_driver_install_v2(&g_config, &t_config, &f_config, &bus_handle));
TEST_ESP_OK(twai_start_v2(bus_handle));
twai_message_t req_msg = {
.identifier = 0x6688,
.data_length_code = 8,
.rtr = true, // remote request
.extd = true,// extended ID
};
TEST_ESP_OK(twai_transmit_v2(bus_handle, &req_msg, portMAX_DELAY));
ESP_LOGI("TWAI", "send remote frame");
uint8_t expected_data[8] = {0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80};
twai_message_t res_msg;
TEST_ESP_OK(twai_receive_v2(bus_handle, &res_msg, portMAX_DELAY));
ESP_LOGI("TWAI", "receive with id %lx\n", res_msg.identifier);
TEST_ASSERT_EQUAL(0x6688, res_msg.identifier);
ESP_LOG_BUFFER_HEX("rx", res_msg.data, res_msg.data_length_code);
TEST_ASSERT_EQUAL_HEX8_ARRAY(expected_data, res_msg.data, res_msg.data_length_code);
TEST_ESP_OK(twai_stop_v2(bus_handle));
TEST_ESP_OK(twai_driver_uninstall_v2(bus_handle));
}
@@ -0,0 +1,222 @@
/*
* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "unity.h"
#include "driver/twai.h"
#include "soc/soc_caps.h"
#include "esp_attr.h"
#include "esp_private/sleep_cpu.h"
#include "esp_private/esp_sleep_internal.h"
#include "esp_private/esp_pmu.h"
TEST_CASE("driver_life_cycle", "[twai-loop-back]")
{
twai_timing_config_t t_config = TWAI_TIMING_CONFIG_100KBITS();
twai_filter_config_t f_config = TWAI_FILTER_CONFIG_ACCEPT_ALL();
twai_general_config_t g_config = TWAI_GENERAL_CONFIG_DEFAULT(0, 0, TWAI_MODE_NO_ACK);
printf("install driver\r\n");
TEST_ESP_OK(twai_driver_install(&g_config, &t_config, &f_config));
// can't install the driver multiple times
TEST_ESP_ERR(ESP_ERR_INVALID_STATE, twai_driver_install(&g_config, &t_config, &f_config));
printf("start driver\r\n");
TEST_ESP_OK(twai_start());
// can't start the driver again if it's already in running state
TEST_ESP_ERR(ESP_ERR_INVALID_STATE, twai_start());
// can't uninstall the driver before stopping it
TEST_ESP_ERR(ESP_ERR_INVALID_STATE, twai_driver_uninstall());
printf("stop driver\r\n");
TEST_ESP_OK(twai_stop());
printf("uninstall driver\r\n");
TEST_ESP_OK(twai_driver_uninstall());
}
TEST_CASE("twai_bit_timing", "[twai-loop-back]")
{
twai_filter_config_t f_config = TWAI_FILTER_CONFIG_ACCEPT_ALL();
twai_general_config_t g_config = TWAI_GENERAL_CONFIG_DEFAULT(0, 0, TWAI_MODE_NO_ACK);
twai_timing_config_t t_config = {
.quanta_resolution_hz = 33333, // invalid resolution
.tseg_1 = 15,
.tseg_2 = 4,
.sjw = 1,
};
TEST_ESP_ERR(ESP_ERR_INVALID_ARG, twai_driver_install(&g_config, &t_config, &f_config));
t_config.quanta_resolution_hz = 2000000;
TEST_ESP_OK(twai_driver_install(&g_config, &t_config, &f_config));
TEST_ESP_OK(twai_driver_uninstall());
}
TEST_CASE("twai_mode_std_no_ack_25kbps", "[twai-loop-back]")
{
twai_timing_config_t t_config = TWAI_TIMING_CONFIG_25KBITS();
twai_filter_config_t f_config = TWAI_FILTER_CONFIG_ACCEPT_ALL();
// bind the TX and RX to the same GPIO to act like a loopback
twai_general_config_t g_config = TWAI_GENERAL_CONFIG_DEFAULT(0, 0, TWAI_MODE_NO_ACK);
printf("install twai driver\r\n");
TEST_ESP_OK(twai_driver_install(&g_config, &t_config, &f_config));
TEST_ESP_OK(twai_start());
twai_message_t tx_msg = {
.identifier = 0x123,
.data_length_code = 8,
.data = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88},
.self = true, // Transmitted message will also received by the same node
};
printf("transmit message\r\n");
TEST_ESP_OK(twai_transmit(&tx_msg, pdMS_TO_TICKS(1000)));
printf("receive message\r\n");
twai_message_t rx_msg;
TEST_ESP_OK(twai_receive(&rx_msg, pdMS_TO_TICKS(1000)));
TEST_ASSERT_TRUE(rx_msg.data_length_code == 8);
for (int i = 0; i < 8; i++) {
TEST_ASSERT_EQUAL(tx_msg.data[i], rx_msg.data[i]);
}
TEST_ESP_OK(twai_stop());
TEST_ESP_OK(twai_driver_uninstall());
}
TEST_CASE("twai_mode_ext_no_ack_250kbps", "[twai-loop-back]")
{
twai_handle_t twai_buses[SOC_TWAI_CONTROLLER_NUM] = {0};
twai_timing_config_t t_config = TWAI_TIMING_CONFIG_250KBITS();
twai_filter_config_t f_config = TWAI_FILTER_CONFIG_ACCEPT_ALL();
// bind the TX and RX to the same GPIO to act like a loopback
twai_general_config_t g_config = TWAI_GENERAL_CONFIG_DEFAULT(0, 0, TWAI_MODE_NO_ACK);
twai_message_t tx_msg = {
.identifier = 0x12345,
.data_length_code = 6,
.data = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66},
.self = true, // Transmitted message will also received by the same node
.extd = true, // Extended Frame Format (29bit ID)
};
for (int i = 0; i < SOC_TWAI_CONTROLLER_NUM; i++) {
g_config.controller_id = i;
g_config.tx_io = i;
g_config.rx_io = i;
printf("install twai driver %d\r\n", g_config.controller_id);
TEST_ESP_OK(twai_driver_install_v2(&g_config, &t_config, &f_config, &twai_buses[i]));
TEST_ESP_OK(twai_start_v2(twai_buses[i]));
}
for (int i = 0; i < SOC_TWAI_CONTROLLER_NUM; i++) {
printf("transmit message from %d\r\n", i);
tx_msg.data[5] = SOC_TWAI_CONTROLLER_NUM - i;
TEST_ESP_OK(twai_transmit_v2(twai_buses[i], &tx_msg, pdMS_TO_TICKS(1000)));
}
twai_message_t rx_msg;
for (int i = 0; i < SOC_TWAI_CONTROLLER_NUM; i++) {
printf("receive message from %d\r\n", i);
TEST_ESP_OK(twai_receive_v2(twai_buses[i], &rx_msg, pdMS_TO_TICKS(1000)));
TEST_ASSERT_TRUE(rx_msg.data_length_code == 6);
tx_msg.data[5] = SOC_TWAI_CONTROLLER_NUM - i;
for (int i = 0; i < 6; i++) {
TEST_ASSERT_EQUAL(tx_msg.data[i], rx_msg.data[i]);
}
}
for (int i = 0; i < SOC_TWAI_CONTROLLER_NUM; i++) {
TEST_ESP_OK(twai_stop_v2(twai_buses[i]));
TEST_ESP_OK(twai_driver_uninstall_v2(twai_buses[i]));
}
}
static void s_test_sleep_retention(bool allow_pd)
{
// Prepare a TOP PD sleep
TEST_ESP_OK(esp_sleep_enable_timer_wakeup(1 * 1000 * 1000));
#if ESP_SLEEP_POWER_DOWN_CPU
TEST_ESP_OK(sleep_cpu_configure(true));
#endif
esp_sleep_context_t sleep_ctx;
esp_sleep_set_sleep_context(&sleep_ctx);
twai_handle_t twai_buses[SOC_TWAI_CONTROLLER_NUM] = {0};
twai_timing_config_t t_config = TWAI_TIMING_CONFIG_250KBITS();
twai_filter_config_t f_config = TWAI_FILTER_CONFIG_ACCEPT_ALL();
// bind the TX and RX to the same GPIO to act like a loopback
twai_general_config_t g_config = TWAI_GENERAL_CONFIG_DEFAULT(0, 0, TWAI_MODE_NO_ACK);
g_config.general_flags.sleep_allow_pd = allow_pd;
twai_message_t tx_msg = {
.identifier = 0x12345,
.data_length_code = 6,
.data = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66},
.self = true, // Transmitted message will also received by the same node
.extd = true, // Extended Frame Format (29bit ID)
};
for (int i = 0; i < SOC_TWAI_CONTROLLER_NUM; i++) {
g_config.controller_id = i;
g_config.tx_io = i;
g_config.rx_io = i;
printf("install twai driver %d\r\n", g_config.controller_id);
TEST_ESP_OK(twai_driver_install_v2(&g_config, &t_config, &f_config, &twai_buses[i]));
}
printf("Going into sleep...\n");
TEST_ESP_OK(esp_light_sleep_start());
printf("Waked up!\n");
// check if the sleep happened as expected
TEST_ASSERT_EQUAL(0, sleep_ctx.sleep_request_result);
#if SOC_TWAI_SUPPORT_SLEEP_RETENTION && CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP
// check if the power domain also is powered down
TEST_ASSERT_EQUAL(allow_pd ? PMU_SLEEP_PD_TOP : 0, (sleep_ctx.sleep_flags) & PMU_SLEEP_PD_TOP);
#endif
for (int i = 0; i < SOC_TWAI_CONTROLLER_NUM; i++) {
TEST_ESP_OK(twai_start_v2(twai_buses[i]));
}
for (int i = 0; i < SOC_TWAI_CONTROLLER_NUM; i++) {
printf("transmit message from %d\r\n", i);
tx_msg.data[5] = SOC_TWAI_CONTROLLER_NUM - i;
TEST_ESP_OK(twai_transmit_v2(twai_buses[i], &tx_msg, pdMS_TO_TICKS(1000)));
}
twai_message_t rx_msg;
for (int i = 0; i < SOC_TWAI_CONTROLLER_NUM; i++) {
printf("receive message from %d\r\n", i);
TEST_ESP_OK(twai_receive_v2(twai_buses[i], &rx_msg, pdMS_TO_TICKS(1000)));
TEST_ASSERT_TRUE(rx_msg.data_length_code == 6);
tx_msg.data[5] = SOC_TWAI_CONTROLLER_NUM - i;
for (int i = 0; i < 6; i++) {
TEST_ASSERT_EQUAL(tx_msg.data[i], rx_msg.data[i]);
}
}
for (int i = 0; i < SOC_TWAI_CONTROLLER_NUM; i++) {
TEST_ESP_OK(twai_stop_v2(twai_buses[i]));
TEST_ESP_OK(twai_driver_uninstall_v2(twai_buses[i]));
}
esp_sleep_set_sleep_context(NULL);
#if ESP_SLEEP_POWER_DOWN_CPU
TEST_ESP_OK(sleep_cpu_configure(false));
#endif
}
#if SOC_TWAI_SUPPORT_SLEEP_RETENTION
TEST_CASE("twai_mode_ext_no_ack_250kbps with sleep retention (allow pd)", "[twai-loop-back]")
{
s_test_sleep_retention(true);
}
#endif
TEST_CASE("twai_mode_ext_no_ack_250kbps with sleep retention (no pd)", "[twai-loop-back]")
{
s_test_sleep_retention(false);
}
@@ -0,0 +1,105 @@
# SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: CC0-1.0
import logging
import subprocess
from time import sleep
import pytest
from can import Bus
from can import Message
from pytest_embedded import Dut
from pytest_embedded_idf.utils import idf_parametrize
@pytest.mark.generic
@pytest.mark.parametrize(
'config',
[
'release',
],
indirect=True,
)
@idf_parametrize(
'target', ['esp32', 'esp32c3', 'esp32c6', 'esp32h2', 'esp32s2', 'esp32s3', 'esp32p4'], indirect=['target']
)
def test_twai_self(dut: Dut) -> None:
dut.run_all_single_board_cases(group='twai-loop-back')
@pytest.fixture(name='socket_can', scope='module')
def fixture_create_socket_can() -> Bus:
# Set up the socket CAN with the bitrate
start_command = 'sudo ip link set can0 up type can bitrate 250000 restart-ms 100'
stop_command = 'sudo ip link set can0 down'
subprocess.run(start_command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
bus = Bus(interface='socketcan', channel='can0', bitrate=250000)
yield bus # test invoked here
bus.shutdown()
subprocess.run(stop_command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
@pytest.mark.twai_std
@pytest.mark.parametrize(
'config',
[
'iram_safe',
],
indirect=True,
)
@idf_parametrize(
'target', ['esp32', 'esp32c3', 'esp32c6', 'esp32h2', 'esp32s2', 'esp32s3', 'esp32p4'], indirect=['target']
)
def test_twai_listen_only(dut: Dut, socket_can: Bus) -> None:
dut.serial.hard_reset()
dut.expect_exact('Press ENTER to see the list of tests')
# TEST_CASE("twai_listen_only", "[twai]")
dut.write('"twai_listen_only"')
# wait the DUT to block at the receive API
sleep(0.03)
message = Message(
arbitration_id=0x123,
is_extended_id=False,
data=[0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88],
)
socket_can.send(message, timeout=0.2)
dut.expect_unity_test_output()
@pytest.mark.twai_std
@pytest.mark.parametrize(
'config',
[
'release',
],
indirect=True,
)
@idf_parametrize(
'target', ['esp32', 'esp32c3', 'esp32c6', 'esp32h2', 'esp32s2', 'esp32s3', 'esp32p4'], indirect=['target']
)
def test_twai_remote_request(dut: Dut, socket_can: Bus) -> None:
dut.serial.hard_reset()
dut.expect_exact('Press ENTER to see the list of tests')
# TEST_CASE("twai_remote_request", "[twai]")
dut.write('"twai_remote_request"')
while True:
req = socket_can.recv(timeout=0.2)
# wait for the remote request frame
if req is not None and req.is_remote_frame:
break
logging.info(f'Received message: {req}')
reply = Message(
arbitration_id=req.arbitration_id,
is_extended_id=req.is_extended_id,
data=[0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80],
)
socket_can.send(reply, timeout=0.2)
print('send', reply)
dut.expect_unity_test_output()
@@ -0,0 +1,12 @@
CONFIG_COMPILER_DUMP_RTL_FILES=y
CONFIG_TWAI_ISR_IN_IRAM=y
CONFIG_COMPILER_OPTIMIZATION_NONE=y
# silent the error check, as the error string are stored in rodata, causing RTL check failure
CONFIG_COMPILER_OPTIMIZATION_CHECKS_SILENT=y
# place non-ISR FreeRTOS functions in Flash
CONFIG_FREERTOS_PLACE_FUNCTIONS_INTO_FLASH=y
# twai driver uses assert in the ISR code path
CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_DISABLE=y
@@ -0,0 +1,7 @@
CONFIG_PM_ENABLE=y
CONFIG_FREERTOS_USE_TICKLESS_IDLE=y
CONFIG_COMPILER_OPTIMIZATION_SIZE=y
CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y
CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT=y
CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP=y
@@ -0,0 +1,4 @@
CONFIG_FREERTOS_HZ=1000
CONFIG_ESP_TASK_WDT_INIT=n
# primitives for checking sleep internal state
CONFIG_ESP_SLEEP_DEBUG=y