Using I2C Peripheral on a PIC64GX Curiosity Board from Linux® Userspace
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:
- I2C Bus Introduction
- Practical I2C: Introduction, Implementation and Troubleshooting
- Debugging Techniques for Serial Communications (I2C/SPI/UART)
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
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.
Build a core-image-minimal-dev Linux image with default configuration:
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:
Extract the source code of the pic64gx-linux recipe into a workspace for modification:
Locate the PIC64GX Curiosity Kit Device Tree Source (DTS) for modification:
Enable the I2C0 peripheral in the DTS:
2
3
status = "okay";
};
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:
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:
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:
Navigate to Device Drivers and enable the I2C support:
Save the config, then select File > Exit.
Now that we've done all the necessary changes in the workspace, we can rebuild the linux-kernel recipe with devtool:
Build and Deploy the Linux Image on PIC64GX Curiosity Board
After completing all the necessary configurations, we can build the image:
After the build is done, you can locate your built image at:
Follow the GitHub instructions to deploy the built Image to the SD card.
After booting Linux on the Curiosity Board, check if the I2C device is appeared under the devices:
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.
Hardware
For this application, we will be controlling I2C_0, which is connected to the mikroBUS™ socket of the Curiosity Board.
We can check the I2C pins that are connected to the mikroBUS socket from the PIC64GX1000 Curiosity Kit schematics.
PIC64 Pin | mikroBUS Pin |
---|---|
I2C_0_SCL (B)/GPIO_1_21 | RC3/SCL |
I2C_0_SDA (B)/GPIO_1_22 | RC4/SDA |
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.
Using 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:
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:
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[]?
i2cget: Read values from specified registers of PAC1934 device. To read Product ID from register address 0xFD:
0x5b
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>
i2ctransfer: Perform complex I2C transactions like performing a combined write and read operation. To read 2 bytes from address 0xFE:
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.
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
Copy and paste the following C code to the i2c_pac1934.c:
#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, ®, 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, ®, 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;
}
After saving the modification, compile the C code:
Run the i2c_pac1934 executable:
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.