mirror of
https://github.com/hathach/tinyusb.git
synced 2026-03-26 09:24:53 +00:00
8
.github/actions/get_deps/action.yml
vendored
8
.github/actions/get_deps/action.yml
vendored
@ -9,8 +9,12 @@ runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Checkout pico-sdk for rp2040
|
||||
if: contains(inputs.arg, 'rp2040') || contains(inputs.arg, 'raspberry_pi_pico')
|
||||
uses: actions/checkout@v4
|
||||
if: >-
|
||||
contains(inputs.arg, 'rp2040') ||
|
||||
contains(inputs.arg, 'rp2350') ||
|
||||
contains(inputs.arg, 'raspberry_pi_pico') ||
|
||||
contains(inputs.arg, 'adafruit_fruit_jam')
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
repository: raspberrypi/pico-sdk
|
||||
ref: master
|
||||
|
||||
@ -13,7 +13,7 @@ runs:
|
||||
steps:
|
||||
- name: Cache Toolchain
|
||||
if: ${{ !startsWith(inputs.toolchain_url, 'https://github.com') }}
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
id: cache-toolchain-download
|
||||
with:
|
||||
path: ~/cache/${{ inputs.toolchain }}
|
||||
|
||||
@ -21,7 +21,7 @@ runs:
|
||||
shell: bash
|
||||
|
||||
- name: Cache Docker Image
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
id: cache-toolchain-espressif
|
||||
with:
|
||||
path: ${{ env.DOCKER_ESP_IDF }}
|
||||
|
||||
6
.github/workflows/build.yml
vendored
6
.github/workflows/build.yml
vendored
@ -29,7 +29,7 @@ jobs:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 2 # Needed for push commit comparison
|
||||
- uses: dorny/paths-filter@v3
|
||||
- uses: dorny/paths-filter@v4
|
||||
id: filter
|
||||
with:
|
||||
filters: |
|
||||
@ -124,7 +124,7 @@ jobs:
|
||||
|
||||
- name: Upload Metrics Artifact
|
||||
if: github.event_name == 'push' || github.event_name == 'release'
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: metrics-tinyusb
|
||||
path: metrics.json
|
||||
@ -179,7 +179,7 @@ jobs:
|
||||
|
||||
- name: Upload Metrics Comment Artifact
|
||||
if: github.event_name == 'pull_request'
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: metrics-comment
|
||||
path: |
|
||||
|
||||
4
.github/workflows/build_util.yml
vendored
4
.github/workflows/build_util.yml
vendored
@ -90,14 +90,14 @@ jobs:
|
||||
|
||||
- name: Upload Artifacts for Metrics
|
||||
if: inputs.upload-metrics == true && inputs.code-changed == true
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: metrics-${{ matrix.arg }}
|
||||
path: cmake-build/cmake-build-*/metrics.json
|
||||
|
||||
- name: Upload Artifacts for Hardware Testing
|
||||
if: inputs.upload-artifacts == true && inputs.code-changed == true
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: binaries-${{ matrix.arg }}
|
||||
path: |
|
||||
|
||||
2
.github/workflows/cifuzz.yml
vendored
2
.github/workflows/cifuzz.yml
vendored
@ -29,7 +29,7 @@ jobs:
|
||||
fuzz-seconds: 400
|
||||
|
||||
- name: Upload Crash
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v7
|
||||
if: failure() && steps.build.outcome == 'success'
|
||||
with:
|
||||
name: artifacts
|
||||
|
||||
6
.github/workflows/static_analysis.yml
vendored
6
.github/workflows/static_analysis.yml
vendored
@ -84,7 +84,7 @@ jobs:
|
||||
category: CodeQL
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: codeql-${{ matrix.board }}
|
||||
path: ${{ steps.analyze.outputs.sarif-output }}
|
||||
@ -136,7 +136,7 @@ jobs:
|
||||
category: PVS-Studio
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: pvs-studio-${{ matrix.board }}
|
||||
path: pvs-studio-${{ matrix.board }}.sarif
|
||||
@ -236,7 +236,7 @@ jobs:
|
||||
category: IAR-CStat
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: iar-cstat-${{ matrix.board }}
|
||||
path: iar-cstat-${{ matrix.board }}.sarif
|
||||
|
||||
@ -27,7 +27,6 @@ information that does not match the info here.
|
||||
## Build Examples
|
||||
|
||||
Choose ONE of these approaches:
|
||||
|
||||
**Option 1: Individual Example with CMake and Ninja (RECOMMENDED)**
|
||||
|
||||
```bash
|
||||
@ -152,6 +151,13 @@ openocd -f interface/stlink.cfg -f target/stm32h7x.cfg
|
||||
openocd -f interface/jlink.cfg -f target/stm32h7x.cfg
|
||||
```
|
||||
|
||||
For **rp2040/rp2350** with a CMSIS-DAP probe (e.g. Picoprobe, debugprobe):
|
||||
```bash
|
||||
openocd -f interface/cmsis-dap.cfg -f target/rp2040.cfg -c "adapter speed 5000"
|
||||
# or for rp2350:
|
||||
openocd -f interface/cmsis-dap.cfg -f target/rp2350.cfg -c "adapter speed 5000"
|
||||
```
|
||||
|
||||
For boards that define `OPENOCD_OPTION` in `board.cmake`, use those options directly:
|
||||
```bash
|
||||
openocd $(cat hw/bsp/FAMILY/boards/BOARD/board.cmake | grep OPENOCD_OPTION | ...)
|
||||
|
||||
2
LICENSE
2
LICENSE
@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018, hathach (tinyusb.org)
|
||||
Copyright (c) 2012-2026, hathach (tinyusb.org)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@ -97,7 +97,15 @@ enum
|
||||
|
||||
#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_HID_INOUT_DESC_LEN)
|
||||
|
||||
#define EPNUM_HID 0x01
|
||||
#if defined(TUD_ENDPOINT_ONE_DIRECTION_ONLY)
|
||||
// MCUs that don't support a same endpoint number with different direction IN and OUT defined in tusb_mcu.h
|
||||
// e.g EP1 OUT & EP1 IN cannot exist together
|
||||
#define EPNUM_HID_OUT 0x01
|
||||
#define EPNUM_HID_IN 0x82
|
||||
#else
|
||||
#define EPNUM_HID_OUT 0x01
|
||||
#define EPNUM_HID_IN 0x81
|
||||
#endif
|
||||
|
||||
uint8_t const desc_configuration[] =
|
||||
{
|
||||
@ -105,7 +113,7 @@ uint8_t const desc_configuration[] =
|
||||
TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, 0x00, 100),
|
||||
|
||||
// Interface number, string index, protocol, report descriptor len, EP Out & In address, size & polling interval
|
||||
TUD_HID_INOUT_DESCRIPTOR(ITF_NUM_HID, 0, HID_ITF_PROTOCOL_NONE, sizeof(desc_hid_report), EPNUM_HID, 0x80 | EPNUM_HID, CFG_TUD_HID_EP_BUFSIZE, 10)
|
||||
TUD_HID_INOUT_DESCRIPTOR(ITF_NUM_HID, 0, HID_ITF_PROTOCOL_NONE, sizeof(desc_hid_report), EPNUM_HID_OUT, EPNUM_HID_IN, CFG_TUD_HID_EP_BUFSIZE, 10)
|
||||
};
|
||||
|
||||
// Invoked when received GET CONFIGURATION DESCRIPTOR
|
||||
|
||||
@ -130,11 +130,16 @@ static bool inquiry_complete_cb(uint8_t dev_addr, const tuh_msc_complete_data_t
|
||||
drive_path[0] += drive_num;
|
||||
|
||||
if (f_mount(&fatfs[drive_num], drive_path, 1) != FR_OK) {
|
||||
puts("mount failed");
|
||||
printf("mount failed\r\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
// change to newly mounted drive
|
||||
f_chdir(drive_path);
|
||||
f_chdrive(drive_path);
|
||||
FRESULT rc = f_chdir("/");
|
||||
if (rc != FR_OK) {
|
||||
printf("chdir failed: %d\r\n", rc);
|
||||
}
|
||||
|
||||
// print the drive label
|
||||
// char label[34];
|
||||
@ -148,7 +153,7 @@ static bool inquiry_complete_cb(uint8_t dev_addr, const tuh_msc_complete_data_t
|
||||
|
||||
//------------- IMPLEMENTATION -------------//
|
||||
void tuh_msc_mount_cb(uint8_t dev_addr) {
|
||||
printf("A MassStorage device is mounted\r\n");
|
||||
printf("A MassStorage device (addr = %u) is mounted\r\n", dev_addr);
|
||||
|
||||
const uint8_t lun = 0;
|
||||
tuh_msc_inquiry(dev_addr, lun, &scsi_resp.inquiry, inquiry_complete_cb, 0);
|
||||
|
||||
@ -1,2 +1,3 @@
|
||||
set(PICO_PLATFORM rp2040)
|
||||
set(PICO_BOARD adafruit_feather_rp2040_usb_host)
|
||||
set(CFG_TUH_RPI_PIO_USB 1)
|
||||
|
||||
@ -2,3 +2,5 @@ set(PICO_PLATFORM rp2350-arm-s)
|
||||
set(PICO_BOARD adafruit_fruit_jam)
|
||||
set(PICO_BOARD_HEADER_DIRS ${CMAKE_CURRENT_LIST_DIR})
|
||||
#set(OPENOCD_SERIAL E6614103E78E8324)
|
||||
|
||||
set(CFG_TUH_RPI_PIO_USB 1)
|
||||
|
||||
@ -2,3 +2,5 @@ set(PICO_PLATFORM rp2350-arm-s)
|
||||
set(PICO_BOARD adafruit_metro_rp2350)
|
||||
set(PICO_BOARD_HEADER_DIRS ${CMAKE_CURRENT_LIST_DIR})
|
||||
#set(OPENOCD_SERIAL E6614103E78E8324)
|
||||
|
||||
set(CFG_TUH_RPI_PIO_USB 1)
|
||||
|
||||
@ -72,6 +72,12 @@ target_compile_definitions(tinyusb_common_base INTERFACE
|
||||
CFG_TUSB_DEBUG=${TINYUSB_DEBUG_LEVEL}
|
||||
)
|
||||
|
||||
if (CFG_TUH_RPI_PIO_USB)
|
||||
target_compile_definitions(tinyusb_common_base INTERFACE
|
||||
CFG_TUH_RPI_PIO_USB=1
|
||||
)
|
||||
endif()
|
||||
|
||||
target_link_libraries(tinyusb_common_base INTERFACE
|
||||
hardware_structs
|
||||
hardware_irq
|
||||
|
||||
@ -289,14 +289,31 @@ size_t board_get_unique_id(uint8_t id[], size_t max_len) {
|
||||
}
|
||||
|
||||
int board_uart_read(uint8_t *buf, int len) {
|
||||
(void) buf;
|
||||
(void) len;
|
||||
#ifdef UART_DEV
|
||||
int count = 0;
|
||||
// clear overrun error if any
|
||||
if (__HAL_UART_GET_FLAG(&UartHandle, UART_FLAG_ORE)) {
|
||||
__HAL_UART_CLEAR_FLAG(&UartHandle, UART_CLEAR_OREF);
|
||||
}
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (__HAL_UART_GET_FLAG(&UartHandle, UART_FLAG_RXNE)) {
|
||||
buf[i] = (uint8_t) UartHandle.Instance->RDR;
|
||||
count++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
#else
|
||||
(void) buf; (void) len;
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
int board_uart_write(void const *buf, int len) {
|
||||
#ifdef UART_DEV
|
||||
HAL_UART_Transmit(&UartHandle, (uint8_t *) (uintptr_t) buf, len, 0xffff);
|
||||
HAL_UART_Transmit(&UartHandle, (uint8_t * )(uintptr_t)
|
||||
buf, len, 0xffff);
|
||||
return len;
|
||||
#else
|
||||
(void) buf; (void) len;
|
||||
@ -316,6 +333,11 @@ uint32_t tusb_time_millis_api(void) {
|
||||
return system_ticks;
|
||||
}
|
||||
|
||||
#elif CFG_TUSB_OS == OPT_OS_THREADX
|
||||
// Keep HAL_GetTick() working for HAL functions called from board_init()
|
||||
void osal_threadx_tick_cb(void) {
|
||||
HAL_IncTick();
|
||||
}
|
||||
#endif
|
||||
|
||||
void HardFault_Handler(void) {
|
||||
|
||||
@ -205,13 +205,23 @@ static int32_t board_i2c_deinit(void) {
|
||||
}
|
||||
|
||||
static int32_t i2c_readreg(uint16_t DevAddr, uint16_t Reg, uint8_t *pData, uint16_t Length) {
|
||||
TU_ASSERT (HAL_OK == HAL_I2C_Mem_Read(&i2c_handle, DevAddr, Reg, I2C_MEMADD_SIZE_8BIT, pData, Length, 10000));
|
||||
return 0;
|
||||
for (int retry = 0; retry < 3; retry++) {
|
||||
if (HAL_OK == HAL_I2C_Mem_Read(&i2c_handle, DevAddr, Reg, I2C_MEMADD_SIZE_8BIT, pData, Length, 10000)) {
|
||||
return 0;
|
||||
}
|
||||
HAL_Delay(10);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int32_t i2c_writereg(uint16_t DevAddr, uint16_t Reg, uint8_t *pData, uint16_t Length) {
|
||||
TU_ASSERT(HAL_OK == HAL_I2C_Mem_Write(&i2c_handle, DevAddr, Reg, I2C_MEMADD_SIZE_8BIT, pData, Length, 10000));
|
||||
return 0;
|
||||
for (int retry = 0; retry < 3; retry++) {
|
||||
if (HAL_OK == HAL_I2C_Mem_Write(&i2c_handle, DevAddr, Reg, I2C_MEMADD_SIZE_8BIT, pData, Length, 10000)) {
|
||||
return 0;
|
||||
}
|
||||
HAL_Delay(10);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int32_t i2c_get_tick(void) {
|
||||
|
||||
@ -275,9 +275,25 @@ size_t board_get_unique_id(uint8_t id[], size_t max_len) {
|
||||
}
|
||||
|
||||
int board_uart_read(uint8_t *buf, int len) {
|
||||
(void) buf;
|
||||
(void) len;
|
||||
#ifdef UART_DEV
|
||||
int count = 0;
|
||||
// clear overrun error if any
|
||||
if (__HAL_UART_GET_FLAG(&UartHandle, UART_FLAG_ORE)) {
|
||||
__HAL_UART_CLEAR_FLAG(&UartHandle, UART_CLEAR_OREF);
|
||||
}
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (__HAL_UART_GET_FLAG(&UartHandle, UART_FLAG_RXNE)) {
|
||||
buf[i] = (uint8_t) UartHandle.Instance->RDR;
|
||||
count++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
#else
|
||||
(void) buf; (void) len;
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
int board_uart_write(void const *buf, int len) {
|
||||
|
||||
@ -494,7 +494,7 @@ static void process_bus_reset(uint8_t rhport) {
|
||||
*------------------------------------------------------------------*/
|
||||
|
||||
#if CFG_TUSB_DEBUG >= MUSB_DEBUG
|
||||
void print_musb_info(musb_regs_t* musb_regs) {
|
||||
static void print_musb_info(musb_regs_t* musb_regs) {
|
||||
// print version, epinfo, raminfo, config_data0, fifo_size
|
||||
TU_LOG1("musb version = %u.%u\r\n", musb_regs->hwvers_bit.major, musb_regs->hwvers_bit.minor);
|
||||
TU_LOG1("Number of endpoints: %u TX, %u RX\r\n", musb_regs->epinfo_bit.tx_ep_num, musb_regs->epinfo_bit.rx_ep_num);
|
||||
|
||||
@ -80,6 +80,11 @@ flash bank $_FLASHNAME wch_riscv 0x00000000 0 0 0 $_TARGETNAME.0
|
||||
echo "Ready for Remote Connections"
|
||||
"""
|
||||
|
||||
MSC_README_TXT = \
|
||||
b"This is tinyusb's MassStorage Class demo.\r\n\r\n\
|
||||
If you find any bugs or get any questions, feel free to file an\r\n\
|
||||
issue at github.com/hathach/tinyusb"
|
||||
|
||||
# -------------------------------------------------------------
|
||||
# Path
|
||||
# -------------------------------------------------------------
|
||||
@ -360,10 +365,27 @@ def test_dual_host_info_to_device_cdc(board):
|
||||
declared_devs = [f'{d["vid_pid"]}_{d["serial"]}' for d in board['tests']['dev_attached']]
|
||||
port = get_serial_dev(uid, 'TinyUSB', "TinyUSB_Device", 0)
|
||||
ser = open_serial_dev(port)
|
||||
ser.timeout = 0.1
|
||||
|
||||
# read from cdc, first line should contain vid/pid and serial
|
||||
data = ser.read(10000)
|
||||
# read until all expected devices are enumerated
|
||||
data = b''
|
||||
timeout = ENUM_TIMEOUT
|
||||
while timeout > 0:
|
||||
new_data = ser.read(ser.in_waiting or 1)
|
||||
if new_data:
|
||||
data += new_data
|
||||
# check if all devices found
|
||||
enum_dev_sn = []
|
||||
for l in data.decode('utf-8', errors='ignore').splitlines():
|
||||
vid_pid_sn = re.search(r'ID ([0-9a-fA-F]+):([0-9a-fA-F]+) SN (\w+)', l)
|
||||
if vid_pid_sn:
|
||||
enum_dev_sn.append(f'{vid_pid_sn.group(1)}_{vid_pid_sn.group(2)}_{vid_pid_sn.group(3)}')
|
||||
if set(declared_devs).issubset(set(enum_dev_sn)):
|
||||
break
|
||||
time.sleep(0.1)
|
||||
timeout -= 0.1
|
||||
ser.close()
|
||||
|
||||
if len(data) == 0:
|
||||
assert False, 'No data from device'
|
||||
lines = data.decode('utf-8', errors='ignore').splitlines()
|
||||
@ -391,13 +413,31 @@ def test_host_device_info(board):
|
||||
|
||||
port = get_serial_dev(flasher["uid"], None, None, 0)
|
||||
ser = open_serial_dev(port)
|
||||
ser.timeout = 0.1
|
||||
|
||||
# reset device since we can miss the first line
|
||||
ret = globals()[f'reset_{flasher["name"].lower()}'](board)
|
||||
assert ret.returncode == 0, 'Failed to reset device'
|
||||
assert ret.returncode == 0, 'Failed to reset device'
|
||||
|
||||
data = ser.read(10000)
|
||||
# read until all expected devices are enumerated
|
||||
data = b''
|
||||
timeout = ENUM_TIMEOUT
|
||||
while timeout > 0:
|
||||
new_data = ser.read(ser.in_waiting or 1)
|
||||
if new_data:
|
||||
data += new_data
|
||||
# check if all devices found
|
||||
enum_dev_sn = []
|
||||
for l in data.decode('utf-8', errors='ignore').splitlines():
|
||||
vid_pid_sn = re.search(r'ID ([0-9a-fA-F]+):([0-9a-fA-F]+) SN (\w+)', l)
|
||||
if vid_pid_sn:
|
||||
enum_dev_sn.append(f'{vid_pid_sn.group(1)}_{vid_pid_sn.group(2)}_{vid_pid_sn.group(3)}')
|
||||
if set(declared_devs).issubset(set(enum_dev_sn)):
|
||||
break
|
||||
time.sleep(0.1)
|
||||
timeout -= 0.1
|
||||
ser.close()
|
||||
|
||||
if len(data) == 0:
|
||||
assert False, 'No data from device'
|
||||
lines = data.decode('utf-8', errors='ignore').splitlines()
|
||||
@ -416,6 +456,173 @@ def test_host_device_info(board):
|
||||
return 0
|
||||
|
||||
|
||||
def print_msc_info(lines):
|
||||
"""Print MSC inquiry and disk size on a single line"""
|
||||
inquiry = ''
|
||||
disk_size = ''
|
||||
for l in lines:
|
||||
if re.match(r'^[A-Za-z].*\s+rev\s+', l):
|
||||
inquiry = l.strip()
|
||||
if 'Disk Size' in l:
|
||||
disk_size = l.strip()
|
||||
if inquiry or disk_size:
|
||||
print(f'\r\n {inquiry} {disk_size} ', end='')
|
||||
|
||||
|
||||
def test_host_cdc_msc_hid(board):
|
||||
flasher = board['flasher']
|
||||
dev_attached = board['tests'].get('dev_attached', [])
|
||||
cdc_devs = [d for d in dev_attached if d.get('is_cdc')]
|
||||
msc_devs = [d for d in dev_attached if d.get('is_msc')]
|
||||
if not cdc_devs and not msc_devs:
|
||||
return
|
||||
|
||||
port = get_serial_dev(flasher["uid"], None, None, 0)
|
||||
ser = open_serial_dev(port)
|
||||
ser.timeout = 0.1
|
||||
|
||||
# reset device to catch mount messages
|
||||
ret = globals()[f'reset_{flasher["name"].lower()}'](board)
|
||||
assert ret.returncode == 0, 'Failed to reset device'
|
||||
|
||||
# Wait for all expected mount messages
|
||||
data = b''
|
||||
timeout = ENUM_TIMEOUT
|
||||
wait_cdc = len(cdc_devs) > 0
|
||||
wait_msc = len(msc_devs) > 0
|
||||
while timeout > 0:
|
||||
new_data = ser.read(ser.in_waiting or 1)
|
||||
if new_data:
|
||||
data += new_data
|
||||
cdc_ok = (not wait_cdc) or (b'CDC Interface is mounted' in data)
|
||||
msc_ok = (not wait_msc) or (b'Disk Size' in data)
|
||||
if cdc_ok and msc_ok:
|
||||
break
|
||||
time.sleep(0.1)
|
||||
timeout -= 0.1
|
||||
|
||||
# Lookup serial chip name from vid_pid
|
||||
vid_pid_name = {
|
||||
'0403_6001': 'FTDI', '0403_6010': 'FTDI', '0403_6011': 'FTDI', '0403_6014': 'FTDI',
|
||||
'10c4_ea60': 'CP210x', '10c4_ea70': 'CP210x',
|
||||
'067b_2303': 'PL2303', '067b_23a3': 'PL2303',
|
||||
'1a86_7523': 'CH340', '1a86_7522': 'CH340',
|
||||
'1a86_55d3': 'CH9102', '1a86_55d4': 'CH9102',
|
||||
}
|
||||
|
||||
lines = data.decode('utf-8', errors='ignore').splitlines()
|
||||
|
||||
# Verify and print CDC mount
|
||||
if cdc_devs:
|
||||
assert b'CDC Interface is mounted' in data, 'CDC device not mounted on host'
|
||||
dev = cdc_devs[0]
|
||||
chip_name = vid_pid_name.get(dev['vid_pid'], dev['vid_pid'])
|
||||
for l in lines:
|
||||
if 'CDC Interface is mounted' in l:
|
||||
print(f'\r\n {chip_name}: {l} ', end='')
|
||||
|
||||
# Verify and print MSC mount (inquiry + disk size)
|
||||
if msc_devs:
|
||||
assert b'MassStorage device is mounted' in data, 'MSC device not mounted on host'
|
||||
assert b'Disk Size' in data, 'MSC Disk Size not reported'
|
||||
print_msc_info(lines)
|
||||
|
||||
# CDC echo test via flasher serial
|
||||
if not cdc_devs:
|
||||
ser.close()
|
||||
return
|
||||
|
||||
time.sleep(2)
|
||||
ser.reset_input_buffer()
|
||||
|
||||
def rand_ascii(length):
|
||||
return "".join(random.choices(string.ascii_letters + string.digits, k=length)).encode("ascii")
|
||||
|
||||
sizes = [8, 32, 64, 128]
|
||||
for size in sizes:
|
||||
test_data = rand_ascii(size)
|
||||
ser.reset_input_buffer()
|
||||
|
||||
# Write byte-by-byte with delay to avoid UART overrun
|
||||
for b in test_data:
|
||||
ser.write(bytes([b]))
|
||||
ser.flush()
|
||||
time.sleep(0.001)
|
||||
|
||||
# Read echo back with timeout
|
||||
echo = b''
|
||||
t = 5.0
|
||||
while t > 0 and len(echo) < size:
|
||||
rd = ser.read(max(1, ser.in_waiting))
|
||||
if rd:
|
||||
echo += rd
|
||||
time.sleep(0.05)
|
||||
t -= 0.05
|
||||
assert echo == test_data, (f'CDC echo wrong data ({size} bytes):\n'
|
||||
f' expected: {test_data}\n received: {echo}')
|
||||
|
||||
ser.close()
|
||||
|
||||
|
||||
def test_host_msc_file_explorer(board):
|
||||
flasher = board['flasher']
|
||||
msc_devs = [d for d in board['tests'].get('dev_attached', []) if d.get('is_msc')]
|
||||
if not msc_devs:
|
||||
return
|
||||
|
||||
port = get_serial_dev(flasher["uid"], None, None, 0)
|
||||
ser = open_serial_dev(port)
|
||||
ser.timeout = 0.1
|
||||
|
||||
# reset device to catch mount messages
|
||||
ret = globals()[f'reset_{flasher["name"].lower()}'](board)
|
||||
assert ret.returncode == 0, 'Failed to reset device'
|
||||
|
||||
# Wait for MSC mount (Disk Size message)
|
||||
data = b''
|
||||
timeout = ENUM_TIMEOUT
|
||||
while timeout > 0:
|
||||
new_data = ser.read(ser.in_waiting or 1)
|
||||
if new_data:
|
||||
data += new_data
|
||||
if b'Disk Size' in data:
|
||||
break
|
||||
time.sleep(0.1)
|
||||
timeout -= 0.1
|
||||
assert b'Disk Size' in data, 'MSC device not mounted'
|
||||
lines = data.decode('utf-8', errors='ignore').splitlines()
|
||||
print_msc_info(lines)
|
||||
|
||||
# Send "cat README.TXT" and read response
|
||||
time.sleep(1)
|
||||
ser.reset_input_buffer()
|
||||
for ch in 'cat README.TXT\r':
|
||||
ser.write(ch.encode())
|
||||
ser.flush()
|
||||
time.sleep(0.002)
|
||||
|
||||
# Read response
|
||||
resp = b''
|
||||
t = 10.0
|
||||
while t > 0:
|
||||
rd = ser.read(max(1, ser.in_waiting))
|
||||
if rd:
|
||||
resp += rd
|
||||
# wait for prompt after command output
|
||||
if b'>' in resp and resp.rstrip().endswith(b'>'):
|
||||
break
|
||||
time.sleep(0.05)
|
||||
t -= 0.05
|
||||
|
||||
# Verify response contains README content
|
||||
resp_text = resp.decode('utf-8', errors='ignore')
|
||||
assert MSC_README_TXT.decode() in resp_text, (f'MSC README.TXT not found in response:\n'
|
||||
f' received: {resp_text}')
|
||||
print('README.TXT matched ', end='')
|
||||
|
||||
ser.close()
|
||||
|
||||
|
||||
# -------------------------------------------------------------
|
||||
# Tests: device
|
||||
# -------------------------------------------------------------
|
||||
@ -491,12 +698,7 @@ def test_device_cdc_msc(board):
|
||||
|
||||
# MSC Block test
|
||||
data = read_disk_file(uid, 0, 'README.TXT')
|
||||
readme = \
|
||||
b"This is tinyusb's MassStorage Class demo.\r\n\r\n\
|
||||
If you find any bugs or get any questions, feel free to file an\r\n\
|
||||
issue at github.com/hathach/tinyusb"
|
||||
|
||||
assert data == readme, f'MSC wrong data in README.TXT\n expected: {readme.decode()}\n received: {data.decode()}'
|
||||
assert data == MSC_README_TXT, f'MSC wrong data in README.TXT\n expected: {MSC_README_TXT.decode()}\n received: {data.decode()}'
|
||||
|
||||
|
||||
def test_device_cdc_msc_freertos(board):
|
||||
@ -742,6 +944,112 @@ def test_device_mtp(board):
|
||||
mtp.disconnect()
|
||||
|
||||
|
||||
def test_device_msc_dual_lun(board):
|
||||
uid = board['uid']
|
||||
|
||||
# Read README from LUN 0
|
||||
data0 = read_disk_file(uid, 0, 'README0.TXT')
|
||||
readme0 = b"LUN0: " + MSC_README_TXT
|
||||
assert data0 == readme0, f'MSC LUN0 wrong data in README0.TXT\n expected: {readme0}\n received: {data0}'
|
||||
|
||||
# Read README from LUN 1
|
||||
data1 = read_disk_file(uid, 1, 'README1.TXT')
|
||||
readme1 = b"LUN1: " + MSC_README_TXT
|
||||
assert data1 == readme1, f'MSC LUN1 wrong data in README1.TXT\n expected: {readme1}\n received: {data1}'
|
||||
|
||||
|
||||
def test_device_midi_test(board):
|
||||
uid = board['uid']
|
||||
|
||||
# Find MIDI device via /dev/snd/by-id using board UID
|
||||
timeout = ENUM_TIMEOUT
|
||||
midi_port = None
|
||||
while timeout > 0:
|
||||
pattern = f'/dev/snd/by-id/usb-*_{uid}-*'
|
||||
devs = glob.glob(pattern)
|
||||
if devs:
|
||||
# by-id entry points to controlCX, derive card number for midiCXD0
|
||||
link = os.path.basename(os.readlink(devs[0])) # e.g. "controlC2"
|
||||
card_num = link.replace('controlC', '')
|
||||
midi_path = f'/dev/snd/midiC{card_num}D0'
|
||||
if os.path.exists(midi_path):
|
||||
midi_port = midi_path
|
||||
break
|
||||
time.sleep(1)
|
||||
timeout -= 1
|
||||
assert midi_port is not None, f'MIDI device not found for {uid}'
|
||||
|
||||
# Read MIDI messages and verify note on/off
|
||||
import select
|
||||
with open(midi_port, 'rb') as f:
|
||||
notes = []
|
||||
# Read for up to 3 seconds to capture a few notes (286ms interval)
|
||||
end_time = time.time() + 3
|
||||
while time.time() < end_time:
|
||||
ready, _, _ = select.select([f], [], [], 0.5)
|
||||
if ready:
|
||||
data = f.read(64)
|
||||
if data:
|
||||
# Parse MIDI bytes: note_on = 0x90, note_off = 0x80
|
||||
i = 0
|
||||
while i + 2 < len(data):
|
||||
status = data[i]
|
||||
if (status & 0xF0) == 0x90: # Note On
|
||||
notes.append(data[i + 1])
|
||||
i += 3
|
||||
elif (status & 0xF0) == 0x80: # Note Off
|
||||
i += 3
|
||||
else:
|
||||
i += 1
|
||||
|
||||
assert len(notes) >= 2, f'Expected at least 2 MIDI notes, got {len(notes)}'
|
||||
# Verify notes are from the expected sequence
|
||||
note_sequence = [
|
||||
74, 78, 81, 86, 90, 93, 98, 102, 57, 61, 66, 69, 73, 78, 81, 85,
|
||||
88, 92, 97, 100, 97, 92, 88, 85, 81, 78, 74, 69, 66, 62, 57, 62,
|
||||
66, 69, 74, 78, 81, 86, 90, 93, 97, 102, 97, 93, 90, 85, 81, 78,
|
||||
73, 68, 64, 61, 56, 61, 64, 68, 74, 78, 81, 86, 90, 93, 98, 102
|
||||
]
|
||||
for n in notes:
|
||||
assert n in note_sequence, f'Unexpected MIDI note {n}'
|
||||
|
||||
|
||||
def test_device_hid_generic_inout(board):
|
||||
uid = board['uid']
|
||||
import hid
|
||||
|
||||
# Find HID device by UID (VID=0xCafe)
|
||||
timeout = ENUM_TIMEOUT
|
||||
dev = None
|
||||
while timeout > 0:
|
||||
for d in hid.enumerate(0xCafe):
|
||||
if d['serial_number'] == uid:
|
||||
dev = d
|
||||
break
|
||||
if dev:
|
||||
break
|
||||
time.sleep(1)
|
||||
timeout -= 1
|
||||
assert dev is not None, f'HID device not found for {uid}'
|
||||
|
||||
h = hid.Device(vid=dev['vendor_id'], pid=dev['product_id'], serial=uid)
|
||||
|
||||
# Echo test: send random data and verify echo
|
||||
for size in [8, 32, 63]:
|
||||
# Report ID (0) + payload, padded to 64 bytes
|
||||
payload = bytes([random.randint(1, 255) for _ in range(size)])
|
||||
report = bytes([0]) + payload + bytes(64 - size)
|
||||
h.write(report)
|
||||
echo = h.read(64, timeout=2000)
|
||||
assert echo is not None and len(echo) >= size, (
|
||||
f'HID echo timeout or short read ({size} bytes)')
|
||||
assert bytes(echo[:size]) == payload, (
|
||||
f'HID echo wrong data ({size} bytes):\n'
|
||||
f' expected: {payload.hex()}\n received: {bytes(echo[:size]).hex()}')
|
||||
|
||||
h.close()
|
||||
|
||||
|
||||
# -------------------------------------------------------------
|
||||
# Main
|
||||
# -------------------------------------------------------------
|
||||
@ -754,7 +1062,10 @@ device_tests = [
|
||||
'device/dfu_runtime',
|
||||
'device/cdc_msc_freertos',
|
||||
'device/hid_boot_interface',
|
||||
'device/msc_dual_lun',
|
||||
'device/hid_generic_inout',
|
||||
'device/printer_to_cdc',
|
||||
'device/midi_test',
|
||||
'device/mtp'
|
||||
]
|
||||
|
||||
@ -763,6 +1074,8 @@ dual_tests = [
|
||||
]
|
||||
|
||||
host_test = [
|
||||
'host/cdc_msc_hid',
|
||||
'host/msc_file_explorer',
|
||||
'host/device_info',
|
||||
]
|
||||
|
||||
|
||||
@ -1,2 +1,4 @@
|
||||
fs
|
||||
hid
|
||||
pyfatfs
|
||||
pyserial
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
},
|
||||
"tests": {
|
||||
"only": ["device/cdc_msc_freertos", "device/hid_composite_freertos", "host/device_info"],
|
||||
"dev_attached": [{"vid_pid": "1a86_55d4", "serial": "52D2002427"}]
|
||||
"dev_attached": [{"vid_pid": "1a86_55d4", "serial": "52D2002427", "is_cdc": true}]
|
||||
},
|
||||
"flasher": {
|
||||
"name": "esptool",
|
||||
@ -26,7 +26,7 @@
|
||||
},
|
||||
"tests": {
|
||||
"only": ["device/cdc_msc_freertos", "device/hid_composite_freertos", "host/device_info"],
|
||||
"dev_attached": [{"vid_pid": "1a86_55d4", "serial": "52D2005402"}]
|
||||
"dev_attached": [{"vid_pid": "1a86_55d4", "serial": "52D2005402", "is_cdc": true}]
|
||||
},
|
||||
"flasher": {
|
||||
"name": "esptool",
|
||||
@ -67,7 +67,7 @@
|
||||
},
|
||||
"tests": {
|
||||
"device": true, "host": false, "dual": true,
|
||||
"dev_attached": [{"vid_pid": "067b_2303", "serial": "0"}],
|
||||
"dev_attached": [{"vid_pid": "067b_2303", "serial": "0", "is_cdc": true}],
|
||||
"comment": "pl23x"
|
||||
},
|
||||
"flasher": {
|
||||
@ -93,7 +93,7 @@
|
||||
"uid": "BAE96FB95AFA6DBB8F00005002001200",
|
||||
"tests": {
|
||||
"device": true, "host": true, "dual": true,
|
||||
"dev_attached": [{"vid_pid": "10c4_ea60", "serial": "0001"}],
|
||||
"dev_attached": [{"vid_pid": "10c4_ea60", "serial": "0001", "is_cdc": true}],
|
||||
"comment": "cp2102"
|
||||
},
|
||||
"flasher": {
|
||||
@ -136,7 +136,7 @@
|
||||
},
|
||||
"tests": {
|
||||
"device": true, "host": true, "dual": true,
|
||||
"dev_attached": [{"vid_pid": "1a86_7523", "serial": "0"}],
|
||||
"dev_attached": [{"vid_pid": "1a86_7523", "serial": "0", "is_cdc": true}],
|
||||
"comment": "ch34x"
|
||||
},
|
||||
"flasher": {
|
||||
@ -150,7 +150,7 @@
|
||||
"uid": "E6614C311B764A37",
|
||||
"tests": {
|
||||
"device": false, "host": true, "dual": false,
|
||||
"dev_attached": [{"vid_pid": "1a86_55d4", "serial": "52D2023934"}]
|
||||
"dev_attached": [{"vid_pid": "1a86_55d4", "serial": "52D2023934", "is_cdc": true}]
|
||||
},
|
||||
"flasher": {
|
||||
"name": "openocd",
|
||||
@ -162,12 +162,9 @@
|
||||
{
|
||||
"name": "raspberry_pi_pico2",
|
||||
"uid": "560AE75E1C7152C9",
|
||||
"build" : {
|
||||
"flags_on": ["CFG_TUH_RPI_PIO_USB"]
|
||||
},
|
||||
"tests": {
|
||||
"device": true, "host": true, "dual": true,
|
||||
"dev_attached": [{"vid_pid": "1a86_55d4", "serial": "533D004242"}]
|
||||
"device": false, "host": true, "dual": false,
|
||||
"dev_attached": [{"vid_pid": "1a86_55d4", "serial": "52D2002694", "is_cdc": true}]
|
||||
},
|
||||
"flasher": {
|
||||
"name": "openocd",
|
||||
@ -175,6 +172,25 @@
|
||||
"args": "-f interface/cmsis-dap.cfg -f target/rp2350.cfg -c \"adapter speed 5000\""
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "adafruit_fruit_jam",
|
||||
"uid": "2B0DC7A45781189E",
|
||||
"tests": {
|
||||
"device": true,
|
||||
"host": true,
|
||||
"dual": true,
|
||||
"dev_attached": [
|
||||
{"vid_pid": "0403_6001", "serial": "0", "is_cdc": true},
|
||||
{"vid_pid": "058f_6387", "serial": "A8BEE062633D", "is_msc": true,
|
||||
"msc_disk_size": 3730, "msc_inquiry": "Generic Flash Disk rev 8.07"}
|
||||
]
|
||||
},
|
||||
"flasher": {
|
||||
"name": "openocd",
|
||||
"uid": "E6614103E78E8324",
|
||||
"args": "-f interface/cmsis-dap.cfg -f target/rp2350.cfg -c \"adapter speed 5000\""
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "stm32f072disco",
|
||||
"uid": "3A001A001357364230353532",
|
||||
@ -193,7 +209,7 @@
|
||||
},
|
||||
"tests": {
|
||||
"device": true, "host": true, "dual": false,
|
||||
"dev_attached": [{"vid_pid": "1a86_55d4", "serial": "52D2003414"}]
|
||||
"dev_attached": [{"vid_pid": "1a86_55d4", "serial": "52D2003414", "is_cdc": true}]
|
||||
},
|
||||
"flasher": {
|
||||
"name": "jlink",
|
||||
|
||||
Reference in New Issue
Block a user