PolarFire® System-on-Chip (SoC) Applications - Inter-Integrated Circuit (I2C)
Introduction
The Inter-Integrated Circuit (I2C) protocol is a widely used solution that facilitates communication between multiple devices over a short distance.
This article describes the I²C capabilities of the PolarFire® System-on-Chip (SoC) devices and explains how to use the I²C peripheral on PolarFire SoC boards using:
- Linux® userspace with the i2c-tools utility and C applications
- A bare metal I²C application
Prerequisites
Hardware Setup
- This application applies to the following PolarFire SoC–based boards:
- Linux host PC for the Yocto Project® building environment or Windows Subsystem for Linux (WSL) on a Windows® machine
- THERMO 5 Click board™ (MIKROE-2571) for experiments
- Digital logic analyzer (optional, but useful for observing and analyzing signals)
Software Setup
Additional Resources: I²C Protocol Background
- I2C Bus Introduction
- Practical I2C: Introduction, Implementation and Troubleshooting
- Debugging Techniques for Serial Communications (I2C/SPI/UART)
Programming a Reference-Design Into SoC
To use the Microcontroller Subsystem (MSS) I²C peripheral in an application, it must first be enabled in the Libero SoC design suite design file.
This example uses the MSS I2C_0 peripheral connected to the mikroBUS™ socket.

Program the reference design so the system can be used later with the Yocto Project. To use the I²C peripheral, you must know how it is connected in the design. The following table shows the connections for the PolarFire SoC Discovery Kit and the PolarFire SoC Icicle Kit:
| Icicle Kit | Discovery Kit | ||
|---|---|---|---|
| I2C 0 | Physical Connection | I2C 0 | Physical Connection |
| SDA | MBUS socket SDA pin | SDA | MBUS socket SDA pin |
| SCL | MBUS socket SCL pin | SCL | MBUS socket SCL pin |
We will rely on the PolarFire SoC Icicle Kit's Libero SoC design suite reference design file.
Start by downloading the Icicle kit reference design generation FlashPro images from the latest release in the PolarFire SoC GitHub® repository:
Because the I²C protocol requires both the SCL (clock) and SDA (data) lines to operate as inputs and outputs at different times, a Bidirectional Buffer (BIBUF) must be used in the design.

Program the Field-Programmable Gate Array (FPGA) by the MPFS_ICICLE_KIT_BASE_DESIGN_{VERSION} design using the FlashPro Express tool (included with the Libero SoC design suite software).
Open FlashPro Express and program the FPGA fabric using the prebuilt reference programming files.
Open FlashPro Express.
Create New Project.
Click the Run button.
Now you have programmed the FPGA Fabric logic.
I²C in Linux® Environment
We need to set up and configure the MSS I2C peripheral in the Linux configurations; that's why we need a Linux build system to configure it. For this article, we will use the Yocto Project.
HSS Configurations
Hart Software Services (HSS) is the bootloader used on PolarFire SoC devices. It runs first, sets up hardware, and launches Linux or other app—essential for multi-core and secure boot.
Objectives:
- Download and import HSS to SoftConsole.
- Update references and build HSS.
- Deploy HSS to PolarFire SoC Icicle Kit.
First, download HSS from the hart-software-services GitHub repository.
Import the HSS project to SoftConsole by selecting File > Import > Import Existing Project Into Workspace.

Browse the HSS folder and import the project into the workspace by clicking Finish.
Copy your MSS XML file into the project.
Copy the XML file to hart-software-services/boards/mpfs-icicle-kit/soc_fpga_design/xml/<your xml>.xml.
Copy and rename configurations for HSS
- Copy hart-software-services/boards/mpfs-icicle-kit/def_config to hart-software-services/
- Rename def_config to .config
- Edit .config file and update path to your xml by changing next line
Build HSS and deploy.
Right click on the project name.
Click on the build project.
Select the PolarFire SoC program non-secure boot mode 1 run option and deploy the project to SoC.
Yocto Project® Configurations
In this section, we will create a Linux image and program it into the PolarFire SoC Icicle Kit.
Objectives:
- Setting up the Yocto Project Building Environment
- Enabling the I2C peripheral and including the necessary packages in the build
- Building a Linux image and deploying it into the SoC
Creating Environment
Open a terminal on your Linux machine or in WSL.
Create an empty directory to hold the workspace:
mkdir yocto-dev && cd yocto-dev
Use the repo tool to fetch all required repositories.
repo init -u https://github.com/linux4microchip/meta-mchp-manifest.git -b refs/tags/linux4microchip+fpga-2025.07 -m polarfire-soc/default.xml
repo sync
Set the TEMPLATECONF environment variable to point to the appropriate configuration template before initializing the build environment:
export TEMPLATECONF=${TEMPLATECONF:-../meta-mchp/meta-mchp-polarfire-soc/meta-mchp-polarfire-soc-bsp/conf/templates/default}
Then initialize the Yocto Project build environment:
source openembedded-core/oe-init-build-env
Build the Linux image with default configurations to make sure that all packages and tools are installed and working properly.
MACHINE=mpfs-icicle-kit bitbake mchp-base-image
Configuring I²C Peripheral
Prepare the Microchip Linux kernel (linux-mchp) source tree for local development.
Add the required libraries and applications to the build. Open the conf/local.conf file and add the following variable at the end of the file.
CORE_IMAGE_EXTRA_INSTALL += "i2c-tools packagegroup-core-buildessential vim"
Save the file and exit.
Open the Linux Kernel configuration menu to enable the I2C peripheral.
MACHINE=mpfs-icicle-kit bitbake linux-mchp -c menuconfig
Navigate down to the Device Drivers.

Enable the I2C Support option.

Save the config by selecting Save > Exit.
Locate the Icicle kit-related Device Tree Source (DTS) files for modification:
yocto-dev/build/workspace/sources/linux-mchp/arch/riscv/boot/dts/microchip/mpfs-icicle-kit-common.dtsi
Make sure that the I2C0 peripheral is enabled in the mpfs-icicle-kit-common.dtsi:
2
3
status = "okay";
};
Compile a customized Yocto Project Linux kernel recipe in a developer-friendly way, producing kernel binaries for PF SoC:
Building Linux Image and Deploying
Execute the following command to build the Linux image:
MACHINE=mpfs-icicle-kit bitbake mchp-base-image
Here's the list of names of supporting machines:
| MACHINE | Board Name | Description |
|---|---|---|
| MACHINE=mpfs-icicle-kit | MPFS-ICICLE-KIT-ES, MPFS-ICICLE-KIT | PolarFire SoC Icicle Kit |
| MACHINE=mpfs-disco-kit | MPFS-DISCO-KIT | PolarFire SoC Discovery Kit |
| MACHINE=mpfs-video-kit | MPFS250-VIDEO-KIT | PolarFire SoC Video Kit |
After the build completes, the Linux image can be found at:
yocto-dev/build/tmp-glibc/deploy/images/<board_name>/<image-name>.rootfs-***.wic
Follow the GitHub Programming a Linux Image instructions to deploy the built Image to the eMMC/SD card memory.
After booting Linux on the PolarFire SoC Icicle Kit, log in as root and check if the I2C device 0 appears under the devices:
i2c-0
i2c-1
i2c-2
If no devices are listed, the I²C device may not be enabled. You have to double-check the configuration and DTS modifications that need to be done.
Software 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
The i2c-tools package provides utilities for accessing the I²C driver from Userspace.
The following commands are included 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 Icicle kit

i2cdetect: Scan for I2C devices connected to the bus. In our case, the THERMO 5 Click board is connected on Bus 0 with a target address of 0x4c:
root@mpfs-icicle-kit:~# i2cdetect -y -r 0
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- 4c -- -- --
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 MIKROE-2571'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: 22 00 04 80 06 55 00 55 00 80 06 55 00 55 00 00 ".???U.U.??U.U..
10: 00 00 00 00 00 55 00 00 00 55 55 0e 00 00 00 00 .....U...UU?....
20: 55 0a 70 00 00 0e 07 12 12 20 00 00 55 00 00 00 U?p..???? ..U...
30: 55 12 00 00 00 00 00 00 00 00 00 00 00 00 00 00 U?..............
40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
60: 06 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ?...............
70: 06 06 06 00 00 00 00 00 00 00 00 00 00 00 00 00 ???.............
80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
a0: 00 00 00 00 00 00 00 00 00 00 00 00 28 d4 44 6f ............(?Do
b0: 20 20 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ..............
c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 25 5d 04 .............%]?
i2cget: Read values from specified registers of the MIKROE-2571 device. To read Product ID from register address 0xFD:
0x25

i2cset: Write values to specified registers of MIKROE-2571 device. To write value 0x24 to Internal Diode Therm Limit (0x20):
i2cset <bus> <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 one byte from address 0xfe:
0x5d
- 0: This represents the I2C bus number.
- w1@0x4c 0xfe: Write 1 byte (0xfe) to the device at address 0x4C to set the register pointer.
- r1@0x4c: Read one byte from the device at address 0x4C.
Application Programming in C Language Using i2c-dev
We can write a C program that runs from Userspace and interacts with I2C devices using the ioctl functions. The following example C program reads the internal diode temperature registers, converts the values to degrees Celsius, and prints the results.
Boot Linux on your Icicle kit. Navigate to /media and create i2c_mikroe2571.c using the vim editor.
Copy and paste the following C code to i2c_mikroe2571.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 MIKROE2571_ADDR 0x4c
// Example MIKROE-2571 register addresses (update as needed)
#define INTERNAL_DIODE_HIGH 0x00
#define INTERNAL_DIODE_LOW 0x29
#define EXT1_DIODE_HIGH 0x01
#define EXT1_DIODE_LOW 0x10
#define EXT2_DIODE_HIGH 0x23
#define EXT2_DIODE_LOW 0x24
#define EXT3_DIODE_HIGH 0x2a
#define EXT3_DIODE_LOW 0x2b
// Function to read a single 8-bit register
int read_register8(int file, uint8_t reg, uint8_t *value) {
if (write(file, ®, 1) != 1) {
perror("Failed to write register address");
return -1;
}
if (read(file, value, 1) != 1) {
perror("Failed to read register value");
return -1;
}
return 0;
}
int main() {
int file;
int retries = 5;
uint8_t temp_int, temp_frac;
uint8_t ext1_int, ext1_frac;
uint8_t ext2_int, ext2_frac;
uint8_t ext3_int, ext3_frac;
// 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, MIKROE2571_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) {
// Read internal diode temperature (high and low byte)
if (read_register8(file, INTERNAL_DIODE_HIGH, &temp_int) < 0 ||
read_register8(file, INTERNAL_DIODE_LOW, &temp_frac) < 0) {
close(file);
exit(1);
}
// Read external diode 1
if (read_register8(file, EXT1_DIODE_HIGH, &ext1_int) < 0 ||
read_register8(file, EXT1_DIODE_LOW, &ext1_frac) < 0) {
close(file);
exit(1);
}
// Read external diode 2
if (read_register8(file, EXT2_DIODE_HIGH, &ext2_int) < 0 ||
read_register8(file, EXT2_DIODE_LOW, &ext2_frac) < 0) {
close(file);
exit(1);
}
// Read external diode 3
if (read_register8(file, EXT3_DIODE_HIGH, &ext3_int) < 0 ||
read_register8(file, EXT3_DIODE_LOW, &ext3_frac) < 0) {
close(file);
exit(1);
}
// Print the temperature values (raw, conversion to Celsius as needed)
printf("Internal: 0x%02X 0x%02X | Ext1: 0x%02X 0x%02X | Ext2: 0x%02X 0x%02X | Ext3: 0x%02X 0x%02X\n",
temp_int, temp_frac, ext1_int, ext1_frac, ext2_int, ext2_frac, ext3_int, ext3_frac);
sleep(1);
}
close(file);
return 0;
}
After saving the modification, compile the C code:
Run the i2c_mikroe2571 executable:
I²C in Bare Metal Applications
To use the MSS I²C peripheral in a bare metal application, we have to use the SoftConsole to develop the application to build, compile, and deploy.
This example uses a bare metal reference application from the official GitHub repository and modifies it to use the MSS I2C_0 peripheral to communicate with the MIKROE‑2571 device.
Download the mpfs-blank-baremetal bare metal application project from the official GitHub repository and import it into SoftConsole.
Replace the MSS Configuration XML file in the bare metal project with the XML file used in your Libero SoC design suite design file. The path to the file that SoftConsole will use to generate header files, which are then used by the MPFS HAL, is:
Now we can begin modifying u54_1.c with our custom code to perform the following:
- Read the ID of the MIKROE-2571 from register 0xFF, referring to the data sheet.
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include <string.h>
#include "mpfs_hal/mss_hal.h"
#include "drivers/mss/mss_mmuart/mss_uart.h"
#include "drivers/mss/mss_i2c/mss_i2c.h"
#define MY_ADDR 0x21u
#define SLAVE_ADDR 0x4cu
#define SLAVE_REVISION_ID_REG 0xffu
#define TX_BUF_SIZE 1u
static uint8_t tx_buffer[TX_BUF_SIZE];
static uint8_t rx_buffer[10];
void u54_1(void)
{
PLIC_init();
PLIC_SetPriority(I2C0_MAIN_PLIC, 2);
__enable_irq();
(void) mss_config_clk_rst(MSS_PERIPH_MMUART1, (uint8_t) 1, PERIPHERAL_ON);
(void) mss_config_clk_rst(MSS_PERIPH_I2C0 , (uint8_t) 1, PERIPHERAL_ON);
MSS_I2C_init(&g_mss_i2c0_lo, MY_ADDR, MSS_I2C_PCLK_DIV_192);
MSS_UART_init(&g_mss_uart1_lo,
MSS_UART_115200_BAUD,
MSS_UART_DATA_8_BITS | MSS_UART_NO_PARITY | MSS_UART_ONE_STOP_BIT);
MSS_UART_polled_tx_string(&g_mss_uart1_lo, "\r\nChecking connection to MIKROE-2571\r\n");
tx_buffer[0] = SLAVE_REVISION_ID_REG;
MSS_I2C_write_read(
&g_mss_i2c0_lo,
SLAVE_ADDR,
tx_buffer,
TX_BUF_SIZE,
rx_buffer,
1,
MSS_I2C_RELEASE_BUS
);
(void)MSS_I2C_wait_complete(&g_mss_i2c0_lo, 3000u);
if(rx_buffer[0] == 0x04u){
MSS_UART_polled_tx_string(&g_mss_uart1_lo, "Connected Successfully");
} else {
uint8_t message[40] = {};
sprintf((char*)&message, "\r\nUnable to connect!\r\nReceived: 0x%X \r\n", (uint8_t)(rx_buffer[0]));
MSS_UART_polled_tx(&g_mss_uart1_lo, message, 40);
}
}
Line 1-14: We start by including the necessary header files for standard input/output, string handling, and the PolarFire SoC HAL, UART, and I2C drivers. We also declare global variables for UART and I2C communication.
Line 16-23: The u54_1() function is the main entry point for the application. It waits for a software interrupt to synchronize startup and enables the required peripherals.
Line 26-32: We initialize the UART and I2C peripherals, I2c as a host.
Line 34-45: Trying to read MIKROE-2571 Revision ID number (0x4h) and store in rx_buffer.
Line 47-53: Checking for wrong data, if data is right, transmitting complete message via UART, else transmitting Fail message.
Build the project and deploy it either in LIM for Debug mode or eNVM for Release mode.
Summary
This article explains how to enable and test I²C communication on PolarFire SoC boards using the 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 can then be 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 temperature register values/device ID from an I2C device. By following this guide, you establish a solid foundation for further development with I2C peripherals on the PolarFire SoC-based boards.