Project 6 Using the SPI and I2C bus

Configuring and using common serial busses



This project introduces the Serial Peripheral Interface (SPI) and Inter-Integrated Circuit (I2C) busses. In the project these busses are used to communicate between the ZYNQ Chip and peripheral devices on the blackboard. The Blackboard has an ST Microelectronics multi-sensor package which includes an accelerometer, gyroscope, and magnometer. This Inertial module is connected through the SPI bus. The Blackboard also has an NXP temperature sensor connectted on the I2C bus. The Zynq’s SPI and I2C modules will be configured and used to talk with devices on each bus.

Before you begin, you should:

  • Know how to setup and use ZYNQs interrupts;
  • Know how to configure and use ZYNQs Global timer module;
  • Know how to use UART ports.

After you’re done, you should:

  • Understand the SPI bus and be able to use it;
  • Understand the I2C bus and be able to use it;
  • Be able to use the SPI and I2C busses with interrupts.


Serial Peripheral Interface (SPI) is a full-duplex serial bus. SPI uses 2 data lines (one for each direction), a clockline and a number of ‘slave select’ lines. The slave select lines allow the master to choose which slave device to communicate with. SPI is often used to achieve high speed transfers between devices. While high-speed, the requirement for dedicated slave select lines limits how many devices can be connected to the master. However dedicated select lines eliminate overhead for address decoding on the bus’ actual data lines.

Inter-Integrated Circut (I2C) is a half-duplex serial bus. I2C has only 2 connections: Serial Clock Line (SCL) and Serial Data Line (SDA). In contrast to SPI, I2C needs no additional lines for more devices ; Regardless of how many devices are on the bus, there is always only SCL and SDA in a I2C topology. In lieu of the slave select lines of SPI, I2C prefixes each transaction with an address. The address denotes which slave the master wishes to read or write to. I2C makes for very flexible designs with its two wire topology. However, being only half-duplex and requiring addresses before each transaction, the throughput of I2C is limited. Additionally, the parasitic capacitances of devices connected to the I2C bus can have an affect on how fast the clock can run; I2C has a standard maximum of 3.4 Mbit/s but commonly is run at 400kbit/s or below.

Both the SPI and I2C modules in the ZYNQ have transmit and receive FIFOs. The processor can queue transfers and read from the RX FIFO after transfers complete. Both modules can be configured to generate interrupts based on FIFO status and other events.


1. Read ID codes from Blackboard’s Inertial Module

Configure the Zynq’s SPI module (Use SPI0) to communicate with the SPI-connected iNemo inertial module. The inertial module is connected with 2 slave select lines. The accelerometer and gyro are accessed through asserting slave select 0, and the magnetometer is accessed using slave select 1. Since the different modules have their own slave selects, you may want to write seperate functions for accessing accelerometer/gyro data and magnetometer data. Each function will be the same, but each will assert a different slave select. Read the background material for the inertial module so you understand its SPI read transactions. Two example function prototypes are given below.

uint32_t acc_gyro_read(uint32_t address);
uint32_t mag_read(uint32_t address);

After you write the functions, use them to read from the WHO_AM_I registers in both the acc/gyro and the magnetometer. Send the value stored in each register to your PC via UART, formatted as a hexidecimal number. The WHO_AM_I registers can be accessed at address 0xF for both the acc/gyro and magnetometer.

2. Acquire data from the Inertial Module

Before you can read data from the nav chip, you must enable the accelerometer or gyroscope.

Enabling Sensor Output

At reset both the gyro and accelerometer are powered down. To enable the accelerometer, you can write 0x20 to address 0x20. The gyroscope can be enabled by writing the same value to 0x10. Once enabled each sensor’s data can be read from the respective output data registers.

Create a write function which lets you write data to the navchip’s configuration registers; Have your write function receive the register’s address and the write data as arguments. An example function prototype is given.

void write_acc_gyro(uint32_t addr, uint32_t data);
Reading the Output data

The gyroscope data can be read from addresses 0x18 to 0x1D, while accelerometer output data can be found at 0x28 to 0x2D.

Each axis has data stored in a pair of registers with sequential addresses (For example the gyroscope X data is in registers 0x18 and 0x19). Consult the LSM9DS1 data sheet for specifics on how to interpret the data.

Write a program that first enables either the accelerometer or gyro. Then, have your program read data from all three axes of the sensor and that data over the UART to the PC. Format the data from each axis into strings (you can use hexidecimal numbers). Then send the string-values over UART. An example of what your formatted output can look like is given below.

X: 0x1234
Y: 0x5678
Z: 0xA034

3. Configure the SPI module to generate interrupts

Configure the SPI module to generate interrupts when data is availible in the receive FIFO. You can test if the interrupt works by queuing any SPI transfer to the module. Once data is in the FIFO, the processor should receive an interrupt and enter the SPI ISR (the interrupt ID for the SPI module is 58).

Write a program that configures two interrupts: Setup the global timer to generate interrupts every second; and setup the SPI module to interrupt when there is data availible in the receive FIFO. Each time the global timer interrupt occurs, initiate a read for accelerometer data. When the data is received, in the SPI FIFO interrupt service routine, send the accelerometer data via UART to the PC terminal. You can format the data the same as in requirement 2.

Have your program configure Timer and SPI interrupts then enter an endless loop. Your program should just consist of interrupt initialization in main and a service routine that handles timer and SPI interrupts.

4. Read the temperature sensor via I2C

Configure the I2C module in the Zynq. Write a program that polls a button and sends a reading from the Temperature Sensor (connected via I2C1) every time it is pushed. As before, send the data to the PC terminal via the Zynq’s UART. Convert your temperature sensor data to degrees in Celsius before transmitting.

5. Setup timer and I2C interrupts to read the temperature sensor

As in requirement 3, setup the Global timer to interrupt every second. Every time the global timer interrupt occurs, initiate a read from the temperature sensor. Configure interrupts for I2C to occur when there is data availible in the receive FIFO and in the service routine for I2C, send the temperature data over UART. The I2C Module’s interrupt ID is 80.

Your program can be structured similarly to the program you wrote for requirement 3.


Create a system that reads from a variety of sensors.

Design a program that can read the acceleromenter, gyroscope, Magnetometer, and XADC voltage (potentiometer value). Have the MIO connected buttons (BTN4/BTN5) start and stop reading data and use the first 2 switches to change which sensor is being read. Use the UART to send readings to the PC. Use the following settings for the switches to choose which data to display:

Switch Value Display Function
0b00 Accelerometer X,Y,Z
0b01 Gyroscope X,Y,Z
0b10 Magnetometer X,Y,Z
0b11 XADC Potentiometer Voltage

Have button 4 start collecting data and sending it via UART once per second. Use the value of the switches to determine the source of data. When button 5 is pressed, stop collecting data. Use interrupts for button presses, timers, and SPI/I2C receives.