SPI Controller

Configuring and using the SPI bus on ZYNQ



The Serial Peripheral Interface (SPI) bus is a four-signal, point-to-point, single master synchronous bus commonly used for low to medium speed communications (up to about 5Mbytes/s). The four signals, Master Out Slave In (MOSI), Master In Slave Out (MISO), Serial Clock (SCLK), and Slave Select (SS) are all unidirectional and single-ended (ground referenced). The bus master always drives SS, MOSI, and SCLK, and the slave device drives MISO back to the master.

SPI busses are commonly used by microcontrollers to communicate with peripheral devices like memories, analog-to-digital converters, sensors, motor controllers, etc. You can read more about SPI busses here: The SPI BUS

Blackboard’s SPI busses

The ZYNQ processing system includes two SPI controllers that are connected to the ARM processing system at fixed AMBA bus addresses. Each controller can drive up to three slave select signals (SS), and both can use clocks that range from 83MHz down to 650KHz. ZYNQ’s SPI controllers are typically used in “master mode”, meaning the ZYNQ drives the MOSI, SCLK, and SS signals, and the slave responds by driving the MISO signal (the controllers can be put in slave mode as well).

On the Blackboard, SPI0 is connected to a sensor chip that includes an accelerometer, gyroscope, and magnetometer (an ST Micro LSM9SDS1), and the SPI1 is connected to the ESP32 WIFI radio. The WIFI radio SPI signals are connected directly to processing system pins using the MIO interface, but the ST Micro device SPI signals are connected through the FPGA using the EMIO interface (all connections are configured in the standard Blackboard configuration file).

Figure 1. ZYNQ SPI connections on Blackboard

ZYNQ’s SPI Controllers

ZYNQ’s SPI controllers are documented in chapter 17 of the ZYNQ TRM. Several of the most useful features and register definitions are also described here.

Clock Management

The SPI controllers use a 166MHz master clock input that can be divided by an user-programmable three-bit parameter N according to the formula:

SCLK = 166MHz / 2 ^ (BAUDDIV + 1),

where BAUDDIV is a 4-bit programmable field in the SPI configuration register. The SPI clock divider can produce 16 frequencies, ranging from 83MHz down to 650KHz.


Each controller includes a 128 byte send FIFO, and a separate 128 byte receive FIFO. Each FIFO is accessed by a single 8-bit port, and several status signals/interrupts are available to indicate the state of the FIFOs (empty, full, almost full, etc.). Data placed in the transmit FIFO will be automatically serialized and sent out on the MOSI pin. Note that transmit data can be written into the FIFO much faster than it can be sent out, so if more than 128 bytes must be written, the transmit FIFO must be managed by polling status bits or setting up interrupts. Data arriving in the receive FIFO can be read after checking status bits (to confirm new data is available) or in response to an interrupt. You can learn more about FIFOs here: MANAGED HARDWARE FIFOs

Figure 2. ZYNQ’s SPI FIFOs (reprinted from the ZYNQ TRM)

The available SPI registers are shown below.

SPI_CR (0x00000000): Configuration register for overall configuration settings (read/write)

MF: ModeFail generation enable (in slave mode, indicates SS was deasserted) (1 to enable, o to disable
MS: Manual Start Command (1 to start transmission)
EN: Manual Start Enable (1 to enable manual start)
CS: Enable Manual Chip Select (Slave Select) (1 to enable)
SS: Slave selects: xxx0 slave 0; xx01 slave 1; 0x011 slave 2; 0111 reserved; 1111 no slave selected
ED: Enable external decode (1 to allow external 3:8 decoder on SS lines)
CK: Master reference clock select (must be 0 to use SPI reference clock; 1 not supported)
Bauddiv: Baud rate divisor (000 not supported; otherwise divide by 2 ^ (N + 1))
PH: Clock Phase (1 to make clock inactive outside word; 0 to make active outside word)
PL: Clock Polarity (1 for quiescent high, 0 for quiescent low)
M: Mode select (1 for master mode, 0 for slave mode)

SPI_SR (0x00000004): Status register for reading FIFO flag status (write a ‘1’ to any bit to clear)

TU: Transmit FIFO underflow (1 for underflow detected)
RF: Receive FIFO full (1 for FIFO full)
RN: Receive FIFO not empty (1 for more at least 1 THRESHOLD entry)
TF: Transmit FIFO full (1 for full)
TN: Transmit FIFO not full (1 for less than THRESHOLD entries)
MV: Modefail voltage detected (1 for mode fail, meaning SS deasserted during transfer)
RO: Receive FIFO overflow (1 for overflow detected)

SPI_IER (0x00000008): Interrupt enable register (write ‘1’ to enable interrupt source)

TU: Transmit FIFO underflow (1 for underflow detected)
RF: Read FIFO full (1 for FIFO full)
RN: Receive FIFO not empty (1 for more at least 1 THRESHOLD entry)
TF: Transmit FIFO full (1 for full)
TN: Transmit FIFO not full (1 for less than THRESHOLD entries)
MV: Modefail voltage detected (1 for mode fail, meaning SS deasserted during transfer)
RO: Receive FIFO overflow (1 for overflow detected)

SPI_IDR: (0x0000000C): Interrupt disable register (write ‘1’ to disable interrupt source)

TU: Transmit FIFO underflow (1 for underflow detected)
RF: Read FIFO full (1 for FIFO full)
RN: Receive FIFO not empty (1 for more at least 1 THRESHOLD entry)
TF: Transmit FIFO full (1 for full)
TN: Transmit FIFO not full (1 for less than THRESHOLD entries)
MV: Modefail voltage detected (1 for mode fail, meaning SS deasserted during transfer)
RO: Receive FIFO overflow (1 for overflow detected)

SPI_IDR: (0x00000010): Interrupt mask register (write ‘1’ to mask/disable interrupt, ‘0’ to enable/unmask)

TU: Transmit FIFO underflow (1 for underflow detected)
RF: Read FIFO full (1 for FIFO full)
RN: Receive FIFO not empty (1 for more at least 1 THRESHOLD entry)
TF: Transmit FIFO full (1 for full)
TN: Transmit FIFO not full (1 for less than THRESHOLD entries)
MV: Modefail voltage detected (1 for mode fail, meaning SS deasserted during transfer)
RO: Receive FIFO overflow (1 for overflow detected)

SPI_ER (0x00000014): SPI controller enable (read/write)

EN: Enable SPI controller (write ‘1’ to enable, ‘0’ to disable)

SPI_DR (0x00000018): Delay register, used to configure various delays (read/write)

NSS: Number of SPI clocks between SS assertions
ISS: Number of SPI clocks between deasserting one CS signal and asserting another
DWW: Number of SPI clocks between last bit of current word and first bit of next word
SSW: Added number of SPI clocks between asserting SS and first bit of transfer data

SPI_TXD (0x0000001C): SPI transmit data register (write only)

TXD: 8-bit transmit data sent to transmit FIFO

SPI_RXD (0x00000020): SPI receive data register (read only)

RXD: 8-bit transmit data read from receive FIFO

SPI_SICR (0x00000024): Slave idle count register (read/write)

ICLK: Number of SPI clocks that must pass (idle) before slave detects a start

SPI_TXWR (0x00000028): Defines threshold level for transmit FIFO not full (read/write)

THLVL: Threshold level at which transmit FIFO not full interrupt is generated

SPI_RX_THRES (0x0000002C): Defines threshold level for receive FIFO not empty (read/write)

THLVL: Threshold level at which receive FIFO not empty interrupt is generated

SPI_MOD_ID: (0x000000FC) SPI module ID number (read only)

ID#: Returns SPI module ID number: 0x90106

Configuring ZYNQ’s SPI Controller

Reset SPI Module

Before the SPI module can be configured for use, it must be reset by writing to Zynq’s System Level Control Registers (SLCR) block. SLCR’s are protected registers that can only be accessed after they are “unlocked” by writing a special key value (0xDF0D) to the unlock register at address 0xF8000008. After unlocking the SLCRs and using them as needed, they should be immediately relocked by writing the lock key (0x767B) to the lock register at 0xF8000004.

//SLCR addresses for SPI reset
#define SLCR_LOCK		*( (uint32_t *) 0xF8000004)
#define SLCR_UNLOCK		*( (uint32_t *) 0xF8000008)
#define SLCR_SPI_RST 	*( (uint32_t *) 0xF800021C)

//SLCR lock and unlock keys
#define UNLOCK_KEY	0xDF0D
#define LOCK_KEY	0x767B

void reset_SPI(void)
	int i=0; //i for delay
	SLCR_SPI_RST = 0xF;		//assert SPI reset
	for(i=0;i<1000;i++);		//make sure Reset occurs
	SLCR_SPI_RST = 0;		//deassert
	SLCR_LOCK = LOCK_KEY;		//relock SLCRs

Once unlocked, the SPI reset signal can be asserted by setting the lower four bits of the SLCR SPI reset register at address 0xF800021C. After a small delay (around 100us), you can clear the bits and the reset process will be complete. The example code below illustrates the SPI reset process.

Chip selects

An SPI slave is enabled (selected) when the SPI master asserts its “slave select” (SS) signal. ZYNQs SPI controllers can each drive three different SS signals, so they can communicate with three different, independent slave devices. The SS signals must be treated as mutually exclusive. As the MISO input to the module is shared between external devices, driving multiple select lines could result indamage to the slave devices. Four bits in the configuration register determine which SS signal is active, and it is not possible to drive two of them at the same time.

The Zynq’s SPI Module supports both automatic and manual control of the module’s device select lines.

Automatic Chip Select

In automatic mode the module controls the select lines during a SPI transfer: When the module starts shifting out data on the MOSI pin, the module will automatically assert the configured select line. When the transmit FIFO becomes empty or module is disabled, the select will deassert. Automatic select is best for small transfer of a known data length, where the transmit data can fit inside the tx FIFO.

Manual Chip Select

Manual chip select allows software to control the chip select lines independent of the SPI module. The SPI transmitter logic will still serialize data in the FIFO when enabled, but the chip-selects will always reflect the state configured in the module’s control register. Manual select can be used for longer transfers where the chip select may need to remain asserted when the transmitter is not actively shifting bits (the SPI CLK is stopped). For longer length or unknown length transfers this allows the slave device to remain selected even if the transmit FIFO is empty.

As an example, an SD card in SPI mode can transfer multiple 512-byte blocks in a single SPI transfer. The card can keep sending contiguous blocks as long as the chip select is asserted and the SPI Master is supplying the card with a transfer clock.

Controlling SPI Transfers

The way transfers are initiated can be configured through the control register. The module supports starting transfers when data is availible in its transmit FIFO or it can be configured to only initiate transfers on command.

Manual Start

The SPI Module can be configured to start transmitting only after a manual-start command is issued to the module. This allows software to queue up bytes in the FIFO before initiating a transfer. Once the transmit FIFO is empty the transfer will end, software can then queue more data into the FIFO and initiate another transfer. With auto chip-select, the configured CS line will only go low after the transfer is initiated.

Auto Start

In automatic start mode, the transmitter will begin to transmit data when at least one byte is in the transmit FIFO. When the transmit FIFO is empty the transmitter will pause the SPI clock signal (it will also deassert chip-selects if the module is configued for auto-CS).

Transfer Modes

The Zynq SPI Module supports various configurations to work with SPI devices which have different protocols within the SPI standard. Transfers in two common configurations are detailed below.

Manual Start with Automatic Chip Select

This configuration is useful for fixed length transfers where data is queued in the transmit FIFO prior to initiating the transfer. This is recommended for communicating with the LSM9DS1. In the SPI control register, configure the baud rate, phase, and polarity as desired. Clear the manual CS bit (SPI_CR[14]=0), and set the manual start enable bit (SPI_CR[15]=1).

Initiating a transfer

  1. Queue initial data to be transmitted in the SPI transmit FIFO. The ‘TX FIFO not full’ bit (SPI_SR[2]) in the status register can be used to check if there is room for more transmit data.

  2. Set the chip select to be driven by configuring the appropriate bits in the control register (SPI_CR[13:10]).

  3. Initiate the SPI Transfer by setting the manual start command bit in the control register (SPI_CR[16]).

The SPI Module will assert the programmed chip select and start transmitting and receiving data.

Receive data will be availible in the receive FIFO once the module has transmitted at least 1 byte. The ‘RX not empty’ bit (SPI_SR[4]) in the status register can be used to determine if there is data which can be read.

Completing a transfer

If no data is added to the transmit FIFO, the transfer will end once the TX FIFO is empty and the last bit is transmitted by the SPI module. The module will stop the SPI clock and deassert the chip-select.

Transfers can be extended by writing additional bytes to TX FIFO before the end of the current transfer. When extending transfers, make sure there is sufficient room in the RX FIFO to receive additional bytes. Data can be read out of the TX FIFO during a transfer to ensure there is no overflow.

Once a transfer completes, another transfer will not begin until it is initiated through the manual start command bit.

Automatic Start with Manual Chip Select

Most SPI slaves consider a transfer complete when the chip-select is deasserted. Using manual CS allows the processor to add additional bytes to a transfer without needing to ensure the TX FIFO remain filled. For longer transfers, this also allows the processor time to read data out of the RX FIFO before continuing the transfer. In auto-start mode, the module will begin transmitting data once it is enqueued in the TX FIFO.

In the SPI control register, configure the baud rate, phase, and polarity as desired. In manual CS mode, the chip select will always reflect the selected line in the control register. Before enabling manual CS, make sure no slave is selected (SPI_CR[13:10] = 4b1111). Now, set the manual CS bit (SPI_CR[14]=1), and clear the manual start enable bit (SPI_CR[15]=0).

Software now has control of the chip select lines through the control register (SPI_CR[13:10]). Transfers can be started by placing data in the TX FIFO.

Configuring the SPI bus on the Blackboard

The SPI bus supports different operating modes, and various options can be set within those modes. Further, although SPI slaves must meet the SPI electronic specifications, they are free to use their own data protocols. In general, SPI slave devices have different configuration requirements, and you must consult their data sheets to learn how to configure the bus. The configuration details discussed below are based on requirements from the LSM9DS1 inertial module on the Blackboard.

The ZYNQ is the SPI bus master, so bit 0 in the control register must be set to ‘1’. Note the SPI controller can be put in slave mode – that would be useful if another processor wanted to treat the ZYNQ device as an SPI slave.

The Inertial Module Uses SPI Mode 3 for communication (CPOL=1 and CPHA = 1). That means data is shifted out on the falling edge of SCLK, and the slave latches data on the next rising SCLK edge.

The LMS9DS1 has a max SPI clock speed of 10MHz, any clock speed below 10MHz will work as well. The SPI controllers clock divider can only divide the input clock by powers of 2 according to the formula Baud_clk = 166MHz / 2^(n+1). A divider value of 32 (n = 4) gives ~5.2MHz, which falls below the 10MHz requirement.

To communicate with the accelerometer/gyro in the LSM9DS1, SS0 must be selected, and to communicate with the magnetometer, SS1 must be selected. These signals are driven directly (and not through an external decoder), so the decoder option should not be enabled.

The chip select mode can be set to automatic by clearing bit 14 in the CR. With auto-CS is enabled, the chip select will only drive when an SPI transaction is active.

In summary, to communicate with the LSM9DS1, you could configure the SPI module as such:

  • SPI in master mode
  • SPI mode 3 (CPOL=1 and CPHA=1)
  • Divide clock to 5.2MHz (BAUD_RATE=4)
  • No external decoder (PERI_SEL=0)
  • Manual chip-select (MANUAL_CS=1)
  • Manual start (MANUAL_START_EN=1)

Once Configured the module can be enabled by writing 1 to the SPI Enable Register.

Different slaves use different protocols

As mentioned, data protocols are not defined in the SPI standard, and they differ from device to device. You must read the data sheet for any given device to learn how it communicates. Often, manufactures define different protocols and timings for different operations (single byte read/write, multibyte, etc). The data protocols for the LSM9DS1 device on the Blackboard are described here: Blackboard’s interial module

After completing an SPI read transaction, data will be available in the SPI RXD FIFO. Most reads have 16 data bits, so after an SPI read the RXD FIFO will generally contain three bytes - the first byte can be ignored, as it contains data read from the inertial module while the bus master was sending the address. The next two bytes will contain the actual data for the register requested.

Transfers with the LSM9DS1

The LSM9DS1 consists of two seperate units, an accelerometer and a magnetometer. These are accessed as seperate SPI devices and each has their own chip-select line. The accelerometer is accessed through chip-select 0 and the magnetometer is selected with chip select 1.

The accelerometer/gyroscope portion onboard nav chip handles SPI transfers in 2 phases. First the SPI Master provides an address and indicates the type of transfer: read or write. The 7-bit address is concatenated with a read/write bit to make up first byte of the SPI transfer (The read/write bit is sent first, followed by the 7-bit address). In the data phase of the transfer the bus master or the LSM9DS1 send bytes of data. There is a minimum of one data byte transferred, and further data in the transfer will be read/written from registers at addresses indexed from the first address.

Reading a Register

To read from a single register in the accelerometer, the SPI module needs to transmit 2 bytes. The first byte contains a bit with the value of 1 (indicating a read), followed by the desired 7-bit register address. The second byte is necessary as the SPI module only receives data when it is transmitting. The data sent is irrelevant to the accelerometer, and thus can have any value. As the SPI Module has sent out 2 bytes, it will also receive 2 bytes. As the data from the requested register is received in the 2nd byte, the first byte read can be discarded.

Writing to a Register

A write to the accelerometer has the read/write bit set to 0. The data sent to the accelerometer in the 2nd byte will be written to the requested register. As with all transfers, the SPI module will receive as many bytes as are transmitted. While the bits received from the accelerometer during a write are irrelevant, they will still be placed in the receive FIFO and should be flushed from the FIFO by being read.

Transfering multiple Bytes

Reading multiple bytes from the accelerometer will read from multiple registers; Each additional byte will have the address read from incremented by 1.

As the data for each axis is held in registers with neighboring addresses this can be used to read all the whole 16-bit reading for an axis in one SPI transfer. Furthermore, the addresses for the 3 different axes of the accelerometer are contiguous (This is the same for the gyroscope’s 3 axes registers as well).