mirror of
https://github.com/hathach/tinyusb.git
synced 2026-03-08 08:34:46 +00:00
add hil test, update readme
This commit is contained in:
@ -87,6 +87,7 @@ Supports multiple device configurations by dynamically changing USB descriptors,
|
||||
- Communication Device Class (CDC)
|
||||
- Device Firmware Update (DFU): DFU mode (WIP) and Runtime
|
||||
- Human Interface Device (HID): Generic (In & Out), Keyboard, Mouse, Gamepad etc ...
|
||||
- Printer class
|
||||
- Mass Storage Class (MSC): with multiple LUNs
|
||||
- Musical Instrument Digital Interface (MIDI)
|
||||
- Media Transfer Protocol (MTP/PTP)
|
||||
|
||||
80
examples/device/printer_to_cdc/README.md
Normal file
80
examples/device/printer_to_cdc/README.md
Normal file
@ -0,0 +1,80 @@
|
||||
#### Printer to CDC
|
||||
|
||||
This example demonstrates a USB composite device with a Printer class interface and a CDC serial interface. Data flows bidirectionally between the two:
|
||||
|
||||
- Data sent to the Printer (from host) is forwarded to the CDC serial port
|
||||
- Data sent to the CDC serial port (from host) is forwarded to the Printer IN endpoint
|
||||
|
||||
This is useful for debugging printer class communication or as a reference for implementing printer class devices.
|
||||
|
||||
#### USB Interfaces
|
||||
|
||||
| Interface | Class | Description |
|
||||
|-----------|-------|-------------|
|
||||
| 0 | CDC ACM | Virtual serial port |
|
||||
| 2 | Printer | USB Printer (bidirectional, protocol 2) |
|
||||
|
||||
#### How to Test
|
||||
|
||||
The device exposes two endpoints on the host:
|
||||
- `/dev/ttyACM0` (CDC serial port)
|
||||
- `/dev/usb/lp0` (USB printer)
|
||||
|
||||
Note: the actual device numbers may vary depending on your system.
|
||||
|
||||
**Prerequisites (Linux):**
|
||||
|
||||
```bash
|
||||
# Load the USB printer kernel module if not already loaded
|
||||
sudo modprobe usblp
|
||||
|
||||
# Check devices exist
|
||||
ls /dev/ttyACM* /dev/usb/lp*
|
||||
```
|
||||
|
||||
**Test Printer to CDC (host writes to printer, reads from CDC):**
|
||||
|
||||
```bash
|
||||
# Terminal 1: read from CDC
|
||||
cat /dev/ttyACM0
|
||||
|
||||
# Terminal 2: write to printer
|
||||
echo "hello from printer" > /dev/usb/lp0
|
||||
# "hello from printer" appears in Terminal 1
|
||||
```
|
||||
|
||||
**Test CDC to Printer (host writes to CDC, reads from printer):**
|
||||
|
||||
```bash
|
||||
# Terminal 1: read from printer IN endpoint
|
||||
cat /dev/usb/lp0
|
||||
|
||||
# Terminal 2: write to CDC
|
||||
echo "hello from cdc" > /dev/ttyACM0
|
||||
# "hello from cdc" appears in Terminal 1
|
||||
```
|
||||
|
||||
**Interactive bidirectional test:**
|
||||
|
||||
```bash
|
||||
# Terminal 1: open CDC serial port
|
||||
minicom -D /dev/ttyACM0
|
||||
|
||||
# Terminal 2: send to printer
|
||||
echo "tinyusb print example" > /dev/usb/lp0
|
||||
# Text appears in minicom. Type in minicom to send data back through printer TX.
|
||||
```
|
||||
|
||||
#### IEEE 1284 Device ID
|
||||
|
||||
The device responds to GET_DEVICE_ID requests with:
|
||||
|
||||
```
|
||||
MFG:TinyUSB;MDL:Printer to CDC;CMD:PS;CLS:PRINTER;
|
||||
```
|
||||
|
||||
Verify with:
|
||||
|
||||
```bash
|
||||
cat /sys/class/usbmisc/lp0/device/ieee1284_id
|
||||
```
|
||||
@ -166,6 +166,32 @@ def open_mtp_dev(uid):
|
||||
return None
|
||||
|
||||
|
||||
def get_printer_dev(id, vendor_str, product_str, ifnum):
|
||||
"""Find /dev/usb/lpX by matching USB serial, vendor, product, and interface number via sysfs"""
|
||||
vendor_str = vendor_str.replace(' ', '_') if vendor_str else ''
|
||||
product_str = product_str.replace(' ', '_') if product_str else ''
|
||||
for lp in glob.glob('/sys/class/usbmisc/lp*'):
|
||||
try:
|
||||
sn = open(f'{lp}/device/../serial').read().strip()
|
||||
if sn == id:
|
||||
return f'/dev/usb/{os.path.basename(lp)}'
|
||||
except (FileNotFoundError, PermissionError, ValueError):
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
def open_printer_dev(id, vendor_str, product_str, ifnum):
|
||||
"""Wait for printer device to enumerate and return its path"""
|
||||
timeout = ENUM_TIMEOUT
|
||||
while timeout > 0:
|
||||
lp_dev = get_printer_dev(id, vendor_str, product_str, ifnum)
|
||||
if lp_dev and os.path.exists(lp_dev):
|
||||
return lp_dev
|
||||
time.sleep(1)
|
||||
timeout -= 1
|
||||
assert False, f'Printer device not found for {id} if{ifnum:02d}'
|
||||
|
||||
|
||||
# -------------------------------------------------------------
|
||||
# Flashing firmware
|
||||
# -------------------------------------------------------------
|
||||
@ -552,6 +578,88 @@ def test_device_hid_composite_freertos(id):
|
||||
pass
|
||||
|
||||
|
||||
def test_device_printer_to_cdc(board):
|
||||
import threading
|
||||
|
||||
uid = board['uid']
|
||||
|
||||
# Wait for CDC port and printer device
|
||||
cdc_port = get_serial_dev(uid, 'TinyUSB', "TinyUSB_Device", 0)
|
||||
ser = open_serial_dev(cdc_port)
|
||||
lp_dev = open_printer_dev(uid, 'TinyUSB', 'TinyUSB_Device', 2)
|
||||
|
||||
# Test 0: Verify IEEE 1284 Device ID from sysfs
|
||||
expected_id = 'MFG:TinyUSB;MDL:Printer to CDC;CMD:PS;CLS:PRINTER;'
|
||||
lp_name = os.path.basename(lp_dev)
|
||||
sysfs_id_path = f'/sys/class/usbmisc/{lp_name}/device/ieee1284_id'
|
||||
if os.path.exists(sysfs_id_path):
|
||||
with open(sysfs_id_path) as f:
|
||||
ieee1284_id = f.read().strip()
|
||||
if ieee1284_id:
|
||||
assert ieee1284_id == expected_id, (f'IEEE 1284 ID mismatch:\n'
|
||||
f' expected: {expected_id}\n got: {ieee1284_id}')
|
||||
|
||||
def rand_ascii(length):
|
||||
return "".join(random.choices(string.ascii_letters + string.digits, k=length)).encode("ascii")
|
||||
|
||||
sizes = [32, 64, 128, 256, 512, random.randint(2000, 5000)]
|
||||
|
||||
# flush any stale data
|
||||
ser.reset_input_buffer()
|
||||
|
||||
# Test 1: Printer -> CDC with multiple sizes
|
||||
for size in sizes:
|
||||
test_data = rand_ascii(size)
|
||||
with open(lp_dev, 'wb') as lp:
|
||||
lp.write(test_data)
|
||||
lp.flush()
|
||||
rd = b''
|
||||
while len(rd) < size:
|
||||
chunk = ser.read(size - len(rd))
|
||||
assert chunk, f'Printer->CDC timeout at {len(rd)}/{size} bytes'
|
||||
rd += chunk
|
||||
assert rd == test_data, (f'Printer->CDC wrong data ({size} bytes):\n'
|
||||
f' expected: {test_data[:64]}\n received: {rd[:64]}')
|
||||
|
||||
# Test 2: CDC -> Printer with multiple sizes
|
||||
# Use a thread to read from printer since /dev/usb/lp read blocks
|
||||
for size in sizes:
|
||||
test_data = rand_ascii(size)
|
||||
rd_result = [b'', None] # [data, error]
|
||||
|
||||
def lp_reader():
|
||||
try:
|
||||
rd = b''
|
||||
with open(lp_dev, 'rb') as lp:
|
||||
while len(rd) < size:
|
||||
chunk = lp.read(size - len(rd))
|
||||
if not chunk:
|
||||
break
|
||||
rd += chunk
|
||||
rd_result[0] = rd
|
||||
except Exception as e:
|
||||
rd_result[1] = e
|
||||
|
||||
reader = threading.Thread(target=lp_reader, daemon=True)
|
||||
reader.start()
|
||||
|
||||
# Write to CDC in chunks
|
||||
offset = 0
|
||||
while offset < size:
|
||||
chunk_size = min(random.randint(1, 64), size - offset)
|
||||
ser.write(test_data[offset:offset + chunk_size])
|
||||
ser.flush()
|
||||
offset += chunk_size
|
||||
|
||||
reader.join(timeout=10)
|
||||
assert not reader.is_alive(), f'CDC->Printer timeout ({size} bytes)'
|
||||
assert rd_result[1] is None, f'CDC->Printer read error: {rd_result[1]}'
|
||||
assert rd_result[0] == test_data, (f'CDC->Printer wrong data ({size} bytes):\n'
|
||||
f' expected: {test_data[:64]}\n received: {rd_result[0][:64]}')
|
||||
|
||||
ser.close()
|
||||
|
||||
|
||||
def test_device_mtp(board):
|
||||
uid = board['uid']
|
||||
|
||||
@ -623,6 +731,7 @@ device_tests = [
|
||||
'device/dfu_runtime',
|
||||
'device/cdc_msc_freertos',
|
||||
'device/hid_boot_interface',
|
||||
'device/printer_to_cdc',
|
||||
# 'device/mtp'
|
||||
]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user