Configuring and using the SPI bus on ZYNQ


ZYNQ contains two independent SPI controllers, and each controller can be routed to MIO pins or to the EMIO interface (the EMIO interface allows ZYNQs built-in peripherals to use pins normally reserved for the FPGA). Each controller can generate three independent SS signals and seven different interrupts, and each controller has its own set of control and status registers (shown in the table below).

Table 1. SPI Registers
Table 1. SPI Registers

ZYNQ’s SPI controllers include 128-byte managed FIFOs for both the transmitter and receiver (see the ZYNQ UART topic document for a discussion of managed FIFOs). Data placed in the transmit FIFO will be automatically serialized and sent out on the MOSI pin. Transmit data can be written into the FIFO much faster than it can be sent out, so if you write more than 128 bytes, you must manage the transmit FIFO 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. The tables below show the status bits that are available in the status register, and the available interrupt sources.

Table 2. SPI Status Register Bits
Table 2. SPI Status Register Bits
Table 3. Interrupt Functions
Table 3. Interrupt Functions

On the Blackboard, SPI0 is connected to an inertial module (the ST Micro LSM9DS1) that contains a gyroscope, an accelerometer, and a magnetometer (compass). SPI1 is connected to an on-board QSPI Flash ROM.

The SPI controller input clock is fixed at 166.666MHz. A mandatory programmable clock divider is applied to the input clock to divide it to a lower value. Bits 3-5 in the SPI Configuration Register define the clock divider value according to the formula: 166.666MHz / 2 ^ n +1, where n is the 3-bit number defined in the BAUD_RATE field (see table below). As an example, setting bits 3-5 to 100 will divide 166.666MHz by 32, resulting in an SCLK frequency of 5.2MHz.

Table 4. SPI Configuration Bits
Table 4. SPI Configuration Bits

Configuring the SPI controller

Before configuring ZYNQ’s SPI controller, you must perform a software reset to ensure it begins operating in the proper state. The SPI controller is a protected system resource, and it can only be reset after unlocking write control for the “System Level Control Registers” (see the ZYNQ TRM page 1578). After unlocking the SLCRs, you can write 1’s to bit fields in the SPI_RST_CNTL register to cause an SPI0 reset, and then write 0’s to release the reset. The function below illustrates the SPI reset procedure:

 #define MOD_RESET_BASEADDR 0xF8000000
void SPI_reset(){
	uint32_t register_value;
	*((uint32_t *) MOD_RESET_BASEADDR+0x8/4) = 0x0000DF0D;  	// unlock the SLCRs
	*((uint32_t *) MOD_RESET_BASEADDR + 0x0000021C/4) = 0xF;	// Reset SPI0
	*((uint32_t *) MOD_RESET_BASEADDR + 0x0000021C/4) = 0;		// Release the reset

After the SPI controller has been reset, it can be configured by setting bit fields correctly in the Configuration Register. A summary of the bit fields is provided above, but you will need to reference the Blackboard Programmer’s Reference or the ZYNQ TRM for specific information on the bit values. Further, since different peripheral devices use different SPI configurations, you will also need to reference the device’s data sheet to learn what SPI configuration must be used.

In this case, to configure the SPI0 controller to interact with the ST inertial module, write values to the SPI Configuration Register to:

  1. Enable ModeFail generation;
  2. Set Manual Start Command to 0;
  3. Set Manual Start Enable to Auto mode;
  4. Enable Manual CS (CS is used here for SS);
  5. Set CS to 0xF to de-assert all the slave selects before the start of transfers;
  6. Set Peripheral select decode to only 1 of 3 selects;
  7. Set the Master Reference Clock Select to SPI Reference Clock (166.66666MHz)
  8. Set Baud Rate Division value to 32 to select an SPI SCLK frequency below 10MHz as required by the NAV sensor (see the NAV sensor data sheet for more information);
  9. Use SPI Mode 3 by setting the clock phase to “clock is inactive outside the word” and the polarity to quiescent high (CPOL = 1 & CPHA =1);
  10. Select SPI in the master mode.

The inertial module uses two semi-independent SPI ports that share SCLK and MISO but use separate SS and MOSI pins. Each port can be accessed separately by controlling their separate SS signals. All data accesses (reads or writes) to the inertial module use 16 bit words, so two bytes must be sent to the module in order to read any data. In this case, when performing a read operation, the first 8 bits must contain a ‘1’ in bit 0, followed by seven bits containing the address of the register you wish to read (see the LSM9DS1 topic document for more information). The remaining 8 bits shifted out are don’t cares (they are only needed to “push out” the remaining 8 data bits), so you can load 0x00.

To read sensor data from the inertial module, you must first reset and configure ZYNQ’s SPI controller as described above, and then (inside of a C function):

  1. Assert the Accelerometer/Gyro SS signal by writing “1110” to the CS bit field in the Configuration Register (or to read magnetometer data, assert it’s SS by writing 1101 to those same bits);
  2. Use the SPI_T_Threshold and SPI_R_Threshold registers to set the threshold levels when the status bit is triggered;
  3. Enable the SPI Controller by writing to the ZYNQ/SPI EN register;
  4. Move data through the SPI registers by writing the R/W bit, register address, and 0x00 (16 bits total) to the transmit FIFO;
  5. Poll on Transmitter/Receiver FIFO Not Empty bit using the SPI0 Status register.
  6. Read the two data bytes from the receive data FIFO;
  7. Disable controller SPI controller (again using the ER register)
  8. De-assert the SS signal (by writing 0xF to the CS bit fields in the Configuration register.

After you’ve created the read function, you might encounter a situation where you’re not receiving any valid data. Thus, call your “read NAV sensor” function twice in a row, you should end up with four bytes of data where 3 bytes are not valid.