How to use SPI on Torizon
Introduction
This article describes how to access SPI devices from both kernel space and user space on Torizon OS. SPI (Serial Peripheral Interface) is a common interface for a variety of sensors and other hardware peripherals. SPI devices operate in a relationship, where the "master" (controller) starts the communication with the "slave" (sensor, display, or other peripheral), which returns information. It requires a software layer to connect the OS system calls to the hardware.
Device Drivers and User Space Applications
A device driver controls how a hardware device interacts with the OS. For instance, it can efficiently use peripheral resources and handle potential problems, such as race conditions, that may occur when multiple threads attempt to communicate with the hardware. However, debugging can be challenging if issues arise, given that driver source codes are often complex and difficult to understand.
Another option is to use the standard SPI interface for Linux and manually program how the hardware and operating system interact. This method provides considerable flexibility in defining the peripheral behavior, but it requires knowing hardware details. If you are working with a widely used device, you will probably find source code in different programming languages that can serve as a starting point for your application. However, it may lead to conflicts and performance issues. Thus, this approach can help with device testing when driver errors arise or no specific device drivers are available.
The steps for each method vary with different devices and use cases. Thus, to support your understanding, this article provides the following sections:
- Connect an SPI Device
- Locate SPI buses in the OS
- Peripheral Access from Kernel Space
- Peripheral Access from User Space
This article complies with the Typographic Conventions for Torizon Documentation.
Prerequisites
- A Toradex System on Module with Torizon OS installed.
- A configured build environment, as described in the Configure Build Environment for Torizon Containers article.
- An understanding of Peripheral Access.
Connect an SPI Device
Toradex SoMs offer multiple SPI buses for connecting peripherals, with some pins having SPI as their default or alternate function. Prioritize using pin functions compatible with all modules within the same family, ensuring the best compatibility with standard software and other modules in the family. To change pin functionalities, refer to the Pin Multiplexing article.
You can find pin information in the SoM and carrier board datasheets.
Always check the IO voltage level of your modules and peripherals before connecting them. The TTL level for Colibri and Apalis IO is 3.3V, while for Verdin it is 1.8V. Do not apply 3.3V signals to Verdin IO.
The Verdin iMX8M Plus module provides multiple SPI buses, including the SPI_1
, which is designed for general use and is the one we will use in examples throughout this article. That bus features the following signals:
SPI_1_MOSI
SPI_1_MISO
SPI_1_CS
SPI_1_CLK
You can find this information in the SPI section of your module datasheet as follows:
X1 Pin# | Verdin Std Function | i.MX 8MP Ball Name | i.MX 8MP Function | I/O | Description |
---|---|---|---|---|---|
200 | SPI_1_MOSI | ECSPI1_MOSI | ECSPI1_MOSI | O | Master Output, Slave Input |
198 | SPI_1_MISO | ECSPI1_MISO | ECSPI1_MISO | I | Master Input, Slave Output |
202 | SPI_1_CS | ECSPI1_SS0 | ECSPI1_SS0 | I/O | Slave Select |
196 | SPI_1_CLK | ECSPI1_SCLK | ECSPI1_SCLK | I/O | Serial Clock |
To connect your device, search for pins that carry these signals in your carrier board schematics or datasheet.
Locate SPI buses in the OS
Torizon OS, as a Linux-based distribution, treats peripheral devices as files. SPI files are located in the /sys/bus/spi
directory. This section explains how to locate SPI buses in the OS using the information available in modules and carrier boards datasheets and device tree files.
As an example, the following steps describe how to locate the SPI_1
bus of the Verdin iMX8M Plus in the OS.
Identify the pin control group that contains the SPI signals: You can find this information in the SoM's device tree.
The following code snippet indicates, for example, that the
pinctrl_ecspi1
control group contains theECSPI1_MISO
pin with theECSPI1_MISO
function.imx8mp-verdin.dtsipinctrl_ecspi1: ecspi1grp {
fsl,pins =
<MX8MP_IOMUXC_ECSPI1_MISO__ECSPI1_MISO 0x1c4>, /* SODIMM 198 */
<MX8MP_IOMUXC_ECSPI1_MOSI__ECSPI1_MOSI 0x4>, /* SODIMM 200 */
<MX8MP_IOMUXC_ECSPI1_SCLK__ECSPI1_SCLK 0x4>, /* SODIMM 196 */
<MX8MP_IOMUXC_ECSPI1_SS0__GPIO5_IO09 0x1c4>; /* SODIMM 202 */
};Locate the bus node that corresponds to the identified pin control group.
The snippet below shows that the
ecspi1
node uses thepinctrl_ecspi1
pin control group.imx8mp-verdin.dtsi/* Verdin SPI_1 */
&ecspi1 {
#address-cells = <1>;
#size-cells = <0>;
cs-gpios = <&gpio5 9 GPIO_ACTIVE_LOW>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_ecspi1>;
};Search for the corresponding register address in the root node: The
reg
property contains the SPI bus address.In the following example, the address is
0x30820000
.imx8mp.dtsiecspi1: spi@30820000 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "fsl,imx8mp-ecspi", "fsl,imx6ul-ecspi";
reg = <0x30820000 0x10000>;
interrupts = <GIC_SPI 31 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clk IMX8MP_CLK_ECSPI1_ROOT>,List the available SPI buses. In your module's terminal, run the following command:
# ls -l /sys/bus/spi/devices/
lrwxrwxrwx 1 root root 0 Oct 19 12:54 spi1.0 -> ../../../devices/platform/soc@0/30800000.bus/30820000.spi/spi_master/spi1/spi1.0The output above shows there is one SPI bus available. It is possible to identify that
spi1.0
is theSPI_1
file, which has the0x30820000
address.
Peripheral Access from Kernel Space
How to Configure and Use SPI Devices
This section provides a step-by-step guide on how to collect the measurements from an FXAS21002C sensor using its driver. It will give you a general understanding of working with kernel drivers in embedded Linux.
Verify if any driver for your device is available in the Linux Kernel Source.
The FXAS21002C driver is named
fxas21002c
and resides at linux/drivers/iio/gyro.tipThe device name often gives insights into how its driver files are labeled.
Search for the corresponding config variable: The Makefile with this information is in the same folder as the driver's source code. The config variable can help you check the driver availability on your OS.
The Makefile for industrial I/O gyroscope sensor drivers has the following configuration:
obj-$(CONFIG_FXAS21002C) += fxas21002c_core.o
obj-$(CONFIG_FXAS21002C_SPI) += fxas21002c_spi.oIf the driver is available on your OS, you will find the
CONFIG_FXAS21002C
andCONFIG_FXAS21002C_SPI
config variables in the kernel configuration.Check the kernel configuration of your OS: The
config.gz
file, located at/proc
, contains driver module information.For example, to check if the FXAS21002C configuration is available on Torizon OS, run the
zcat
command:# zcat /proc/config.gz | grep FXAS21002C
CONFIG_FXAS21002C=m
CONFIG_FXAS21002C_I2C=m
CONFIG_FXAS21002C_SPI=mThe configuration set as
m
indicates that the driver is available in the OS as a Loadable Kernel Module (LKM) rather than being built-in.If the driver is not available on Torizon OS, follow the instructions described in the Building External Kernel Modules With Torizon article.
Customize your OS image: Since this driver is an LKM, it has to be activated using a Device Tree Overlay. You can find examples of overlays on the web that can serve as a base for your customization.
Identify the node of an SPI bus as described in the Locate SPI buses in the OS. For instance, the
ecspi1
node contains theSPI_1
bus configuration of the Verdin iMX8M Plus module. To activate the device driver for this bus, you can create a Device Tree Overlay to configure theecspi1
node.The following overlay activates the FXAS21002C driver:
// Definitions for the FXAS21002C Gyroscope
/dts-v1/;
/plugin/;
/ {
compatible = "toradex,verdin-imx8mp";
};
&ecspi1 { // select interface as per board
#address-cells = <1>;
#size-cells = <0>;
status = "okay";
gyroscope@0 {
compatible = "nxp,fxas21002c";
reg = <0x0>;
spi-max-frequency = <2000000>;
interrupt-parent = <&gpio3>;
interrupts = <4 IRQ_TYPE_EDGE_RISING>;
interrupt-names = "INT2";
status = "okay";
};
};For detailed information about OS image customization, refer to the Device Tree Overlays Technical Overview article and the TorizonCore Builder documentation.
Check if the overlay was successfully applied: Refer to Device Tree Overlays on Torizon**
Confirm if the driver is active: run the
lsmod
command in your module terminal.For example:
# lsmod | grep fxas21002c
fxas21002c_spi 16384 0
fxas21002c_core 24576 1 fxas21002c_spiFind the device driver's files: Torizon OS, as a Linux-based distribution, treats peripheral devices as files. SPI device files reside in the
/sys/bus/spi/devices/
folder. For detailed information, see Overview of Linux kernel SPI support.For an FXAS21002C connected to the
SPI_1
bus (accessible via thespi1.0
file), the sensor data is located in the/sys/bus/spi/devices/spi1.0/iio:device1/
folder, as presented in the following examples.Display the device driver:
# readlink /sys/bus/spi/devices/spi1.0/driver
../../../../../../../../bus/spi/drivers/fxas21002c_spiRead the device name:
# cat /sys/bus/spi/devices/spi1.0/iio\:device1/name
fxas21002cLocate the sensor measurements:
# ls /sys/bus/spi/devices/spi1.0/iio\:device1/
buffer in_anglvel_filter_low_pass_3db_frequency_available in_anglvel_z_raw scan_elements
buffer0 in_anglvel_sampling_frequency in_temp_raw subsystem
dev in_anglvel_scale name trigger
in_anglvel_filter_high_pass_3db_frequency in_anglvel_scale_available of_node uevent
in_anglvel_filter_high_pass_3db_frequency_available in_anglvel_x_raw power
in_anglvel_filter_low_pass_3db_frequency in_anglvel_y_raw sampling_frequency_availableinfoThe sensor measurements appear in the
iio:deviceX
folder because the FXAS21002C falls into the category of IIO devices.Integrate the driver into your application: without worrying about hardware details, you can use standard read/write functions of any programming language to communicate with the peripheral.
Container Access
The files in the /sys/bus/spi/devices
folder are symbolic links to the actual device in /sys/devices/platform/<soc>/<bus-address>.bus/<spi-addres>.spi/spi_master/spiX/spiX.0
. Mounting symbolic links results in a read-only filesystem issue. Therefore, use absolute paths of symbolic links, such as /sys/bus/spi/devices/spi1.0/iio:device1/
.
You can use the pwd -P
command to see the absolute path of the device.
To access the files created by the driver within the container, you have to mount the directory into the container using the -v
flag as follows:
# docker run --rm -it -v <device-absolute-path>:<container-path-destination> <container-image>
The -v
flag uses colon characters (:
) to separate parameters. Therefore, if the folder's path contains :
, add bind mounts or volumes using the --mount flag instead of using -v
as follows:
docker run --rm -it --mount source='<device-absolute-path>',target='<container-path-destination>',type=bind <container-image>
For comprehensive information, see Manage data in Docker.
Use Case Example
This section contains a short example of collecting FXAS21002C measurements from within a container after enabling its driver.
Run a Debian Container for Torizon: Do not forget to mount the device data folder into the container.
# docker run --rm -it --mount source=/sys/bus/spi/devices/spi1.0/iio:device1/,target=/fxas21002c,type=bind torizon/debian:${CT_TAG_DEBIAN}
Once inside the container, locate the device driver files.
## ls /fxas21002c
buffer in_anglvel_filter_low_pass_3db_frequency_available in_anglvel_z_raw scan_elements
buffer0 in_anglvel_sampling_frequency in_temp_raw subsystem
dev in_anglvel_scale name trigger
in_anglvel_filter_high_pass_3db_frequency in_anglvel_scale_available of_node uevent
in_anglvel_filter_high_pass_3db_frequency_available in_anglvel_x_raw power
in_anglvel_filter_low_pass_3db_frequency in_anglvel_y_raw sampling_frequency_availableSet up the sensor and collect data: The example below uses Linux terminal commands to interact with the device.
Read the raw value of the gyroscope on the X-axis.
## cat /fxas21002c/in_anglvel_x_raw
1598Get the available scale values. Then, change the current scale value.
## cat /fxas21002c/in_anglvel_scale
62.5
## cat /fxas21002c/in_anglvel_scale
62.5 31.25 15.625 7.8125
## echo 31.25 > /fxas21002c/in_anglvel_scale
## cat /fxas21002c/in_anglvel_scale
31.25Read the new
in_anglvel_x_raw
value.## cat /fxas21002c/in_anglvel_x_raw
3168To convert the above value into a meaningful one, refer to your peripheral datasheet. For the FXAS21002C, the gyroscope value on the X-axis expressed in º/s is obtained by multiplying the
in_anglvel_x_raw
byin_anglvel_scale
and then dividing the result by 1000:in_accel_x = (in_anglvel_x_raw * in_anglvel_scale)/1000
in_accel_x = (3168 * 31.25)/1000 = 99 °/s
Peripheral Access from User Space
Without a driver exporting device data and operations into files, you can access the device through the Linux SPI interface, which is located in the /dev
directory. When using this approach, the signals exchanged between your module and the peripheral are merely a sequence of bytes. Therefore, you will have to convert them into meaningful information, which requires understanding peripheral properties, such as read and write operations, register addresses, and pin functionalities.
Container Access
Containers run in an isolated environment and are not allowed to access any devices by default. To enable access to specific SPI buses within containers, Expose Hardware to Containers: identify the device files through which they are accessible, as described in the Locate SPI buses in the OS section, and then run the container using the --device
flag.
The command below allows the container to access the SPI_1
bus of the Verdin iMX8M Plus, which is accessible through the spi1.0
device file.
# docker run -it --rm --device /dev/verdin-spi-cs0 <container_namespace>
You can also mount the /dev
directory with the -v
flag. Then, give the container access to all I²C buses with the --device_cgroup_rules
flag:
# docker run -it --rm -v /dev:/dev --device-cgroup-rule='c 89:* rmw' <container_namespace>
For additional information regarding those flags, refer to the docker run and Docker run reference documentation.
Use Case Example
This section provides a step-by-step guide about a sample in C that configures and uses the SPI communication protocol through the SPI userspace API. Note that while the steps in this approach vary by languages/frameworks and use cases, they provide a general idea of working with peripherals in user space.
This sample uses the source code for testing the spidev present in the Linux Kernel. After running it, the final result that you are going to see at the debug terminal is expected to be the following:
...
spi mode: 0x0
bits per word: 8
max speed: 500000 Hz (500 kHz)
...
To use this sample, perform the following steps:
Download the source code from GitHub: This application is part of the Toradex-provided Samples.
Modify the source code to match the SPI interface you are using: The second commit shows the modifications made to a Torizon IDE Extension template to use the SPI device.
For example, if using an Apalis iMX8 and using the SPI through the
SPI1_MOSI
,SPI1_MISO
,SPI1_CS
andSPI1_CLK
signals, you would modify the code as follows:- static const char *device = "/dev/verdin-spi-cs0";
+ static const char *device = "/dev/apalis-spi-cs0";
- Build and run the container. On Torizon OS, there are two possible approaches:
- Use the Torizon IDE extension for Visual Studio Code, which provides automated tasks and facilitates embedded application development. Since this sample is based on a Torizon IDE Extension template, the extension is the recommended approach for running it.
- Use Dockerfiles, Docker Compose files and Docker CLI commands to Deploy Container Images to Torizon OS.