summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsakumisu <[email protected]>2026-07-02 13:23:15 +0800
committersakumisu <[email protected]>2026-07-02 13:23:15 +0800
commit8f04252f0fd77926ad369346ce3e3442552d5f1f (patch)
treea9af23f2a9bb7a7aba9ed171c7ea312d25ea81df
parentb3195b95e0289ca04d079ab2c686dc122d7690f7 (diff)
update(tools): update usb speed test
Signed-off-by: sakumisu <[email protected]>
-rw-r--r--docs/zh/demo/usbd_cdc_acm.rst8
-rw-r--r--docs/zh/index.rst1
-rw-r--r--docs/zh/quick_start/usb_speed.rst27
-rw-r--r--tools/test_srcipts/libusb_winusb_device_for_speed.c238
-rw-r--r--tools/test_srcipts/python_cdc_acm_for_speed.c175
-rw-r--r--tools/test_srcipts/test_cdc_speed.c506
-rw-r--r--tools/test_srcipts/test_cdc_speed.exebin0 -> 107594 bytes
7 files changed, 947 insertions, 8 deletions
diff --git a/docs/zh/demo/usbd_cdc_acm.rst b/docs/zh/demo/usbd_cdc_acm.rst
index 5ae3568c..758c9e73 100644
--- a/docs/zh/demo/usbd_cdc_acm.rst
+++ b/docs/zh/demo/usbd_cdc_acm.rst
@@ -98,13 +98,5 @@ CDC ACM Device
}
- 上述我们需要注意,长度设置为 2048 是为了测试 ZLP 功能,通常实际使用时,接收长度应该使用 CDC_MAX_MPS 。具体原因参考 :ref:`usb_ext`
-- 如果需要做性能测试,使用 tools/test_srcipts/test_cdc_speed.py 进行测试,并在测试之前删除 `usbd_cdc_acm_bulk_out` 和 `usbd_cdc_acm_bulk_in` 中的打印,否则会影响测试结果。
-
-测试命令如下:
-
-.. code-block:: python
-
- python test_cdc_speed.py --port COM17 --mode tx --chunk-length 4M
- python test_cdc_speed.py --port COM17 --mode rx --chunk-length 4M
.. note:: 对于 CDC ACM 搭配 OS 的情况,通常我们 read 使用异步并将数据存储到 ringbuffer 中,write 使用同步搭配 sem 使用。 \ No newline at end of file
diff --git a/docs/zh/index.rst b/docs/zh/index.rst
index f524fe42..6e3b2f4a 100644
--- a/docs/zh/index.rst
+++ b/docs/zh/index.rst
@@ -59,6 +59,7 @@ CherryUSB 是一个小而美的、可移植性高的、用于嵌入式系统的
quick_start/q&a
quick_start/migration
quick_start/opensource
+ quick_start/usb_speed
.. toctree::
:maxdepth: 1
diff --git a/docs/zh/quick_start/usb_speed.rst b/docs/zh/quick_start/usb_speed.rst
new file mode 100644
index 00000000..24a43c3c
--- /dev/null
+++ b/docs/zh/quick_start/usb_speed.rst
@@ -0,0 +1,27 @@
+USB 极限速度测试
+======================
+
+在 tools 中我们提供了一个 USB 极限速度测试工具,帮助用户测试 USB 设备的传输速度。该工具可以测量 USB 设备在不同条件下的读写速度,并生成详细的测试报告。
+
+- test_cdc_speed.c + libusb_winusb_device_for_speed.c
+
+该工具主要使用 libusb 进行测试,因此,需要保证本地文件需要有 libusb 库和头文件,device 使用 winusb,因为如果使用 cdc acm,则需要进行驱动转换。使用如下:
+
+.. code-block:: C
+
+ #默认已经提供 test_cdc_speed.exe
+
+ gcc -O2 -o test_cdc_speed.exe .\test_cdc_speed.c .\libusb-1.0.dll.a -I. -lm
+ .\test_cdc_speed.exe --vendor 0xFFFF --product 0xFFFF --interface 0 --mode tx --chunk-length 4M
+ .\test_cdc_speed.exe --vendor 0xFFFF --product 0xFFFF --interface 0 --mode rx --chunk-length 4M
+
+- test_cdc_speed.py
+
+该工具主要使用 pyserial 进行测试,因此,需要保证已经安装 pyserial 库。使用如下:
+
+.. code-block:: C
+
+ python test_cdc_speed.py --port COM6 --mode tx --chunk-length 4M
+ python test_cdc_speed.py --port COM6 --mode rx --chunk-length 4M
+
+.. note:: python 测试 TX 和 libusb 基本相同,rx 会略低于 libusb,主要原因是因为 pyserial 读取后会放到缓存中,多一次拷贝导致 \ No newline at end of file
diff --git a/tools/test_srcipts/libusb_winusb_device_for_speed.c b/tools/test_srcipts/libusb_winusb_device_for_speed.c
new file mode 100644
index 00000000..bcd02a03
--- /dev/null
+++ b/tools/test_srcipts/libusb_winusb_device_for_speed.c
@@ -0,0 +1,238 @@
+/*
+ * Copyright (c) 2026, sakumisu
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#include "usbd_core.h"
+
+#define WCID_VENDOR_CODE 0x17
+
+#define USBD_VID 0xffff
+#define USBD_PID 0xffff
+#define USBD_MAX_POWER 100
+#define USBD_LANGID_STRING 1033
+
+#define WINUSB_IN_EP 0x81
+#define WINUSB_OUT_EP 0x02
+
+#define USBD_PID_WINUSB10 USBD_PID
+#define USB_CONFIG_SIZE (9 + 9 + 7 + 7)
+#define INTF_NUM 1
+
+static const uint8_t WCID_StringDescriptor_MSOS[18] = {
+ USB_MSOSV1_STRING_DESCRIPTOR_INIT(WCID_VENDOR_CODE)
+};
+
+static const uint8_t WINUSB_WCIDDescriptor[] = {
+ USB_MSOSV1_COMP_ID_HEADER_DESCRIPTOR_INIT(INTF_NUM),
+ USB_MSOSV1_COMP_ID_FUNCTION_WINUSB_DESCRIPTOR_INIT(0),
+};
+
+static const uint8_t WINUSB_IF0_WCIDProperties[142] = {
+ /*
+ * WCID property descriptor
+ */
+ 0x8e, 0x00, 0x00, 0x00, /* dwLength */
+ 0x00, 0x01, /* bcdVersion */
+ 0x05, 0x00, /* wIndex */
+ 0x01, 0x00, /* wCount */
+
+ /*
+ * registry propter descriptor
+ */
+ 0x84, 0x00, 0x00, 0x00, /* dwSize */
+ 0x01, 0x00, 0x00, 0x00, /* dwPropertyDataType */
+ 0x28, 0x00, /* wPropertyNameLength */
+ /* DeviceInterfaceGUID */
+ 'D', 0x00, 'e', 0x00, 'v', 0x00, 'i', 0x00, /* wcName_20 */
+ 'c', 0x00, 'e', 0x00, 'I', 0x00, 'n', 0x00, /* wcName_20 */
+ 't', 0x00, 'e', 0x00, 'r', 0x00, 'f', 0x00, /* wcName_20 */
+ 'a', 0x00, 'c', 0x00, 'e', 0x00, 'G', 0x00, /* wcName_20 */
+ 'U', 0x00, 'I', 0x00, 'D', 0x00, 0x00, 0x00, /* wcName_20 */
+ 0x4e, 0x00, 0x00, 0x00, /* dwPropertyDataLength */
+ /* {1D4B2365-4749-48EA-B38A-7C6FDDDD7E26} */
+ '{', 0x00, '1', 0x00, 'D', 0x00, '4', 0x00, /* wcData_39 */
+ 'B', 0x00, '2', 0x00, '3', 0x00, '6', 0x00, /* wcData_39 */
+ '5', 0x00, '-', 0x00, '4', 0x00, '7', 0x00, /* wcData_39 */
+ '4', 0x00, '9', 0x00, '-', 0x00, '4', 0x00, /* wcData_39 */
+ '8', 0x00, 'E', 0x00, 'A', 0x00, '-', 0x00, /* wcData_39 */
+ 'B', 0x00, '3', 0x00, '8', 0x00, 'A', 0x00, /* wcData_39 */
+ '-', 0x00, '7', 0x00, 'C', 0x00, '6', 0x00, /* wcData_39 */
+ 'F', 0x00, 'D', 0x00, 'D', 0x00, 'D', 0x00, /* wcData_39 */
+ 'D', 0x00, '7', 0x00, 'E', 0x00, '2', 0x00, /* wcData_39 */
+ '6', 0x00, '}', 0x00, 0x00, 0x00, /* wcData_39 */
+};
+
+const uint8_t *WINUSB_IFx_WCIDProperties[] = {
+ WINUSB_IF0_WCIDProperties,
+};
+
+struct usb_msosv1_descriptor msosv1_desc = {
+ .string = WCID_StringDescriptor_MSOS,
+ .vendor_code = WCID_VENDOR_CODE,
+ .compat_id = WINUSB_WCIDDescriptor,
+ .comp_id_property = WINUSB_IFx_WCIDProperties,
+};
+
+static const uint8_t device_descriptor[] = {
+ USB_DEVICE_DESCRIPTOR_INIT(USB_2_0, 0x00, 0x00, 0x00, USBD_VID, USBD_PID_WINUSB10, 0x0001, 0x01),
+};
+
+static const uint8_t config_descriptor_hs[] = {
+ USB_CONFIG_DESCRIPTOR_INIT(USB_CONFIG_SIZE, INTF_NUM, 0x01, USB_CONFIG_BUS_POWERED, USBD_MAX_POWER),
+ USB_INTERFACE_DESCRIPTOR_INIT(0x00, 0x00, 0x02, 0xff, 0xff, 0x00, 0x04),
+ USB_ENDPOINT_DESCRIPTOR_INIT(WINUSB_IN_EP, USB_ENDPOINT_TYPE_BULK, USB_BULK_EP_MPS_HS, 0x00),
+ USB_ENDPOINT_DESCRIPTOR_INIT(WINUSB_OUT_EP, USB_ENDPOINT_TYPE_BULK, USB_BULK_EP_MPS_HS, 0x00),
+};
+
+static const uint8_t config_descriptor_fs[] = {
+ USB_CONFIG_DESCRIPTOR_INIT(USB_CONFIG_SIZE, INTF_NUM, 0x01, USB_CONFIG_BUS_POWERED, USBD_MAX_POWER),
+ USB_INTERFACE_DESCRIPTOR_INIT(0x00, 0x00, 0x02, 0xff, 0xff, 0x00, 0x04),
+ USB_ENDPOINT_DESCRIPTOR_INIT(WINUSB_IN_EP, USB_ENDPOINT_TYPE_BULK, USB_BULK_EP_MPS_FS, 0x00),
+ USB_ENDPOINT_DESCRIPTOR_INIT(WINUSB_OUT_EP, USB_ENDPOINT_TYPE_BULK, USB_BULK_EP_MPS_FS, 0x00),
+};
+
+static const uint8_t device_quality_descriptor[] = {
+ USB_DEVICE_QUALIFIER_DESCRIPTOR_INIT(USB_2_0, 0x00, 0x00, 0x00, 0x01),
+};
+
+static const uint8_t other_speed_config_descriptor_hs[] = {
+ USB_OTHER_SPEED_CONFIG_DESCRIPTOR_INIT(USB_CONFIG_SIZE, INTF_NUM, 0x01, USB_CONFIG_BUS_POWERED, USBD_MAX_POWER),
+ USB_INTERFACE_DESCRIPTOR_INIT(0x00, 0x00, 0x02, 0xff, 0xff, 0x00, 0x04),
+ USB_ENDPOINT_DESCRIPTOR_INIT(WINUSB_IN_EP, USB_ENDPOINT_TYPE_BULK, USB_BULK_EP_MPS_FS, 0x00),
+ USB_ENDPOINT_DESCRIPTOR_INIT(WINUSB_OUT_EP, USB_ENDPOINT_TYPE_BULK, USB_BULK_EP_MPS_FS, 0x00),
+};
+
+static const uint8_t other_speed_config_descriptor_fs[] = {
+ USB_OTHER_SPEED_CONFIG_DESCRIPTOR_INIT(USB_CONFIG_SIZE, INTF_NUM, 0x01, USB_CONFIG_BUS_POWERED, USBD_MAX_POWER),
+ USB_INTERFACE_DESCRIPTOR_INIT(0x00, 0x00, 0x02, 0xff, 0xff, 0x00, 0x04),
+ USB_ENDPOINT_DESCRIPTOR_INIT(WINUSB_IN_EP, USB_ENDPOINT_TYPE_BULK, USB_BULK_EP_MPS_HS, 0x00),
+ USB_ENDPOINT_DESCRIPTOR_INIT(WINUSB_OUT_EP, USB_ENDPOINT_TYPE_BULK, USB_BULK_EP_MPS_HS, 0x00),
+};
+
+static const char *string_descriptors[] = {
+ (const char[]){ 0x09, 0x04 }, /* Langid */
+ "CherryUSB", /* Manufacturer */
+ "CherryUSB WinUSB Demo", /* Product */
+ "2024051701", /* Serial Number */
+ "CherryUSB WinUSB0",
+};
+
+static const uint8_t *device_descriptor_callback(uint8_t speed)
+{
+ (void)speed;
+
+ return device_descriptor;
+}
+
+static const uint8_t *config_descriptor_callback(uint8_t speed)
+{
+ if (speed == USB_SPEED_HIGH) {
+ return config_descriptor_hs;
+ } else if (speed == USB_SPEED_FULL) {
+ return config_descriptor_fs;
+ } else {
+ return NULL;
+ }
+}
+
+static const uint8_t *device_quality_descriptor_callback(uint8_t speed)
+{
+ (void)speed;
+
+ return device_quality_descriptor;
+}
+
+static const uint8_t *other_speed_config_descriptor_callback(uint8_t speed)
+{
+ if (speed == USB_SPEED_HIGH) {
+ return other_speed_config_descriptor_hs;
+ } else if (speed == USB_SPEED_FULL) {
+ return other_speed_config_descriptor_fs;
+ } else {
+ return NULL;
+ }
+}
+
+static const char *string_descriptor_callback(uint8_t speed, uint8_t index)
+{
+ (void)speed;
+
+ if (index >= (sizeof(string_descriptors) / sizeof(char *))) {
+ return NULL;
+ }
+ return string_descriptors[index];
+}
+
+const struct usb_descriptor winusb_descriptor = {
+ .device_descriptor_callback = device_descriptor_callback,
+ .config_descriptor_callback = config_descriptor_callback,
+ .device_quality_descriptor_callback = device_quality_descriptor_callback,
+ .other_speed_descriptor_callback = other_speed_config_descriptor_callback,
+ .string_descriptor_callback = string_descriptor_callback,
+ .msosv1_descriptor = &msosv1_desc,
+};
+
+USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t read_buffer[16384];
+USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t write_buffer[16384];
+
+static void usbd_event_handler(uint8_t busid, uint8_t event)
+{
+ switch (event) {
+ case USBD_EVENT_RESET:
+ break;
+ case USBD_EVENT_CONNECTED:
+ break;
+ case USBD_EVENT_DISCONNECTED:
+ break;
+ case USBD_EVENT_RESUME:
+ break;
+ case USBD_EVENT_SUSPEND:
+ break;
+ case USBD_EVENT_CONFIGURED:
+ /* setup first out ep read transfer */
+ usbd_ep_start_read(busid, WINUSB_OUT_EP, read_buffer, sizeof(read_buffer));
+ usbd_ep_start_write(busid, WINUSB_IN_EP, write_buffer, sizeof(write_buffer));
+ break;
+ case USBD_EVENT_SET_REMOTE_WAKEUP:
+ break;
+ case USBD_EVENT_CLR_REMOTE_WAKEUP:
+ break;
+
+ default:
+ break;
+ }
+}
+
+void usbd_winusb_out(uint8_t busid, uint8_t ep, uint32_t nbytes)
+{
+ usbd_ep_start_read(busid, WINUSB_OUT_EP, read_buffer, sizeof(read_buffer));
+}
+
+void usbd_winusb_in(uint8_t busid, uint8_t ep, uint32_t nbytes)
+{
+ usbd_ep_start_write(busid, WINUSB_IN_EP, write_buffer, sizeof(write_buffer));
+}
+
+struct usbd_endpoint winusb_out_ep1 = {
+ .ep_addr = WINUSB_OUT_EP,
+ .ep_cb = usbd_winusb_out
+};
+
+struct usbd_endpoint winusb_in_ep1 = {
+ .ep_addr = WINUSB_IN_EP,
+ .ep_cb = usbd_winusb_in
+};
+
+struct usbd_interface intf0;
+
+void winusb_init(uint8_t busid, uint32_t reg_base)
+{
+ usbd_desc_register(busid, &winusb_descriptor);
+
+ usbd_add_interface(busid, &intf0);
+ usbd_add_endpoint(busid, &winusb_out_ep1);
+ usbd_add_endpoint(busid, &winusb_in_ep1);
+
+ usbd_initialize(busid, reg_base, usbd_event_handler);
+}
diff --git a/tools/test_srcipts/python_cdc_acm_for_speed.c b/tools/test_srcipts/python_cdc_acm_for_speed.c
new file mode 100644
index 00000000..e436bae4
--- /dev/null
+++ b/tools/test_srcipts/python_cdc_acm_for_speed.c
@@ -0,0 +1,175 @@
+/*
+ * Copyright (c) 2026, sakumisu
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#include "usbd_core.h"
+#include "usbd_cdc_acm.h"
+
+#define USBD_VID 0xffff
+#define USBD_PID 0xffff
+#define USBD_MAX_POWER 100
+#define USBD_LANGID_STRING 1033
+
+/*!< endpoint address */
+#define CDC_IN_EP 0x81
+#define CDC_OUT_EP 0x02
+#define CDC_INT_EP 0x83
+
+/*!< config descriptor size */
+#define USB_CONFIG_SIZE (9 + CDC_ACM_DESCRIPTOR_LEN)
+
+static const uint8_t device_descriptor[] = {
+ USB_DEVICE_DESCRIPTOR_INIT(USB_2_0, 0xEF, 0x02, 0x01, USBD_VID, USBD_PID, 0x0100, 0x01)
+};
+
+static const uint8_t config_descriptor_hs[] = {
+ USB_CONFIG_DESCRIPTOR_INIT(USB_CONFIG_SIZE, 0x02, 0x01, USB_CONFIG_BUS_POWERED, USBD_MAX_POWER),
+ CDC_ACM_DESCRIPTOR_INIT(0x00, CDC_INT_EP, CDC_OUT_EP, CDC_IN_EP, USB_BULK_EP_MPS_HS, 0x02),
+};
+
+static const uint8_t config_descriptor_fs[] = {
+ USB_CONFIG_DESCRIPTOR_INIT(USB_CONFIG_SIZE, 0x02, 0x01, USB_CONFIG_BUS_POWERED, USBD_MAX_POWER),
+ CDC_ACM_DESCRIPTOR_INIT(0x00, CDC_INT_EP, CDC_OUT_EP, CDC_IN_EP, USB_BULK_EP_MPS_FS, 0x02),
+};
+
+static const uint8_t device_quality_descriptor[] = {
+ USB_DEVICE_QUALIFIER_DESCRIPTOR_INIT(USB_2_0, 0xEF, 0x02, 0x01, 0x01),
+};
+
+static const uint8_t other_speed_config_descriptor_hs[] = {
+ USB_OTHER_SPEED_CONFIG_DESCRIPTOR_INIT(USB_CONFIG_SIZE, 0x02, 0x01, USB_CONFIG_BUS_POWERED, USBD_MAX_POWER),
+ CDC_ACM_DESCRIPTOR_INIT(0x00, CDC_INT_EP, CDC_OUT_EP, CDC_IN_EP, USB_BULK_EP_MPS_FS, 0x02),
+};
+
+static const uint8_t other_speed_config_descriptor_fs[] = {
+ USB_OTHER_SPEED_CONFIG_DESCRIPTOR_INIT(USB_CONFIG_SIZE, 0x02, 0x01, USB_CONFIG_BUS_POWERED, USBD_MAX_POWER),
+ CDC_ACM_DESCRIPTOR_INIT(0x00, CDC_INT_EP, CDC_OUT_EP, CDC_IN_EP, USB_BULK_EP_MPS_HS, 0x02),
+};
+
+static const char *string_descriptors[] = {
+ (const char[]){ 0x09, 0x04 }, /* Langid */
+ "CherryUSB", /* Manufacturer */
+ "CherryUSB CDC DEMO", /* Product */
+ "2024051702", /* Serial Number */
+};
+
+static const uint8_t *device_descriptor_callback(uint8_t speed)
+{
+ (void)speed;
+
+ return device_descriptor;
+}
+
+static const uint8_t *config_descriptor_callback(uint8_t speed)
+{
+ if (speed == USB_SPEED_HIGH) {
+ return config_descriptor_hs;
+ } else if (speed == USB_SPEED_FULL) {
+ return config_descriptor_fs;
+ } else {
+ return NULL;
+ }
+}
+
+static const uint8_t *device_quality_descriptor_callback(uint8_t speed)
+{
+ (void)speed;
+
+ return device_quality_descriptor;
+}
+
+static const uint8_t *other_speed_config_descriptor_callback(uint8_t speed)
+{
+ if (speed == USB_SPEED_HIGH) {
+ return other_speed_config_descriptor_hs;
+ } else if (speed == USB_SPEED_FULL) {
+ return other_speed_config_descriptor_fs;
+ } else {
+ return NULL;
+ }
+}
+
+static const char *string_descriptor_callback(uint8_t speed, uint8_t index)
+{
+ (void)speed;
+
+ if (index >= (sizeof(string_descriptors) / sizeof(char *))) {
+ return NULL;
+ }
+ return string_descriptors[index];
+}
+
+const struct usb_descriptor cdc_descriptor = {
+ .device_descriptor_callback = device_descriptor_callback,
+ .config_descriptor_callback = config_descriptor_callback,
+ .device_quality_descriptor_callback = device_quality_descriptor_callback,
+ .other_speed_descriptor_callback = other_speed_config_descriptor_callback,
+ .string_descriptor_callback = string_descriptor_callback,
+};
+
+USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t read_buffer[16384];
+USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t write_buffer[16384];
+
+static void usbd_event_handler(uint8_t busid, uint8_t event)
+{
+ switch (event) {
+ case USBD_EVENT_RESET:
+ break;
+ case USBD_EVENT_CONNECTED:
+ break;
+ case USBD_EVENT_DISCONNECTED:
+ break;
+ case USBD_EVENT_RESUME:
+ break;
+ case USBD_EVENT_SUSPEND:
+ break;
+ case USBD_EVENT_CONFIGURED:
+ usbd_ep_start_read(busid, CDC_OUT_EP, read_buffer, sizeof(read_buffer));
+ usbd_ep_start_write(busid, CDC_IN_EP, write_buffer, sizeof(write_buffer));
+ break;
+ case USBD_EVENT_SET_REMOTE_WAKEUP:
+ break;
+ case USBD_EVENT_CLR_REMOTE_WAKEUP:
+ break;
+
+ default:
+ break;
+ }
+}
+
+void usbd_cdc_acm_bulk_out(uint8_t busid, uint8_t ep, uint32_t nbytes)
+{
+ usbd_ep_start_read(busid, CDC_OUT_EP, read_buffer, sizeof(read_buffer));
+}
+
+void usbd_cdc_acm_bulk_in(uint8_t busid, uint8_t ep, uint32_t nbytes)
+{
+ usbd_ep_start_write(busid, CDC_IN_EP, write_buffer, sizeof(write_buffer));
+}
+
+/*!< endpoint call back */
+struct usbd_endpoint cdc_out_ep = {
+ .ep_addr = CDC_OUT_EP,
+ .ep_cb = usbd_cdc_acm_bulk_out
+};
+
+struct usbd_endpoint cdc_in_ep = {
+ .ep_addr = CDC_IN_EP,
+ .ep_cb = usbd_cdc_acm_bulk_in
+};
+
+static struct usbd_interface intf0;
+static struct usbd_interface intf1;
+
+/* function ------------------------------------------------------------------*/
+
+void cdc_acm_init(uint8_t busid, uint32_t reg_base)
+{
+ usbd_desc_register(busid, &cdc_descriptor);
+ usbd_add_interface(busid, usbd_cdc_acm_init_intf(busid, &intf0));
+ usbd_add_interface(busid, usbd_cdc_acm_init_intf(busid, &intf1));
+ usbd_add_endpoint(busid, &cdc_out_ep);
+ usbd_add_endpoint(busid, &cdc_in_ep);
+ usbd_initialize(busid, reg_base, usbd_event_handler);
+} \ No newline at end of file
diff --git a/tools/test_srcipts/test_cdc_speed.c b/tools/test_srcipts/test_cdc_speed.c
new file mode 100644
index 00000000..28aa7ebd
--- /dev/null
+++ b/tools/test_srcipts/test_cdc_speed.c
@@ -0,0 +1,506 @@
+/*
+ * Copyright (c) 2026, sakumisu
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <time.h>
+#include <signal.h>
+#include <getopt.h>
+#include "libusb.h"
+
+#ifdef _WIN32
+#include <windows.h>
+#endif
+
+#define DEFAULT_VENDOR_ID 0xFFFF
+#define DEFAULT_PRODUCT_ID 0xFFFF
+#define DEFAULT_INTERFACE 0
+#define DEFAULT_TIMEOUT 1000 /* 1 second */
+#define MAX_PACKET_SIZE (4 * 1024 * 1024)
+#define REPORT_INTERVAL 1.0
+#define TEST_PATTERN 0xAA
+
+typedef struct {
+ int vendor_id;
+ int product_id;
+ int interface_num;
+ int timeout;
+ char mode; /* 't' for tx, 'r' for rx */
+ uint32_t chunk_length;
+ double report_interval;
+ unsigned char endpoint_out;
+ unsigned char endpoint_in;
+} test_config_t;
+
+static volatile int should_exit = 0;
+
+void print_device_layout(libusb_device *device)
+{
+ struct libusb_config_descriptor *config_desc = NULL;
+ int r = libusb_get_config_descriptor(device, 0, &config_desc);
+ if (r < 0 || !config_desc) {
+ return;
+ }
+
+ for (int if_num = 0; if_num < config_desc->bNumInterfaces; if_num++) {
+ const struct libusb_interface *interface = &config_desc->interface[if_num];
+ for (int alt = 0; alt < interface->num_altsetting; alt++) {
+ const struct libusb_interface_descriptor *if_desc = &interface->altsetting[alt];
+ printf(" Interface %d alt %d class 0x%02X endpoints %d\n",
+ if_desc->bInterfaceNumber,
+ if_desc->bAlternateSetting,
+ if_desc->bInterfaceClass,
+ if_desc->bNumEndpoints);
+
+ for (int ep = 0; ep < if_desc->bNumEndpoints; ep++) {
+ const struct libusb_endpoint_descriptor *ep_desc = &if_desc->endpoint[ep];
+ const char *dir = (ep_desc->bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK) ? "IN" : "OUT";
+ const char *type = "OTHER";
+ int transfer_type = ep_desc->bmAttributes & LIBUSB_TRANSFER_TYPE_MASK;
+ if (transfer_type == LIBUSB_TRANSFER_TYPE_BULK) {
+ type = "BULK";
+ } else if (transfer_type == LIBUSB_TRANSFER_TYPE_INTERRUPT) {
+ type = "INT";
+ } else if (transfer_type == LIBUSB_TRANSFER_TYPE_ISOCHRONOUS) {
+ type = "ISO";
+ } else if (transfer_type == LIBUSB_TRANSFER_TYPE_CONTROL) {
+ type = "CTRL";
+ }
+
+ printf(" EP 0x%02X %s %s\n", ep_desc->bEndpointAddress, dir, type);
+ }
+ }
+ }
+
+ libusb_free_config_descriptor(config_desc);
+}
+
+libusb_device_handle* open_device_with_diagnostics(libusb_context *ctx, int vendor_id, int product_id)
+{
+ libusb_device **list = NULL;
+ ssize_t count = libusb_get_device_list(ctx, &list);
+ if (count < 0) {
+ fprintf(stderr, "Failed to enumerate USB devices: %s\n", libusb_error_name((int)count));
+ return NULL;
+ }
+
+ libusb_device_handle *handle = NULL;
+ int found_match = 0;
+
+ for (ssize_t i = 0; i < count; i++) {
+ struct libusb_device_descriptor desc;
+ int r = libusb_get_device_descriptor(list[i], &desc);
+ if (r < 0) {
+ continue;
+ }
+
+ if (desc.idVendor == vendor_id && desc.idProduct == product_id) {
+ found_match = 1;
+
+ printf("Matched device VID:0x%04X PID:0x%04X bus %u addr %u\n",
+ vendor_id,
+ product_id,
+ libusb_get_bus_number(list[i]),
+ libusb_get_device_address(list[i]));
+ print_device_layout(list[i]);
+
+ r = libusb_open(list[i], &handle);
+ if (r == 0 && handle) {
+ break;
+ }
+
+ fprintf(stderr,
+ "Matched VID:0x%04X PID:0x%04X but open failed: %s\n",
+ vendor_id, product_id, libusb_error_name(r));
+
+#ifdef _WIN32
+ if (r == LIBUSB_ERROR_NOT_FOUND || r == LIBUSB_ERROR_ACCESS) {
+ fprintf(stderr,
+ "Hint: On Windows this usually means the target interface is not bound to WinUSB/libusbK.\n");
+ fprintf(stderr,
+ " Rebind the target interface driver (not the whole composite device) and retry --interface N.\n");
+ }
+#endif
+ }
+ }
+
+ if (!handle && !found_match) {
+ fprintf(stderr, "No USB device matched VID:0x%04X PID:0x%04X in enumeration\n",
+ vendor_id, product_id);
+ }
+
+ libusb_free_device_list(list, 1);
+ return handle;
+}
+
+void signal_handler(int signum)
+{
+ should_exit = 1;
+}
+
+double get_time_ms(void)
+{
+#ifdef _WIN32
+ return (double)GetTickCount();
+#else
+ struct timespec ts;
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+ return ts.tv_sec * 1000.0 + ts.tv_nsec / 1000000.0;
+#endif
+}
+
+const char* format_speed(uint64_t byte_count, double duration_sec)
+{
+ static char buffer[64];
+ if (duration_sec <= 0) {
+ snprintf(buffer, sizeof(buffer), "0.00 MB/s (0.00 Mbps)");
+ return buffer;
+ }
+
+ double mb_per_sec = byte_count / duration_sec / 1024.0 / 1024.0;
+ double mbps = byte_count * 8.0 / duration_sec / 1000.0 / 1000.0;
+ snprintf(buffer, sizeof(buffer), "%.2f MB/s (%.2f Mbps)", mb_per_sec, mbps);
+ return buffer;
+}
+
+void print_progress(const char *prefix, uint64_t done_bytes, uint64_t bytes_in_window,
+ double start_time, double window_start)
+{
+ double now = get_time_ms() / 1000.0;
+ double avg_speed_duration = (now - start_time);
+ double instant_speed_duration = (now - window_start);
+
+ printf("%s: total %llu bytes, instant %s, average %s\n",
+ prefix, (unsigned long long)done_bytes,
+ format_speed(bytes_in_window, instant_speed_duration),
+ format_speed(done_bytes, avg_speed_duration));
+}
+
+uint64_t parse_size(const char *value)
+{
+ char *endptr;
+ double number = strtod(value, &endptr);
+
+ if (endptr == value) {
+ return 0;
+ }
+
+ while (*endptr && (*endptr == ' ' || *endptr == '\t')) {
+ endptr++;
+ }
+
+ const char *unit = endptr;
+ uint64_t factor = 1;
+
+ if (strcasecmp(unit, "b") == 0 || strcmp(unit, "") == 0) {
+ factor = 1;
+ } else if (strcasecmp(unit, "k") == 0 || strcasecmp(unit, "kb") == 0) {
+ factor = 1024;
+ } else if (strcasecmp(unit, "m") == 0 || strcasecmp(unit, "mb") == 0) {
+ factor = 1024 * 1024;
+ } else if (strcasecmp(unit, "g") == 0 || strcasecmp(unit, "gb") == 0) {
+ factor = 1024 * 1024 * 1024;
+ }
+
+ return (uint64_t)(number * factor);
+}
+
+int find_cdc_endpoints(libusb_device *device, test_config_t *config)
+{
+ struct libusb_device_descriptor desc;
+ struct libusb_config_descriptor *config_desc;
+ const struct libusb_interface *interface;
+ const struct libusb_interface_descriptor *if_desc;
+ const struct libusb_endpoint_descriptor *ep_desc;
+ int r;
+
+ r = libusb_get_device_descriptor(device, &desc);
+ if (r < 0) {
+ fprintf(stderr, "Failed to get device descriptor\n");
+ return r;
+ }
+
+ r = libusb_get_config_descriptor(device, 0, &config_desc);
+ if (r < 0) {
+ fprintf(stderr, "Failed to get config descriptor\n");
+ return r;
+ }
+
+ interface = &config_desc->interface[config->interface_num];
+ if_desc = &interface->altsetting[0];
+
+ for (int i = 0; i < if_desc->bNumEndpoints; i++) {
+ ep_desc = &if_desc->endpoint[i];
+
+ if ((ep_desc->bmAttributes & LIBUSB_TRANSFER_TYPE_MASK) == LIBUSB_TRANSFER_TYPE_BULK) {
+ if (ep_desc->bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK) {
+ config->endpoint_in = ep_desc->bEndpointAddress;
+ printf("Found IN endpoint: 0x%02X\n", config->endpoint_in);
+ } else {
+ config->endpoint_out = ep_desc->bEndpointAddress;
+ printf("Found OUT endpoint: 0x%02X\n", config->endpoint_out);
+ }
+ }
+ }
+
+ libusb_free_config_descriptor(config_desc);
+
+ if (!config->endpoint_in || !config->endpoint_out) {
+ fprintf(stderr, "Could not find IN or OUT endpoint\n");
+ return LIBUSB_ERROR_NOT_FOUND;
+ }
+
+ return 0;
+}
+
+int run_tx(libusb_device_handle *handle, test_config_t *config)
+{
+ uint8_t *buffer = malloc(config->chunk_length);
+ if (!buffer) {
+ fprintf(stderr, "Failed to allocate buffer\n");
+ return LIBUSB_ERROR_NO_MEM;
+ }
+
+ memset(buffer, TEST_PATTERN, config->chunk_length);
+
+ uint64_t sent_bytes = 0;
+ double start_time = get_time_ms() / 1000.0;
+ double window_start = start_time;
+ uint64_t window_bytes = 0;
+
+ printf("Starting TX test, packet size: %u bytes\n", config->chunk_length);
+ printf("Press Ctrl+C to stop\n");
+
+ while (!should_exit) {
+ int transferred = 0;
+ int r = libusb_bulk_transfer(handle, config->endpoint_out, buffer,
+ config->chunk_length, &transferred, config->timeout);
+
+ if (r < 0) {
+ fprintf(stderr, "Transfer error: %s\n", libusb_error_name(r));
+ continue;
+ }
+
+ sent_bytes += transferred;
+ window_bytes += transferred;
+
+ double now = get_time_ms() / 1000.0;
+ if (now - window_start >= config->report_interval) {
+ print_progress("TX", sent_bytes, window_bytes, start_time, window_start);
+ window_start = now;
+ window_bytes = 0;
+ }
+ }
+
+ if (window_bytes > 0) {
+ print_progress("TX", sent_bytes, window_bytes, start_time, window_start);
+ }
+
+ double total_time = get_time_ms() / 1000.0 - start_time;
+ printf("TX stopped: %s\n", format_speed(sent_bytes, total_time));
+
+ free(buffer);
+ return 0;
+}
+
+int run_rx(libusb_device_handle *handle, test_config_t *config)
+{
+ uint8_t *buffer = malloc(config->chunk_length);
+ if (!buffer) {
+ fprintf(stderr, "Failed to allocate buffer\n");
+ return LIBUSB_ERROR_NO_MEM;
+ }
+
+ uint64_t recv_bytes = 0;
+ double start_time = get_time_ms() / 1000.0;
+ double window_start = start_time;
+ uint64_t window_bytes = 0;
+
+ printf("Starting RX test, packet size: %u bytes\n", config->chunk_length);
+ printf("Press Ctrl+C to stop\n");
+
+ while (!should_exit) {
+ int transferred = 0;
+ int r = libusb_bulk_transfer(handle, config->endpoint_in, buffer,
+ config->chunk_length, &transferred, config->timeout);
+
+ if (r < 0) {
+ fprintf(stderr, "Transfer error: %s\n", libusb_error_name(r));
+ continue;
+ }
+
+ if (transferred > 0) {
+ recv_bytes += transferred;
+ window_bytes += transferred;
+
+ double now = get_time_ms() / 1000.0;
+ if (now - window_start >= config->report_interval) {
+ print_progress("RX", recv_bytes, window_bytes, start_time, window_start);
+ window_start = now;
+ window_bytes = 0;
+ }
+ }
+ }
+
+ if (window_bytes > 0) {
+ print_progress("RX", recv_bytes, window_bytes, start_time, window_start);
+ }
+
+ double total_time = get_time_ms() / 1000.0 - start_time;
+ printf("RX stopped: %s\n", format_speed(recv_bytes, total_time));
+
+ free(buffer);
+ return 0;
+}
+
+void print_usage(const char *prog_name)
+{
+ printf("Usage: %s [options]\n", prog_name);
+ printf("Options:\n");
+ printf(" -m, --mode <tx|rx> Mode: tx (send) or rx (receive) [required]\n");
+ printf(" -v, --vendor <id> Vendor ID in hex (default: 0x%04X)\n", DEFAULT_VENDOR_ID);
+ printf(" -p, --product <id> Product ID in hex (default: 0x%04X)\n", DEFAULT_PRODUCT_ID);
+ printf(" -i, --interface <num> Interface number (default: %d)\n", DEFAULT_INTERFACE);
+ printf(" -c, --chunk-length <size> Packet size (default: 4096, supports K/M/G suffixes)\n");
+ printf(" -r, --report-interval <sec> Report interval in seconds (default: %.1f)\n", REPORT_INTERVAL);
+ printf(" -t, --timeout <ms> USB timeout in milliseconds (default: %d)\n", DEFAULT_TIMEOUT);
+ printf(" -h, --help Show this help message\n");
+}
+
+int main(int argc, char *argv[])
+{
+ test_config_t config = {
+ .vendor_id = DEFAULT_VENDOR_ID,
+ .product_id = DEFAULT_PRODUCT_ID,
+ .interface_num = DEFAULT_INTERFACE,
+ .timeout = DEFAULT_TIMEOUT,
+ .mode = 0,
+ .chunk_length = 4096,
+ .report_interval = REPORT_INTERVAL,
+ .endpoint_in = 0,
+ .endpoint_out = 0,
+ };
+
+ static struct option long_options[] = {
+ {"mode", required_argument, 0, 'm'},
+ {"vendor", required_argument, 0, 'v'},
+ {"product", required_argument, 0, 'p'},
+ {"interface", required_argument, 0, 'i'},
+ {"chunk-length", required_argument, 0, 'c'},
+ {"report-interval", required_argument, 0, 'r'},
+ {"timeout", required_argument, 0, 't'},
+ {"help", no_argument, 0, 'h'},
+ {0, 0, 0, 0}
+ };
+
+ int opt;
+ while ((opt = getopt_long(argc, argv, "m:v:p:i:c:r:t:h", long_options, NULL)) != -1) {
+ switch (opt) {
+ case 'm':
+ if (strcmp(optarg, "tx") == 0) {
+ config.mode = 't';
+ } else if (strcmp(optarg, "rx") == 0) {
+ config.mode = 'r';
+ } else {
+ fprintf(stderr, "Invalid mode: %s\n", optarg);
+ print_usage(argv[0]);
+ return 1;
+ }
+ break;
+ case 'v':
+ config.vendor_id = strtol(optarg, NULL, 16);
+ break;
+ case 'p':
+ config.product_id = strtol(optarg, NULL, 16);
+ break;
+ case 'i':
+ config.interface_num = atoi(optarg);
+ break;
+ case 'c':
+ config.chunk_length = parse_size(optarg);
+ if (config.chunk_length > MAX_PACKET_SIZE) {
+ fprintf(stderr, "Chunk length cannot exceed %u bytes\n", MAX_PACKET_SIZE);
+ return 1;
+ }
+ break;
+ case 'r':
+ config.report_interval = atof(optarg);
+ break;
+ case 't':
+ config.timeout = atoi(optarg);
+ break;
+ case 'h':
+ print_usage(argv[0]);
+ return 0;
+ default:
+ print_usage(argv[0]);
+ return 1;
+ }
+ }
+
+ if (!config.mode) {
+ fprintf(stderr, "Error: mode (-m) is required\n");
+ print_usage(argv[0]);
+ return 1;
+ }
+
+ signal(SIGINT, signal_handler);
+
+ libusb_context *ctx = NULL;
+ int r = libusb_init(&ctx);
+ if (r < 0) {
+ fprintf(stderr, "Failed to initialize libusb: %s\n", libusb_error_name(r));
+ return 1;
+ }
+
+ libusb_device_handle *handle = open_device_with_diagnostics(ctx,
+ config.vendor_id,
+ config.product_id);
+ if (!handle) {
+ fprintf(stderr, "Device not found (VID:0x%04X, PID:0x%04X), please use winusb device\n",
+ config.vendor_id, config.product_id);
+ libusb_exit(ctx);
+ return 1;
+ }
+
+ printf("Device found: VID=0x%04X, PID=0x%04X\n", config.vendor_id, config.product_id);
+
+ /* Find endpoints */
+ libusb_device *device = libusb_get_device(handle);
+ r = find_cdc_endpoints(device, &config);
+ if (r < 0) {
+ fprintf(stderr, "Failed to find CDC endpoints\n");
+ libusb_close(handle);
+ libusb_exit(ctx);
+ return 1;
+ }
+
+ /* Claim interface */
+ r = libusb_claim_interface(handle, config.interface_num);
+ if (r < 0) {
+ fprintf(stderr, "Failed to claim interface: %s\n", libusb_error_name(r));
+ libusb_close(handle);
+ libusb_exit(ctx);
+ return 1;
+ }
+
+ printf("Interface claimed successfully\n");
+ printf("Chunk length: %u bytes\n", config.chunk_length);
+
+ int result = 0;
+ if (config.mode == 't') {
+ result = run_tx(handle, &config);
+ } else {
+ result = run_rx(handle, &config);
+ }
+
+ libusb_release_interface(handle, config.interface_num);
+ libusb_close(handle);
+ libusb_exit(ctx);
+
+ return result;
+}
diff --git a/tools/test_srcipts/test_cdc_speed.exe b/tools/test_srcipts/test_cdc_speed.exe
new file mode 100644
index 00000000..deb6f44d
--- /dev/null
+++ b/tools/test_srcipts/test_cdc_speed.exe
Binary files differ