diff --git a/components/driver/.build-test-rules.yml b/components/driver/.build-test-rules.yml index f5ec2a0dfd..3cbdbaecc6 100644 --- a/components/driver/.build-test-rules.yml +++ b/components/driver/.build-test-rules.yml @@ -72,3 +72,7 @@ components/driver/test_apps/touch_sensor_v1: components/driver/test_apps/touch_sensor_v2: disable: - if: SOC_TOUCH_VERSION_2 != 1 + +components/driver/test_apps/twai: + disable: + - if: SOC_TWAI_SUPPORTED != 1 diff --git a/components/driver/test_apps/twai/CMakeLists.txt b/components/driver/test_apps/twai/CMakeLists.txt new file mode 100644 index 0000000000..d1b8b52293 --- /dev/null +++ b/components/driver/test_apps/twai/CMakeLists.txt @@ -0,0 +1,18 @@ +# This is the project CMakeLists.txt file for the test subproject +cmake_minimum_required(VERSION 3.16) + +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-dir ${CMAKE_BINARY_DIR}/esp-idf/driver/ + --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() diff --git a/components/driver/test_apps/twai/README.md b/components/driver/test_apps/twai/README.md new file mode 100644 index 0000000000..4d24f6538c --- /dev/null +++ b/components/driver/test_apps/twai/README.md @@ -0,0 +1,8 @@ +| Supported Targets | ESP32 | ESP32-C3 | ESP32-C6 | 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 +``` diff --git a/components/driver/test_apps/twai/main/CMakeLists.txt b/components/driver/test_apps/twai/main/CMakeLists.txt new file mode 100644 index 0000000000..24a3bff0da --- /dev/null +++ b/components/driver/test_apps/twai/main/CMakeLists.txt @@ -0,0 +1,8 @@ +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} + WHOLE_ARCHIVE) diff --git a/components/driver/test_apps/twai/main/test_app_main.c b/components/driver/test_apps/twai/main/test_app_main.c new file mode 100644 index 0000000000..8d0972047f --- /dev/null +++ b/components/driver/test_apps/twai/main/test_app_main.c @@ -0,0 +1,51 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "unity.h" +#include "unity_test_runner.h" +#include "esp_heap_caps.h" + +// Some resources are lazy allocated in the TWAI driver, the threshold is left for that case +#define TEST_MEMORY_LEAK_THRESHOLD (-200) + +static size_t before_free_8bit; +static size_t before_free_32bit; + +static void check_leak(size_t before_free, size_t after_free, const char *type) +{ + ssize_t delta = after_free - before_free; + printf("MALLOC_CAP_%s: Before %u bytes free, After %u bytes free (delta %d)\n", type, before_free, after_free, delta); + TEST_ASSERT_MESSAGE(delta >= TEST_MEMORY_LEAK_THRESHOLD, "memory leak"); +} + +void setUp(void) +{ + before_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT); + before_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT); +} + +void tearDown(void) +{ + size_t after_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT); + size_t after_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT); + check_leak(before_free_8bit, after_free_8bit, "8BIT"); + check_leak(before_free_32bit, after_free_32bit, "32BIT"); +} + +void app_main(void) +{ + // _______ ___ ___ _____ _ + // |_ _\ \ / / \ |_ _| |_ _|__ ___| |_ + // | | \ \ /\ / / _ \ | | | |/ _ \/ __| __| + // | | \ V V / ___ \ | | | | __/\__ \ |_ + // |_| \_/\_/_/ \_\___| |_|\___||___/\__| + printf(" _______ ___ ___ _____ _\r\n"); + printf("|_ _\\ \\ / / \\ |_ _| |_ _|__ ___| |_\r\n"); + printf(" | | \\ \\ /\\ / / _ \\ | | | |/ _ \\/ __| __|\r\n"); + printf(" | | \\ V V / ___ \\ | | | | __/\\__ \\ |_\r\n"); + printf(" |_| \\_/\\_/_/ \\_\\___| |_|\\___||___/\\__|\r\n"); + unity_run_menu(); +} diff --git a/components/driver/test_apps/twai/main/test_twai_interactive.c b/components/driver/test_apps/twai/main/test_twai_interactive.c new file mode 100644 index 0000000000..d72a0e0847 --- /dev/null +++ b/components/driver/test_apps/twai/main/test_twai_interactive.c @@ -0,0 +1,85 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#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_attr.h" + +#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(0, 2, 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\r\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); + for (int i = 0; i < rx_msg.data_length_code; i++) { + TEST_ASSERT_EQUAL_HEX8(expected_data[i], rx_msg.data[i]); + } + + TEST_ESP_OK(twai_stop()); + TEST_ESP_OK(twai_driver_uninstall()); +} + +TEST_CASE("twai_remote_request", "[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(0, 2, TWAI_MODE_NORMAL); + TEST_ESP_OK(twai_driver_install(&g_config, &t_config, &f_config)); + TEST_ESP_OK(twai_start()); + + twai_message_t req_msg = { + .identifier = 0x6688, + .data_length_code = 8, + .rtr = true, // remote request + .extd = true,// extended ID + }; + TEST_ESP_OK(twai_transmit(&req_msg, portMAX_DELAY)); + + uint8_t expected_data[8] = {0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80}; + twai_message_t res_msg; + TEST_ESP_OK(twai_receive(&res_msg, portMAX_DELAY)); + TEST_ASSERT_EQUAL(0x6688, res_msg.identifier); + for (int i = 0; i < 8; i++) { + TEST_ASSERT_EQUAL_HEX8(expected_data[i], res_msg.data[i]); + } + + TEST_ESP_OK(twai_stop()); + TEST_ESP_OK(twai_driver_uninstall()); +} diff --git a/components/driver/test_apps/twai/main/test_twai_loop_back.c b/components/driver/test_apps/twai/main/test_twai_loop_back.c new file mode 100644 index 0000000000..e5612f018f --- /dev/null +++ b/components/driver/test_apps/twai/main/test_twai_loop_back.c @@ -0,0 +1,117 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#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" + +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_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); + 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 = 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) + }; + 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 == 6); + for (int i = 0; i < 6; i++) { + TEST_ASSERT_EQUAL(tx_msg.data[i], rx_msg.data[i]); + } + + TEST_ESP_OK(twai_stop()); + TEST_ESP_OK(twai_driver_uninstall()); +} diff --git a/components/driver/test_apps/twai/pytest_twai.py b/components/driver/test_apps/twai/pytest_twai.py new file mode 100644 index 0000000000..870c2727ce --- /dev/null +++ b/components/driver/test_apps/twai/pytest_twai.py @@ -0,0 +1,104 @@ +# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: CC0-1.0 + +import logging +from time import sleep + +import pytest +from can import Bus, Message +from pytest_embedded import Dut + + +@pytest.mark.esp32 +@pytest.mark.esp32c3 +@pytest.mark.esp32c6 +@pytest.mark.esp32s2 +@pytest.mark.esp32s3 +@pytest.mark.generic +@pytest.mark.parametrize( + 'config', + [ + 'release', + ], + indirect=True, +) +def test_twai_self(dut: Dut) -> None: + dut.expect_exact('Press ENTER to see the list of tests') + dut.write('[twai-loop-back]') + dut.expect_unity_test_output() + + +@pytest.fixture(name='socket_can', scope='module') +def fixture_create_socket_can() -> Bus: + # See README.md for instructions on how to set up the socket CAN with the bitrate + bus = Bus(interface='socketcan', channel='can0', bitrate=250000) + yield bus + bus.shutdown() + + +@pytest.mark.esp32 +@pytest.mark.esp32c3 +@pytest.mark.esp32c6 +@pytest.mark.esp32s2 +@pytest.mark.esp32s3 +@pytest.mark.skip(reason='Runner not set up yet') +@pytest.mark.parametrize( + 'config', + [ + 'iram_safe', + ], + indirect=True, +) +def test_twai_listen_only(dut: Dut, socket_can: Bus) -> None: + 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.esp32 +@pytest.mark.esp32c3 +@pytest.mark.esp32c6 +@pytest.mark.esp32s2 +@pytest.mark.esp32s3 +@pytest.mark.skip(reason='Runner not set up yet') +@pytest.mark.parametrize( + 'config', + [ + 'release', + ], + indirect=True, +) +def test_twai_remote_request(dut: Dut, socket_can: Bus) -> None: + 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) + + dut.expect_unity_test_output() diff --git a/components/driver/test_apps/twai/sdkconfig.ci.iram_safe b/components/driver/test_apps/twai/sdkconfig.ci.iram_safe new file mode 100644 index 0000000000..c0060c0887 --- /dev/null +++ b/components/driver/test_apps/twai/sdkconfig.ci.iram_safe @@ -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 diff --git a/components/driver/test_apps/twai/sdkconfig.ci.release b/components/driver/test_apps/twai/sdkconfig.ci.release new file mode 100644 index 0000000000..91d93f163e --- /dev/null +++ b/components/driver/test_apps/twai/sdkconfig.ci.release @@ -0,0 +1,5 @@ +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 diff --git a/components/driver/test_apps/twai/sdkconfig.defaults b/components/driver/test_apps/twai/sdkconfig.defaults new file mode 100644 index 0000000000..b308cb2ddd --- /dev/null +++ b/components/driver/test_apps/twai/sdkconfig.defaults @@ -0,0 +1,2 @@ +CONFIG_FREERTOS_HZ=1000 +CONFIG_ESP_TASK_WDT=n diff --git a/tools/requirements/requirements.pytest.txt b/tools/requirements/requirements.pytest.txt index e576fb7f1a..a13ce3edfe 100644 --- a/tools/requirements/requirements.pytest.txt +++ b/tools/requirements/requirements.pytest.txt @@ -17,3 +17,6 @@ netifaces rangehttpserver dbus-python; sys_platform == 'linux' protobuf + +# for twai tests, communicate with socket can device (e.g. Canable) +python-can