Full picture
11

并行 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 引脚图
Z80 PIO 引脚排列

上图中某些信号有两个名称,中间用破折号 - 分隔。第一个描述是来自 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 映射在 [0xD00xDF] 之间。更多信息见逻辑胶合章节
  • $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

EN | 中文Beta