Full picture
13

SPI Controller

13.1 Overview

The SPI Controller allows the host CPU to communicate with an external TF (microSD) card using hardware SPI transfers.

For maximum compatibility with TF cards, the interface employs SPI transfer modes with CPOL=0 and CPHA=0."

It is designed for simplicity, suitable for small block data exchange, and removes the need for precise timing on the CPU side.

Like other controllers, the SPI Controller can be mapped into the device bank, starting at address 0xA0 on the I/O port. Its device index is 1.

⚠️ The controller is currently only connected to the TF card interface, and not accessible via external SPI pins.

13.2 Chip Select

The SPI controller allows the host CPU to control the Chip Select (CS) line independently of any active SPI transaction. This makes it possible to keep the CS line active across multiple transfers, for example, to start a transaction, process the received data, and then send another command without releasing CS.

It is also possible to initiate a transaction with no chip select active, which is required during the initialization sequence of all TF cards.

Available Chip Select Lines

The hardware currently supports two CS lines:

  • 0Reserved
  • 1 — External TF card

Controlling the CS Line

Chip Select control is managed through the CTRL register:

  • CS_SEL — Selects the CS line to control (0 or 1).
  • CS_START — Pulls the selected CS line low (active).
  • CS_END — Releases the selected CS line high (inactive).

If both CS_START and CS_END are 0, the state of the CS line remains unchanged. If both are set to 1, the CS line will be activated.

Example: Enabling TF Card CS (Z80)

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

Example: Disabling TF Card CS (Z80)

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

13.3 Internal Buffers

Internally, the controller uses two 8-byte RAM blocks:

  • OUT RAM: holds up to 8 bytes to transmit
  • IN RAM: stores the 8 bytes received during transmission

Both these RAMs can be accessed via the 8 registers, starting from RAM_FROM to RAM_TO. When writing to these registers, the data will be stored in the internal OUT_RAM, when reading from these registers, the date from the IN RAM will be ouputted.

The number of valid bytes in these RAMs can be written to RAM_LEN before starting a transfer. Please note that each transfer always starts from RAM offset 0.

For example on a Z80, populating the OUT RAM with data cane be done as follows:

    ; Assumption: SPI controller is mapped at 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
    ; Set the RAM size to 4 since we wrote 4 bytes
    ld a, 4
    out (0xa0 + RAM_LEN), a
    ; 4 bytes are now read to be sent on the bus: 0x12, 0x34, 0x56 and 0x78

13.4 Pseudo-FIFO access

For the sack of optimization (mainly using outi or otir instruction on the Z80), it is possible to access the RAM as pseudo-FIFOs. The SPI controller keeps two indexes, one for the OUT RAM and one for IN RAM.

By writing data to register RAM_FIFO, the OUT RAM will be populated at its index position, the latter is then incremented. Same goes for the IN RAM when the same register is read.

It is possible to reset both indexes by writing the bit REST_FIFO of register RAM_LEN.

For example on a Z80 target, if we want to populate the OUT RAM with the same bytes as the example above, we can do:

    ; Assumption: SPI controller is mapped at 0xa0
populate_spi:
    ; Prepare the bytes to write and the size
    ld hl, data
    ld b, data_end - data   ; Size of the data array
    ; Reset the pseudo-INFO index, take advantage of this instruction to also set
    ; the final number of bytes we will write to the RAM.
    ld a, 0x80
    or b        ; A is now 0x84
    ld (0xa0 + RAM_LEN), a
    ; Use `otir` to populate the SPI OUT RAM
    ld c, 0xa0 + RAM_FIFO
    otir
    ret

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

Similarly, after a transfer, it is possible to read all the bytes received into an array:

    ; Assumption: SPI controller is mapped at 0xa0
read_spi:
    ld hl, data
    ld b, data_end - data
    ; Reset the pseudo-INFO index, take advantage of this instruction to also set
    ; the final number of bytes to read from RAM. It must be the same as the write size.
    ld a, 0x80
    or b        ; A is now 0x84
    ld (0xa0 + RAM_LEN), a
    ; Use `inir` to populate the read the data from the SPI IN RAM
    ld c, 0xa0 + RAM_FIFO
    inir
    ret

data: ds 4  ; Allocate 4 bytes in RAM to store the data since we sent 4 bytes
data_end:

13.5 Clock Frequency

The SPI controller is driven by the system’s main 50 MHz clock. To generate the SPI bus clock, this base frequency is divided down using the CLK_DIV register.

The CLK_DIV value can range from 1 to 255, and the resulting SPI bus frequency is calculated as:

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

If the CLK_DIV register is set to 0, the hardware automatically forces it to 1 to avoid a division-by-zero condition.

Example Frequencies

CLK_DIV SPI Frequency
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 Transfer Process

Overall, to perform a transfer, the steps are:

  1. Set the SPI Bus freqency (Optional)

    • Choose the desired bus frequency by setting the CLK_DIV register.
  2. Fill the Output Buffer

    • Write up to 8 bytes into the OUT RAM, either using the RAM_FIFO or using the RAM_FROM-RAM_TO registers.
    • Set the number of valid bytes in the FIFO in RAM_LEN
  3. Start the Transfer

    • Set the START bit in the CTRL register. It is possible to specify, in the same byte, whether a CS line shall be enabled. (CS_START and CS_SEL fields)
    • The SPI controller immediately begins shifting the 8 bytes out, while simultaneously receiving 8 bytes into the input buffer.
  4. Wait for Completion

    • Poll the IDLE flag in the STATUS register.
    • Once set, the transfer is complete.
  5. Read the Input Buffer

    • Read the 8 received bytes from the IN RAM,either using the RAM_FIFO or using the RAM_FROM-RAM_TO registers.
  6. Deassert the CS line (Optional)

    • Deassert the previously enabled CS line by setting the CS_END and CS_SEL fields in the CTRL register

⚠️ Always wait for the IDLE flag to be set before starting a new transfer. Starting a transfer while another is in progress will result in undefined behavior.

13.7 Registers Summary

Name Description Base-relative address Access
CTRL0 Reserved or future use 0x00
CTRL Control register for starting and managing SPI transactions 0x01 varies
CLK_DIV Clock divider for SPI transactions 0x02 RW
RAM_LEN Controls the RAM length and FIFO reset 0x03 varies
RAM_FIFO FIFO data register 0x07 RW
RAM_FROM Start of memory-mapped data for SPI transactions 0x08 RW
RAM_TO End of memory-mapped data for SPI transactions 0x0F RW

13.8 Registers

CTRL0 (0x00)
Reserved or future use
(reserved)
7 6 5 4 3 2 1 0
0 0 0 0 0 0 0 0
Reset
CTRL (0x01)
Control register for starting and managing SPI transactions
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
Start transaction (WO)
RESET
Reset the SPI controller (WO)
CS_START
Assert chip select (active low) (WO)
CS_END
De-assert chip select (inactive high) (WO)
CS_SEL
Select among two chip selects (0 or 1) (WO)
IDLE
SPI controller in IDLE state (no transaction) (RO)
CLK_DIV (0x02)
Clock divider for SPI transactions
DIV
7 6 5 4 3 2 1 0
0 0 0 0 1 0 1 0
Reset
DIV
Divider value to scale the SPI clock frequency (RW)
RAM_LEN (0x03)
Controls the RAM length and FIFO reset
RESET_FIFO
(reserved)
LENGTH
7 6 5 4 3 2 1 0
0 0 0 0 0 0 0 0
Reset
RESET_FIFO
Set to 1 to reset the FIFO indexes (WO)
LENGTH
Length of the RAM buffer (number of active entries) (RW)
RAM_FIFO (0x07)
FIFO data register
DATA
7 6 5 4 3 2 1 0
0 0 0 0 0 0 0 0
Reset
DATA
Read/write access to the SPI FIFO buffer (RW)
RAM_FROM (0x08)
Start of memory-mapped data for SPI transactions
DATA
7 6 5 4 3 2 1 0
0 0 0 0 0 0 0 0
Reset
DATA
Byte of data from RAM (RW)
RAM_TO (0x0F)
End of memory-mapped data for SPI transactions
DATA
7 6 5 4 3 2 1 0
0 0 0 0 0 0 0 0
Reset
DATA
Byte of data from RAM (RW)