跳到主要内容

DAPLinkV2 设备协议栈

本文档描述 XRUSB 的 CMSIS-DAP v2(Bulk) 设备类实现:LibXR::USB::DapLinkV2Class<SwdPort>。测试可用的VID:PID0x0D28:0x2040,BCD版本:0x0201

该设备类面向通用 CMSIS-DAP v2 主机工具链(如 pyOCD、OpenOCD 的 CMSIS-DAP backend、DAPLink 兼容客户端等)的 USB Bulk 传输方式,采用 1 个 Vendor Interface + 2 个 Bulk 端点(1 IN + 1 OUT) 的传输模型,实现 DAP v2 常用命令子集(以 SWD 为主),并提供 Windows 侧即插即用的 WinUSB(MS OS 2.0)能力宣告。

由于 DAPLink 通常会声明自身为复合设备,因此需要搭配其他 USB Class 使用(例如 CDC 虚拟串口等)。上位机识别设备时也常会查找带有 CMSIS-DAP 字符串的设备,因此建议的 LanguagePack 为:

static constexpr auto USB_FS_LANG_PACK =
LibXR::USB::DescriptorStrings::MakeLanguagePack(
LibXR::USB::DescriptorStrings::Language::EN_US, "XRobot", "CMSIS-DAP",
"XRUSB-DEMO-XRDAP-");

支持能力:

  • CMSIS-DAP v2 Bulk transport
  • SWD-onlyDAP_Connect 仅支持 SWD;JTAG 未实现)
  • 可选 nRESET 控制(通过 GPIO* nreset_gpio 注入,缺省为不支持)
  • SWJ_Pins shadow 语义(SWDIO/SWCLK 以 shadow 状态对主机表现;nRESET 若连线可返回真实电平)
  • WinUSB(MS OS 2.0)BOS 平台能力(CompatibleID="WINUSB" + DeviceInterfaceGUIDs)
  • DAP_Transfer / DAP_TransferBlock(含 AP posted-read pipeline;Transfer 支持 match / timestamp 约束检查)

1. 类与构造方式

1.1 LibXR::USB::DapLinkV2Class<SwdPort>

该类为模板类,SWD 后端类型由模板参数 SwdPort 指定。SwdPort 需提供本类用到的 SWD 能力(进入/关闭 SWD、DP/AP 读写事务、序列读写、设置时钟与传输策略等)。

构造函数:

template <typename SwdPort>
explicit DapLinkV2Class(
SwdPort& swd_link,
LibXR::GPIO* nreset_gpio = nullptr,
Endpoint::EPNumber data_in_ep_num = Endpoint::EPNumber::EP_AUTO,
Endpoint::EPNumber data_out_ep_num = Endpoint::EPNumber::EP_AUTO);

参数说明:

  • swd_link:SWD 链路对象引用(SwdPort 实例)。
  • nreset_gpio:可选 nRESET GPIO;若为空则 reset 相关命令以 best-effort 方式处理。
  • data_in_ep_num / data_out_ep_num:Bulk IN/OUT 端点号;支持自动分配(EP_AUTO)。

常用接口:

  • SetInfoStrings(info):覆盖 DAP_Info 字符串。
  • GetState():获取内部 DAP 状态结构(读)。
  • IsInited():是否已完成绑定初始化。

1.2 InfoStrings

DAP_Info 字符串集合:

struct InfoStrings {
const char* vendor;
const char* product;
const char* serial;
const char* firmware_ver;

const char* device_vendor;
const char* device_name;
const char* board_vendor;
const char* board_name;
const char* product_fw_ver;
};

返回约定:

  • 字符串返回包含末尾 NUL。
  • 截断时保证以 NUL 结尾。

2. USB 接口与端点

2.1 接口描述符

DapLinkV2Class 贡献 1 个接口,不使用 IAD:

  • GetInterfaceCount() = 1
  • HasIAD() = false

接口 class 固定为 0xFF(Vendor Specific),并暴露 2 个 Bulk 端点。

2.2 Bulk 端点

  • Bulk OUT:Host → Device(DAP 请求包)
  • Bulk IN:Device → Host(DAP 响应包)

端点分配与配置发生在 BindEndpoints()

  • EndpointPool 分配 OUT/IN 端点(支持指定/自动端点号)。
  • 端点类型为 BULK;最大传输长度以 UINT16_MAX 作为上限,底层会选择合法值。
  • 配置描述符中的 wMaxPacketSize 来自端点对象的 MaxPacketSize()

3. WinUSB(MS OS 2.0)支持

该类在 BOS 中声明 MS OS 2.0 平台能力,并提供 MS OS 2.0 descriptor set:

  • BOS Capability:MS OS 2.0 Platform Capability(数量 1)
  • Vendor code:0x20
  • Compatible ID:"WINUSB"
  • DeviceInterfaceGUIDs(REG_MULTI_SZ,UTF-16LE):{CDB3B5AD-293B-4663-AA36-1AAE46463776}(单 GUID + 双 NUL 结束)

接口号在绑定时确定,因此 function subset 的 bFirstInterface 会在 BindEndpoints() 时更新,以确保 Windows 枚举一致。


4. 传输模型(Bulk 请求/响应)

本类实现 CMSIS-DAP v2 over Bulk 的同步请求/响应模型:

  1. 主机向 Bulk OUT 发送一帧请求。
  2. 设备在 OUT 完成回调中解析请求并生成响应(写入 IN 端点缓冲)。
  3. 设备通过 Bulk IN 发送响应。
  4. IN 完成后,设备重新 arm OUT 接收下一帧请求。

5. 生命周期:Bind / Unbind

5.1 BindEndpoints(endpoint_pool, start_itf_num)

要点:

  • 记录 interface_num_ = start_itf_num,并更新 WinUSB function subset 的接口号字段。
  • 分配并配置 Bulk OUT/IN 端点,注册回调。
  • 生成并提交配置描述符块(Interface + 2x Endpoint)。
  • 运行时默认值:
    • debug_port = DISABLED
    • transfer_abort = false
    • swj_clock_hz = 1MHz 并同步到 swd_link
    • SWJ shadow 默认:SWDIO=1、nRESET=1、SWCLK=0
  • inited_=true,arm OUT 接收。

5.2 UnbindEndpoints(endpoint_pool)

要点:

  • 清运行态标志并关闭 SWD 后端。
  • 关闭并释放 IN/OUT 端点,归还给端点池。
  • 复位 shadow 默认值(SWDIO=1、nRESET=1、SWCLK=0)。

6. 运行时状态与默认值

6.1 DAP 状态

内部状态结构 LibXR::USB::DapLinkV2Def::State 关键字段包括:

  • debug_port:默认 DISABLED;CONNECT 后为 SWD
  • transfer_abort:TransferAbort 标志
  • transfer_cfg:TransferConfigure 解析后的策略(idle_cycles / retry_count / match_retry)

6.2 SWJ 时钟

  • 默认:1,000,000 Hz
  • DAP_SWJ_Clock 更新内部变量并调用 swd_link.SetClockHz(hz)

6.3 SWJ shadow 语义

类内部维护 SWJ pin shadow:

  • 默认:SWDIO=1、nRESET=1、SWCLK=0
  • DAP_SWJ_Pins 会对被选择的 pin 更新 shadow
  • nRESET:
    • nreset_gpio 存在,则会驱动 GPIO;读取时返回真实电平
    • nreset_gpio 不存在,则按 shadow 表现

7. 命令集(实现概览)

命令分发逻辑位于 ProcessOneCommand(),以请求首字节 CMD 决定处理器;未识别命令返回单字节 0xFF

7.1 已实现命令列表

CommandID行为概述
DAP_InfoINFO返回字符串/数值信息(含 CAPABILITIES / PACKET_COUNT / PACKET_SIZE / TIMESTAMP_CLOCK 等)
DAP_HostStatusHOST_STATUS返回 OK
DAP_ConnectCONNECT仅支持 SWD;成功返回端口 SWD
DAP_DisconnectDISCONNECT关闭 SWD 并回到 DISABLED
DAP_TransferConfigureTRANSFER_CONFIGURE设置 idle_cycles / retry / match_retry,并映射到 SWD policy
DAP_TransferTRANSFERDP/AP 读写;支持 match / timestamp;AP posted-read pipeline
DAP_TransferBlockTRANSFER_BLOCKDP/AP block 读写;AP read 使用 posted pipeline;不支持 match/timestamp
DAP_TransferAbortTRANSFER_ABORT置 abort 标志,下一次 Transfer/Block 返回错误并清标志
DAP_WriteABORTWRITE_ABORT写 ABORT,按 ack/EC 返回 OK/ERROR
DAP_DelayDELAY微秒延时
DAP_ResetTargetRESET_TARGET若有 nRESET 则执行脉冲并 Execute=1;否则 Execute=0;始终返回 DAP_OK
DAP_SWJ_PinsSWJ_PINS更新 shadow 并 best-effort 控制 nRESET;支持 PinWait
DAP_SWJ_ClockSWJ_CLOCK更新 SWJ clock
DAP_SWJ_SequenceSWJ_SEQUENCE写入 SWJ bit 序列(LSB-first),并更新 shadow(SWDIO=last bit, SWCLK=0)
DAP_SWD_ConfigureSWD_CONFIGUREbest-effort 解析,返回 OK
DAP_SWD_SequenceSWD_SEQUENCE多段输入/输出序列;输入数据追加在响应尾部(LSB-first)
DAP_QueueCommandsQUEUE_COMMANDS返回 <CMD, DAP_ERROR>
DAP_ExecuteCommandsEXECUTE_COMMANDS返回 <CMD, DAP_ERROR>

注:具体数值 ID 取决于 DapLinkV2Def::CommandId 的定义;本文以枚举名表示。

7.2 DAP_Info 关键字段

  • CAPABILITIESDAP_CAP_SWD
  • PACKET_COUNT127
  • PACKET_SIZE:返回 IN 端点的 MaxTransferSize()
  • TIMESTAMP_CLOCK1,000,000(与微秒时间基准匹配)

8. 使用示例

8.1 设备侧初始化(示意)

#include "daplink_v2.hpp"
#include "usb/device.hpp"
#include "debug/swd.hpp"

LibXR::Debug::Swd swd(/* ... init ... */);
LibXR::GPIO nreset(/* ... optional ... */);

LibXR::USB::DapLinkV2Class<LibXR::Debug::Swd> dap(swd, &nreset);

// 可选:覆盖 DAP_Info 字符串
LibXR::USB::DapLinkV2Class<LibXR::Debug::Swd>::InfoStrings info;
info.vendor = "XRobot";
info.product = "DAPLinkV2";
info.serial = "00000001";
info.firmware_ver = "2.0.0";
dap.SetInfoStrings(info);

// USB device class list: {{&dap}}
// usb_dev.Init();
// usb_dev.Start();

8.2 Windows/WinUSB 侧访问

该类通过 BOS/MS OS 2.0 描述符集声明 WinUSB 与 DeviceInterfaceGUIDs,Windows 通常可在无需自定义 INF 的情况下枚举为 WinUSB 设备,并可通过 GUID 在用户态进行枚举与打开。