Applications - I²C

Introduction

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

Usually, I²C devices are controlled by a kernel driver. But it is also possible to access all devices on an adapter from user space through the I²C dev interface. I²C dev is a character device node file. It can be accessed by read(), write(), and ioctl(). The interface of each I²C bus can be exported to user space through its own I²C dev device node.


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 I²C bus of the mikroBUS 1 expansion socket of the ATSAMA5D27-SOM1-EK1. The figure below shows the expansion capability of the SOM1-EK1.

ATSAMA5D27_SOM1_EK1_expansion_01.png

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

You will control pin PD23 and PA24 from the ATSAMA5D27 SOM1 which connects to J25 pins 5 and 6 of the mikroBUS 1 connector (labeled TWCK_mBUS1 and TWD_mBUS1 on the schematic).

mikroBUS pin Schematic Name FLEXCOM I/O Package Pin
J25 pin 5 TWCK_mBUS1 FLEXCOM1_IO1 PA23
J25 pin 6 TWD_mBUS1 FLEXCOM1_IO0 PA24

For more details of the Package and Pinout of the SAMA5D2, refer to Table 6-2. Pinouts in SAMA5D2 Series Data Sheet.

mikrobus1_R1.png

THERMO 5 click

For this demonstration, we will use the Mikroe THERMO 5 click board. The THERMO 5 click measures temperature in default range of 0°C to 127°C and in an extended range of -64°C to 191°C with ±1°C accuracy. The THERMO 5 click features the Microchip EMC1414 temperature sensor.

thermo-5-click.png

Insert the THERMO 5 click into the mikroBUS 1 slot on the ATSAMA5D27-SOM1-EK1 as shown in the figure below:

thermo-5-click-installed.png

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 in the article Create Project with Default Configuration. You will use the default configuration file: atmel_sama5d27_som1_ek_mmc_dev_defconfig.


Device Tree

Objective: Observe how the FLEXCOM1 peripheral was configured for I²C in the device tree. No changes are required.

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

1

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

679   flx1_clk: flx1_clk {
680      #clock-cells = <0>;
681      reg = <20>;
682      atmel,clk-output-range = <0 83000000>;
683   };
.
.
1255   flx1: flexcom@f8038000 {
1256     compatible = "atmel,sama5d2-flexcom";
1257     reg = <0xf8038000 0x200>;
1258     clocks = <&flx1_clk>;
1259     #address-cells = <1>;
1260     #size-cells = <1>;
1261     ranges = <0x0 0xf8038000 0x800>;
1262     status = "disabled";
1263   };

Line 681 the PID for FLEXCOM1 is 20, this definition of the offset will be used to enable FLEXCOM1 clock in PMC.

Line 682 the FLEXCOM1 input clock, max frequency is 83 MHz.

Line 1256 specifies which driver will be used for the FLEXCOM peripheral.

Line 1257 the FLEXCOM1 base address is 0xf8038000, size is 0x200.

Line 1258 is the definition for the FLEXCOM1 clock source.

Line 1262 shows the default is “disabled” and will be changed to “okay” in line 195 of the at91-sama5d27_som1_ek.dts file below.

2

Examine the at91-sama5d27_som1_ek.dts file and observe the I²C device assignments:

56   aliases {
57      serial0 = &uart1;    /* DBGU */
58      serial1 = &uart4;    /* mikro BUS 1 */
59      serial2 = &uart2;    /* mikro BUS 2 */
60      i2c1    = &i2c1;
61      i2c2    = &i2c2;
62   };
.
.
179  flx1: flexcom@f8038000 {
180     atmel,flexcom-mode = <ATMEL_FLEXCOM_MODE_TWI>;
181     status = "okay";
182
183     i2c2: i2c@600 {
184        compatible = "atmel,sama5d2-i2c";
185        reg = <0x600 0x200>;
186        interrupts = <20 IRQ_TYPE_LEVEL_HIGH 7>;
187        dmas = <0>, <0>;
188        dma-names = "tx", "rx";
189        #address-cells = <1>;
190        #size-cells = <0>;
191        clocks = <&flx1_clk>;
192        pinctrl-names = "default";
193        pinctrl-0 = <&pinctrl_mikrobus_i2c>;
194        atmel,fifo-size = <16>;
195        status = "okay";
196     };
197  };
.
.
522  pinctrl_mikrobus_i2c: mikrobus1_i2c {
523     pinmux = <PIN_PA24__FLEXCOM1_IO0>,
524        <PIN_PA23__FLEXCOM1_IO1>;
525     bias-disable;
526  };

Line 61, the aliases of FLEXCOM1 I²C is i2c2. FLEXCOM1 I²C will be registered as I²C adapter 2.

Line 180 specifies the I²C mode for the FLEXCOM1 peripheral.

Line 181 enables the device.

Line 184 specifies which driver will be used for this I²C device.

Line 185 is the register offset address for I²C in FLEXCOM1 is 0x600, size is 0x200.

Line 186 the PID for FLEXCOM4 is 23, high level triggered, priority is 7. It is used to configure FLEXCOM4 interrupt in AIC.

Line 187 shows that the DMA feature is not enabled.

Line 191 is the definition for the FLEXCOM1 clock source

Line 193 is the pin definition for I²C.

Line 194 specifies the size of the FIFO as 16.

Line 195 enables the device.

Line 523 is the mux of PA24 to be switched to FLEXCOM1_IO0.

Line 524 is the mux of PA23 to be switched to FLEXCOM1_IO1

Line 525 the pull up/down feature is disabled.


Kernel

Objective: Observe how the I²C functionality was configured in the Linux kernel. No changes are required.

1

From the buildroot directory, run the Linux kernel menuconfig:

$ make linux-menuconfig

The top-level menu will be displayed:

linux-config-top-level.png

Device Driver

2

Select Device Drivers —->

linux-config-device-drivers.png

3

Select I²C Support —->

i2c-support.png

4

Observe <*> I²C device interface is selected.

This will select the driver for the I²C_CHARDEV device.

5

Select I²C Hardware Bus support —->

i2c-hardware-bus-support.png

6

Observe <*> Atmel AT91 I²C Two-Wire interface (TWI) is selected.

This supports the use of the I²C interface on Microchip (formerly Atmel) AT91 processors.


Rootfs

There are two methods to access I²C bus driver:

Kernel Space:
Register your own I²C driver via i2c_add_driver() interface, then access I²C bus driver via struct i2c_client handle.

User Space:
As discussed in the "Device Tree" section above, the FLEXCOM1 peripheral is registered as I²C adapter 2. Enabling the I²C_CHARDEV kernel feature (default) you can access the I²C bus driver via /dev/i2c-2 device node.

I²C_CHARDEV is a good choice because all codes are running in user space (it’s easier for developing).


Application

The following is a C-Language demonstration program (i2c_dev.c) for reading temperature from the THERMO 5 click board via the /dev/i2c-2 node.

To compile:

$ buildroot/output/host/bin/arm-buildroot-linux-uclibcgnueabihf-gcc i2c_dev.c -o i2c_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/i2c.h>
#include <linux/i2c-dev.h>

#define DEV_I²C "/dev/i2c-2"

#define SLAVE_ADDR 0x4C /* EMC1414 I2C slave address */
//#define COMBINED_TRANSCTION

int main(int argc, char *argv[])
{
    int fd;
    int ret;
    unsigned char buf[2];

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

    if (ioctl(fd, I2C_SLAVE, SLAVE_ADDR) < 0) {
        printf("ERROR ioctl() set slave address\n");
        return -1;
    }

#ifdef COMBINED_TRANSCTION
    struct i2c_rdwr_ioctl_data data;
    struct i2c_msg messages[2];

    // Set conversion rate
    buf[0] = 0x04; // Conversion rate register address
    buf[1] = 0x04; // Set conversion rate to 1 second
    messages[0].addr  = SLAVE_ADDR; //device address
    messages[0].flags = 0; //write
    messages[0].len   = 2;
    messages[0].buf   = buf; //data address

    data.msgs  = &messages[0];
    data.nmsgs = 1;
    if (ioctl(fd, I2C_RDWR, &data) < 0) {
        printf("ERROR ioctl() conversion rate\n");
        return -1;
    }

    // Read temperature
    buf[0] = 0x00; // Internal Diode High Byte register address
    buf[1] = 0;    // clear receive buffer
    messages[0].addr  = SLAVE_ADDR; //device address
    messages[0].flags = 0; //write
    messages[0].len   = 1;
    messages[0].buf   = &buf[0]; //data address

    messages[1].addr  = SLAVE_ADDR; //device address
    messages[1].flags = I2C_M_RD; //read
    messages[1].len   = 1;
    messages[1].buf   = &buf[1];

    data.msgs  = messages;
    data.nmsgs = 2;
    while (1) {
        if (ioctl(fd, I2C_RDWR, &data) < 0) {
            printf("ERROR ioctl() read data\n");
            return -1;
        }

        printf("Temperature is %d\n", buf[1]);
        sleep(1);
    }

#else
    // Set conversion rate
    buf[0] = 0x04; // Conversion rate register address
    buf[1] = 0x04; // Set conversion rate to 1 second
    ret = write(fd, buf, 2);
    if (ret != 2) {
        printf("ERROR write() conversion rate\n");
        return -1;
    }

    // Read temperature
    // Set internal address register pointer
    buf[0] = 0x00; // Internal Diode High Byte register address
    ret = write(fd, &buf[0], 1);
    if (ret != 1) {
        printf("ERROR write() register address\n");
        return -1;
    }

    while (1) {
        // Read temperature
        // Read data
        buf[1] = 0; // clear receive buffer
        ret = read(fd, &buf[1], 1);
        if (ret != 1) {
            printf("ERROR read() data\n");
            return -1;
        }

        printf("Temperature is %d\n", buf[1]);
        sleep(1);
    }
#endif

    // close device node
    close(fd);

    return 0;
}

Hands-On

Copy the i2c_test program to the target and execute. The temperature data will be printed out.

# chmod +x i2c_test
# ./i2c_test
Temperature is 26
Temperature is 26
Temperature is 26
Temperature is 26
Temperature is 26
Temperature is 26

Tools and Utilities

I2C Tools is a tool for I2C bus testing. It is included in the default configuration of Buildroot. You can view the selection by performing the following:

1

From the Buildroot directory, start menuconfig:

$ make menuconfig

2

Select Target packages ——>

3

Select Hardware handling ——>

4

Observe [*] i2c-tools is selected.

hardware-handling.png

i2c-tools Commands

There are several commands in i2c-tools:

# i2cdetect
Error: No i2c-bus specified!
Usage: i2cdetect [-y] [-a] [-q|-r] I2CBUS [FIRST LAST]
       i2cdetect -F I2CBUS
       i2cdetect -l
  I2CBUS is an integer or an I2C bus name
  If provided, FIRST and LAST limit the probing range.
# i2cdetect -y 3
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- -- 
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
50: UU 51 52 53 54 55 56 57 -- -- -- -- -- -- -- -- 
60: 60 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
70: -- -- -- -- -- -- -- --
# i2cdump
Error: No i2c-bus specified!
Usage: i2cdump [-f] [-y] [-r first-last] I2CBUS ADDRESS [MODE [BANK [BANKREG]]]
  I2CBUS is an integer or an I2C bus name
  ADDRESS is an integer (0x03 - 0x77)
  MODE is one of:
    b (byte, default)
    w (word)
    W (word on even register addresses)
    s (SMBus block)
    i (I2C block)
    c (consecutive byte)
    Append p for SMBus PEC
# i2cdump -f -y 3 0x50
No size specified (using byte-data access)
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f    0123456789abcdef
00: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff    ................
10: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff    ................
20: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff    ................
30: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff    ................
40: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff    ................
50: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff    ................
60: 20 4d 43 48 49 50 20 52 46 4f 12 02 42 42 31 ff     MCHIP RFO??BB1.
70: 00 00 44 32 37 2d 53 4f 4d 31 2d 45 4b 31 44 f7    ..D27-SOM1-EK1D?
80: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff    ................
90: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff    ................
# i2cget
Usage: i2cget [-f] [-y] I2CBUS CHIP-ADDRESS [DATA-ADDRESS [MODE]]
  I2CBUS is an integer or an I2C bus name
  ADDRESS is an integer (0x03 - 0x77)
  MODE is one of:
    b (read byte data, default)
    w (read word data)
    c (write byte/read byte)
Append p for SMBus PEC
# i2cget -f -y 3 0x50 0x60
0x20
# i2cget -f -y 3 0x50 0x61
0x4d

WARNING: for the i2cset command, DO NOT modify any data in the EEPROM of the ATSAMA5D27-SOM1-EK1.

# i2cset
Usage: i2cset [-f] [-y] [-m MASK] [-r] I2CBUS CHIP-ADDRESS DATA-ADDRESS [VALUE] ... [MODE]
  I2CBUS is an integer or an I2C bus name
  ADDRESS is an integer (0x03 - 0x77)
  MODE is one of:
    c (byte, no value)
    b (byte data, default)
    w (word data)
    i (I2C block data)
    s (SMBus block data)
Append p for SMBus PEC
# i2ctransfer
Usage: i2ctransfer [-f] [-y] [-v] [-V] I2CBUS DESC [DATA] [DESC [DATA]]...
  I2CBUS is an integer or an I2C bus name
  DESC describes the transfer in the form: {r|w}LENGTH[@address]
    1) read/write-flag 2) LENGTH (range 0-65535) 3) I2C address (use last one if omitted)
  DATA are LENGTH bytes for a write message. They can be shortened by a suffix:
    = (keep value constant until LENGTH)
    + (increase value by 1 until LENGTH)
    - (decrease value by 1 until LENGTH)
    p (use pseudo random generator until LENGTH with value as seed)

Example (bus 0, read 8 byte at offset 0x64 from EEPROM at 0x50):
# i2ctransfer 0 w1@0x50 0x64 r8
Example (same EEPROM, at offset 0x42 write 0xff 0xfe ... 0xf0):
# i2ctransfer 0 w17@0x50 0x42 0xff-

Summary

You used Buildroot to build an image with I²C bus support for the ATSAMA5D2 Series MPU. You accessed the I²C bus via two different methods: kernel space using i2c_add_driver() interface and user space by the /dev/i2c-X device. You walked through the device tree and kernel to observe how the embedded Linux system configures the source code for building.

© 2024 Microchip Technology, Inc.
Notice: ARM and Cortex are the registered trademarks of ARM Limited in the EU and other countries.
Information contained on this site regarding device applications and the like is provided only for your convenience and may be superseded by updates. It is your responsibility to ensure that your application meets with your specifications. MICROCHIP MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND WHETHER EXPRESS OR IMPLIED, WRITTEN OR ORAL, STATUTORY OR OTHERWISE, RELATED TO THE INFORMATION, INCLUDING BUT NOT LIMITED TO ITS CONDITION, QUALITY, PERFORMANCE, MERCHANTABILITY OR FITNESS FOR PURPOSE. Microchip disclaims all liability arising from this information and its use. Use of Microchip devices in life support and/or safety applications is entirely at the buyer's risk, and the buyer agrees to defend, indemnify and hold harmless Microchip from any and all damages, claims, suits, or expenses resulting from such use. No licenses are conveyed, implicitly or otherwise, under any Microchip intellectual property rights.