SWD GPIO实现
LibXR::Debug::SwdGeneralGPIO<SwclkGpioType, SwdioGpioType, SwdIoDriveMode> 是一个基于 GPIO 轮询(bit-bang)的 SWD 探针实现。它继承自 LibXR::Debug::Swd,提供 SWD 链路层能力,通常由上层(如 CMSIS-DAP 处理器或调试器)调用。
重点放在使用方式和延时参数 loops_per_us 的选取/标定,不展开实现细节。
1. 总览
SwdGeneralGPIO 使用两根 GPIO 完成 SWD:
- SWCLK:由
SwclkGpioType驱动输出时钟。 - SWDIO:由
SwdioGpioType承担双向数据,运行时在“输出驱动”和“输入采样”之间切换。
时序由软件生成:通过目标时钟 hz 和延时标定系数 loops_per_us 计算“半周期应空转的循环次数”,从而逼 近指定 SWCLK。
推荐外围电路(强烈建议按此做):
- SWCLK/SWDIO 串联 33Ω(限流/抑制振铃)
- SWDIO 端接 10k 上拉
2. 模板参数与 GPIO 依赖能力
类定义:
template <typename SwclkGpioType, typename SwdioGpioType,
SwdIoDriveMode IO_DRIVE_MODE = SwdIoDriveMode::PUSH_PULL>
class SwdGeneralGPIO final : public Swd;
期望 GPIO 类型提供的最小能力(抽象描述):
SetConfig({Direction, Pull}) -> ErrorCodeWrite(bool)Read() -> bool
其中 SWDIO 需要支持:
- 输入采样:
INPUT + PULL_UP - 输出驱动由
IO_DRIVE_MODE决定SwdIoDriveMode::PUSH_PULL->OUTPUT_PUSH_PULLSwdIoDriveMode::OPEN_DRAIN->OUTPUT_OPEN_DRAIN
工程上选择输出模式时,可按下面这条经验先做:
- 板上已有明确的外部上拉时,优先考虑开漏输出;
- 没有外部上拉时,优先考虑推挽输出。
但这只是起步经验,不是硬规则。SWDIO / SWCLK 的实际电气行为会受到 GPIO 驱动能力、布线长度、阻尼电阻、探头负载、目标板输入结构等多种因素共同影响。
3. 快速开始
构造函数:
explicit SwdGeneralGPIO(SwclkGpioType& swclk,
SwdioGpioType& swdio,
uint32_t loops_per_us,
uint32_t default_hz = DEFAULT_CLOCK_HZ);
典型使用流程:
- 初始化 GPIO 对象与探针实例( 先给一个“保守”的频率与已标定的
loops_per_us)。 - 对目标执行
EnterSwd()(不确定目标当前处于何种调试模式时建议调用)。 - 上层用
Swd基类的“带重试”接口进行传输(处理 WAIT、插入 idle clocks 等)。
示例:
using Probe = LibXR::Debug::SwdGeneralGPIO<
MyGpio, MyGpio, LibXR::Debug::SwdIoDriveMode::PUSH_PULL>;
MyGpio swclk, swdio;
// loops_per_us 建议先标定;default_hz 从保守值起步(如 100k~500k)
Probe probe(swclk, swdio, /*loops_per_us=*/calibrated, /*default_hz=*/500000);
probe.EnterSwd();
// 上层建议统一走带重试的路径(示意,具体看你的 Swd 基类封装)
uint32_t idcode = 0;
LibXR::Debug::SwdProtocol::Ack ack;
probe.ReadIdCode(idcode, ack);
4. 时钟与延时参数(重点)
4.1 hz 的含义与行为
SetClockHz(hz) 用于设置“期望的 SWCLK 频率”。
hz会在实现支持的范围内被钳制(低于最小值会被抬高,高于最大值会被压低)。- 频率是否“真的等于设定值”,取决于
loops_per_us是否合理、GPIO 翻转速度、CPU 负载等。 hz == 0会使内部进入“无延时路径”(不主动插入 BusyLoop 延时),此时 SWCLK 实际频率由 CPU 与 GPIO 翻转速度决定,通常是“尽可能快”。
4.2 loops_per_us 是什么
loops_per_us 是一个“延时标定系数”:表示 BusyLoop 大约每 1 微秒需要的循环迭代次数。
它不是通用常量,通常会随以下因素显著变化:
- CPU 主频
- 编译优化等级(
-O0/-O2/-Os等) - 是否开启 LTO、不同编译器版本
- 指令/总线等待状态(某些 MCU 在不同电源/时钟域下也可能变化)
只要上述条件变化,就应重新标定 loops_per_us。
4.3 何时会进入“无延时路径”
实际工作时有两种情况会进入“无延时路径”:
- 你显式把
loops_per_us设为 0;或 - 在某个较高的
hz下,计算出来“半周期需要的循环次数 < 1”,内部会把半周期循环数置 0,从而自动走无延时路径。
无延时路径的意义:
- 优点:开销更低,吞吐更高。
- 代价:SWCLK 频率不再受
hz精准约束,而是“尽可能快/尽可能稳定”,以平台极限为准。
工程建议:
- 需要“可控且可复现”的 SWCLK:让
half_period_loops_明确大于 0(即loops_per_us标定合理,且hz不要设得过高)。 - 只追求“能跑、尽量快”:可以把
loops_per_us=0,或把hz调到足够高让系统自然进入无延时。