In last week’s blog, we looked at how we could build PetaLinux from scratch. This week we are going to explore how we can use a common embedded systems interface from the Linux user space.

The ability to access and work with SPI interfaces is really very useful, as often in our applications we want to be able to interface with sensors, memories, and devices which use this protocol for transfer of data. The other popular embedded system interface is I2C and we will examine that in a future blog as well.

Starting at the beginning, the first thing we need to do is to is to include SPI interfaces within our design in Vivado. Without being included in either the PS or PL, we of course cannot use it.

Enabling SPI in the PS (MPSoC example)

Once we have the hardware built, we are then able to export a hardware definition file (HDF) and use this to create a PetaLinux project. However before we build the project, if we want to us the SPI from the user space, we need to make a few modifications to the PetaLinux kernel and device tree.

Once we have created the project and applied the HDF file, the first step is to configure the kernel. We can do this using the command:

$ petalinux-config -c kernel

This will open a configuration screen. Navigate from the initial screen to:

Device Drivers -> SPI Support

Enable the user mode SPI support option as shown below:

Initial configuration screen — select device drivers

Device drivers menu select — SPI support

Enable — user mode SPI device driver support

Once you have done that, exit the configuration menu and save the configuration changes. It may then take a few minutes to regenerate everything.

The next step is to update the device tree. Now there are several device trees in a PetaLinux project and many of them are automatically generated. As such, we want to be sure we edit the correct one.

Therefore, under your PetaLinux project, open the following directory:

/project-spec/meta-user/recipes-bsp/device-tree/files

Open the file system-user.dsti

Add in the lines below, this defines the PS SPI0 as compatible with the SPI user mode device support we just enabled in the kernel configuration (SPIDev). In this example, there are two entries for SPI0 — one for each of the slave selects I enabled in the Vivado design.

Updates to the device tree in system-user.dsti

We can now save the file and build PetaLinux.

After the build completes, modify your selected target board with the updated boot files. For this example, I am using the Ultra96.

The next step is to check that we can see the SPI devices in the user space, power on the board, and wait for it to boot Linux. Once booted, if necessary log in, otherwise change directory into /dev/ and list its contents.

If we have performed the PetaLinux configuration successfully, you will see the two SPI devices listed as SPIDev — one for each definition in the device tree.

In the example above, SPI 0 in the Zynq MPSoC PS is available for use with both slave select zero and one. The numbering scheme is:

spidev,

So, how do we use these in our code? It is actually remarkably simple and we have two options depending upon if we desire half-duplex or duplex communication with the SPI device (In most cases we desire duplex).

To be able to use the SPIDev in our C application, we must first define it using the code:

static const char *device = “/dev/spidev0.1”;

The next step is to open the file, for read and writing:

fd = open(device, O_RDWR);

To configure the SPI speed, number of bits sent etc we can use IOCTL functions:

ioctl(fd, SPI_IOC_WR_MODE32, &mode); // SPI Mode

ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits);// bits per word

ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed); //max write speed

We can also use the same functions to read the settings.

To communicate using half-duplex — that is the CS is de asserted between write and reads — we can use the read and write functions.

write(fd,tx_buf,ARRAY_SIZE(tx_buf))

read(fd,rx_buf,ARRAY_SIZE(rx_buf))

Often, though, we need to be able to read the MISO at the same time as we drive the MOSI port on the SPI for proper operation. To do this we need to use the IOCTL command again:

struct spi_ioc_transfer tr = {

.tx_buf = (unsigned long)tx_buf,

.rx_buf = (unsigned long)rx_buf,

.len = ARRAY_SIZE(tx_buf),

.delay_usecs = delay,

.speed_hz = 0,

.bits_per_word = 0,

};

ioctl(fd, SPI_IOC_MESSAGE(1), &tr); // perform duplex transfer

Now we know more about how we can use SPI in our Linux user space. It enables us to be able to leverage the power of a high performance OS such as PetaLinux and still be able to control common embedded system interfaces — exactly what we want for out Zynq and Zynq MPSoC developments.

You can find more on SPIDev here.

See My FPGA / SoC Projects: Adam Taylor on Hackster.io

Get the Code: ATaylorCEngFIET (Adam Taylor)

Access the MicroZed Chronicles Archives with over 270 articles on the Zynq / Zynq MpSoC updated weekly at MicroZed Chronicles.’