How to use I²C on Torizon OS
Introduction
I²C is a serial communication protocol commonly used by countless sensors and other hardware peripherals. It requires a software layer to connect the OS system calls to the hardware. This article describes how to access I²C devices from both kernel space and user space on Torizon OS.
Device Drivers and User Space Applications
A device driver controls how a piece of hardware 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 I²C 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 following diagram illustrates the differences between the described approaches for interacting with an MPU6050 sensor:
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 I²C device
- I²C Testing
- 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 Overview.
Connect an I²C Device
Toradex SoMs offer multiple I²C buses for connecting peripherals, with some pins having I²C 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.
In addition, some standard I²C interfaces may be dedicated to specific uses, such as DSI. To ensure compatibility and prevent issues, use I²C buses designed for general purposes. If necessary, you can modify the behavior of these interfaces. For detailed information, see Device Tree Overlays Technical Overview.
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 I²C buses, including the I2C4, which is designed for general use and is the one we will use in examples throughout this article. That bus features the I2C_1_SDA
and I2C_1_SCL
. You can find this information in the I²C section of your module datasheet as follows:
X1 Pin# | Verdin Std Function | i.MX 8MP Ball Name | i.MX 8MP Function | I2C Port | Description |
---|---|---|---|---|---|
14 | I2C_1_SCL | I2C4_SCL | I2C4_SCL | I2C4 | Generic I²C |
12 | I2C_1_SDA | I2C4_SDA | I2C4_SDA | I2C4 | Generic I²C |
To connect your device, search for pins that carry these signals in your carrier board schematics or datasheet.
I²C Testing
On Torizon OS, you can install the I²C Tools for testing I²C devices within containers. Remember to Expose Hardware to Containers.
Run a Debian Container for Torizon:
# docker run -it --rm -v /dev:/dev --device-cgroup-rule='c 89:* rmw' torizon/debian:${CT_TAG_DEBIAN}
Identify the available I²C buses: Run the command
ls -l /dev
in the container's terminal.Note that Torizon OS creates symbolic links to facilitate access to I²C buses from your application, ensuring pin compatibility between different modules and carrier boards within the same family. These links are named
<module-family>-i2c<number>
, wherenumber
is associated with theI2C_<number>_SCL
andI2C_<number>_SDA
signals. Referencing these standardized names simplifies software migration between different modules.The command's output for the Verdin iMX8M Plus is as follows:
## ls -l /dev | grep verdin-i2c
lrwxrwxrwx 1 root root 5 Apr 28 2022 verdin-i2c-on-module -> i2c-0
lrwxrwxrwx 1 root root 5 Apr 28 2022 verdin-i2c1 -> i2c-3
lrwxrwxrwx 1 root root 5 Apr 28 2022 verdin-i2c2 -> i2c-1
lrwxrwxrwx 1 root root 5 Apr 28 2022 verdin-i2c4 -> i2c-2The output above indicates that the
verdin-i2c1
link, related to theI2C_1_SDA
andI2C_1_SCL
signals, is accessible via thei2c-3
device file.Verify whether the I²C address of your peripheral is already in use:
By referring to the MPU6050 datasheet, you can see that its I²C address is
0x68
. Then, use the i2cdetect command to confirm that neither the module nor other peripherals use this address.The following command scans the standard addresses on
i2c-3
.## i2cdetect -y 3
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- UU -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- 2c -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: UU -- -- -- -- -- -- -- UU UU -- -- -- -- -- UU
50: UU -- -- -- -- -- -- UU -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --After connecting the MPU6050, you can rerun the command and verify if the device appears in the correct position.
## i2cdetect -y 3
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- UU -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- 2c -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: UU -- -- -- -- -- -- -- UU UU -- -- -- -- -- UU
50: UU -- -- -- -- -- -- UU -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- 68 -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --
Peripheral Access from Kernel Space
How to Configure and Use I²C Devices
This section provides a step-by-step guide on how to collect the measurements from an MPU6050 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 MPU6050 driver is named
inv_mpu6050
and resides at linux/drivers/iio/imu.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
inv_mpu6050
Makefile has the following configuration:obj-$(CONFIG_INV_MPU6050_IIO) += inv-mpu6050.o
inv-mpu6050-y := inv_mpu_core.o inv_mpu_ring.o inv_mpu_trigger.o \
inv_mpu_aux.o inv_mpu_magn.o
obj-$(CONFIG_INV_MPU6050_I2C) += inv-mpu6050-i2c.o
inv-mpu6050-i2c-y := inv_mpu_i2c.o inv_mpu_acpi.oIf the driver is available on your OS, you will find the
CONFIG_INV_MPU6050_IIO
andCONFIG_INV_MPU6050_I2C
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 MPU6050 configuration is available on Torizon OS, run the
zcat
command:# zcat /proc/config.gz | grep MPU6050
CONFIG_INV_MPU6050_IIO=m
CONFIG_INV_MPU6050_I2C=m
CONFIG_INV_MPU6050_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.
As described in the Connect an I²C device section, the
I2C_1_SDA
andI2C_1_SCL
are part of the I2C4 bus of the Verdin iMX8M Plus module. To activate the device driver for this bus, you can create a Device Tree Overlay to configure thei2c4
node. The following overlay activates the MPU6050 driver:// Definitions for MPU6050
/dts-v1/;
/plugin/;
/ {
compatible = "toradex,verdin-imx8mp";
};
&i2c4 { // select interface as per board
#address-cells = <1>;
#size-cells = <0>;
clock-frequency = <400000>;
status = "okay";
mpu6050: inv-mpu6050@68 { // address of the sensor
compatible = "invensense,mpu6050";
reg = <0x68>; // address of the sensor
orientation = <0xff 0 0 0 1 0 0 0 0xff>;
status = "okay";
};
};For detailed information about OS image customization, refer to the Device Tree Overlays Technical Overview article.
Confirm if the driver is activated: run the
lsmod
command in your module terminal.For example:
# lsmod | grep mpu6050
inv_mpu6050_i2c 16384 0
inv_mpu6050 36864 1 inv_mpu6050_i2ctipBy now, if you try to identify your device as described in the I²C testing section, you will get the following output:
## i2cdetect -y 3
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- UU -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- 2c -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: UU -- -- -- -- -- -- -- UU UU -- -- -- -- -- UU
50: UU -- -- -- -- -- -- UU -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- UU -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --Note that the device appears as
UU
instead of68
.That indicates that kernel drivers are managing this device, confirming the activation of your driver.Find the device driver's data: Torizon OS, as a Linux-based distribution, treats peripheral devices as files. I2C device files reside in the
/sys/bus/i2c/devices/
folder. For detailed information, see Location of I2C Sysfs.For an MPU6050 connected to the I2C4 bus (accessible via the
i2c-3
file), the sensor data is located in the/sys/bus/i2c/devices/3-0068
folder, as presented in the following examples.Read the device name:
# cat /sys/bus/i2c/devices/3-0068/name
mpu6050Display the device driver:
# readlink /sys/bus/i2c/devices/3-0068/driver
../../../../../../../bus/i2c/drivers/inv-mpu6050-i2cLocate the sensor measurements:
# ls /sys/bus/i2c/devices/3-0068/driver/iio\:device1/
current_timestamp_clock in_accel_y_raw in_anglvel_y_calibbias name
in_accel_matrix in_accel_z_calibbias in_anglvel_y_raw of_node
in_accel_mount_matrix in_accel_z_raw in_anglvel_z_calibbias power
in_accel_scale in_anglvel_mount_matrix in_anglvel_z_raw sampling_frequency
in_accel_scale_available in_anglvel_scale in_gyro_matrix sampling_frequency_available
in_accel_x_calibbias in_anglvel_scale_available in_temp_offset subsystem
in_accel_x_raw in_anglvel_x_calibbias in_temp_raw uevent
in_accel_y_calibbias in_anglvel_x_raw in_temp_scaleinfoThe sensor measurements appear in the
iio:deviceX
folder because the MPU6050 falls into the category of IIO devices.Integrate the data into your application: To read the information gathered by your peripheral or control it, you can use standard read/write functions of any programming language without worrying about hardware details.
Container Access
The files in the /sys/bus/i2c/devices
folder are symbolic links to the actual device in /sys/devices/platform/<soc>/<bus-address>.bus/<i2c-addres>.2c/i2c-X
. Mounting symbolic links results in a read-only filesystem issue. Therefore, use absolute paths of symbolic links, such as /sys/bus/i2c/devices/i2c-3/iio:device1
or /sys/bus/i2c/devices/3-0068/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 MPU6050 measurements from within a container after enabling the inv_mpu6050
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/i2c/devices/3-0068/iio:device1,target=/mpu6050,type=bind torizon/debian:${CT_TAG_DEBIAN}
Once inside the container, locate the device driver files.
## ls /mpu6050
current_timestamp_clock in_accel_y_raw in_anglvel_y_calibbias name
in_accel_matrix in_accel_z_calibbias in_anglvel_y_raw of_node
in_accel_mount_matrix in_accel_z_raw in_anglvel_z_calibbias power
in_accel_scale in_anglvel_mount_matrix in_anglvel_z_raw sampling_frequency
in_accel_scale_available in_anglvel_scale in_gyro_matrix sampling_frequency_available
in_accel_x_calibbias in_anglvel_scale_available in_temp_offset subsystem
in_accel_x_raw in_anglvel_x_calibbias in_temp_raw uevent
in_accel_y_calibbias in_anglvel_x_raw in_temp_scaleRead or write sensor data: The example below uses Linux terminal commands to interact with the device.
Read the raw value of the accelerometer on the X-axis.
## cat /mpu6050/in_accel_x_raw
-7844Get the available scale values. Then, change the current scale value.
## cat /mpu6050/in_accel_scale
0.000598
## cat /mpu6050/in_accel_scale_available
0.000598 0.001196 0.002392 0.004785
## echo 0.001196 > /mpu6050/in_accel_scale
## cat /mpu6050/in_accel_scale
0.001196Read the new
in_accel_x_raw
value.## cat /mpu6050/in_accel_x_raw
-4224To get the accelerometer value on the X-axis expressed in m/s², multiply the
in_accel_x_raw
byin_accel_scale
:in_accel_x = in_accel_x_raw * in_accel_scale
in_accel_x = -4224 * 0.001196 = 5.05 m/s²
Peripheral Access from User Space
Without a driver exporting device data into files, access the device through the Linux I²C interface, which is located in the /dev
directory.
Container Access
Containers run in an isolated environment and are not allowed to access any devices by default. To access peripherals within containers, you will need to Expose Hardware to Containers. To give a container access to specific I²C buses, identify the device files through which they are accessible as described in the I²C testing section. Then, run the container using the --device
.
The command below allows the container to access the I2C4 bus of the Verdin iMX8M Plus, which is accessible through the i2c-3
device file.
# docker run -it --rm --device /dev/i2c-3 <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 sample-based guide on collecting measurements from an MPU6050. The sample is written in Python and uses the SMBus API to communicate with the device. The main idea is to access and manipulate the I²C interface through the application code in user space. 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.
The application reads data from an MPU6050, converting it into meaningful accelerometer and gyroscope values. Even though SMBus is used for I²C interfacing, it still requires developing code for reading, writing, and processing device measurements. The application's expected output is as follows:
Accel X = -0.27g Accel Y = -0.04g Accel Z = -0.93g | Gyro X = -0.67rad/s Gyro Y =0.06rad/s Gyro Z = -0.55rad/s
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 I²C bus you are using: The second commit shows the modifications made to a Torizon IDE Extension template to use the I²C device.
For example, if using an Apalix iMX8 and connecting it to the MPU6050 through the
I2C_1_SDA
andI2C_1_SCL
signals, you would modify the code as follows:- bus = smbus2.SMBus('/dev/verdin-i2c1')
+ bus = smbus2.SMBus('/dev/apalis-i2c1')Note that you can directly use the symbolic link referring to the bus your device is connected to, as described in the I²C Testing section.
infoThis sample uses the smbus2 Python module for providing I²C interfacing capabilities.
- 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.