From b8e8042c4e48c5ac00aeb17fa621d32d1e34a1be Mon Sep 17 00:00:00 2001 From: Cao Sen Miao Date: Tue, 28 Nov 2023 18:50:21 +0800 Subject: [PATCH 1/2] fix(usb_serial_jtag): Fix issue that buffer seems not flush when TX buffer is full and flush slow, Closes https://github.com/espressif/esp-idf/issues/12628 --- .../driver/usb_serial_jtag/usb_serial_jtag.c | 33 ++++++++++++++++--- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/components/driver/usb_serial_jtag/usb_serial_jtag.c b/components/driver/usb_serial_jtag/usb_serial_jtag.c index fc6292c1c8a8..85ac7b9b92f4 100644 --- a/components/driver/usb_serial_jtag/usb_serial_jtag.c +++ b/components/driver/usb_serial_jtag/usb_serial_jtag.c @@ -6,6 +6,7 @@ #include #include +#include #include "esp_log.h" #include "hal/usb_serial_jtag_ll.h" #include "hal/usb_phy_ll.h" @@ -25,12 +26,19 @@ #define USJ_RCC_ATOMIC() #endif +typedef enum { + FIFO_IDLE = 0, /*!< Indicates the fifo is in idle state */ + FIFO_BUSY = 1, /*!< Indicates the fifo is in busy state */ +} fifo_status_t; + // The hardware buffer max size is 64 #define USB_SER_JTAG_ENDP_SIZE (64) #define USB_SER_JTAG_RX_MAX_SIZE (64) typedef struct{ intr_handle_t intr_handle; /*!< USB-SERIAL-JTAG interrupt handler */ + portMUX_TYPE spinlock; /*!< Spinlock for usb_serial_jtag */ + _Atomic fifo_status_t fifo_status; /*!< Record the status of fifo */ // RX parameters RingbufHandle_t rx_ring_buf; /*!< RX ring buffer handler */ @@ -67,7 +75,7 @@ static void usb_serial_jtag_isr_handler_default(void *arg) { // If the hardware fifo is available, write in it. Otherwise, do nothing. if (usb_serial_jtag_ll_txfifo_writable() == 1) { // We disable the interrupt here so that the interrupt won't be triggered if there is no data to send. - usb_serial_jtag_ll_disable_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY); + size_t queued_size; uint8_t *queued_buff = NULL; bool is_stashed_data = false; @@ -91,7 +99,10 @@ static void usb_serial_jtag_isr_handler_default(void *arg) { // On ringbuffer wrap-around the size can be 0 even though the buffer returned is not NULL if (queued_size > 0) { + portENTER_CRITICAL_ISR(&p_usb_serial_jtag_obj->spinlock); + atomic_store(&p_usb_serial_jtag_obj->fifo_status, FIFO_BUSY); uint32_t sent_size = usb_serial_jtag_write_and_flush(queued_buff, queued_size); + portEXIT_CRITICAL_ISR(&p_usb_serial_jtag_obj->spinlock); if (sent_size < queued_size) { // Not all bytes could be sent at once; stash the unwritten bytes in a tx buffer @@ -108,7 +119,6 @@ static void usb_serial_jtag_isr_handler_default(void *arg) { if (is_stashed_data == false) { vRingbufferReturnItemFromISR(p_usb_serial_jtag_obj->tx_ring_buf, queued_buff, &xTaskWoken); } - usb_serial_jtag_ll_ena_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY); } else { // The last transmit may have sent a full EP worth of data. The host will interpret // this as a transaction that hasn't finished yet and keep the data in its internal @@ -119,6 +129,7 @@ static void usb_serial_jtag_isr_handler_default(void *arg) { // flush will not by itself cause this ISR to be called again. } } else { + atomic_store(&p_usb_serial_jtag_obj->fifo_status, FIFO_IDLE); usb_serial_jtag_ll_clr_intsts_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY); } } @@ -147,6 +158,7 @@ esp_err_t usb_serial_jtag_driver_install(usb_serial_jtag_driver_config_t *usb_se p_usb_serial_jtag_obj->rx_buf_size = usb_serial_jtag_config->rx_buffer_size; p_usb_serial_jtag_obj->tx_buf_size = usb_serial_jtag_config->tx_buffer_size; p_usb_serial_jtag_obj->tx_stash_cnt = 0; + p_usb_serial_jtag_obj->spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED; if (p_usb_serial_jtag_obj == NULL) { ESP_LOGE(USB_SERIAL_JTAG_TAG, "memory allocate error"); err = ESP_ERR_NO_MEM; @@ -171,6 +183,7 @@ esp_err_t usb_serial_jtag_driver_install(usb_serial_jtag_driver_config_t *usb_se USJ_RCC_ATOMIC() { usb_serial_jtag_ll_enable_bus_clock(true); } + atomic_store(&p_usb_serial_jtag_obj->fifo_status, FIFO_IDLE); // Configure PHY usb_phy_ll_int_jtag_enable(&USB_SERIAL_JTAG); @@ -220,10 +233,22 @@ int usb_serial_jtag_write_bytes(const void* src, size_t size, TickType_t ticks_t ESP_RETURN_ON_FALSE(src != NULL, ESP_ERR_INVALID_ARG, USB_SERIAL_JTAG_TAG, "Invalid buffer pointer."); ESP_RETURN_ON_FALSE(p_usb_serial_jtag_obj != NULL, ESP_ERR_INVALID_ARG, USB_SERIAL_JTAG_TAG, "The driver hasn't been initialized"); + size_t sent_data = 0; + BaseType_t result = pdTRUE; const uint8_t *buff = (const uint8_t *)src; + if (p_usb_serial_jtag_obj->fifo_status == FIFO_IDLE) { + portENTER_CRITICAL(&p_usb_serial_jtag_obj->spinlock); + atomic_store(&p_usb_serial_jtag_obj->fifo_status, FIFO_BUSY); + sent_data = usb_serial_jtag_write_and_flush(src, size); + portEXIT_CRITICAL(&p_usb_serial_jtag_obj->spinlock); + } + // Blocking method, Sending data to ringbuffer, and handle the data in ISR. - BaseType_t result = xRingbufferSend(p_usb_serial_jtag_obj->tx_ring_buf, (void*) (buff), size, ticks_to_wait); - // Now trigger the ISR to read data from the ring buffer. + if (size - sent_data > 0) { + result = xRingbufferSend(p_usb_serial_jtag_obj->tx_ring_buf, (void*) (buff+sent_data), size-sent_data, ticks_to_wait); + } else { + atomic_store(&p_usb_serial_jtag_obj->fifo_status, FIFO_IDLE); + } usb_serial_jtag_ll_ena_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY); return (result == pdFALSE) ? 0 : size; } From 2a0debde3cfe5c6ccdbd69e9d4e2beb01a315499 Mon Sep 17 00:00:00 2001 From: Cao Sen Miao Date: Tue, 28 Nov 2023 18:50:50 +0800 Subject: [PATCH 2/2] change(usb_serial_jtag): Add a usb_serial_jtag echo example for how to use usb_serial_jtag APIs, Closes https://github.com/espressif/esp-idf/issues/12620, Closes https://github.com/espressif/esp-idf/issues/12605 --- .gitlab/ci/target-test.yml | 9 +++ examples/peripherals/.build-test-rules.yml | 8 +++ .../usb_serial_jtag_echo/CMakeLists.txt | 6 ++ .../usb_serial_jtag_echo/README.md | 60 +++++++++++++++++++ .../usb_serial_jtag_echo/main/CMakeLists.txt | 2 + .../main/usb_serial_echo_main.c | 52 ++++++++++++++++ .../pytest_usj_echo_example.py | 49 +++++++++++++++ .../usb_serial_jtag_echo/sdkconfig.defaults | 1 + tools/ci/idf_pytest/constants.py | 1 + 9 files changed, 188 insertions(+) create mode 100644 examples/peripherals/usb_serial_jtag/usb_serial_jtag_echo/CMakeLists.txt create mode 100644 examples/peripherals/usb_serial_jtag/usb_serial_jtag_echo/README.md create mode 100644 examples/peripherals/usb_serial_jtag/usb_serial_jtag_echo/main/CMakeLists.txt create mode 100644 examples/peripherals/usb_serial_jtag/usb_serial_jtag_echo/main/usb_serial_echo_main.c create mode 100644 examples/peripherals/usb_serial_jtag/usb_serial_jtag_echo/pytest_usj_echo_example.py create mode 100644 examples/peripherals/usb_serial_jtag/usb_serial_jtag_echo/sdkconfig.defaults diff --git a/.gitlab/ci/target-test.yml b/.gitlab/ci/target-test.yml index 9f15c4c960ee..d8f8f9a4904a 100644 --- a/.gitlab/ci/target-test.yml +++ b/.gitlab/ci/target-test.yml @@ -229,6 +229,15 @@ pytest_examples_esp32c6_generic: artifacts: false tags: [ esp32c6, generic ] +pytest_examples_esp32c6_usj_device: + extends: + - .pytest_examples_dir_template + - .rules:test:example_test-esp32c6 + needs: + - job: build_pytest_examples_esp32c6 + artifacts: false + tags: [ esp32c6, usj_device ] + pytest_examples_esp32h2_generic: extends: - .pytest_examples_dir_template diff --git a/examples/peripherals/.build-test-rules.yml b/examples/peripherals/.build-test-rules.yml index 8372892050d9..b64f399b6f79 100644 --- a/examples/peripherals/.build-test-rules.yml +++ b/examples/peripherals/.build-test-rules.yml @@ -397,3 +397,11 @@ examples/peripherals/uart/uart_echo_rs485: examples/peripherals/usb: disable: - if: SOC_USB_OTG_SUPPORTED != 1 + +examples/peripherals/usb_serial_jtag/usb_serial_jtag_echo: + disable: + - if: SOC_USB_SERIAL_JTAG_SUPPORTED != 1 + disable_test: + - if: IDF_TARGET not in ["esp32c6"] + temporary: true + reason: lack of runners. Hardware is similar, test on one target is enough currently. diff --git a/examples/peripherals/usb_serial_jtag/usb_serial_jtag_echo/CMakeLists.txt b/examples/peripherals/usb_serial_jtag/usb_serial_jtag_echo/CMakeLists.txt new file mode 100644 index 000000000000..3b5cf5be013e --- /dev/null +++ b/examples/peripherals/usb_serial_jtag/usb_serial_jtag_echo/CMakeLists.txt @@ -0,0 +1,6 @@ +# 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) +project(usb_serial_jtag_echo) diff --git a/examples/peripherals/usb_serial_jtag/usb_serial_jtag_echo/README.md b/examples/peripherals/usb_serial_jtag/usb_serial_jtag_echo/README.md new file mode 100644 index 000000000000..1b053da468f6 --- /dev/null +++ b/examples/peripherals/usb_serial_jtag/usb_serial_jtag_echo/README.md @@ -0,0 +1,60 @@ +| Supported Targets | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-S3 | +| ----------------- | -------- | -------- | -------- | -------- | + +# USB SERIAL JTAG Echo Example + +(See the README.md file in the upper level 'examples' directory for more information about examples.) + +This example demonstrates how to utilize USB_SERIAL_JTAG interfaces by echoing back to the sender any data received on USB_SERIAL_JTAG. + +## How to use example + +### Hardware Required + +The example can be run on development board that supports usb_serial_jtag, that is based on the Espressif SoC. The board shall be connected to a computer with a single USB cable for flashing and monitoring with UART port. The usb_serial_jtag port on board can be be connected to computer with another USB cable to get the echo. + +### Configure the project + +Use the command below to configure project using Kconfig menu as showed in the table above. +The default Kconfig values can be changed such as disable the `ESP_CONSOLE_SECONDARY_USB_SERIAL_JTAG` +``` +idf.py menuconfig +``` + +### Build and Flash + +Build the project and flash it to the board, then run monitor tool to view serial output: + +``` +idf.py -p PORT flash monitor +``` + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects. + +## Example Output + +Type some characters in the terminal connected to the external serial interface. As result you should see echo in the same terminal which you used for typing the characters. + +For example, If I type `hi espressif`, `See you again!`, `Echo a very long buffer. Assume this buffer is very large and you can see whole buffer` + +And you can get the log with: + +``` +I (296) main_task: Started on CPU0 +I (296) main_task: Calling app_main() +I (296) main_task: Returned from app_main() +I (13346) Recv str: : 0x408117b8 68 69 20 65 73 70 72 65 73 73 69 66 0d 0a |hi espressif..| +I (16606) Recv str: : 0x408117b8 53 65 65 20 79 6f 75 20 61 67 61 69 6e 21 0d 0a |See you again!..| +I (18726) Recv str: : 0x408117b8 45 63 68 6f 20 61 20 76 65 72 79 20 6c 6f 6e 67 |Echo a very long| +I (18726) Recv str: : 0x408117c8 20 62 75 66 66 65 72 2e 20 41 73 73 75 6d 65 20 | buffer. Assume | +I (18726) Recv str: : 0x408117d8 74 68 69 73 20 62 75 66 66 65 72 20 69 73 20 76 |this buffer is v| +I (18736) Recv str: : 0x408117e8 65 72 79 20 6c 61 72 67 65 20 61 6e 64 20 79 6f |ery large and yo| +I (18746) Recv str: : 0x408117b8 75 20 63 61 6e 20 73 65 65 20 77 68 6f 6c 65 20 |u can see whole | +I (18756) Recv str: : 0x408117c8 62 75 66 66 65 72 0d 0a |buffer..| +``` + +## Troubleshooting + +Note that you are not supposed to see the echo in the terminal if usb_serial_jtag port is used for flashing and monitoring. diff --git a/examples/peripherals/usb_serial_jtag/usb_serial_jtag_echo/main/CMakeLists.txt b/examples/peripherals/usb_serial_jtag/usb_serial_jtag_echo/main/CMakeLists.txt new file mode 100644 index 000000000000..5134a9108021 --- /dev/null +++ b/examples/peripherals/usb_serial_jtag/usb_serial_jtag_echo/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "usb_serial_echo_main.c" + INCLUDE_DIRS ".") diff --git a/examples/peripherals/usb_serial_jtag/usb_serial_jtag_echo/main/usb_serial_echo_main.c b/examples/peripherals/usb_serial_jtag/usb_serial_jtag_echo/main/usb_serial_echo_main.c new file mode 100644 index 000000000000..8a774408438e --- /dev/null +++ b/examples/peripherals/usb_serial_jtag/usb_serial_jtag_echo/main/usb_serial_echo_main.c @@ -0,0 +1,52 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "driver/usb_serial_jtag.h" +#include "sdkconfig.h" +#include "esp_log.h" +#include "esp_check.h" + +#define BUF_SIZE (1024) +#define ECHO_TASK_STACK_SIZE (2048) + +static void echo_task(void *arg) +{ + // Configure USB SERIAL JTAG + usb_serial_jtag_driver_config_t usb_serial_jtag_config = { + .rx_buffer_size = BUF_SIZE, + .tx_buffer_size = BUF_SIZE, + }; + + ESP_ERROR_CHECK(usb_serial_jtag_driver_install(&usb_serial_jtag_config)); + ESP_LOGI("usb_serial_jtag echo", "USB_SERIAL_JTAG init done"); + + // Configure a temporary buffer for the incoming data + uint8_t *data = (uint8_t *) malloc(BUF_SIZE); + if (data == NULL) { + ESP_LOGE("usb_serial_jtag echo", "no memory for data"); + return; + } + + while (1) { + + int len = usb_serial_jtag_read_bytes(data, (BUF_SIZE - 1), 20 / portTICK_PERIOD_MS); + + // Write data back to the USB SERIAL JTAG + if (len) { + usb_serial_jtag_write_bytes((const char *) data, len, 20 / portTICK_PERIOD_MS); + data[len] = '\0'; + ESP_LOG_BUFFER_HEXDUMP("Recv str: ", data, len, ESP_LOG_INFO); + } + } +} + +void app_main(void) +{ + xTaskCreate(echo_task, "USB SERIAL JTAG_echo_task", ECHO_TASK_STACK_SIZE, NULL, 10, NULL); +} diff --git a/examples/peripherals/usb_serial_jtag/usb_serial_jtag_echo/pytest_usj_echo_example.py b/examples/peripherals/usb_serial_jtag/usb_serial_jtag_echo/pytest_usj_echo_example.py new file mode 100644 index 000000000000..5ffc6746148c --- /dev/null +++ b/examples/peripherals/usb_serial_jtag/usb_serial_jtag_echo/pytest_usj_echo_example.py @@ -0,0 +1,49 @@ +# SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: CC0-1.0 + +from time import sleep + +import pytest +import serial +import serial.tools.list_ports +from pytest_embedded import Dut + + +@pytest.mark.esp32c6 # usb_serial_jtag is very similar, test C6 is enough. +@pytest.mark.usj_device +def test_usb_device_serial_example(dut: Dut) -> None: + dut.expect_exact('USB_SERIAL_JTAG init done') + sleep(2) + + ports = list(serial.tools.list_ports.comports()) + for p in ports: + if (p.device == '/dev/ttyACM0'): # Get the usb_serial_jtag port + with serial.Serial(p.device) as s: + s.write(b'hi, espressif\n') + sleep(1) + dut.expect_exact('hi, espressif') + res = s.readline() + assert b'hi, espressif' in res + s.write(b'See you again!\n') + sleep(1) + dut.expect_exact('See you again!') + res = s.readline() + assert b'See you again!' in res + s.write(b'Echo a very long buffer. Assume this buffer is very large and you can see whole buffer\n') + sleep(1) + dut.expect_exact('Echo a very long') # 16 bytes a line because we use `ESP_LOG_BUFFER_HEXDUMP` in code. + dut.expect_exact(' buffer. Assume ') + dut.expect_exact('this buffer is v') + dut.expect_exact('ery large and yo') + dut.expect_exact('u can see whole ') + dut.expect_exact('buffer') + res = s.readline() + assert b'Echo a very long buffer. Assume this buffer is very large and you can see whole buffer' in res + s.write(b'64 bytes buffer:-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-\n') + sleep(1) + res = s.readline() + assert b'64 bytes buffer:-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-' in res + + return + + raise Exception('usb_serial_jtag port not found') diff --git a/examples/peripherals/usb_serial_jtag/usb_serial_jtag_echo/sdkconfig.defaults b/examples/peripherals/usb_serial_jtag/usb_serial_jtag_echo/sdkconfig.defaults new file mode 100644 index 000000000000..44bd84822e56 --- /dev/null +++ b/examples/peripherals/usb_serial_jtag/usb_serial_jtag_echo/sdkconfig.defaults @@ -0,0 +1 @@ +CONFIG_ESP_CONSOLE_SECONDARY_NONE=y diff --git a/tools/ci/idf_pytest/constants.py b/tools/ci/idf_pytest/constants.py index cddb18808c29..a933333adc9d 100644 --- a/tools/ci/idf_pytest/constants.py +++ b/tools/ci/idf_pytest/constants.py @@ -108,6 +108,7 @@ 'twai_network': 'multiple runners form a TWAI network.', 'sdio_master_slave': 'Test sdio multi board, esp32+esp32', 'sdio_multidev_32_c6': 'Test sdio multi board, esp32+esp32c6', + 'usj_device': 'Test usb_serial_jtag and usb_serial_jtag is used as serial only (not console)' }