Merge branch 'feat/add_example_usj_v5.1' into 'release/v5.1'

change(usb_serial_jtag): Add example for usb serial jtag echo (backport v5.1)

See merge request espressif/esp-idf!30020
release/v5.1
morris 2024-05-11 22:37:34 +08:00
commit 442a798083
16 zmienionych plików z 278 dodań i 23 usunięć

Wyświetl plik

@ -217,6 +217,14 @@ pytest_examples_esp32c6_generic:
- build_pytest_examples_esp32c6
tags: [ esp32c6, generic ]
pytest_examples_esp32c6_usj_device:
extends:
- .pytest_examples_dir_template
- .rules:test:example_test-esp32c6
needs:
- job: build_pytest_examples_esp32c6
tags: [ esp32c6, usj_device ]
pytest_examples_esp32h2_generic:
extends:
- .pytest_examples_dir_template

Wyświetl plik

@ -1,11 +1,12 @@
/*
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include <stdbool.h>
#include <stdatomic.h>
#include "esp_log.h"
#include "hal/usb_serial_jtag_ll.h"
#include "hal/usb_fsls_phy_ll.h"
@ -17,12 +18,19 @@
#include "soc/periph_defs.h"
#include "esp_check.h"
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 */
@ -59,7 +67,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;
@ -83,10 +91,13 @@ 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
// Not all bytes could be sent at once; stash the unwritten bytes in a tx buffer
// stash_size will not larger than USB_SER_JTAG_ENDP_SIZE because queued_size is got from xRingbufferReceiveUpToFromISR
size_t stash_size = queued_size - sent_size;
memcpy(p_usb_serial_jtag_obj->tx_data_buf, &queued_buff[sent_size], stash_size);
@ -100,9 +111,17 @@ 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
// buffers rather than releasing it to the program listening on the CDC serial port.
// We need to flush again in order to send a 0-byte packet that ends the transaction.
usb_serial_jtag_ll_txfifo_flush();
// Note that since this doesn't re-enable USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY, the
// 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);
}
}
@ -131,6 +150,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;
@ -153,6 +173,7 @@ esp_err_t usb_serial_jtag_driver_install(usb_serial_jtag_driver_config_t *usb_se
// Enable USB-Serial-JTAG peripheral module clock
usb_serial_jtag_ll_enable_bus_clock(true);
atomic_store(&p_usb_serial_jtag_obj->fifo_status, FIFO_IDLE);
// Configure PHY
usb_fsls_phy_ll_int_jtag_enable(&USB_SERIAL_JTAG);
@ -202,10 +223,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;
}

Wyświetl plik

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@ -161,8 +161,14 @@ static inline int usb_serial_jtag_ll_txfifo_writable(void)
* @brief Flushes the TX buffer, that is, make it available for the
* host to pick up.
*
* @note When fifo is full (with 64 byte), HW will flush the buffer automatically.
* It won't be executed if there is nothing in the fifo.
* @note When fifo is full (with 64 byte), HW will flush the buffer automatically,
* if this function is called directly after, this effectively turns into a
* no-op. Because a 64-byte packet will be interpreted as a not-complete USB
* transaction, you need to transfer either more data or a zero-length packet
* for the data to actually end up at the program listening to the CDC-ACM
* serial port. To send a zero-length packet, call
* usb_serial_jtag_ll_txfifo_flush() again when
* usb_serial_jtag_ll_txfifo_writable() returns true.
*
* @return na
*/

Wyświetl plik

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@ -161,8 +161,14 @@ static inline int usb_serial_jtag_ll_txfifo_writable(void)
* @brief Flushes the TX buffer, that is, make it available for the
* host to pick up.
*
* @note When fifo is full (with 64 byte), HW will flush the buffer automatically.
* It won't be executed if there is nothing in the fifo.
* @note When fifo is full (with 64 byte), HW will flush the buffer automatically,
* if this function is called directly after, this effectively turns into a
* no-op. Because a 64-byte packet will be interpreted as a not-complete USB
* transaction, you need to transfer either more data or a zero-length packet
* for the data to actually end up at the program listening to the CDC-ACM
* serial port. To send a zero-length packet, call
* usb_serial_jtag_ll_txfifo_flush() again when
* usb_serial_jtag_ll_txfifo_writable() returns true.
*
* @return na
*/

Wyświetl plik

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@ -161,8 +161,14 @@ static inline int usb_serial_jtag_ll_txfifo_writable(void)
* @brief Flushes the TX buffer, that is, make it available for the
* host to pick up.
*
* @note When fifo is full (with 64 byte), HW will flush the buffer automatically.
* It won't be executed if there is nothing in the fifo.
* @note When fifo is full (with 64 byte), HW will flush the buffer automatically,
* if this function is called directly after, this effectively turns into a
* no-op. Because a 64-byte packet will be interpreted as a not-complete USB
* transaction, you need to transfer either more data or a zero-length packet
* for the data to actually end up at the program listening to the CDC-ACM
* serial port. To send a zero-length packet, call
* usb_serial_jtag_ll_txfifo_flush() again when
* usb_serial_jtag_ll_txfifo_writable() returns true.
*
* @return na
*/

Wyświetl plik

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@ -161,6 +161,15 @@ static inline int usb_serial_jtag_ll_txfifo_writable(void)
* @brief Flushes the TX buffer, that is, make it available for the
* host to pick up.
*
* @note When fifo is full (with 64 byte), HW will flush the buffer automatically,
* if this function is called directly after, this effectively turns into a
* no-op. Because a 64-byte packet will be interpreted as a not-complete USB
* transaction, you need to transfer either more data or a zero-length packet
* for the data to actually end up at the program listening to the CDC-ACM
* serial port. To send a zero-length packet, call
* usb_serial_jtag_ll_txfifo_flush() again when
* usb_serial_jtag_ll_txfifo_writable() returns true.
*
* @return na
*/
static inline void usb_serial_jtag_ll_txfifo_flush(void)

Wyświetl plik

@ -270,6 +270,10 @@ static int usb_serial_jtag_fsync(int fd)
while ((esp_timer_get_time() - s_ctx.last_tx_ts) < TX_FLUSH_TIMEOUT_US) {
if (usb_serial_jtag_ll_txfifo_writable()) {
s_ctx.last_tx_ts = esp_timer_get_time();
//The last transfer may have been a 64-byte one. Flush again in order to
//send a 0-byte packet to indicate the end of the USB transfer, otherwise
//those 64 bytes will get stuck in the hosts buffer.
usb_serial_jtag_ll_txfifo_flush();
break;
}
}

Wyświetl plik

@ -1,8 +1,6 @@
# SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
# SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
# pylint: disable=W0621 # redefined-outer-name
# This file is a pytest root configuration file and provide the following functionalities:
# 1. Defines a few fixtures that could be used under the whole project.
# 2. Defines a few hook functions.
@ -12,7 +10,6 @@
#
# This is an experimental feature, and if you found any bug or have any question, please report to
# https://github.com/espressif/pytest-embedded/issues
import logging
import os
import re
@ -20,10 +17,14 @@ import sys
import xml.etree.ElementTree as ET
from datetime import datetime
from fnmatch import fnmatch
from typing import Callable, List, Optional, Tuple
from typing import Callable
from typing import List
from typing import Optional
from typing import Tuple
import pytest
from _pytest.config import Config, ExitCode
from _pytest.config import Config
from _pytest.config import ExitCode
from _pytest.fixtures import FixtureRequest
from _pytest.main import Session
from _pytest.nodes import Item
@ -31,7 +32,8 @@ from _pytest.python import Function
from _pytest.reports import TestReport
from _pytest.runner import CallInfo
from _pytest.terminal import TerminalReporter
from pytest_embedded.plugin import multi_dut_argument, multi_dut_fixture
from pytest_embedded.plugin import multi_dut_argument
from pytest_embedded.plugin import multi_dut_fixture
from pytest_embedded.utils import find_by_suffix
from pytest_embedded_idf.dut import IdfDut
@ -138,6 +140,7 @@ ENV_MARKERS = {
'generic_multi_device': 'generic multiple devices whose corresponding gpio pins are connected to each other.',
'twai_network': 'multiple runners form a TWAI network.',
'sdio_master_slave': 'Test sdio multi board.',
'usj_device': 'Test usb_serial_jtag and usb_serial_jtag is used as serial only (not console)',
}

Wyświetl plik

@ -58,6 +58,10 @@ There are several limitations to the USB Serial/JTAG console feature. These may
{IDF_TARGET_BOOT_PIN:default = "Not Updated!", esp32c3 = "GPIO9", esp32s3 = "GPIO0", esp32c6 = "GPIO9"}
1. If the application accidentally reconfigures the USB peripheral pins, or disables the USB Serial/JTAG Controller, the device will disappear from the system. After fixing the issue in the application, you will need to manually put the {IDF_TARGET_NAME} into download mode by pulling low {IDF_TARGET_BOOT_PIN} and resetting the chip.
.. note::
In rare cases it's possible that data sent from the {IDF_TARGET_NAME} to the host gets 'stuck' in host memory. Sending more data will get it 'unstuck', but if the application does not send more data, depending on the driver, this data needs to be flushed to the host manually. The non-blocking (default) driver and the VFS implementation will flush automatically after a newline. The blocking (interrupt-based) driver will automatically flush when its transmit buffer becomes empty.
2. If the application enters deep sleep mode, the USB Serial/JTAG device will disappear from the system.

Wyświetl plik

@ -288,6 +288,14 @@ 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 in ["esp32s3", "esp32c3", "esp32h2"]
temporary: true
reason: lack of runners. Hardware is similar, test on one target is enough currently.
examples/peripherals/wave_gen:
enable:
- if: IDF_TARGET == "esp32"

Wyświetl plik

@ -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)

Wyświetl plik

@ -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.

Wyświetl plik

@ -0,0 +1,2 @@
idf_component_register(SRCS "usb_serial_echo_main.c"
INCLUDE_DIRS ".")

Wyświetl plik

@ -0,0 +1,52 @@
/*
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <stdio.h>
#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);
}

Wyświetl plik

@ -0,0 +1,47 @@
# SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: CC0-1.0
from time import sleep
import pytest
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_echo_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')

Wyświetl plik

@ -0,0 +1 @@
CONFIG_ESP_CONSOLE_SECONDARY_NONE=y