- 嵌入式
- 驱动开发
- 通信
- 物联网
【免费下载链接】tinyusb
An open source cross-platform USB stack for embedded system
examples/device/usbtest是 TinyUSB 设备侧的“被测对象”,它实现了 Linux 内核usbtest.ko/testusb(Gadget Zero 风格 source/sink 协议)的设备端对端,让宿主机侧约 30 个编号测试用例可以完整压测 TinyUSB 的设备控制器驱动(DCD)。本文以仓库内 .claude/skills/usbtest/SKILL.md 技能文档为主体,结合 示例固件、主机运行脚本 与 HIL 测试框架 源码,系统讲解这套电池的构建、运行、调试、修复与移植方法。读完本文,你将掌握:如何跑通整组电池、如何把一个新 MCU/DCD 移植到 30/30 全绿、以及当某个用例报 errno 110/32/5/71 时如何按部就班定位到具体的 DCD 路径。
一、认识 usbtest:这是 DCD 测试,不是固件测试
Linux 内核自带一对 USB 测试工具,即usbtest.ko内核模块(源码位于内核drivers/usb/misc/usbtest.c)与testusb用户态调度器(源码位于内核tools/usb/testusb.c)。testusb通过 usbfs ioctl 通知内核模块执行编号用例,而usbtest.ko负责实际驱动总线流量,并在宿主机侧完成所有数据校验。
TinyUSB 侧的examples/device/usbtest固件实现的就是这个协议的设备端对端:在 vendor 专用接口上实现了 Gadget Zero 风格的 source/sink 协议——bulk IN 端点是无限数据源(pattern 0:全零字节),bulk OUT 端点是无限数据汇(数据直接丢弃),覆盖 bulk、EP0 控制、interrupt、isochronous 四类传输,包含 halt、data-toggle、unlink(URB 中途取消)等对抗性场景。因此整套电池(battery)是 DCD 能遇到的最具对抗性的压力测试——据该技能文档记载,到目前为止每一个移植过的端口(port)都至少暴露了一个真实驱动缺陷。
核心原则:这套电池测的是 DCD,不是应用固件。当某个用例失败时,应当怀疑它所压测的 DCD 路径(见下文“用例 → DCD 子系统映射表”),单独复现该用例,并且在改动任何代码之前先在硬件上定位根因(仓库中的hw-debugger技能负责此事)。一次只改一个变量;一个修复只有在失败用例通过、且完整电池在多次重新烧录周期后仍然保持 30/30 时才算是被证明有效。
协议分层上有一个容易混淆的点:TinyUSB 在这套测试里扮演的是USB 设备(device role),宿主机是 Linux PC。也就是说,它压测的是 TinyUSB 的 DCD(设备控制器驱动),而不是 TinyUSB 的 host 栈——这是该技能文档明确强调的边界。
二、固件侧实现:Tier 能力通告与 source/sink 泵
2.1 能力分层与 bcdDevice 通告
固件在 usb_descriptors.h 中定义了USBTEST_TIER宏,默认完整实现第 4 层;能力层越低,可运行的用例越少。这个层数被写进设备描述符的bcdDevice低字节(0x0100 | USBTEST_TIER,见 usb_descriptors.c),主机脚本据此自动挑选匹配的用例组合:
| Tier | 能力 | usbtest 用例 |
|---|---|---|
| 1 | bulk source/sink | 0, 9, 10, 1–8, 11, 12, 24, 13, 29, 17–20, 27, 28 |
| 2 | + vendor 控制 0x5b/0x5c(ctrl_out) | + 14, 21 |
| 3 | + interrupt source/sink | + 25, 26 |
| 4 | + isochronous source/sink | + 15, 16, 22, 23 |
厂商类同时使能了 interrupt 端点对(CFG_TUD_VENDOR_EP_INT_OUT/IN)、isochronous 端点对(CFG_TUD_VENDOR_EP_ISO_OUT/IN)与 altsetting 支持(CFG_TUD_VENDOR_ALT_SETTINGS),配置见 tusb_config.h。alt 0 不携带任何端点(默认 altsetting 不为 iso 端点申请带宽,符合 USB 2.0 5.6.3),alt 1 携带完整 source/sink 端点集;宿主机 usbtest 驱动会自行选择 alt 1。由于没有现成的 TUD_ 宏能描述这种布局,配置描述符是手写的(见 usb_descriptors.c)。另外,接口必须是编号 0——testusb -D的 ioctl 固定发给接口 0。
2.2 端点尺寸的按速协商
TUD_OPT_HIGH_SPEED是编译期能力标志,不是实时总线速度。因此完整速度(FS)配置描述符(以及 HS 主机读到的 OTHER_SPEED 描述符)必须使用 FS 合法尺寸——interrupt 每端点 ≤ 64 B、isochronous ≤ 1023 B/帧——即使在 HS 编译下也如此。所以头文件中区分了_FS/_HS两套尺寸宏(USBTEST_INT_EP_MPS_FS/HS、USBTEST_ISO_EP_MPS_FS/HS),并针对特定 MCU 覆盖:例如 CH32 USB IP 的每端点缓冲区极小(usb_descriptors.h 中 WCH USBFS 部件把所有端点限制在 64 B),Renesas RA 的 interrupt 管道 6–9 固定 64 B 单缓冲(同文件 #L71-L75,依据 RA6M5 参考手册 R01UH0891 第 29.1 节)。
运行时写长度跟随实际协商速度:main.c中usbtest_int_len()/usbtest_iso_len()用tud_speed_get() == TUSB_SPEED_HIGH判断,确保 HS 构建在 FS 枚举时提交 FS 长度(bulk 例外:它提交多包传输)。源码缓冲区按编译期能力最大值分配:
static uint8_t const tx_chunk[CFG_TUD_VENDOR_TX_EPSIZE]; static uint8_t const int_tx_chunk[USBTEST_INT_EP_MPS]; #if USBTEST_TIER >= 4 static uint8_t const iso_tx_chunk[USBTEST_ISO_EP_MPS]; #endif2.3 source/sink 泵与回调:自我愈合的端点管理
usbtest_pump()(main.c)轮询式保持四条/六条端点都处于 armed 状态,并且能在端点 halt 测试(SET/CLEAR_FEATURE endpoint halt)之后自我愈合:stall 会把端点标记为 busy,此时调用静默失败,直到宿主机清除 halt,下一轮 tick 重新武装。这依赖tusb_config.h中的CFG_TUD_VENDOR_RX_MANUAL_XFER 1(应用自行重新武装 RX,因为 halt 测试中不会有任何 completion 回调触发)。同时CFG_TUD_VENDOR_RX_BUFSIZE 0/CFG_TUD_VENDOR_TX_BUFSIZE 0开启非缓冲模式:每次传输都提交精确长度,宿主机永远不会在传输中途看到意外的短包或 ZLP——usbtest 的数据完整性用例会把它们判为失败。
回调把管道持续灌满:tud_vendor_rx_cb丢弃数据后立即tud_vendor_read_xfer()重新武装;tud_vendor_tx_cb完成后继续写下一块。interrupt 与 iso 端点对走同样的泵(tud_vendor_int_rx_cb等),iso 完成回调即使是错过帧也会无条件重新武装(main.c)。EP0 上,tud_vendor_control_xfer_cb实现了 ctrl_out 用例的协议:0x5b把宿主机wLength字节写入ctrl_buf,0x5c读回,尺寸可变(main.c)。
主循环 / FreeRTOS 两条路径都支持:非 RTOS 构建在主循环里每 tick 调一次usbtest_task(NULL);FreeRTOS 构建则创建 blinky、usbd、usbtest 三个静态任务(见main.c的freertos_init())。
三、运行整套电池:构建、HIL 全流程与手动单用例
3.1 构建固件
通过构建契约编译device/usbtest与device/board_test。描述符尺寸会经示例自带的 usb_descriptors.h 与 tusb_config.h 按 MCU 自动适配,不要额外加-D:--variants已经为 roster 中每个变体把各自 define 送进独立的cmake-build-<variant>(见 hil 技能的 Prerequisites),多出的-D会粘在 HIL 实际烧录的目录上。不在 roster 上的板子则去掉--variants。board_test是 HIL 在电池跑完后烧录的“驻留固件”:
python3 .claude/skills/build/scripts/check_build.py --board <board> -e device/usbtest -e device/board_test --shared --variants <this host's config>单 J-Link 的台架可以用ninja -C cmake-build/cmake-build-<variant> usbtest-jlink直接烧录;Espressif 板子用idf.py烧录。
3.2 HIL 全流程
搭好板子后,跑完整电池。HIL 框架会自动上锁(无需预先 hold)、经 roster 中记录的 probe 烧录、为电池设置预算、并在板子允许时启用挂死恢复:
python3 test/hil/hil_test.py -b <board> -t device/usbtest <this host's config>HIL 集成按板子执行电池并上报✅ 30/30单元格。CI(hil_test.py)还会额外传--budget,并在板子的恢复 flasher 满足 convoy-safe 且烧录可用时传--recover-board/--recover-fw:一旦某用例 HUNG,电池会中止,先通过 roster probe复位DUT(非破坏性,约 130 ms),只有复位无法解除楔死(wedge)时才重新烧录。手动运行不带这些标志,HUNG 后的设备会保持楔死——这是预期行为,需要你自己复位或重新烧录。
两个关键环境变量定义并发预算(见 hil_lock.py 与 hil_test.py):
HIL_USBTEST_PARALLEL(默认 2):每台宿主控制器最多并发 2 个电池。HIL_USBTEST_BATTERY_BUDGET(默认 260):单个电池的时长预算,超过即停止启动新用例。
技能文档特别警告:这个并发宽度是一个经过实测的吞吐/带宽权衡,不是安全天花板——但超出预算的并发电池是真实危险:无预算的并发电池曾让 VFIO 透传的 xHCI 出现致命 PCIe 错误而硬冻结整机;在并发电池下劣质 DUT 端口抖动曾直接烧毁一颗 uPD720201,而单纯降低并发宽度并不能修复这类问题。
另外几个实战细节:
- 电池运行会把
cafe 4010以 Gadget Zero 的 profile 注册进 usbtest 模块(每台机一次),并保留 id 与绑定:unbind 曾卡死宿主机 xHCI(usb_hcd_alloc_bandwidth),而下一个示例会在自己的 PID 下枚举。 - 烧录后总是等几秒钟再跑——枚举可能弹跳一次,
testusb若撞进这个间隙会看到设备在用例中途掉线。 - 在 CI 机器上做手动工作时:先 hold 板锁,结束后 release——绝不要停掉 actions runner;它一直运行,per-board flock 才是仲裁者(见 hil 技能)。绝不要在一台正在跑电池的机器旁边手动再启一个电池。
3.3 手动运行单个用例
调试时往往只想复现一个用例。由于 HIL 框架在电池结束后会把板子重新烧成驻留固件,手动单用例流程是:hold 板子 → 用 roster 里记录的 probe 参数烧录 usbtest(并钉住 probe)→ 等约 3–5 秒完成枚举 → 跑用例 → release。从本机 HIL 配置 JSON 中板子的条目读取:<probe-uid>/<args>是该板 flasher 的 uid/args,<uid>是板子自身的 uid,<variant>是待测变体(若条目没有 variant 列表则用板名)。非 J-Link flasher 则运行 hil_flash.py 中该 flasher 的flash_<flasher>()函数——它按条目的FLASHER_SUFFIX构建 usbtest 镜像:
python3 test/hil/helper/hil_lock.py hold <board> --reason "usbtest case 29" JLinkExe -USB <probe-uid> <args> -if swd -JTAGConf -1,-1 -speed auto -NoGui 1 -ExitOnError 1 \ -CommandFile cmake-build/cmake-build-<variant>/device/usbtest/usbtest.jlink python3 test/hil/usbtest.py --serial <uid> --tests 29 python3 test/hil/helper/hil_lock.py release <board>3.4 主机运行脚本的关键行为
test/hil/usbtest.py 是主机的权威运行器,处理驱动绑定、逐用例参数与结果解析。它的核心行为(均可在源码中验证):
- 5 字段绑定形式:绑定必须用
cafe 4010 0 0525 a4a0引用 Gadget Zero(0525:a4a0),使动态 id 继承其能力 profile(autoconf + ctrl_out + iso + intr)。绝不要注册裸的vid pid动态 id:那样 driver_info 为 NULL,usbtest_probe()会无 NULL 检查地解引用它,造成内核 oops。autoconf 还负责启用 bulk 端点发现。 - 模式设置:
set_pattern(0)(usbtest.py)把内核模块的 pattern 参数设为 0——Tier 1 固件源出全零,perf 用例 27/28 也依赖它。 - testusb 输出怪癖:其退出码只要设备存在就恒为 0,结果必须从 stdout 解析(正则
test (\d+),\s*(\d+)\.(\d+) secs与test (\d+) --> (\d+),见 usbtest.py);被能力 profile 门控(-EOPNOTSUPP)的用例会被 testusb 静默跳过——缺失结果行 = NOT RUN,脚本把它报为失败,因为所选电池中每个用例都应当运行。 - 逐用例参数表
PARAMS(usbtest.py):FS/HS 各一套,所有-s/-v值都是 512 的倍数以保持整包对齐——设备流送整包,非对齐的 IN 长度会溢出(-EOVERFLOW);用例 14/21 绝不能用默认参数跑(内核里 vary ≥ length 是 -EINVAL)。 - 宿主控制器兼容性检查
check_host_compat(usbtest.py):直接拒绝在 MosChip MCS9990(9710:9990)后面的 DUT 上运行(FRINDEX 硅片缺陷:EHCI 从不调度 int-OUT URB,且会把 unlinked read 当作短传输完成);Renesas uPD720201/202 则必须固件 ≥ 2.0.2.6(PCI 配置 0x6c 读取版本,固件是 RAM 上传的,断电即回退 ROM),否则其命令环会在 unlink 压力下死掉、hub worker 死锁持锁。 - HUNG 判定与恢复(usbtest.py):某用例 HUNG 时先经
confirm_wedge观察节点上的 D 状态进程最长WEDGE_CONFIRM_S=30秒——只有节点仍有持锁者才算楔死;确认后先 probe 复位(非破坏、约 130 ms,失败即可在源头上解开 URB),无效才重烧。全程用wedged_pids()扫描/proc中仍处 D 状态且命令行含该设备节点的进程来仲裁,失败即关闭(fail closed):扫描不完整时宁可继续按楔死处理。
3.5 手动 testusb 使用(了解陷阱)
仓库 README 同时给出了手动路径,但要特别当心testusb默认值:永远显式传 512 的倍数给-s/-v,且绝不要裸跑testusb -a(默认参数集包含参数非法的用例,以及全速下长达小时级的运行):
sudo modprobe usbtest echo "cafe 4010 0 0525 a4a0" | sudo tee /sys/bus/usb/drivers/usbtest/new_id sudo testusb -D /dev/bus/usb/<BBB>/<DDD> -t 1 -c 128 -s 1024 -v 512 # bulk write sudo testusb -D /dev/bus/usb/<BBB>/<DDD> -t 2 -c 128 -s 1024 -v 512 # bulk read宿主机侧要求:usbtest内核模块(CONFIG_USB_TEST)、由内核tools/usb/testusb.c构建的testusb二进制、以及 sudo 权限(usbfs ioctl + 驱动 bind/unbind)。
四、修复错误的 profile:模块重载而不是 remove_id
/sys/bus/usb/drivers/usbtest/new_id列表只显示cafe 4010,永远看不出它背后的能力 profile;一个已绑定的接口会保留它被 probe 时的 profile。错误 profile 的症状(比如用户态 profile0525 a4a4):用例 14/21、25/26、15/16/22/23 全部 NOTRUN,而 bulk 用例通过,且dmesg会点名 probe(正确的 probe 名是Linux gadget zero)。
修复方法是在预留的空闲机上重载模块;remove_id单独使用会让所有已绑定接口停留在旧 profile 上:
- 按 hil 技能的机群级规则整群预留(该命令及其 per-host 配置陷阱归 hil 技能所有),确认锁已持有、且没有
testusb/usbtest.py在运行。 sudo modprobe -r usbtest,然后sudo modprobe usbtest。若卸载被拒绝,说明仍有接口在使用:找到持有者并等待,绝不要强制卸载。- 注册正确条目:下一次
usbtest.py或hil_test.py运行会自动完成。 - 用
dmesg验证下一个枚举的板子出现Linux gadget zeroprobe,然后释放。
五、移植阶梯:新 MCU/DCD 到 30/30
把一块新 MCU/DCD 跑满整套电池,按层次推进,每层干净后才升 Tier,且每层之后都跑完整电池(不是只跑本层新增用例):
- Tier 1(bulk):置
USBTEST_TIER 1,先做到枚举成功 + 用例 0、9、10(EP0)+ 1–8、17–20、27、28 稳定。EP0 正确性优先——一切其他结果都通过它上报。 - Tier 2(ctrl_out 14/21)、Tier 3(interrupt 25/26)、Tier 4(iso 15/16/22/23)——逐层提高。
- 端点适配:Tier 4 需要 6 个端点 + EP0。小容量器件需要在示例自己的 usb_descriptors.h(
USBTEST_INT/ISO_EP_MPS_FS)和 tusb_config.h(CFG_TUD_VENDOR_TX_EPSIZE,LPC11/13 因 2 KB USB RAM 降到 512)里做 per-MCU mps/epbuf 覆盖,参考现有 CH32/LPC11 模式。实在装不下的部件进 skip.txt。 - 签核 = 可靠性,不是一次通过:3–10 次完整的 烧录→电池 循环。一次 30/30 在不稳定 bring-up 上说明不了任何问题;确定性的部分丢失计数(例如恰好 1/8 丢失)是特征签名而非噪声——要追查。
- 在 test/hil/tinyusb.json 注册板子,让 HIL 套件开始跑它。
六、用例 → DCD 子系统映射表
这是定位问题的核心索引。任何用例失败,先看它压测的是哪条 DCD 路径:
| 失败用例 | 压测内容 | 第一怀疑对象 |
|---|---|---|
| 9, 10 | EP0 控制风暴 | EP0 状态机、ZLP/状态阶段、负载下的控制饥饿 |
| 1–8, 17–20, 27, 28 | bulk source/sink、sg、perf | FIFO 处理、多包传输、ZLP 容忍度 |
| 11, 12, 24 | URB 传输中途 unlink | abort/close 路径留下半武装状态 |
| 13 | set/clear halt | stall 必须杀死传输;已武装 IN 上的 halt 必须冲刷 TX FIFO |
| 29 | 在已武装、未 halt的端点上 clear-halt | 经典坑:dcd_edpt_clear_stall复位 toggle 但解除武装了已排队的接收 → 永久 NAK,errno 110。修复:toggle 复位到 DATA0并且重新武装/保留挂起的传输。曾在 rp2040、fsdev、ch32_usbhs、rusb2 上独立发现 |
| 14, 21 | vendor EP0 写/读回 | 多包 control-OUT 分块、DCP 流控 |
| 25, 26 | interrupt src/sink | bulk 通了之后通常就免费 |
| 15, 16, 22, 23 | isochronous | 见下节 ISO 规则 |
七、ISO 规则:最常被违反的契约
等时端点是这套电池里被违反最多的约束:
- FS 下双向都只允许 DATA0——绝不要在 iso 端点上跑 bulk 式 toggle 逻辑(手工 toggle 的部件:跳过 iso IN 的 ISR toggle 翻转,以及 iso OUT 的 toggle 失配丢弃)。违反的症状:恰好每隔一个包丢失。
- 无握手——iso 从不 NAK/STALL;带响应字段的部件使用其“无响应”编码(例如 WCH 上的 NYET)。
dcd_edpt_iso_alloc/iso_activate绝不能是返回 false 的桩——usbd 会因此让接口 open 失败,内核报 "did not bind"/SET_CONFIG 超时。如果 DCD 以“硬件不支持”拒绝 iso,以数据手册为准——手册高于代码注释(本仓库树里两条“无 iso 支持”声明都是假的,其中一条参考手册只对某个端点号记载了例外)。- 多包 iso IN 提交是合法的:DCD 每帧流送一个包,在 ISR 中重新填充。慢速核可能需要双缓冲 iso 才能赶上帧截止时间(CH32V20X fsdev 只有 512 B PMA,
tusb_config.h中通过CFG_TUD_FSDEV_DOUBLE_BUFFERED_ISO_EP 1开启双缓冲,同时描述符把 iso mps 降到 32 以保持 2×32=64 B/端点 的 PMA 预算)。
八、调试阶梯:从 errno 到根因
8.1 errno 速查
| errno | 含义 |
|---|---|
| 110 | 超时——端点永久 NAK / 设备楔死 |
| 32 | EPIPE——意外 STALL |
| 5 | EIO——iso 包错误(查dmesg:"N errors out of M") |
| 71 | EPROTO——设备应答错误/太慢(在 HC 重试之后) |
8.2 Step 0:读用例到底在做什么
内核模块是ground truth,映射表只是摘要。在任何理论化之前——也总是在判断一个 HUNG 用例是否可恢复之前——先读内核源码。获取与运行机内核匹配的上游版本(uname -r;发行版打了自己的补丁时用发行版源码):
curl --fail -sSO "https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/plain/drivers/usb/misc/usbtest.c?h=v$(uname -r | sed -E 's/^([0-9.]+(-rc[0-9]+)?).*/\1/; s/\.0(-|$)/\1/')" # 在运行机上执行;x.y.0[-rcN] 对应 tag vx.y[-rcN]用例 N 位于内核usbtest_do_ioctl()的case N:下;内核tools/usb/testusb.c映射这些标志:-c= param.iterations,-s= param.length,-g= param.sglen(含义并不像字面那样)。注意 ABI 名(Debian ≤12 的6.1.0-18-amd64、Ubuntu 原装6.8.0-45-generic)映射到 vx.y,所以要从/proc/version(Debian)或/proc/version_signature(Ubuntu)读真实版本;Fedora(6.14.0-0.rc3…)或 Ubuntu-mainline(6.12.0-061200rc3-…)的 rc 内核会隐藏其-rcN,需手工补上 tag。
两个实战判读示例(技能文档中的精确记录):
- 真实流量与通过标准:用例 24 在
-c 256 -s 1024 -g 8下是 256 轮、每轮 8 个 bulk-OUT URB,unlinkurbs[num-4]/urbs[num-2],要求这两个返回-ECONNRESET、其余 6 个正常完成——不是标志看起来的“256 个 URB”。 - 等待是否有界——决定可恢复性:
simple_io用wait_for_completion_timeout(内核 :481);unlink 路径用裸的wait_for_completion(内核 :1502、:1615)。设备在那里 stall 会把 ioctl 永久卡在D 状态——它持有设备锁,没有任何办法恢复它(见 usb-kernel-recover 技能"The terminal case")。先知道这一点,能避免你把整台机器烧在注定无用的尝试上。
8.3 定位 DCD 路径:按序升级
usbtest.py的逐用例输出 + 捕获的dmesg(TEST n标记括住每个用例)。- usbmon(usb-kernel-debug 技能):URB 级 ground truth。它看不到 data toggle 或 NAK——toggle 失步和端点已死看起来一模一样(有 Submits 无 Completes);要在设备侧用 GDB 区分。
- 设备侧诊断:在原始失败用例上跑
target-debug(Espressif 用esp-target-debug)。 - 对照参考手册(
read-doc技能)再动任何寄存器级代码——按 CLAUDE.md,因为 DCD 里的注释/假设对硬件能力可能一直是错的。 - 尽早查厂商硅片 errata中的时序/DMA 挂起(一个未实现的 erratum 工作区曾在某端口造成 case-10 挂起)。
九、能通过 gcc/桌面审查、却在他处翻车的陷阱
TUD_OPT_HIGH_SPEED是编译期能力,不是实时速度:FS 配置描述符(以及 OTHER_SPEED)即使在 HS 构建上也必须用 FS 合法尺寸(int ≤ 64,iso IN+OUT ≤ 1023 B/帧)——使用独立的_FS/_HS描述符宏。- 未使用的
static inline辅助函数:clang 的-Wunused-function和 IAR 的Pe177会报错,而 gcc 保持安静 → 加TU_ATTR_UNUSED。 - 只在裸 asm 里引用的符号对 LTO 不可见,会在
-fltomake 构建中被丢弃 → 保留一个TU_ATTR_USED的 C 引用。 - 带硬件上下文栈(QingKe HWSTK)的核上嵌套 USB 中断:普通
__attribute__((interrupt))会破坏返回 → 使用依赖硬件栈的 naked 处理函数。 - 专用 USB RAM 预算(PMA/USB-RAM)随器件和构建系统段放置而异:查链接映射表,而不只是“能编译”。
十、红旗:停下来重新审视
- “跑通一次就算完事” → 跑多次重新烧录循环。
- “DCD 注释说硬件不支持” → 打开数据手册。
- “usbmon 看不出 toggle 问题” → usbmon 本来就看不到 toggle。
- “gcc 上没问题” → clang/IAR/LTO/make 还没验证。
- “修好了 iso IN” → 对 iso OUT 应用同样的豁免(toggle 逻辑是对称的)。
- 单板干净运行不代表并发/机群行为正确——机群运行每台宿主控制器最多 2 个电池(
HIL_USBTEST_PARALLEL)外加同一 hub 上行的并发烧录,单板永远不会压到这些。 - 凭用例名或表格行推理 → 打开
usbtest.c(第 8.2 节的 Step 0)。标志含义与表象不符,且可恢复性是那个用例的 wait 的属性,不是机器的属性。
十一、继续深入:仓库内的相关资源
- 固件实现:examples/device/usbtest/src/main.c、usb_descriptors.c、usb_descriptors.h、tusb_config.h
- 固件使用说明与完整用例表:examples/device/usbtest/README.md
- 主机运行器:test/hil/usbtest.py;HIL 编排:test/hil/hil_test.py;烧录与锁:test/hil/hil_flash.py、test/hil/helper/hil_lock.py;板子注册表:test/hil/tinyusb.json
- 配套技能:机群预留与并发规则见 .claude/skills/hil/SKILL.md;内核侧楔死恢复见 .claude/skills/usb-kernel-recover/SKILL.md
掌握这套电池后,你会获得一套可复现的“30/30 全绿”签核标准:它既是新 DCD bring-up 的验收门槛,也是任何 DCD 改动后回归的可靠性底线。
- 嵌入式
- 驱动开发
- 通信
- 物联网
【免费下载链接】tinyusb
An open source cross-platform USB stack for embedded system
相关推荐
Plate 项目 Lexical 测试收割:用可移植测试索引驱动 Slate v2 行为移植
Plate 项目 Lexical 测试收割:用可移植测试索引驱动 Slate v2 行为移植 导读 本文讲解 plate 仓库中 docs/editor tes
前端富文本UI组件如何把自己的 PC 游戏串到任何屏幕上:自托管串流主机 Sunshine 完整指南
如何把自己的 PC 游戏串到任何屏幕上:自托管串流主机 Sunshine 完整指南 Sunshine 是一款跑在自家游戏 PC 上的自托管游戏串流主机,它用硬件
音视频后端Semaphore 项目备份与恢复实战:TC-008 测试用例驱动的数据可移植性指南
Semaphore 项目备份与恢复实战:TC 008 测试用例驱动的数据可移植性指南 本文以 Semaphore 开源仓库中的功能测试用例 TC 008(项目备
后端DevOps任务调度认证鉴权
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考