Applications - I²C

Last modified by Microchip on 2023/11/10 11:03

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.

Back to Top

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.

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 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 pinSchematic NameFLEXCOM I/OPackage Pin
J25 pin 5TWCK_mBUS1FLEXCOM1_IO1PA23
J25 pin 6TWD_mBUS1FLEXCOM1_IO0PA24

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

Schematic of mikrobus sockets on the ATSAMA5D27-SOM1-EK1

Back to Top

THERMO 5 click

For this demonstration, we will use the Mikroe THERMO 5 Click Board. The THERMO 5 click measures temperature in the 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.

mikro Thermo5 click

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

Photo showing mikro Thermo5 click installed on ATSAMA5D27-SOM1-EK1

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

Back to Top

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

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

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 alias 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 the 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.

Back to Top

Kernel

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

From the buildroot directory, run the Linux kernel menuconfig:

$ make linux-menuconfig

 The top-level menu will be displayed:

Linux menuconfig top menu

Back to Top

Device Driver

Select Device Drivers ---->

Linux menuconfig device drivers


Select I²C Support ---->

Linux menuconfig I2C support


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

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


Select I²C Hardware Bus support ---->

Linux menuconfig I2C bus support


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.

Back to Top

Rootfs

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

Kernel Space:
Register your own I²C driver via the i2c_add_driver() interface, then access the I²C bus driver via the 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 code is running in user space so it’s easier for development.

Back to Top

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;
}

Back to Top

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

Back to Top

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:

From the Buildroot directory, start menuconfig:

$ make menuconfig


Select Target packages ---->

Select Hardware handling ---->

Observe [*] i2c-tools is selected.

Buildroot Menuconfig: i2c-tools

Back to Top

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

Back to Top

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

Back to Top