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 线(0或1)。CS_START— 将所选 CS 线拉低(激活)。CS_END— 将所选 CS 线释放为高(未激活)。
如果 CS_START 和 CS_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_FROM 到 RAM_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 上使用 outi 或 otir 指令),可以将 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 值的范围可以从 1 到 255,由此得到的 SPI 总线频率计算公式如下:
如果 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 传输过程
总体而言,执行传输的步骤如下:
-
设置 SPI 总线频率 (可选)
- 通过设置
CLK_DIV寄存器选择所需的总线频率。
- 通过设置
-
填充输出缓冲区
-
启动传输
- 设置
CTRL寄存器中的START位。可以在同一字节中指定是否启用 CS 线(CS_START和CS_SEL字段)。 - SPI 控制器立即开始移出 8 个字节,同时将接收到的 8 个字节移入输入缓冲区。
- 设置
-
等待完成
- 轮询
STATUS寄存器中的IDLE标志。 - 一旦置位,传输即完成。
- 轮询
-
读取输入缓冲区
- 使用
RAM_FIFO或RAM_FROM-RAM_TO寄存器,从 IN RAM 读取 8 个接收到的字节。
- 使用
-
取消 CS 线断言 (可选)
- 通过设置
CTRL寄存器中的CS_END和CS_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 寄存器
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
| 0 | 0 | 0 | 0 | 1 | 0 | 1 | 0 |
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |