复古计算爱好者在调试 Z80 系统时,常常面临一个尴尬困境:现代示波器和逻辑分析仪虽然功能强大,但价格不菲,且与 8 位时代的总线协议交互时往往显得笨重。Raspberry Pi Pico 2(RP2350)的出现为这一场景提供了新的解题思路 —— 利用其可编程 IO(PIO)和 DMA 引擎,以不到 10 美元的硬件成本,构建一个能够实时捕获 16 位地址总线、8 位数据总线及全部控制信号的嵌入式逻辑分析方案。
Z80 总线架构与关键时序参数
Z80 的并行总线包含 16 根地址线(A0-A15)、8 根数据线(D0-D7)以及多根控制信号线,包括 /MREQ(存储器请求)、/IORQ(I/O 请求)、/RD(读使能)、/WR(写使能)、/M1(取指周期标志)等。理解这些信号的时序关系是实现可靠捕获的前提。
Z80 的时钟特性值得特别关注。虽然常见型号标称运行频率为 4MHz 或 10MHz,但其时钟周期存在严格的上下限约束。根据原始 Z80A 数据手册,时钟低电平脉冲宽度不得超过 2μs,而高电平脉冲宽度理论上可以任意延长(手册保证功能正常的高电平宽度上限为 200μs)。这意味着 Z80 的最低运行频率约为 5kHz—— 这一特性为 "单步执行" 调试提供了理论基础。此外,RESET 信号需要保持至少 3 个时钟周期才能确保可靠复位。
Z80 采用机器周期(M cycle)和时钟周期(T state)两级时序概念。一条指令通常需要 3 到 6 个 T 周期完成一个 M 周期,而完整指令可能包含多个 M 周期。外部设备可通过拉低 /WAIT 信号请求 CPU 等待,这为慢速存储器与高速 CPU 的同步提供了机制。
RP2350 GPIO 映射策略
RP2350B 型号提供 48 路 GPIO,恰好能够完整覆盖 Z80 的全部总线信号。一个实用的映射方案如下:
| RP2350 GPIO | Z80 信号 |
|---|---|
| 0-15 | A0-A15 |
| 16-23 | D0-D7 |
| 24 | /RD |
| 25 | /WR |
| 26 | /BUSACK |
| 27 | /HALT |
| 28 | /M1 |
| 29 | /RFSH |
| 30 | /MREQ |
| 31 | /IORQ |
| 32 | /BUSRQ |
| 33 | /INT |
| 34 | /WAIT |
| 35 | CLK |
| 36 | /NMI |
| 38 | /RESET |
这一映射方案与 picoZ80 项目保持一致,便于复用现有的硬件设计资源。
需要注意的是,RP2350 相比 RP2040 引入了 GPIO base 的概念。在标准 SDK API 中,base 0 对应 GPIO 0-31,base 1 对应 GPIO 32-47。然而 PIO 外设的处理方式略有不同:PIO 的 base 0 对应 GPIO 0-31,而 base 1 对应 GPIO 16-47(而非 32-63),这是为了在两个 base 配置下都能实现 32 路并行采样。这种不一致性在跨模块编程时需要特别留意。
从软件轮询到 PIO+DMA 的演进
项目的实现可分为三个阶段,每个阶段对应不同的技术复杂度和捕获精度。
阶段一:基础 GPIO 轮询
最简单的实现方式是使用 gpio_get_all() 轮询读取 GPIO 状态。RP2350 的 SDK 提供了批量操作 API,如 gpio_init_mask() 和 gpio_set_dir_in_masked(),可以一次性配置多个引脚。然而,软件轮询方式存在明显的采样率瓶颈 —— 即使以 150MHz 主频运行,也难以可靠捕获 10MHz Z80 的总线活动。
// 基础轮询示例
uint32_t gpio31 = gpio_get_all();
uint8_t data8 = (GP_DATA_MASK & gpio31) >> GP_DATA_START;
这种方式适合验证硬件连接和基础功能,例如将捕获的数据总线值实时显示到 LED 上,或检测特定 I/O 地址的写入操作。
阶段二:条件触发捕获
在轮询基础上增加条件判断,可以实现简单的总线监控功能。例如,检测 /IORQ 和 /WR 同时为低电平(表示 I/O 写操作),且地址总线低 8 位匹配目标地址时,捕获数据总线值。
if (((gpio32 & GP_CTRL_MASK) == ((1<<GP_RD)|(0<<GP_WR)|(1<<GP_M1)|
|(1<<GP_MREQ)|(0<<GP_IORQ))) && ((gpio32 & 0xFF) == 0)) {
uint8_t data8 = (GP_DATA_MASK & gpio32) >> GP_DATA_START;
// 处理捕获的数据
}
这种方式本质上是用一颗 150MHz 的双核 Cortex-M33 模拟几片 TTL 逻辑芯片的功能,虽然可行,但显然不是最优解。
阶段三:PIO+DMA 实时捕获
真正的解决方案是利用 RP2350 的 PIO 和 DMA 外设。PIO 是可编程状态机,能够以确定的时钟周期采样 GPIO 状态,不受 CPU 中断和任务调度的影响。DMA 则负责将 PIO 捕获的数据批量传输到内存,实现零 CPU 开销的数据采集。
一个典型的 PIO 程序会配置为在 Z80 时钟边沿触发采样,同时捕获地址总线、数据总线和控制信号的状态。采样数据以 32 位字的形式存储,每个字包含一个时钟周期的完整总线快照。DMA 通道配置为循环模式,将采样数据流式传输到环形缓冲区,供后续分析或实时上传。
PIO 的 FIFO 深度为 8 级,配合 DMA 的突发传输模式,可以轻松应对 Z80 的总线速率。以 10MHz Z80 为例,每个时钟周期 100ns,而 RP2350 的 PIO 运行在系统时钟(默认 150MHz)下,每个 PIO 指令周期约 6.67ns,足以实现每个 Z80 时钟周期多次采样,或精确对齐到特定边沿。
工程化参数与实施清单
基于上述分析,构建一个可用的 RP2350-Z80 逻辑分析仪需要关注以下工程参数:
硬件连接
- 使用 47Ω 串联电阻保护 RP2350 GPIO,防止 Z80 总线冲突时过流
- Z80 的 5V 逻辑电平需要通过电平转换器或分压电阻适配到 RP2350 的 3.3V 输入
- CLK 信号建议通过缓冲器接入,避免负载效应影响 Z80 时序
PIO 配置
- 状态机时钟分频:建议设置为 Z80 时钟的 4-8 倍,确保每个 T 状态有多个采样点
- 采样触发条件:可选择 CLK 上升沿或 /MREQ、/IORQ 下降沿作为采样触发点
- 数据打包格式:32 位字中,高 16 位存储地址总线,次 8 位存储数据总线,低 8 位存储控制信号状态
DMA 配置
- 传输宽度:32 位字对齐
- 传输模式:循环缓冲区模式,避免数据覆盖
- 缓冲区大小:根据捕获时长和采样率计算,例如 10k 样本 × 4 字节 = 40KB
调试功能扩展
- 时钟控制:RP2350 可输出可变频率时钟到 Z80,实现单步执行或降速调试
- 指令感知单步:监控 /M1 信号,配合 /WAIT 实现指令级单步
- 存储器映射外设:模拟 ROM/RAM,实现代码注入和断点设置
固件开发环境
- 推荐使用 Earle Philhower 的 Arduino Pico Core,或直接使用 Pico C/C++ SDK
- 注意 RP2350 的 GPIO base 处理差异,PIO 使用
pio_set_gpio_base()设置 base
这种基于 RP2350 的方案不仅成本极低,而且具有极高的可编程性。从简单的总线监视器到复杂的指令级调试器,都可以通过软件迭代实现,为复古计算爱好者提供了一个功能强大且灵活的调试平台。
资料来源
- Kevin's Blog: "Watching a Z80 from an RP2350" (2026-05-26)
- GitHub: gamblor21/rp2040-logic-analyzer
内容声明:本文无广告投放、无付费植入。
如有事实性问题,欢迎发送勘误至 i@hotdrydog.com。