Yuki
August 25, 2025

Cross-Compiling C++ I2C LCD Program for Raspberry Pi with CLion

Uncategorized

A working project is available below

Overview

This guide shows how to cross-compile a C++ program on x86-64 Linux to control an I2C LCD display on Raspberry Pi 3 (aarch64) using CLion IDE and the standard Linux I2C interface.

Development System Setup (x86-64 Linux)

1. Install Cross-Compilation Tools

sudo apt update
sudo apt install gcc-aarch64-linux-gnu g++-aarch64-linux-gnu cmake

2. Match Cross-Compiler Version to Target Pi

It’s important to match your cross-compiler version with the GCC version running on your Raspberry Pi to avoid compatibility issues.

Check Pi GCC Version

# On your Raspberry Pi, check the GCC version
ssh pi@your-pi-ip "gcc --version"
# Example output: gcc (Debian 12.2.0-14) 12.2.0

Downgrade Cross-Compiler if Needed

# If your host has a newer version, downgrade to match the Pi
# For Arch Linux users:
sudo downgrade aarch64-linux-gnu-gcc

# Select version 12.2.0 when prompted
# Also downgrade the C++ compiler:
sudo downgrade aarch64-linux-gnu-g++

# For Ubuntu/Debian users, you may need to install specific versions:
# sudo apt install gcc-12-aarch64-linux-gnu g++-12-aarch64-linux-gnu
# sudo update-alternatives --install /usr/bin/aarch64-linux-gnu-gcc aarch64-linux-gnu-gcc /usr/bin/aarch64-linux-gnu-gcc-12 100
# sudo update-alternatives --install /usr/bin/aarch64-linux-gnu-g++ aarch64-linux-gnu-g++ /usr/bin/aarch64-linux-gnu-g++-12 100

Verify Cross-Compiler Version

# Verify the versions match
aarch64-linux-gnu-gcc --version
aarch64-linux-gnu-g++ --version
# Should show: gcc (GCC) 12.2.0 or similar

CLion IDE Setup

3. Create New Project in CLion

  1. File → New Project
  2. Choose C++ Executable
  3. Set project name: Rpi_Project1
  4. Choose location and create project

4. Create Toolchain File in CLion

  1. In CLion, create a new directory: Right-click project root → New → Directory → name it cmake
  2. Right-click cmake folder → New → File → name it aarch64-toolchain.cmake
  3. Add the following content:

cmake/aarch64-toolchain.cmake

set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR aarch64)

set(CMAKE_C_COMPILER aarch64-linux-gnu-gcc)
set(CMAKE_CXX_COMPILER aarch64-linux-gnu-g++)

set(CMAKE_C_FLAGS "-D_GNU_SOURCE -fno-builtin")
set(CMAKE_CXX_FLAGS "-D_GNU_SOURCE -fno-builtin")

set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE NEVER)

5. Update CMakeLists.txt

Replace the content of CMakeLists.txt with:

cmake_minimum_required(VERSION 3.10)
project(Rpi_Project1)
set(CMAKE_CXX_STANDARD 17)

add_executable(Rpi_Project1 main.cpp)
target_compile_options(Rpi_Project1 PRIVATE -Wall -Wextra)

message(STATUS "Using Linux I2C interface - no special libraries needed")

6. Create Source Code

Replace the content of main.cpp with:

#include <iostream>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/i2c-dev.h>

int main() {
    // Open I2C device
    int fd = open("/dev/i2c-1", O_RDWR);
    if (fd < 0 || ioctl(fd, I2C_SLAVE, 0x27) < 0) {
        std::cerr << "I2C error" << std::endl;
        return 1;
    }

    // Helper functions
    auto write_byte = [&](uint8_t data) { write(fd, &data, 1); };

    auto strobe = [&](uint8_t data) {
        write_byte(data | 0x0C);  // EN high + backlight
        usleep(1);
        write_byte(data | 0x08);  // EN low + backlight
        usleep(50);
    };

    auto write_4bits = [&](uint8_t value) {
        uint8_t data = (value & 0x0F) << 4;
        write_byte(data | 0x08);  // backlight on
        strobe(data);
    };

    auto send_cmd = [&](uint8_t value) {
        uint8_t high = value & 0xF0;
        uint8_t low = (value << 4) & 0xF0;
        write_byte(high | 0x08); strobe(high);
        write_byte(low | 0x08); strobe(low);
    };

    auto send_char = [&](char c) {
        uint8_t high = c & 0xF0;
        uint8_t low = (c << 4) & 0xF0;
        write_byte(high | 0x09); strobe(high | 0x01);  // RS=1 for data
        write_byte(low | 0x09); strobe(low | 0x01);
    };

    // Initialize LCD
    write_byte(0x08); usleep(50000);  // backlight on, wait
    write_4bits(0x03); usleep(5000);  // 8-bit mode
    write_4bits(0x03); usleep(5000);
    write_4bits(0x03); usleep(5000);
    write_4bits(0x02); usleep(5000);  // switch to 4-bit

    send_cmd(0x28);  // 4-bit, 2 lines
    send_cmd(0x0C);  // display on
    send_cmd(0x06);  // auto increment
    send_cmd(0x01);  // clear
    usleep(2000);

    // Display "Hello World!"
    for (char c : std::string("Cat!")) {
        send_char(c);
    }

    std::cout << "Hello World displayed!" << std::endl;
    sleep(3);

    // Clean up: clear screen and turn off backlight
    send_cmd(0x01);    // clear display
    usleep(2000);      // wait for clear to complete
    write_byte(0x00);  // turn off backlight

    close(fd);
    return 0;
}

7. Configure CLion Toolchain

  1. Settings → Build, Execution, Deployment → Toolchains
  2. Click + and select System
  3. Configure:
  • Name: RPi3
  • CMake: Bundled
  • Build tool: empty (detected: ninja)
  • C Compiler: aarch64-linux-gnu-gcc
  • C++ Compiler: aarch64-linux-gnu-g++
  • Debugger: Bundled GDB
  1. Click OK

8. Configure CLion CMake Profile

  1. Settings → Build, Execution, Deployment → CMake
  2. Click +
  3. Configure:
  • Name: RPi
  • Build type: Debug
  • Toolchain: RPi3
  • Generator: Use default Ninja
  • CMake options: -DCMAKE_TOOLCHAIN_FILE=cmake/aarch64-toolchain.cmake
  1. Click OK

9. Configure Remote Debugging

  1. Click Run/Debug configurations → Edit configurations
  2. Click + → Remote GDB Server
  3. Click the cog wheel next to Credentials
  4. Enter your Raspberry Pi SSH credentials and save password
  5. Click OK
  6. Configure:
  • Name: Rpi-config
  • GDB: Bundled GDB
  • Credentials: Select your SSH credentials
  • Upload: Always
  1. Verify Before launch section shows Build

Raspberry Pi Setup

1. Enable I2C Interface

sudo raspi-config
# Navigate to: Interface Options → I2C → Enable
sudo reboot

2. Install Required Tools

sudo apt update
sudo apt install i2c-tools gdbserver

3. Setup I2C Permissions for Non-Root Users

By default, only root can access I2C devices. Here’s how to setup proper permissions:

Setup I2C Group and Permissions

# 1. Remove any existing broken rules
sudo rm -f /etc/udev/rules.d/10-local_i2c_group.rules

# 2. Create the correct udev rule
sudo sh -c 'echo "SUBSYSTEM==\"i2c-dev\", GROUP=\"i2c\", MODE=\"0664\"" > /etc/udev/rules.d/99-i2c.rules'

# 3. Create i2c group if it doesn't exist
sudo groupadd i2c 2>/dev/null || true

# 4. Add your user to the i2c group
sudo usermod -aG i2c $USER

# 5. Apply the rules immediately
sudo udevadm control --reload-rules
sudo udevadm trigger

# 6. Manually fix current session (temporary)
sudo chown root:i2c /dev/i2c-1
sudo chmod 664 /dev/i2c-1

# 7. Reboot for all changes to take effect
sudo reboot

Verify I2C Access

# After reboot, check permissions
ls -l /dev/i2c*
# Should show: crw-rw-r-- 1 root i2c 89, 1 Aug 25 16:39 /dev/i2c-1

# Check you're in the i2c group
groups $USER
# Should show: pi adm dialout cdrom sudo audio video plugdev games users input netdev i2c

# Test I2C access (should work without sudo)
i2cdetect -y 1

4. Hardware Connection

Connect I2C LCD to Raspberry Pi:

  • VCC → 5V (Pin 2)
  • GND → Ground (Pin 6)
  • SDA → GPIO 2 (Pin 3)
  • SCL → GPIO 3 (Pin 5)

5. Test I2C Connection

# Check I2C devices exist
ls /dev/i2c*  # Should show /dev/i2c-1

# Scan for devices (should work without sudo now)
i2cdetect -y 1  # Should show your LCD address (commonly 0x27)

Expected output when LCD is connected:

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

Running and Debugging

Build and Run from CLion

  1. Select RPi build configuration at the top
  2. Click Build (hammer icon) to cross-compile
  3. Select Rpi-config debug configuration
  4. Click Debug (bug icon) for remote debugging

The program will automatically build, upload to your Raspberry Pi, and start debugging remotely.

feed previous next