By Adam Taylor

Recently I received two different questions from engineers on how to use SPI with the Zynq SoC and Zynq UltraScale+ MPSoC. Having answered these I thought a detailed blog on the different uses of SPI would be of interest.

When we use a Zynq SoC or Zynq UltraScale+ MPSoC in our design we have two options for implementing SPI interfaces:

Use one of the two SPI controllers within the Processing System (PS)

Use an AXI Quad SPI (QSPI) IP module, configured for standard SPI communication within the programmable logic (PL)

Selecting which controller to use comes down to understanding the application’s requirements. Both SPI implementations can support all four SPI modes and both can function as either a SPI master or SPI slave. However, there are some suitable differences as the table below demonstrates:

Initially, we will examine using the SPI controller integrated into the PS. We include this peripheral within our design by selecting the SPI controller within the Zynq MIO configuration tab. For this example I will route the SPI signals to the ARTY Z7 SPI connector, which requires use of EMIO via the PL I/O.

Enabling the SPI and mapping to the EMIO

With this selected all that remains within Vivado, is to connect the I/O from the SPI ports. How we do this depends upon whether we want a master or salve SPI implementation. Looking at the available ports on the SPI Controller, you will notice there are matching input (marked xxx_i) and output (marked xxx_o) ports for each SPI port. It is very important that we correctly connect these ports based on the master or slave implementation. Failure to do so will lead to hours of head scratching later when we develop the software because nothing will work as expected if we get the connections wrong. In addition, there is one Slave Select input when the controller is used as a SPI slave and three select pins for use in SPI master mode.

Once the I/O is correctly configured and the project built, we configure the SPI controller as either a master or slave using the SPI configuration options within the application software. To both configure and transfer data using the PS SPI controller, we use the API provided with the BSP, which is defined by XSPIps.H. In this first example, we will configure the PS SPI controller as the SPI Master.

By default, SPI transfers are 8-bit transfers. However we can transmit larger 16- or 32-bit words as well. To transmit 8-bit words, we use the type u8 within our C application. For 16- or 32-bit transfers, we use types u16 or u32 respectively for the read and write buffers.

At first, this may appear to cause a problem or at least generate a compiler warning because both API functions that perform data transfers require a u8 input for the transmit and receive buffers as shown below:

s32 XSpiPs_Transfer(XSpiPs *InstancePtr, u8 *SendBufPtr, u8 *RecvBufPtr, u32 ByteCount);

s32 XSpiPs_PolledTransfer(XSpiPs *InstancePtr, u8 *SendBufPtr, u8 *RecvBufPtr, u32 ByteCount);

To address this issue when using u16 or u32 types, we need to cast the buffers to a u8 pointer as demonstrated:

XSpiPs_PolledTransfer(&SpiInstance, (u8*)&TxBuffer, (u8*)&RxBuffer, 8);

This allows us to work with transfer sizes of 8, 16 or 32 bits. To demonstrate this, I connected the SPI master example to a Digilent Digital Discovery to capture the transmitted data. With the data width changed on the fly from 8- to 16-bits using the above methods in the application software.

Zynq SoC PS SPI Master transmitting four 8-bit words





PS SPI Master transmitting four 16-bit words

The alternative to implementing a SPI interface using the Zynq PS is to implement an AXI QSPI IP core within the Zynq PS. Doing this requires more options being set in the Vivado design, which will limit run-time flexibility. Within the AXI QSPI configuration dialog, we can configure the transaction width, frequency, and number of slaves. One of the most important things we also configure here is whether the AXI QSPI IP core will act as a SPI slave or master. To enable a SPI master, you must check the enable master mode option. If this module is to operate as a slave, this option must be unchecked to ensure the SPISel input pin is present. When the SPI IP core acts as a slave, this pin must be connected to the master’s slave select.

Configuring the AXI Quad SPI

As with the PS SPI controller, the BSP also provides an API for the SPI IP. We use it to develop the application software. This API is defined within the file XSPI.h. I used this API to configure the AXI QSPI as a SPI slave for the second part of the example.

To demonstrate the AXI QSPI core working properly as a SPI Slave once I had created the software. I used Digilent’s Digital Discovery to act as the SPI master, allowing data to be easily transferred between the two.

Transmitting and Receiving Data with the SPI slave. (Blue data is output by the Zynq SPI Slave)

The final design created in Vivado to support both these examples has been uploaded to github.

Final example block diagram

Of course, if you are using a Xilinx FPGA in place of a Zynq SoC or Zynq UltraScale MPSoC, it is possible to use a MicroBlaze soft processor with the same AXI QSPI configuration to implement a SPI interface. Just remember to correctly define it as a master or slave.

I hope this helps outline how we can create both master and slave SPI systems using the two different approaches.

Code is available on Github as always.

If you want E book or hardback versions of previous MicroZed chronicle blogs, you can get them below.

First Year E Book here

First Year Hardback here.

Second Year E Book here

Second Year Hardback here