Skip to main content
Version: Torizon OS 7.x.y

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:

This article complies with the Typographic Conventions for Torizon Documentation.

Prerequisites

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.

danger

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 Functioni.MX 8MP Ball Namei.MX 8MP FunctionI2C PortDescription
14I2C_1_SCLI2C4_SCLI2C4_SCLI2C4Generic I²C
12I2C_1_SDAI2C4_SDAI2C4_SDAI2C4Generic 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.

  1. Run a Debian Container for Torizon:

    # docker run -it --rm -v /dev:/dev --device-cgroup-rule='c 89:* rmw' torizon/debian:${CT_TAG_DEBIAN}
  2. 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>, where number is associated with the I2C_<number>_SCL and I2C_<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-2

    The output above indicates that the verdin-i2c1 link, related to the I2C_1_SDA and I2C_1_SCL signals, is accessible via the i2c-3 device file.

  3. 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.

  1. 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.

    tip

    The device name often gives insights into how its driver files are labeled.

  2. 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.o

    If the driver is available on your OS, you will find the CONFIG_INV_MPU6050_IIO and CONFIG_INV_MPU6050_I2C config variables in the kernel configuration.

  3. 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=m

    The 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.

  4. 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 and I2C_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 the i2c4 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.

  5. Check if the overlay was successfully applied

  6. 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_i2c
    tip

    By 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 of 68.That indicates that kernel drivers are managing this device, confirming the activation of your driver.

  7. 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
    mpu6050

    Display the device driver:

    # readlink /sys/bus/i2c/devices/3-0068/driver
    ../../../../../../../bus/i2c/drivers/inv-mpu6050-i2c

    Locate 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_scale
    info

    The sensor measurements appear in the iio:deviceX folder because the MPU6050 falls into the category of IIO devices.

  8. 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.

tip

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>
tip

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.

  1. 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}
  2. 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_scale

  3. Read 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
    -7844

    Get 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.001196

    Read the new in_accel_x_raw value.

    ## cat /mpu6050/in_accel_x_raw
    -4224

    To get the accelerometer value on the X-axis expressed in m/s², multiply the in_accel_x_raw by in_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:

  1. Download the source code from GitHub: This application is part of the Toradex-provided Samples.

  2. 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 and I2C_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.

    info

    This sample uses the smbus2 Python module for providing I²C interfacing capabilities.

  1. Build and run the container. On Torizon OS, there are two possible approaches:


Send Feedback!