|
| 1 | +--- |
| 2 | +id: debug-swd-gpio |
| 3 | +title: SWD GPIO实现 |
| 4 | +sidebar_position: 2 |
| 5 | +--- |
| 6 | + |
| 7 | +# SWD GPIO实现 |
| 8 | + |
| 9 | +本文档描述 `LibXR::Debug::SwdGeneralGPIO<SwclkGpioType, SwdioGpioType>`:一个基于 GPIO 轮询(bit-bang)的 SWD 探针实现。它继承自 `LibXR::Debug::Swd`,提供 SWD 链路层能力,通常由上层(如 CMSIS-DAP 处理器/调试器)调用。 |
| 10 | + |
| 11 | +本文重点放在“如何使用”和“延时参数(loops_per_us)的选择/标定”,不展开实现细节。 |
| 12 | + |
| 13 | +--- |
| 14 | + |
| 15 | +## 1. 总览 |
| 16 | + |
| 17 | +`SwdGeneralGPIO` 使用两根 GPIO 完成 SWD: |
| 18 | + |
| 19 | +- SWCLK:由 `SwclkGpioType` 驱动输出时钟。 |
| 20 | +- SWDIO:由 `SwdioGpioType` 承担双向数据,运行时在“输出驱动”和“输入采样”之间切换。 |
| 21 | + |
| 22 | +时序由软件生成:通过目标时钟 `hz` 和延时标定系数 `loops_per_us` 计算“半周期应空转的循环次数”,从而逼近指定 SWCLK。 |
| 23 | + |
| 24 | +推荐外围电路(强烈建议按此做): |
| 25 | +- SWCLK/SWDIO 串联 33Ω(限流/抑制振铃) |
| 26 | +- SWDIO 端接 10k 上拉 |
| 27 | + |
| 28 | +--- |
| 29 | + |
| 30 | +## 2. 模板参数与 GPIO 依赖能力 |
| 31 | + |
| 32 | +类定义: |
| 33 | + |
| 34 | +```cpp |
| 35 | +template <typename SwclkGpioType, typename SwdioGpioType> |
| 36 | +class SwdGeneralGPIO final : public Swd; |
| 37 | +``` |
| 38 | +
|
| 39 | +期望 GPIO 类型提供的最小能力(抽象描述): |
| 40 | +- `SetConfig({Direction, Pull}) -> ErrorCode` |
| 41 | +- `Write(bool)` |
| 42 | +- `Read() -> bool` |
| 43 | +
|
| 44 | +其中 SWDIO 需要支持两种配置: |
| 45 | +- 输出驱动:`OUTPUT_PUSH_PULL` |
| 46 | +- 输入采样:`INPUT + PULL_UP` |
| 47 | +
|
| 48 | +--- |
| 49 | +
|
| 50 | +## 3. 快速开始 |
| 51 | +
|
| 52 | +构造函数: |
| 53 | +
|
| 54 | +```cpp |
| 55 | +explicit SwdGeneralGPIO(SwclkGpioType& swclk, |
| 56 | + SwdioGpioType& swdio, |
| 57 | + uint32_t loops_per_us, |
| 58 | + uint32_t default_hz = DEFAULT_CLOCK_HZ); |
| 59 | +``` |
| 60 | + |
| 61 | +典型使用流程: |
| 62 | +1) 初始化 GPIO 对象与探针实例(先给一个“保守”的频率与已标定的 `loops_per_us`)。 |
| 63 | +2) 对目标执行 `EnterSwd()`(不确定目标当前处于何种调试模式时建议调用)。 |
| 64 | +3) 上层用 `Swd` 基类的“带重试”接口进行传输(处理 WAIT、插入 idle clocks 等)。 |
| 65 | + |
| 66 | +示例: |
| 67 | + |
| 68 | +```cpp |
| 69 | +using Probe = LibXR::Debug::SwdGeneralGPIO<MyGpio, MyGpio>; |
| 70 | + |
| 71 | +MyGpio swclk, swdio; |
| 72 | + |
| 73 | +// loops_per_us 建议先标定;default_hz 从保守值起步(如 100k~500k) |
| 74 | +Probe probe(swclk, swdio, /*loops_per_us=*/calibrated, /*default_hz=*/500000); |
| 75 | + |
| 76 | +probe.EnterSwd(); |
| 77 | + |
| 78 | +// 上层建议统一走带重试的路径(示意,具体看你的 Swd 基类封装) |
| 79 | +uint32_t idcode = 0; |
| 80 | +LibXR::Debug::SwdProtocol::Ack ack; |
| 81 | +probe.ReadIdCode(idcode, ack); |
| 82 | +``` |
| 83 | +
|
| 84 | +--- |
| 85 | +
|
| 86 | +## 4. 时钟与延时参数(重点) |
| 87 | +
|
| 88 | +### 4.1 `hz` 的含义与行为 |
| 89 | +
|
| 90 | +`SetClockHz(hz)` 用于设置“期望的 SWCLK 频率”。 |
| 91 | +
|
| 92 | +- `hz` 会在实现支持的范围内被钳制(低于最小值会被抬高,高于最大值会被压低)。 |
| 93 | +- 频率是否“真的等于设定值”,取决于 `loops_per_us` 是否合理、GPIO 翻转速度、CPU 负载等。 |
| 94 | +- `hz == 0` 会使内部进入“无延时路径”(不主动插入 BusyLoop 延时),此时 SWCLK 实际频率由 CPU 与 GPIO 翻转速度决定,通常是“尽可能快”。 |
| 95 | +
|
| 96 | +### 4.2 `loops_per_us` 是什么 |
| 97 | +
|
| 98 | +`loops_per_us` 是一个“延时标定系数”:表示 BusyLoop 大约每 1 微秒需要的循环迭代次数。 |
| 99 | +
|
| 100 | +它不是通用常量,通常会随以下因素显著变化: |
| 101 | +- CPU 主频 |
| 102 | +- 编译优化等级(`-O0/-O2/-Os` 等) |
| 103 | +- 是否开启 LTO、不同编译器版本 |
| 104 | +- 指令/总线等待状态(某些 MCU 在不同电源/时钟域下也可能变化) |
| 105 | +
|
| 106 | +只要上述条件变化,就应重新标定 `loops_per_us`。 |
| 107 | +
|
| 108 | +### 4.3 何时会进入“无延时路径” |
| 109 | +
|
| 110 | +实际工作时有两种情况会进入“无延时路径”: |
| 111 | +- 你显式把 `loops_per_us` 设为 0;或 |
| 112 | +- 在某个较高的 `hz` 下,计算出来“半周期需要的循环次数 < 1”,内部会把半周期循环数置 0,从而自动走无延时路径。 |
| 113 | +
|
| 114 | +无延时路径的意义: |
| 115 | +- 优点:开销更低,吞吐更高。 |
| 116 | +- 代价:SWCLK 频率不再受 `hz` 精准约束,而是“尽可能快/尽可能稳定”,以平台极限为准。 |
| 117 | +
|
| 118 | +工程建议: |
| 119 | +- 需要“可控且可复现”的 SWCLK:让 `half_period_loops_` 明确大于 0(即 `loops_per_us` 标定合理,且 `hz` 不要设得过高)。 |
| 120 | +- 只追求“能跑、尽量快”:可以把 `loops_per_us=0`,或把 `hz` 调到足够高让系统自然进入无延时。 |
| 121 | +
|
| 122 | +--- |
| 123 | +
|
| 124 | +## 5. `loops_per_us` 的选择与标定方法 |
| 125 | +
|
| 126 | +下面给出两种常用标定方法。原则是:标定应在与你最终固件“相同编译选项 + 相同主频”条件下进行。 |
| 127 | +
|
| 128 | +### 方法 A:用硬件计时器/周期计数器标定 |
| 129 | +
|
| 130 | +思路:执行一次已知循环次数的 BusyLoop,测出耗时,再反推 `loops_per_us`。 |
| 131 | +
|
| 132 | +操作要点: |
| 133 | +1) 选择足够大的循环次数 N(例如 1e6 级别),保证测量时间明显大于计时器分辨率。 |
| 134 | +2) 记录开始时间 t0,执行 BusyLoop(N),记录结束时间 t1。 |
| 135 | +3) 计算耗时 `dt_us`(微秒)。 |
| 136 | +4) `loops_per_us ≈ N / dt_us`(取整即可)。 |
| 137 | +
|
| 138 | +优点:不依赖 SWD 外设、也不依赖目标板状态;标定完成后频率可预期。 |
| 139 | +
|
| 140 | +### 方法 B:用逻辑分析仪/示波器对 SWCLK 实测回归 |
| 141 | +
|
| 142 | +思路:先给一个粗略的 `loops_per_us`,设置一个低到中等的 `hz`(例如 100 kHz 或 500 kHz),测 SWCLK 实际频率,再调整 `loops_per_us` 使之贴合。 |
| 143 | +
|
| 144 | +操作要点: |
| 145 | +1) 先用“可确保进入有延时路径”的配置(例如较低 `hz`)。 |
| 146 | +2) 测量 SWCLK 频率 `f_meas`。 |
| 147 | +3) 按比例调整:`loops_per_us_new ≈ loops_per_us_old * (f_meas / f_target)`,反复迭代 1~2 次即可收敛。 |
| 148 | +
|
| 149 | +优点:不需要访问计时器资源;同时把 GPIO 翻转开销一并纳入拟合。 |
| 150 | +注意:当你已经进入无延时路径时(频率很高),这种方法会失效,因为再调 `loops_per_us` 也未必能“拉回”到有延时路径。 |
| 151 | +
|
| 152 | +--- |
| 153 | +
|
| 154 | +## 6. 频率选择建议(实践导向) |
| 155 | +
|
| 156 | +建议从保守到激进逐步上调,避免一上来就把链路推到边界: |
| 157 | +
|
| 158 | +- 初始 bring-up:100 kHz ~ 500 kHz(更容易排查连线/电气问题) |
| 159 | +- 稳定后提速:1 MHz、2 MHz、4 MHz 分档尝试 |
| 160 | +- 如果需要更高:优先确认走线长度、阻尼电阻、地参考与目标板 SWD 引脚驱动能力;然后再上调 |
| 161 | +
|
| 162 | +当出现以下症状,通常意味着频率过高或信号完整性不足: |
| 163 | +- ACK 频繁出现 WAIT/FAULT/NO_ACK |
| 164 | +- 读回数据 parity 失败 |
| 165 | +- 只在某些线长/某些板子上不稳定 |
| 166 | +
|
| 167 | +处理顺序建议: |
| 168 | +1) 先降频; |
| 169 | +2) 确认 33Ω 串阻与 SWDIO 上拉; |
| 170 | +3) 缩短线缆/改善接地; |
| 171 | +4) 再考虑重新标定 `loops_per_us`(尤其在改了优化选项之后)。 |
| 172 | +
|
| 173 | +--- |
| 174 | +
|
| 175 | +## 7. 关闭与安全态 |
| 176 | +
|
| 177 | +调用 `Close()` 会把探针引脚恢复到更安全的状态(用于退出调试或切换复用功能时): |
| 178 | +- SWCLK 置高 |
| 179 | +- SWDIO 切到输入上拉 |
| 180 | +
|
| 181 | +--- |
0 commit comments