SPI Controller

Configuring and using the SPI bus on ZYNQ

1338

Overview

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
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.

FIFOs

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)
Figure 2. ZYNQ’s SPI FIFOs (reprinted from the ZYNQ TRM)
Registers

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: Read FIFO full (1 for FIFO full)
RN: Read FIFO not full (1 for less than THRESHOLD entries)
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: Read FIFO not full (1 for less than THRESHOLD entries)
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: Read FIFO not full (1 for less than THRESHOLD entries)
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: Read FIFO not full (1 for less than THRESHOLD entries)
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

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_UNLOCK = UNLOCK_KEY;	//unlock SLCRs
	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 are mutually independent – if two were driven simultaneously, damage to the slave devices could result. 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 SPI controller will automatically drive the selected SS signal during a bus transfer if bit 15 in the control register is ‘0’; if bit 15 is a ‘1’, then the selected SS signal is always driven. The controller also supports using an external decoder if more than three SS signals are needed (see the ZYNQ TRM for more information).

Configuring the SPI bus for 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 1 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, configure:

  • 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=0)
  • Automatic start (MANUAL_START_EN=0)

After configuration, writing data to the SPI TXD FIFO will start an SPI bus transaction. Remember to select the SS signal for the instrument you want to communicate with!

Starting SPI transfers

SPI transfers can be started in two ways. If the manual start enable bit (bit 15) is setto a ‘1’ in the control register, transfers will begin only after the manual start bit (bit 16) in the register is set. If the manual start enable (bit 15) is ‘0’, transfers will start automatically when there is at least one byte in the transmit FIFO (this is the more typical case). In both modes, transfers will automatically continue as long as there is data in the TXD FIFO. When the TXD FIFO is empty, the bus transaction ends.

Recall that to read the SPI bus, you must transmit the same number of bytes you wish to read to cause the data to be shifted out. So, if you wish to read N bytes, you must N bytes in the transmit FIFO first.

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.