Using I2C Peripheral on a PIC64GX Curiosity Board from Linux® Userspace

Last modified by Microchip on 2025/05/19 12:20

Introduction

The Inter-Integrated Circuit (I2C) protocol is a widely used solution that facilitates communication between multiple devices over a short distance.

This article focuses on the I2C capabilities of the PIC64GX MPU series and will guide you through the basics of using the I2C peripheral on a PIC64GX Curiosity Board from the Linux® Userspace using the i2c-tools utility and writing C application code.

Prerequisites

This application is developed for the CURIOSITY-PIC64GX1000-KIT-ES development platform. See the following platform-related documentation:

Get familiar with I2C:

Hardware Setup

For this application, we will need:

  • CURIOSITY-PIC64GX1000-KIT-ES
  • Linux host PC
  • PAC1934 Click board™ (MIKROE-2735)
  • Digital Logic Analyzer (not required, but useful for observing and analyzing signals)

Yocto Project® Configurations

Objectives:

  • Setting up the Yocto Project Building Environment
  • Configuring the device tree to enable the I2C Peripheral
  • Including the i2c-tools utility software package
  • Build the image and deploy
Information

This application article is verified on PIC64GX Yocto Project BSP release 2024.10.

Back to Top

Setting Up the Yocto Project Building Environment

Follow the GitHub PIC64GX Yocto Project BSP Repo Readme steps to set up the BitBake environment and build a core-image-minimal-dev Linux image with the default configuration.

Warning

Ensure that you've set your GIT username and user email as global variables on your Linux host PC:

git config --global user.name "your name"
git config --global user.email "your email"

Back to Top


Build a core-image-minimal-dev Linux image with default configuration:

MACHINE=pic64gx-curiosity-kit bitbake core-image-minimal-dev

Please ensure that the initial Linux image has been built without error, so we can start making the modifications.

Back to Top


Device Tree

To modify the device tree, we will use devtool to extract, edit, and rebuild the necessary files before integrating the changes into the build system.

Set up the BitBake environment:

. ./meta-pic64gx-yocto-bsp/pic64gx_yocto_setup.sh

Back to Top


Extract the source code of the pic64gx-linux recipe into a workspace for modification:

devtool modify pic64gx-linux

Back to Top


Locate the PIC64GX Curiosity Kit Device Tree Source (DTS) for modification:

yocto-dev/build/workspace/sources/pic64gx-linux/arch/riscv/boot/dts/microchip/pic64gx-curiosity-kit.dts

Back to Top


Enable the I2C0 peripheral in the DTS:

1
2
3
&i2c0 {
 status = "okay";
};

Back to Top


Adding i2c-tools Software Package

To run I2C test commands from the Linux Userspace, we need to include the i2c-tools utility software package in our Linux build. This utility allows us to interact with I2C peripherals from the Userspace.

Locate the local.conf file, which defines various configuration variables that govern the OpenEmbedded build process:

yocto-dev/build/conf/local.conf

Back to Top


Define the additional software packages to be installed by setting the CORE_IMAGE_EXTRA_INSTALL variable at the end of the config file. This variable specifies the packages to be added on top of the default software package list defined for your base image. In our case, the base image is core-image-minimal-dev:

CORE_IMAGE_EXTRA_INSTALL += "i2c-tools"
Information

To check the list of software packages included in your base image by default, refer to the following BitBake file:
yocto-dev/meta-pic64gx-yocto-bsp/meta-pic64gx-bsp/recipes-core/images/core-image-minimal-dev.bbappend

Back to Top


Linux Kernel Configuration

The I2C and I2C device driver must be enabled in your Linux kernel configuration.

Open the terminal and navigate to the build directory​​​. Run the following command to run the Linux Kernel configuration:

MACHINE=pic64gx-curiosity-kit bitbake pic64gx-linux -c menuconfig

I2C build directory

Back to Top


Navigate to Device Drivers and enable the I2C support:

I2C Support

Back to Top


Save the config, then select File > Exit.

Back to Top


Now that we've done all the necessary changes in the workspace, we can rebuild the linux-kernel recipe with devtool:

devtool build pic64gx-linux

Back to Top


Build and Deploy the Linux Image on PIC64GX Curiosity Board

After completing all the necessary configurations, we can build the image:

MACHINE=pic64gx-curiosity-kit bitbake core-image-minimal-dev

Back to Top


After the build is done, you can locate your built image at:

yocto-dev/build/tmp-glibc/deploy/images/pic64gx-curiosity-kit/core-image-minimal-dev-pic64gx-curiosity-kit***.wic

Back to Top


Follow the GitHub instructions to deploy the built Image to the SD card.

Back to Top


After booting Linux on the Curiosity Board, check if the I2C device is appeared under the devices:

root@pic64gx-curiosity-kit:~# ls /dev | grep i2c
i2c-0

If nothing returns, that means the I2C device is not enabled and you have to double-check the configuration and DTS modification needed to be done.

Back to Top


Hardware

For this application, we will be controlling I2C_0, which is connected to the mikroBUS™ socket of the Curiosity Board.

pic64gx board I2C_0 pins

We can check the I2C pins that are connected to the mikroBUS socket from the PIC64GX1000 Curiosity Kit schematics.

PIC64 PinmikroBUS Pin
I2C_0_SCL (B)/GPIO_1_21RC3/SCL
I2C_0_SDA (B)/GPIO_1_22RC4/SDA

Back to Top

Tools and Utilities

i2c-tools Utility

i2c-tools is a utility program used to test and verify the functionality of I2C devices on Linux systems. It is typically used to send and receive data over I2C to ensure that the I2C bus and connected devices are working correctly.

The i2c-tools program interacts with the I2C device driver through the /dev/i2c-X device files, where X represents the bus number. It allows users to perform basic read and write operations, configure I2C settings, and observe the data exchanged between the host (Master) and the I2C Target device (Slave).

Tools of i2c-tools

There are some simple tools provided by i2c-tools for accessing the I2C driver from the Userspace.

There are several commands in i2c-tools:

  • i2cdetect: List all I2C buses present on the system and scan for devices on a specified bus.
  • i2cdump: Display the contents of an I2C device's registers.
  • i2cget: Read values from specified registers of an I2C device.
  • i2cset: Write values to specified registers of an I2C device.
  • i2ctransfer: Perform complex I2C transactions, including multiple read and write operations in a single command.

Back to Top

Using i2c-tools on PIC64GX Curiosity Board

Information

All the following commands are running from the Curiosity Board connected with the PAC1934 Click board on the mikroBUS socket.

i2c-tools on PIC64GX Curiosity Board

i2cdetect: Scan for I2C devices connected to the bus. In our case, the PAC1934 is connected on Bus 0 with slave address of 0x10:

#  i2cdetect -y -r 0
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:                         -- -- -- -- -- -- -- --
10: 10 -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

-y: This option is to run without asking for confirmation.

-r: This option performs a more detailed read-based scan.

i2cdump: Display the contents of a PAC1934's registers:

# i2cdump -y 0 0x10
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 00 00 00 00 00 00 02 01 01 01 00 00 00 00 02    .......????....?
10: 01 01 01 00 00 00 00 00 00 00 00 XX 00 00 ff ff    ???........X....
20: 95 00 00 00 00 00 00 XX XX XX XX XX XX XX XX XX    ?......XXXXXXXXX
30: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX    XXXXXXXXXXXXXXXX
40: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX    XXXXXXXXXXXXXXXX
50: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX    XXXXXXXXXXXXXXXX
60: 00 XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX    .XXXXXXXXXXXXXXX
70: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX    XXXXXXXXXXXXXXXX
80: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX    XXXXXXXXXXXXXXXX
90: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX    XXXXXXXXXXXXXXXX
a0: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX    XXXXXXXXXXXXXXXX
b0: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX    XXXXXXXXXXXXXXXX
c0: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX    XXXXXXXXXXXXXXXX
d0: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX    XXXXXXXXXXXXXXXX
e0: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX    XXXXXXXXXXXXXXXX
f0: XX XX XX XX XX XX XX XX XX XX XX XX XX 5b 5d 03    XXXXXXXXXXXXX[]?
Success

We can see the Manufacturer ID with Register Address 0xFE = 5d & Revision ID with Register Address 0xFF = 03.

i2cget: Read values from specified registers of PAC1934 device. To read Product ID from register address 0xFD:

# i2cget -y 0 0x10 0xFD
0x5b

pic64gx i2c pattern

i2cset: Write values to specified registers of PAC1934 device. To write value 0x08 to CTRL Regiser (0x01):

i2cset <Target_Address> <Register_address_to_be_written> <the_value_to_be_write>

# i2cset -y 0 0x10 0x01 0x08

i2ctransfer: Perform complex I2C transactions like performing a combined write and read operation. To read 2 bytes from address 0xFE:

# i2ctransfer -f -y 0 w1@0x10 0xfe r2@0x10
0x5d 0x03
  • 0: The I2C bus number.
  • w1@0x10 0xfe: Write 1 byte (0xfe) to the device at address 0x10 to set the register pointer.
  • r2@0x10: Read 2 bytes from the device at address 0x10.

Back to Top

Application Programming in C Language Using i2c-dev with ioctl

We can write a C program that runs from Userspace and interacts with I2C devices using the ioctl functions. The following example is a C code that reads VBUS register values and prints the results.

Boot Linux on your Curiosity board. Navigate to /media and create i2c_pac1934.c using the vim editor

cd /media && vim i2c_pac1934.c

Back to Top


Copy and paste the following C code to the i2c_pac1934.c:

#include <stdio.h>
#include
<stdlib.h>
#include
<stdint.h>
#include
<unistd.h>
#include
<fcntl.h>
#include
<sys/ioctl.h>
#include
<linux/i2c.h>
#include
<linux/i2c-dev.h>
#include
<errno.h>

#define I2C_BUS "/dev/i2c-0"
#define PAC1934_ADDR 0x10

// Register addresses for voltage channels
#define V_BUS1_REG 0x07
#define V_BUS2_REG 0x08
#define V_BUS3_REG 0x09
#define V_BUS4_REG 0x0A
#define REFRESH_REG 0x1F  

// Function to write a single byte to a register
int write_register(int file, uint8_t reg) {
   if (write(file, &reg, 1) != 1) {
        perror("Failed to write register");
       return -1;
    }
   return 0;
}

// Function to read a 2-byte register
int read_register(int file, uint8_t reg, uint16_t *value) {
   uint8_t buf[2] = {0};

   // Write the register address
   if (write(file, &reg, 1) != 1) {
        perror("Failed to write register address");
       return -1;
    }

   // Read 2 bytes from the register
   if (read(file, buf, 2) != 2) {
        perror("Failed to read register value");
       return -1;
    }

   // Combine the two bytes into a 16-bit value
   *value = (buf[0] << 8) | buf[1];
   return 0;
}

int main() {
   int file;
   uint16_t v_bus1, v_bus2, v_bus3, v_bus4;
   int retries = 5;

   // Open the I2C bus
   if ((file = open(I2C_BUS, O_RDWR)) < 0) {
        perror("Failed to open the I2C bus");
        exit(1);
    }

   // Retry setting the I2C address if the bus is busy
   while (retries--) {
       if (ioctl(file, I2C_SLAVE, PAC1934_ADDR) < 0) {
           if (errno == EBUSY) {
                printf("I2C bus is busy, retrying...\n");
                sleep(1);
               continue;
            } else {
                perror("Failed to acquire bus access and/or talk to slave");
                close(file);
                exit(1);
            }
        }
       break;
    }

   if (retries <= 0) {
        printf("Failed to acquire bus access after multiple attempts\n");
        close(file);
        exit(1);
    }

   while (1) {
       // Refresh the measurements
       if (write_register(file, REFRESH_REG) < 0) {
            close(file);
            exit(1);
        }

       // Read voltage channels
       if (read_register(file, V_BUS1_REG, &v_bus1) < 0 ||
            read_register(file, V_BUS2_REG, &v_bus2) < 0 ||
            read_register(file, V_BUS3_REG, &v_bus3) < 0 ||
            read_register(file, V_BUS4_REG, &v_bus4) < 0) {
            close(file);
            exit(1);
        }

       // Print the voltage values
       printf(" V_BUS1: %u", v_bus1);
        printf(" V_BUS2: %u", v_bus2);
        printf(" V_BUS3: %u", v_bus3);
        printf(" V_BUS4: %u \n", v_bus4);

       // Sleep for a second before the next read
       sleep(1);
    }

   // Close the I2C bus
   close(file);

   return 0;
}

Back to Top


After saving the modification, compile the C code:

gcc -o i2c_pac1934 i2c_pac1934.c -li2c

Back to Top


Run the i2c_pac1934 executable:

./i2c_pac1934
Success

Running the application, you will get the following response:

root@pic64gx-curiosity-kit:/media# ./i2c_pac1934
 V_BUS1: 558 V_BUS2: 485 V_BUS3: 465 V_BUS4: 458
 V_BUS1: 554 V_BUS2: 474 V_BUS3: 464 V_BUS4: 461
 V_BUS1: 561 V_BUS2: 482 V_BUS3: 464 V_BUS4: 453
 V_BUS1: 554 V_BUS2: 478 V_BUS3: 458 V_BUS4: 457
 V_BUS1: 556 V_BUS2: 476 V_BUS3: 466 V_BUS4: 462

The values do not represent the actual voltage as they require calibration and formula based on the configuration registers.

Back to Top

Summary

This guide provides a comprehensive guide for enabling and testing I2C communication on a PIC64GX Curiosity Board using Yocto Project. The process begins with configuring the device tree to enable the I2C peripheral, then ensuring the I2C driver is enabled in the Linux kernel. Userspace testing is then performed using the i2c-tools utility to interact with I2C devices from the Linux userspace. Additionally, a custom C program is written and executed to read voltage values from an I2C device. By following this guide, you establish a solid foundation for further development with I2C peripherals on the PIC64GX Curiosity Board.

Back to Top