news 2026/9/29 5:56:49

TinyUSB usbtest 实战指南:用 Linux 内核 30 项 USB 电池测试验证与移植 DCD 驱动

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
TinyUSB usbtest 实战指南:用 Linux 内核 30 项 USB 电池测试验证与移植 DCD 驱动
  • 嵌入式
  • 驱动开发
  • 通信
  • 物联网

【免费下载链接】tinyusb

An open source cross-platform USB stack for embedded system

项目地址:https://gitcode.com/gh_mirrors/ti/tinyusb
点击查看免费下载

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 用例
1bulk source/sink0, 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]; #endif

2.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 上:

  1. 按 hil 技能的机群级规则整群预留(该命令及其 per-host 配置陷阱归 hil 技能所有),确认锁已持有、且没有testusb/usbtest.py在运行。
  2. sudo modprobe -r usbtest,然后sudo modprobe usbtest。若卸载被拒绝,说明仍有接口在使用:找到持有者并等待,绝不要强制卸载。
  3. 注册正确条目:下一次usbtest.py或hil_test.py运行会自动完成。
  4. 用dmesg验证下一个枚举的板子出现Linux gadget zeroprobe,然后释放。

五、移植阶梯:新 MCU/DCD 到 30/30

把一块新 MCU/DCD 跑满整套电池,按层次推进,每层干净后才升 Tier,且每层之后都跑完整电池(不是只跑本层新增用例):

  1. Tier 1(bulk):置USBTEST_TIER 1,先做到枚举成功 + 用例 0、9、10(EP0)+ 1–8、17–20、27、28 稳定。EP0 正确性优先——一切其他结果都通过它上报。
  2. Tier 2(ctrl_out 14/21)、Tier 3(interrupt 25/26)、Tier 4(iso 15/16/22/23)——逐层提高。
  3. 端点适配: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。
  4. 签核 = 可靠性,不是一次通过:3–10 次完整的 烧录→电池 循环。一次 30/30 在不稳定 bring-up 上说明不了任何问题;确定性的部分丢失计数(例如恰好 1/8 丢失)是特征签名而非噪声——要追查。
  5. 在 test/hil/tinyusb.json 注册板子,让 HIL 套件开始跑它。

六、用例 → DCD 子系统映射表

这是定位问题的核心索引。任何用例失败,先看它压测的是哪条 DCD 路径:

失败用例压测内容第一怀疑对象
9, 10EP0 控制风暴EP0 状态机、ZLP/状态阶段、负载下的控制饥饿
1–8, 17–20, 27, 28bulk source/sink、sg、perfFIFO 处理、多包传输、ZLP 容忍度
11, 12, 24URB 传输中途 unlinkabort/close 路径留下半武装状态
13set/clear haltstall 必须杀死传输;已武装 IN 上的 halt 必须冲刷 TX FIFO
29在已武装、未 halt的端点上 clear-halt经典坑:dcd_edpt_clear_stall复位 toggle 但解除武装了已排队的接收 → 永久 NAK,errno 110。修复:toggle 复位到 DATA0并且重新武装/保留挂起的传输。曾在 rp2040、fsdev、ch32_usbhs、rusb2 上独立发现
14, 21vendor EP0 写/读回多包 control-OUT 分块、DCP 流控
25, 26interrupt src/sinkbulk 通了之后通常就免费
15, 16, 22, 23isochronous见下节 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 / 设备楔死
32EPIPE——意外 STALL
5EIO——iso 包错误(查dmesg:"N errors out of M")
71EPROTO——设备应答错误/太慢(在 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 路径:按序升级

  1. usbtest.py的逐用例输出 + 捕获的dmesg(TEST n标记括住每个用例)。
  2. usbmon(usb-kernel-debug 技能):URB 级 ground truth。它看不到 data toggle 或 NAK——toggle 失步和端点已死看起来一模一样(有 Submits 无 Completes);要在设备侧用 GDB 区分。
  3. 设备侧诊断:在原始失败用例上跑target-debug(Espressif 用esp-target-debug)。
  4. 对照参考手册(read-doc技能)再动任何寄存器级代码——按 CLAUDE.md,因为 DCD 里的注释/假设对硬件能力可能一直是错的。
  5. 尽早查厂商硅片 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

项目地址:https://gitcode.com/gh_mirrors/ti/tinyusb
点击查看免费下载

相关推荐

上一篇:Superpaper终极指南:免费跨平台多显示器壁纸管理神器,让你的桌面视觉体验飙升
下一篇:终极跨平台macOS系统镜像获取方案:gibMacOS深度解析

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/29 5:55:47

身体在“去繁就简”?解读中年7个变化信号,别误读为衰老

天还没亮&#xff0c;大概五点出头&#xff0c;你又醒了。翻来覆去睡不着&#xff0c;手机屏幕的光刺得眼睛发酸。身边人还在打呼&#xff0c;你却清醒得像被什么东西叫醒了一样。以前周末能睡到十一点&#xff0c;现在六点准时睁眼&#xff0c;连闹钟都成了摆设。再看一眼日程…

作者头像 李华
网站建设 2026/9/29 5:54:00

NTFS权限深度解析:从ACL到所有者,彻底解决Windows权限问题

1. 这事儿得从“删不掉的文件”说起你是不是也遇到过这种情况&#xff1a;明明自己是管理员&#xff0c;删一个文件夹却弹出“你需要来自Administrators的权限才能对此文件夹进行更改”&#xff0c;点“继续”没用&#xff0c;右键改安全权限改不动&#xff0c;属性里“安全”标…

作者头像 李华
网站建设 2026/9/29 5:53:34

JupyterLab 从 Notebook 迁移、安装配置到避坑实战指南

这两年我从 Jupyter Notebook 切到 JupyterLab 之后&#xff0c;最直接的感受就是&#xff1a;我不用再同时开着五六个浏览器标签页来回找了。很多人第一次听说 JupyterLab&#xff0c;都觉得它只是换了层皮的 Notebook&#xff0c;实际上它是 Jupyter 生态里的新一代交互界面&…

作者头像 李华