Applications - Serial Peripheral Interface (SPI)

Last modified by Microchip on 2024/06/20 12:43

Introduction

This article shows how the SPI bus functionality of the SAMA5D2 Series ARM® Cortex®-A5 Microprocessor Unit (MPU) is enabled in the Linux® kernel and how to access the SPI bus in user space.

Since the SPI device interface was introduced into the Linux kernel, you can access the SPI driver via spi_register_driver() interface via the structure spi_device handle.

You can also access the SPI driver in user space via the /dev/spidev device node. SPI devices have a limited user-space API, supporting basic half-duplex read() and write() access to SPI slave devices. Using ioctl() requests, full-duplex transfers and device I/O configuration are also available. We show you how using a C-language program.

Prerequisites

This application is developed for the ATSAMA5D27-SOM1-EK1 development platform:

This application is developed using the Buildroot build system.

Hardware

For this application, you will be controlling the SPI bus of the mikroBUS 1 expansion socket of the ATSAMA5D27-SOM1-EK1. The accompanying figure shows the expansion capability of the SOM1-EK1.

Expansion features of the ATSAMA5D27-SOM1-EK1

The ATSAMA5D27 SOM1 contains five Flexible Serial Communications Controller (FLEXCOM) peripherals to provide serial communications protocols: USART, SPI, and TWI.

You will control pins PD0, PC30, PC29 and PA28 from the ATSAMA5D27 SOM1 which connects to J24 pins 3, 4, 5, and 6 of the mikroBUS 1 connector (labeled NPCS1, SPCK_mBUS1, MISO_mBUS1, and MOSI_mBUS1 on the schematic).

mikroBUS pinSchematic NameFLEXCOM I/OPackage Pin
J24 pin 3NPCS1FLEXCOM1_IO4PD0
J24 pin 4SPCK_mBUS1FLEXCOM1_IO2PC30
J24 pin 5MISO_mBUS1FLEXCOM1_IO1PC29
J24 pin 6MOSI_mBUS1FLEXCOM1_IO0PC28

​For more details on the SAMA5D2 Package and Pinout, refer to Table 6-2. Pinouts in SAMA5D2 series datasheet.

mikroBUS schematic


Back to Top

Buildroot Configuration

Objective: Using Buildroot, build a bootable image and FLASH onto an SD Memory Card for the ATSAMA5D27-SOM1-EK1 development board.

Follow the steps for building the image on the "Buildroot - Create Project with Default Configuration" page. You will use the default configuration file: atmel_sama5d27_som1_ek_mmc_dev_defconfig.

Device Tree

Objective: Observe how the FLEXCOM4 peripheral was configured for SPI in the device tree. A small addition is shown for the at91-sama5d27_som1_ek.dts file below for the ability to communicate in user space.

Once Buildroot has completed its build, the SPI definitions for the ATSAMA5D27-SOM1-EK1 were configured by a device tree. The device tree source includes (*.dtsi and *.dts) files are located in the Buildroot output directory: /output/build/linux-linux4sam_6.0/arch/arm/boot/dts/.

Examine the sama5d2.dtsi file and observe the FLEXCOM4 device assignments:

697   flx4_clk: flx4_clk {
698      #clock-cells = <0>;
699      reg = <23>;
700      atmel,clk-output-range = <0 83000000>;
701   };
.
.
1424  flx4: flexcom@fc018000 {
1425     compatible = "atmel,sama5d2-flexcom";
1426     reg = <0xfc018000 0x200>;
1427     clocks = <&pmc PMC_TYPE_PERIPHERAL 23>;
1428     #address-cells = <1>;
1429     #size-cells = <1>;
1430     ranges = <0x0 0xfc018000 0x800>;
1431     status = "disabled";
1432  };

Line 699 shows the PID for FLEXCOM4 is 23; this definition of the offset will be used to enable FLEXCOM4 clock in PMC.

Line 700 shows the FLEXCOM4 input clock; the max frequency is 83MHz.

Line 1425 specifies which driver will be used for this FLEXCOM device.

Line 1426 shows the FLEXCOM4 base address of 0xfc018000; the size is 0x200.

Line 1427 shows the definition for the FLEXCOM4 clock source.

Line 1431 the status is set to "disabled" by default. It will be set to "okay" in the at91-sama5d27_som1_ek.dts file below.


Examine the at91-sama5d27_som1_ek.dts file and observe the SPI device assignments:

258   flx4: flexcom@fc018000 {
259      atmel,flexcom-mode = <ATMEL_FLEXCOM_MODE_SPI>;
260      status = "okay";
261
262      uart6: serial@200 {
263         compatible = "atmel,at91sam9260-usart";
264         reg = <0x200 0x200>;
265         interrupts = <23 IRQ_TYPE_LEVEL_HIGH 7>;
266         clocks = <&pmc PMC_TYPE_PERIPHERAL 23>;
267         clock-names = "usart";
268         pinctrl-names = "default";
269         pinctrl-0 = <&pinctrl_flx4_default>;
270         atmel,fifo-size = <32>;
271         status = "disabled"; /* Conflict with spi3 and i2c3. */
272      };
273
274      spi3: spi@400 {
275         compatible = "atmel,at91rm9200-spi";
276         reg = <0x400 0x200>;
277         interrupts = <23 IRQ_TYPE_LEVEL_HIGH 7>;
278         clocks = <&flx4_clk>;
279         clock-names = "spi_clk";
280         pinctrl-names = "default";
281         pinctrl-0 = <&pinctrl_mikrobus_spi &pinctrl_mikrobus1_spi_cs &pinctrl_mikrobus2_spi_cs>;
282         atmel,fifo-size = <16>;
283         status = "okay"; /* Conflict with uart6 and i2c3. */

// the following code is added to enable spidev in userspace
283a        spidev@1{
283b           compatible = “spidev”;
283c           reg = <1>;
283d           spi-max-frequency = <100000>;
283e        }
// - - - - - - - - - - - - - - - - - - - - - - -

284      };
285
286      i2c3: i2c@600 {
287         compatible = "atmel,sama5d2-i2c";
288         reg = <0x600 0x200>;
289         interrupts = <23 IRQ_TYPE_LEVEL_HIGH 7>;
290         dmas = <0>, <0>;
291         dma-names = "tx", "rx";
292         #address-cells = <1>;
293         #size-cells = <0>;
294         clocks = <&pmc PMC_TYPE_PERIPHERAL 23>;
295         pinctrl-names = "default";
296         pinctrl-0 = <&pinctrl_flx4_default>;
297         atmel,fifo-size = <16>;
298         status = "disabled"; /* Conflict with uart6 and spi3. */
299      };
300   };
.
.
473         pinctrl_mikrobus1_spi_cs: mikrobus1_spi_cs {
474            pinmux = <PIN_PD0__FLEXCOM4_IO4>;
475            bias-disable;
476         };
.
.
483         pinctrl_mikrobus_spi: mikrobus_spi {
484            pinmux = <PIN_PC28__FLEXCOM4_IO0>,
485                     <PIN_PC29__FLEXCOM4_IO1>,
486                     <PIN_PC30__FLEXCOM4_IO2>;
487            bias-disable;
488         };

 Line 258 specifies SPI mode for this FLEXCOM.

Line 275 enables this device.

Line 275 specifies which driver will be used for this SPI device.

Line 276 sets the register offset address to 0x400, size 0x200.

Line 277 specifies the PID for FLEXCOM4 is 23, high level triggered, priority 7 (used to configure FLEXCOM4 interrupt in the AIC).

Line 278 is the definition for the FLEXCOM4 clock source.

Line 281 is the pin definition for the FLEXCOM4 SPI function.

Line 283 shows the SPI function status is "okay" while the UART and I²C functionality are "disabled".

Line 283a-283e are the changes you make (see the following information box).

​See /output/build/linux-linux4sam_6.0/drivers/spi/spi.c of _spi_parse_dt() for more options.

Line 283b specifies which driver will be used for this device.

Line 283c is the definition that will be used as the CS number for SPIDEV.

Line 283d specifies the clock frequency for SPIDEV.

Line 474 assigns pin PD0 to FLEXCOM4_IO4.

Line 484 assigns pin PC28 to FLEXCOM4_IO0.

Line 485 assigns pin PC29 to FLEXCOM4_IO1.

Line 486 assigns pin PC30 to FLEXCOM4_IO2.

It is not recommended to use spidev as a device tree-compatible name. It will work, but you will get the following warning:

# dmesg | grep spidev
 spidev spi1.1: buggy DT: spidev listed directly in DT
 WARNING: CPU: 0 PID: 1 at drivers/spi/spidev.c:730 0xc045d630

Because spidev is a Linux implementation construct, rather than a description of the hardware, it should never be referenced in a device tree without a specific name. To avoid this warning, use another compatible name instead of spidev, for example:

spidev@1 {
    compatible = "atmel,at91rm9200-spidev";
    reg = <1>;
    spi-max-frequency = <1000000>;
};

Next, edit spidev driver file /output/build/linux-linux4sam_6.0/drivers/spi/spidev.c and add a new compatible name to the compatible table of spidev driver:

static const struct of_device_id spidev_dt_ids[] = {
 { .compatible = "rohm,dh2228fv" },
 { .compatible = "lineartechnology,ltc2488" },
 { .compatible = "ge,achc" },
 { .compatible = "semtech,sx1301" },
 { .compatible = "atmel,at91rm9200-spidev" },
 {},
};

Back to Top


Kernel

Objective:
Observe how SPI functionality was configured in the Linux kernel.

In this exercise, you will configure the Linux kernel for spidev functionality.

From the buildroot directory, run the Linux kernel menuconfig:

​$ make linux-menuconfig

The top-level menu will be displayed:

Linux menuconfig top menu


Device Driver

Select Device Drivers ---->

Select [*] SPI Support ---->

Linux menuconfig device drivers


Observe that <*> Atmel SPI Controller is selected.

This selects the driver for the SPI controller.

Selecting Atmel SPI Controller


Observe that <*> User mode SPI device driver support is selected.

This enables the spidev functionality.

User mode SPI device driver support

Back to Top

Rootfs

There are two ways to access SPI bus driver:

Kernel space

Register your SPI driver via spi_register_driver() interface, then access the SPI bus driver via the structure spi_device handle.

There is no definition for SPI bus number in device tree files and the bus number of SPI controller will be allocated automatically when registering. For example, the first registered SPI controller will be assigned bus number 0, and the second assigned bus number should be 1, and so on. The following device node will be used to access the SPI bus driver. The first number 1 refers to the bus number, and the second number 1 refers to the CS number/dev/spidev1.1

User space

You can access the SPI device from user space by enabling the SPIDEV kernel feature as shown above and then access the SPI bus driver via /dev/spidev device node as shown in the "Application" section below. SPIDEV is a good choice because all application codes are running in user space (it’s easier for developing). However, you will have to configure the SPIDEV feature first in the Linux kernel.

Back to Top

Application

The following is a C-Language demonstration program for accessing the SPI bus driver via the SPIDEV node:

To compile:

​$ buildroot/output/host/bin/arm-buildroot-linux-uclibcgnueabihf-gcc spi_dev.c -o spi_test

​Be sure to type in the location of the cross-compiler on your host computer.

Source code:

#include <stdio.h>
#include
<stdlib.h>
#include
<unistd.h>
#include
<fcntl.h>
#include
<sys/ioctl.h>
#include
<linux/spi/spidev.h>

#define DEV_SPI "/dev/spidev1.1"

int main(int argc, char *argv[])
{
   int fd;
   int ret;
   unsigned int mode, speed;
   char tx_buf[1];
   char rx_buf[1];
   struct spi_ioc_transfer xfer[2] = {0};

   // open device node
   fd = open(DEV_SPI, O_RDWR);
   if (fd < 0) {
        printf("ERROR open %s ret=%d\n", DEV_SPI, fd);
       return -1;
    }

   // set spi mode
   mode = SPI_MODE_0;
   if (ioctl(fd, SPI_IOC_WR_MODE32, &mode) < 0) {
        printf("ERROR ioctl() set mode\n");
       return -1;
    }
   if (ioctl(fd, SPI_IOC_RD_MODE32, &ret) < 0) {
        printf("ERROR ioctl() get mode\n");
       return -1;
    } else
        printf("mode set to %d\n", (unsigned int)ret);

   // set spi speed
   speed = 1*1000*1000;
   if (ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed) < 0) {
        printf("ERROR ioctl() set speed\n");
       return -1;
    }
   if (ioctl(fd, SPI_IOC_RD_MAX_SPEED_HZ, &ret) < 0) {
        printf("ERROR ioctl() get speed\n");
       return -1;
    } else
        printf("speed set to %d\n", ret);

   // transfer data
   tx_buf[0] = 0xa5;
    xfer[0].tx_buf = (unsigned long)tx_buf;
    xfer[0].len = 1;
    xfer[1].rx_buf = (unsigned long)rx_buf;
    xfer[1].len = 1;

   do {
       if (ioctl(fd, SPI_IOC_MESSAGE(2), xfer) < 0)
            perror("SPI_IOC_MESSAGE");
        usleep(100*1000);
    } while (1);

   // close device node
   close(fd);

   return 0;
}

spidev_test Application

There is a spidev_test application that you can configure in Buildroot.

Select Target packages ---->

Select Debugging, profiling and benchmark ---->

Select [*] spidev_test

selecting spidev_test

Back to Top

Hands On

Copy the spi_test program to the target and execute it.

​# chmod +x spi_test
# ./spi_test

The SPI waveform can be monitored on a logic analyzer:

Legend:

  • Yellow – NPCS1
  • Green – SPCK_mBUS1
  • Blue – MOSI_mBUS1
  • Red – MISO_mBUS1

Screen capture of logic analyzer displaying SPI waveforms

Back to Top

Tools and Utilities

spi-tools is a tool for SPI bus testing. It is included in the default configuration of Buildroot. You can view the selection by performing the following:

From the Buildroot directory, start menuconfig:

​$ make menuconfig

Select Target packages - - ->

Select Hardware handling - - ->

Observe [*] spi-tools is selected.

Linux kernel selecting SPI Tools

spi-tools Commands

There are two commands in spi-tools:

spi-config:

# spi-config -h
usage: spi-config options…
options:
-d —device=<dev> use the given spi-dev character device.
-q —query print the current configuration.
-m —mode=[0-3] use the selected spi mode:
0: low iddle level, sample on leading edge,
1: low iddle level, sample on trailing edge,
2: high iddle level, sample on leading edge,
3: high iddle level, sample on trailing edge.
-l —lsb={0,1} LSB first (1) or MSB first (0).
-b —bits=[7…] bits per word.
-s —speed=<int> set the speed in Hz.
-h —help this screen.
-v —version display the version number.
# spi-config -d /dev/spidev1.1 -q
/dev/spidev1.1: mode=0, lsb=0, bits=8, speed=1000000

spi-pipe:

# spi-pipe -h
usage: spi-pipe options…
options:
-d —device=<dev> use the given spi-dev character device.
-b —blocksize=<int> transfer block size in byte.
-n —number=<int> number of blocks to transfer (-1 = infinite).
-h —help this screen.
-v —version display the version number.
# spi-pipe -d /dev/spidev1.1 -b 6 -n 1
111111

Input "111111" (six ones) and press the Enter key. The waves could be captured from an oscilloscope accordingly.

The SPI waveform can be monitored on a logic analyzer:

Legend:

  • Yellow – NPCS1
  • Green – SPCK_mBUS1
  • Blue – MOSI_mBUS1
  • Red – MISO_mBUS1

 Screen capture of logic analyzer displaying SPI waveforms

Summary

In this article, you used Buildroot to build an image with SPI Bus support for the ATSAMA5D2 Series MPU. You accessed the SPI Bus via two different methods: kernel space using the SPI driver via spi_register_driver() interface, then access SPI bus driver via structure spi_device handle, and user space by enabling the SPIDEV kernel feature and then accessing the SPI bus driver via /dev/spidev device node. You walked through the device tree and kernel to observe how the embedded Linux system configures the source code for building.

Back to Top