并行 I/O:Zilog Z80 PIO
11.1 概述
拥有一台能运行复杂程序的计算机固然很好,但如果无法与外部世界交互,它就毫无用处。 其主要目的是与用户交互,这就是为什么我们需要一些输入和输出,即 I/O。
I/O 让 CPU 能够通过切换引脚电平、产生可被任何外部实体解读的信号,或读取由这些实体驱动的引脚,来与外部世界通信。这样,我们就可以连接鼠标、键盘、显示器、存储设备等……
11.2 硬件实现
在 Zeal 8-bit Computer 上,使用了官方 Zilog Z80 PIO 集成电路来提供 GPIO。在 PCB 上标记为 U5,采用 PLCC44 封装并配有插座。由于它使用并行接口,因此直接连接到主数据总线。
由于它还需要连接 CPU 时钟线,因此选择了 Z80 PIO 的 10MHz 版本,即 Z84C2010。
Z80 PIO 集成电路在我们应用场景中的主要优势有:
- 提供 2 个端口,A 和 B,每个端口 8 条线
- 位控制模式:允许我们独立处理端口中的每条线
- 完全兼容 Z80 CPU 模式 2 中断模式
- 文档完善且编程简单
- 使用并行接口,直接连接到数据总线
以下是其引脚排列方式:
上图中某些信号有两个名称,中间用破折号 - 分隔。第一个描述是来自 Z80 PIO 数据手册 的官方名称,第二个是该引脚在 Zeal 8-bit Computer 主板上的实际功能。
标记为 $NC$ 的信号未连接任何东西,且不得连接到任何信号。
以下是负责确保芯片正常工作的引脚描述:
- $DATA\ 0$ 到 $DATA\ 7$:数据总线,所有并行组件共用。包含要写入特定 I/O 端口的字节或从端口读取的字节。
- $\overline{CLK}$:来自 10MHz 有源振荡器的信号,与送往 Z80 CPU 的时钟相同。
- $\overline{RD}$:来自 CPU 的信号,当 CPU 执行读取操作时有效。
- $\overline{IORQ}$:来自 CPU 的信号,当 CPU 执行 I/O 请求时有效。由于 PIO 映射在 I/O 总线上,此信号必须有效才能启用 PIO。
- $\overline{INT}$:用于向 CPU 请求中断的输出信号,请注意这是一个开漏信号。换句话说,PIO 仅在需要时将其拉低,但从不拉高,这是由一个 1kΩ 的上拉电阻负责的。
- $IEI$ 和 $EIO$:Zilog 组件实现的中断输入和输出信号,用于创建具有优先级的菊花链中断。在我们的情况下未使用这些信号,但可通过扩展端口使用,以便任何 Zilog 组件可以在那里链接。请注意 $IEI$ 通过 1kΩ 电阻上拉。
- $\overline{M1}$:来自 CPU 的信号,当 CPU 执行指令取指时有效,主要用于通知 PIO 正在处理中断。
- $\overline{CE}$:来自逻辑胶合电路的信号,连接到 $\overline{PIO\_ENABLE}$ 信号,当 CPU 对 PIO 执行 I/O 请求时有效,PIO 映射在 [
0xD0,0xDF] 之间。更多信息见逻辑胶合章节。 - $B/\overline{A}$ - $ADDR\ 0$:PIO 用来区分 CPU 试图与哪个端口通信的信号。当此线为低电平时,端口 A 是目标;为高电平时,端口 B 是目标。在 Zeal 8-bit Computer 上,此线连接到 $ADDR\ 0$。
- $C/\overline{D}$ - $ADDR\ 1$:PIO 用来区分 CPU 试图读取或写入哪个寄存器的信号。当此线为低电平时,CPU 读/写端口的当前状态(I/O 值)。当其为高电平时,CPU 读/写其中一个端口的配置。
后三个信号定义了 PIO 的以下 I/O 映射:
0xD0 - 端口 A 数据 0xD1 - 端口 B 数据 0xD2 - 端口 A 配置 0xD3 - 端口 B 配置 0xD4 - 同 0xD0(不推荐使用) 0xD5 - 同 0xD1(不推荐使用) 0xD6 - 同 0xD2(不推荐使用) 0xD7 - 同 0xD3(不推荐使用) 0xD8 - 同 0xD0(不推荐使用) 0xD9 - 同 0xD1(不推荐使用) 0xDA - 同 0xD2(不推荐使用) 0xDB - 同 0xD3(不推荐使用) 0xDC - 同 0xD0(不推荐使用) 0xDD - 同 0xD1(不推荐使用) 0xDE - 同 0xD2(不推荐使用) 0xDF - 同 0xD3(不推荐使用)
11.3 端口 A 和 B
上图中出现的其他引脚与端口 A 和 B 相关。PIO 可以在四种模式下操作每个端口:
- 输出模式:全部 8 个引脚代表一个 8 位输出值
- 输入模式:全部 8 个引脚代表一个 8 位输入值
- 双向模式:全部 8 个引脚代表一个同时支持输入和输出的 8 位值
- 位控制模式:每个引脚相互独立,因此可以独立地作为输入或输出
由于双向模式仅在端口 A 上可用,并强制端口 B 处于位控制模式,因此端口 A 被视为用户端口,可由用户自由使用,而端口 B 是系统端口。
因此,用户总共有 8 个可自由使用的 I/O。更多详情见以下章节。
11.4 用户端口
在上图所示的引脚排列中,与用户端口(即端口 A)相关的信号有:
- ${PA}\ 0$ 到 $PA\ 7$:代表端口 A 上可用的 8 个 I/O。用户可自由使用。
- $ARDY$:"就绪"输出引脚,根据端口 A 配置的模式以不同方式使用:
- 在输出模式下,它有效时表示端口已加载了可供读取的新值。
- 在输入模式下,它有效时通知用户端口已空,准备好接收数据。
- 在双向模式下,它有效时表示端口上有数据可用。但是,只有在 $\overline{ASTB}$ 有效时,数据才会出现在数据总线上。
- 在位控制模式下,它不被使用,并被 PIO 强制拉低。
- $\overline{ASTB}$:"选通"输入引脚,根据端口 A 配置的模式以不同方式使用:
- 在输出模式下,外设发出的上升沿告知 PIO 写入端口的数据已成功接收。
- 在输入模式下,该线上的选通信号将当前数据线状态存入 PIO 端口 A 的内部寄存器。
- 在双向模式下,当该信号有效时,端口 A 的内部寄存器值被放置到数据线上。该信号的上升沿标记数据的接收。
- 在位控制模式下,它不被使用/内部未连接。
有关引脚工作原理的更多详细信息,请查阅官方 Z80 PIO 用户手册。
⚠️ 警告
对端口 A 使用双向模式需要 $BRDY$ 和 $\overline{BSTB}$ 信号可用。然而,如上图引脚排列所示,$BRDY$ 未连接到外部连接器,而 $\overline{BSTB}$ 直接连接到 $Vcc$(5V)。因此,使用这些引脚需要对主板进行一些硬件修改才能访问。
不过,可以通过在运行时在软件中切换输入和输出模式,来模拟一个更简单的双向端口 A。
11.5 系统端口
端口 B 由 Zeal 8-bit Computer 内部使用,必须始终编程为位控制模式,因为所有信号都需要独立管理:
- PB 7 - $\overline{KB\ Signal}$:由 PS/2 键盘电路产生的输入信号,每当从 PS/2 接口接收到一个新字节时变为低电平。更多详情见 PS/2 键盘章节。
- PB 6 - $\overline{V Sync}$:输入信号,当外部视频卡进入 V-blank 状态时有效。原始信号位于视频连接器上,然后经过板子上标记为
U15的 74LS07 芯片提供的集电极开路缓冲器,再通过此引脚进入 PIO。得益于这个 74LS07 集成电路,该信号为开漏信号,并通过标记为RN3的 1kΩ 电阻上拉。这也意味着,来自视频连接器的原始信号在电压至少达到 2V 时被视为高电平,因此它与 3.3V 逻辑电平完全兼容。 - PB 5 - $\overline{H Sync}$:输入信号,当外部视频卡进入 H-blank 状态时有效。与 $\overline{V Sync}$ 信号相同,它也来自视频连接器,然后经过 74LS07 缓冲器。它也与 3.3V 逻辑电平兼容。由于这两个信号以固定间隔出现,可用于时序计算。
- PB 4 - UART TX:输出信号,通过 512Ω 电阻连接到用户端口。顾名思义,它的目标是作为 UART 发送(TX)信号使用,但由于 Zeal 8-bit Computer 没有配备任何 UART 或串行芯片,此信号必须在软件中模拟。
- PB 3 - UART RX:输出信号,通过 512Ω 电阻连接到用户端口。顾名思义,它的目标是作为 UART 接收(RX)信号使用。同样,出于相同原因,此信号必须在软件中模拟。更多详情将在 UART 章节中给出。
- PB2 - I2C SDA IN:输入信号,连接到内部集成电路(I²C)章节中描述的 I2C 设备以及用户端口。用于读取 SDA 线的当前状态,这是一个经过同一个 74LS07 芯片的开漏信号。与 UART 相同的原因,此信号在软件中模拟。
- PB1 - I2C SCL OUT:输出信号,连接到内部集成电路(I²C)章节中描述的 I2C 设备以及用户端口。用于输出在软件中模拟的时钟信号。这也是一个开漏信号。
- PB0 - I2C SDA OUT:输出信号,连接到内部集成电路(I²C)章节中描述的 I2C 设备以及用户端口。用于写入 SDA 线的当前状态,这是一个经过同一个 74LS07 芯片的开漏信号。
- $BRDY$:未连接任何东西的信号
- $\overline{BSTB}$:直接连接到 $Vcc$(无任何电阻)的信号,切勿尝试将其连接到任何信号,以防短路。
11.6 编程 PIO
由于系统端口具有硬件实现的预定义功能,我们必须根据上述描述对每个功能进行配置。为此,以下是一段正确初始化它的 Z80 汇编代码示例:
; PIO System port macros
DEFC IO_PIO_DATA_B = 0xd1
DEFC IO_PIO_CTRL_B = 0xd3
DEFC IO_PIO_SYSTEM_DATA = IO_PIO_DATA_B
DEFC IO_PIO_SYSTEM_CTRL = IO_PIO_CTRL_B
pio_init:
; Disable interrupts for system port
ld a, IO_PIO_DISABLE_INT
out (IO_PIO_SYSTEM_CTRL), a
; Set system port as bit-control, according to the datasheet, we should write 0xcf
ld a, 0xcf
out (IO_PIO_SYSTEM_CTRL), a
; Set the proper direction for each pin, 1 means input, 0 means output.
; Out input pins are keyboard (7), v-blank (6), h-blank (5), UART RX (3) and I2C SDA IN (2)
ld a, 0xec
out (IO_PIO_SYSTEM_CTRL), a
; Set default value for all the output pins, set them all to high
ld a, 0xff
out (IO_PIO_SYSTEM_DATA), a
ret
此代码片段将正确配置端口 B,但不会激活中断,因此程序需要轮询此寄存器以检查是否有任何位处于活动状态(低电平)。
也可以配置端口 B 使用中断,但这还需要 CPU 设置其中断向量表:
pio_enable_interrupt:
; Set the CPU interrupt vector high byte
ld a, cpu_vector_table >> 8
ld i, a
; Set interrupt vector to 0, so cpu_vector_table[0] contains the address to jump to on interrupt from the PIO.
ld a, 0
out (IO_PIO_SYSTEM_CTRL), a
; Enable the interrupts globally for the system port
ld a, 0x83
out (IO_PIO_SYSTEM_CTRL), a
; PIO System port interrupt control word
; Bit 7: 1 = Interrupt function enable
; Bit 6: 1 = AND function (0 = OR)
; Bit 5: 1 = Active High
; Bit 4: 1 = Mask follows
; In our case, we will:
; * Enable the interrupts
; * OR function (one of the pins going low will trigger the interrupt)
; * Active low signals
; * Provide a mask
DEFC IO_PIO_SYSTEM_INT_CTRL = 0x97
ld a, IO_PIO_SYSTEM_INT_CTRL
out (IO_PIO_SYSTEM_CTRL), a
; Mask must follow with monitored bit set to 0, only monitor the keyboard pin for our example, so bit 7 must be 0.
ld a, 0x7f
out (IO_PIO_SYSTEM_CTRL), a
; Enable CPU interrupts
ei
ret
; Must be aligned on 2
cpu_vector_table:
DW pio_int_handler
; Called when an interrupt occurs if any line of the PIO Port B goes low
pio_int_handler:
; Check which line went low
in a, (IO_PIO_SYSTEM_DATA)
; Process the interrupt
; [...]
; Re-enable the interrupts and exit
ei
reti