Full picture
13

SPI 控制器

13.1 概述

SPI 控制器允许主机 CPU 通过硬件 SPI 传输与外部 TF(microSD)卡进行通信。

为了与 TF 卡实现最大兼容性,该接口采用 CPOL=0 和 CPHA=0 的 SPI 传输模式。

其设计以简洁为目标,适用于小块数据交换,并消除了 CPU 侧精确时序的要求。

与其他控制器一样,SPI 控制器可以映射到 I/O 端口上起始地址为 0xA0设备分片中。其设备索引为 1

⚠️ 该控制器目前仅连接到 TF 卡接口,无法通过外部 SPI 引脚访问。

13.2 片选

SPI 控制器允许主机 CPU 独立于任何正在进行的 SPI 事务来控制片选(CS)线。 这使得可以在多次传输之间保持 CS 线激活,例如,启动一个事务、处理接收到的数据,然后在不释放 CS 的情况下发送另一条命令。

也可以在没有片选激活的情况下启动事务,这在所有 TF 卡的初始化序列中是必需的。

可用片选线

硬件当前支持两条 CS 线:

  • 0保留
  • 1 — 外部 TF 卡

控制 CS 线

片选控制通过 CTRL 寄存器进行管理:

  • CS_SEL — 选择要控制的 CS 线(01)。
  • CS_START — 将所选 CS 线拉(激活)。
  • CS_END — 将所选 CS 线释放为(未激活)。

如果 CS_STARTCS_END 均为 0,CS 线的状态保持不变。 如果两者均设置为 1,CS 线将被激活。

示例: 启用 TF 卡 CS(Z80)

    ld a, (1 << CS_SEL) | (1 << CS_START)
    out (0xa0 + CTRL), a

示例: 禁用 TF 卡 CS(Z80)

    ld a, (1 << CS_SEL) | (1 << CS_END)
    out (0xa0 + CTRL), a

13.3 内部缓冲区

控制器内部使用两个 8 字节的 RAM 块:

  • OUT RAM:保存最多 8 个待发送字节
  • IN RAM:存储传输过程中接收到的 8 个字节

这两个 RAM 均可通过从 RAM_FROMRAM_TO 的 8 个寄存器访问。写入这些寄存器时,数据将存储在内部 OUT_RAM 中;从这些寄存器读取时,将输出 IN RAM 中的数据。

在开始传输之前,可以将这些 RAM 中的有效字节数写入 RAM_LEN。请注意,每次传输始终从 RAM 偏移量 0 开始。

例如,在 Z80 上,填充 OUT RAM 的操作如下:

    ; 假设:SPI 控制器映射在 0xa0
    ld a, 0x12
    out (0xa0 + RAM_FROM + 0), a
    ld a, 0x34
    out (0xa0 + RAM_FROM + 1), a
    ld a, 0x56
    out (0xa0 + RAM_FROM + 2), a
    ld a, 0x78
    out (0xa0 + RAM_FROM + 3), a
    ; 设置 RAM 大小为 4,因为我们写了 4 个字节
    ld a, 4
    out (0xa0 + RAM_LEN), a
    ; 现在有 4 个字节准备发送到总线上:0x12、0x34、0x56 和 0x78

13.4 伪 FIFO 访问

为了优化(主要是在 Z80 上使用 outiotir 指令),可以将 RAM 作为伪 FIFO 访问。SPI 控制器维护两个索引,一个用于 OUT RAM,一个用于 IN RAM。

通过向 RAM_FIFO 寄存器写入数据,OUT RAM 将在其索引位置被填充,然后索引递增。读取同一寄存器时,IN RAM 也是如此。

可以通过写入 RAM_LEN 寄存器的 REST_FIFO 位来重置两个索引。

例如,在 Z80 目标上,如果我们想用与上例相同的字节填充 OUT RAM,可以这样做:

    ; 假设:SPI 控制器映射在 0xa0
populate_spi:
    ; 准备要写入的字节和大小
    ld hl, data
    ld b, data_end - data   ; 数据数组的大小
    ; 重置伪 FIFO 索引,利用此指令同时设置
    ; 将要写入 RAM 的最终字节数。
    ld a, 0x80
    or b        ; A 现在为 0x84
    ld (0xa0 + RAM_LEN), a
    ; 使用 `otir` 填充 SPI OUT RAM
    ld c, 0xa0 + RAM_FIFO
    otir
    ret

data: db 0x12, 0x34, 0x56, 0x78
data_end:

类似地,在传输之后,可以将所有接收到的字节读入一个数组:

    ; 假设:SPI 控制器映射在 0xa0
read_spi:
    ld hl, data
    ld b, data_end - data
    ; 重置伪 FIFO 索引,利用此指令同时设置
    ; 要从 RAM 读取的最终字节数。必须与写入大小相同。
    ld a, 0x80
    or b        ; A 现在为 0x84
    ld (0xa0 + RAM_LEN), a
    ; 使用 `inir` 从 SPI IN RAM 读取数据
    ld c, 0xa0 + RAM_FIFO
    inir
    ret

data: ds 4  ; 在 RAM 中分配 4 个字节来存储数据,因为我们发送了 4 个字节
data_end:

13.5 时钟频率

SPI 控制器由系统的主 50 MHz 时钟驱动。 为了生成 SPI 总线时钟,使用 CLK_DIV 寄存器对该基频进行分频。

CLK_DIV 值的范围可以从 1255,由此得到的 SPI 总线频率计算公式如下:

$\text{SPI 频率} = \frac{50{,}000{,}000}{2 \times \text{CLK_DIV}} = \frac{25{,}000{,}000}{\text{CLK_DIV}} \ \text{Hz}$

如果 CLK_DIV 寄存器设置为 0,硬件会自动将其强制为 1,以避免除零错误。

示例频率

CLK_DIV SPI 频率
1 25 MHz
2 12.5 MHz
5 5 MHz
10 2.5 MHz
25 1 MHz
50 500 kHz
125 200 kHz
255 ~98 kHz

13.6 传输过程

总体而言,执行传输的步骤如下:

  1. 设置 SPI 总线频率 (可选)

    • 通过设置 CLK_DIV 寄存器选择所需的总线频率。
  2. 填充输出缓冲区

    • 使用 RAM_FIFORAM_FROM-RAM_TO 寄存器,向 OUT RAM 写入最多 8 个字节。
    • RAM_LEN 中设置 FIFO 中的有效字节数。
  3. 启动传输

    • 设置 CTRL 寄存器中的 START 位。可以在同一字节中指定是否启用 CS 线(CS_STARTCS_SEL 字段)。
    • SPI 控制器立即开始移出 8 个字节,同时将接收到的 8 个字节移入输入缓冲区。
  4. 等待完成

    • 轮询 STATUS 寄存器中的 IDLE 标志。
    • 一旦置位,传输即完成。
  5. 读取输入缓冲区

    • 使用 RAM_FIFORAM_FROM-RAM_TO 寄存器,从 IN RAM 读取 8 个接收到的字节。
  6. 取消 CS 线断言 (可选)

    • 通过设置 CTRL 寄存器中的 CS_ENDCS_SEL 字段,取消之前启用的 CS 线。

⚠️ 始终在启动新传输之前等待 IDLE 标志置位。 在另一个传输正在进行时启动新传输将导致未定义的行为。

13.7 寄存器总览

Name Description Base-relative address Access
CTRL0 预留或将来使用 0x00
CTRL 用于启动和管理SPI传输的控制寄存器 0x01 varies
CLK_DIV SPI传输的时钟分频器 0x02 RW
RAM_LEN 控制RAM长度和FIFO复位 0x03 varies
RAM_FIFO FIFO数据寄存器 0x07 RW
RAM_FROM SPI传输的内存映射数据起始地址 0x08 RW
RAM_TO SPI传输的内存映射数据结束地址 0x0F RW

13.8 寄存器

CTRL0 (0x00)
预留或将来使用
(reserved)
7 6 5 4 3 2 1 0
0 0 0 0 0 0 0 0
Reset
CTRL (0x01)
用于启动和管理SPI传输的控制寄存器
START
RESET
CS_START
CS_END
CS_SEL
(reserved)
IDLE
7 6 5 4 3 2 1 0
0 0 0 0 0 0 0 0
Reset
START
开始传输 (WO)
RESET
复位SPI控制器 (WO)
CS_START
使能片选(低电平有效) (WO)
CS_END
禁用片选(高电平无效) (WO)
CS_SEL
选择两个片选之一(0或1) (WO)
IDLE
SPI控制器处于空闲状态(无传输) (RO)
CLK_DIV (0x02)
SPI传输的时钟分频器
DIV
7 6 5 4 3 2 1 0
0 0 0 0 1 0 1 0
Reset
DIV
用于分频SPI时钟频率的分频值 (RW)
RAM_LEN (0x03)
控制RAM长度和FIFO复位
RESET_FIFO
(reserved)
LENGTH
7 6 5 4 3 2 1 0
0 0 0 0 0 0 0 0
Reset
RESET_FIFO
置1以复位FIFO索引 (WO)
LENGTH
RAM缓冲区的长度(有效条目数) (RW)
RAM_FIFO (0x07)
FIFO数据寄存器
DATA
7 6 5 4 3 2 1 0
0 0 0 0 0 0 0 0
Reset
DATA
对SPI FIFO缓冲区的读写访问 (RW)
RAM_FROM (0x08)
SPI传输的内存映射数据起始地址
DATA
7 6 5 4 3 2 1 0
0 0 0 0 0 0 0 0
Reset
DATA
来自RAM的数据字节 (RW)
RAM_TO (0x0F)
SPI传输的内存映射数据结束地址
DATA
7 6 5 4 3 2 1 0
0 0 0 0 0 0 0 0
Reset
DATA
来自RAM的数据字节 (RW)
EN | 中文Beta