The Useful RaspberryPi Cross Compile Guide

Going farther than a simple ‘Hello World’ can take you…

Alex C.U.
8 min readJul 31, 2017

--

Here I am, working on the weekend developing my project for a plant that can water itself. When I started this project, I didn’t have time to setup a proper development environment — so I was left with coding directly on the RaspberryPi itself. This isn’t a deal-breaker when you can directly hookup your keyboard and monitor, but it does limit you to the computing power of the Pi.

Sooner or later into more “serious” development you’ll begin to notice some things that just drag your productivity into the ground:

  • Lag from your VNC session making you feel like you’re coding through molasses
  • Long compile times and a lack of responsiveness from the Pi as it’s churning
  • Inability to connect keyboard/monitor/Ethernet due to position, integration into other hardware, or lack of extra equipment
  • No Internet connection to Pi with sideloading as your only option
  • Lack of GUI in your distribution

Even in a small side project such as mine, I ran into almost all of these issues at one point or another. Because of my “deadline” for vacation, I just worked through these issues as they drove me crazy — until now.

Why Cross Compile?

In my career working with embedded devices, cross compiling is an indispensable process of developing software for the electronic devices you see everywhere (coffee makers, cell phones, car computers, digital cameras, etc.) Wikipedia has a great intro on this, so I’d advise everyone to read through their page if anything is unclear at this point. It’s this process that will allow us to build software for the RaspberryPi on our already-established main computer, using all their power and benefits it can bring us.

Unfortunately, due to the significant changes between versions of RaspberryPi and the various build methods available, it’s not the easiest thing to find an up-to-date & simple cross compile guide on the Internet. Besides this — most do not go past the simple “hello world” example project, ignoring the issues that arise when doing work with standard libraries or shared libraries that may also need to be cross compiled. This guide will aim to work through these steps using the popular open-source wiringPi library as an example.

As this is a topic that takes some prerequisite knowledge to use correctly, I’m going to skip the basic setup instructions and assume you can:

  • Pull code with git
  • Compile with gcc
  • Install cmake using your preferred install method

I will also be basing this guides instructions on a Ubuntu x64 host system. If you do not have the above packages already installed, you will need to find their platform-specific install guides. The option of using a VirtualBox VM is always available on both PC and Mac, (I’m using an Ubuntu 17.04 release in a VM on Win 10 myself as a dedicated environment.)

Get the Platform

The first thing we need to start off on our path, is to get the cross compile tools and platform on our computer. Sometimes external tools like crosstool-ng will help you build your own kit, and there are some excellent guides already on the Internet if you’re curious about that method. For simplicity, we’re lucky that the official RaspberryPi foundation has already provided the tools for us online.

  1. (optional) Create a directory to store everything. I’ll be using ~/Development in my Ubuntu VM.
  2. Pull the RaspberryPi toolchain from https://github.com/raspberrypi/tools
  3. Check you have the directory raspberrypi-tool ( or just tools ) fully downloaded.

That’s it! You will be using the tools in this directory to build your RaspberryPi applications. This folder contains not just the “building” tools, but also all the standard libraries and system calls that could be needed by your application — we will be using several parts of this directory later.

Get the libraries

Most non-trivial programs you will build may require standard libraries, or external libraries not yet present on your system to be linked to. In most cases, simply downloading the relevant libraries and placing them in a lib directory was all that was needed. However, this is a bit more complicated for cross-compiling since you can’t reference an Intel compiled library for an ARM binary. There are ways to copy your library files directly onto your main development machine and reference them that way — however, that means you are still dependant on compiling something on your Pi.

For this example I’m going to be using the standard pthreads library, a locally-compiled open-source wiringPi library, and the current C11 standard with our compiler. These should give you a good example base to start adding more libraries and options if you need them.

The pthreads library is standard, and already included in our already-downloaded toolchain, so we don’t need to worry about that for right now. Our last step here is to simply pull the current wiringPi repository from http://wiringpi.com/download-and-install/.

Get the test code

Right now, we’re just focused on getting the compiler and build system working — so let’s not worry about compiling anything too complex. The “blink” example from the wiringPi website should fill our needs nicely:

#include <wiringPi.h>
int main (void)
{
wiringPiSetup() ;
pinMode(0, OUTPUT) ;
for(;;)
{
digitalWrite (0, HIGH) ; delay (500) ;
digitalWrite (0, LOW) ; delay (500) ;
}
return 0 ;
}

CMake & Bake

Next up, we need to get our build system up and running. This guide will use CMake as the tool of choice and requires some background knowledge on how it works.

The main feature of cmake we’re going to implement is using a toolchain file. When using it to generate a makefile for a project, we can use a toolchain file to specify which compilers, linkers, and libraries are used to build the project (instead of the default tools installed on our main system.)

We’re also going to need to run cmake at least twice: once for our main blink app, and once for our custom library. Each of these will need it’s own CmakeLists.txt file to specify how to build the sources, as well as the specified RaspberryPi toolchain file. For this example, see my Toolchain-rpi.cmake file below:

# Define our host system
SET(CMAKE_SYSTEM_NAME Linux)
SET(CMAKE_SYSTEM_VERSION 1)
# Define the cross compiler locations
SET(CMAKE_C_COMPILER /home/alex/Development/raspberrypi-tools/arm-bcm2708/arm-rpi-4.9.3-linux-gnueabihf/bin/arm-linux-gnueabihf-gcc)
SET(CMAKE_CXX_COMPILER /home/alex/Development/raspberrypi-tools/arm-bcm2708/arm-rpi-4.9.3-linux-gnueabihf/bin/arm-linux-gnueabihf-gcc)
# Define the sysroot path for the RaspberryPi distribution in our tools folder
SET(CMAKE_FIND_ROOT_PATH /home/alex/Development/raspberrypi-tools/arm-bcm2708/arm-rpi-4.9.3-linux-gnueabihf/arm-linux-gnueabihf/sysroot/)
# Use our definitions for compiler tools
SET(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
# Search for libraries and headers in the target directories only
SET(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
SET(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
add_definitions(-Wall -std=c11)

Note: Remember to change /home/alex/Development/raspberrypi-tools in these examples to wherever you’re keeping these files. This may be something like /Users/alex/Development/pidev/tools instead — it’s whatever folder contains arm-bcm2708 on your machine. You may also need to make a different toolchain file without the std=c11 compiler argument for your wiringPi build.

As you can see, our goal is to ensure cmake will use the correct tools and directory includes for our current goal. The meanings for all of these keyword variables can be found on the cmake wiki, This file is referenced directly in the cmake call, and can be used in the terminal by specifying it as such:

cmake . -DCMAKE_TOOLCHAIN_FILE=toolchain-rpi.cmake

Of course, this won’t work just yet. We need something else…

Our first step in the compilation process will be to build our wiringPi shared library, and to make it available for linking. While cmake offers many different methods for large project compilation and management, this example will focus on simplicity and clarity to get our project working.

First, we need our CMakeLists.txt placed in the proper directory at ~/Development/wiringPi/wiringPi (where the .c and .h source files are)

cmake_minimum_required(VERSION 3.0)# Have CMake find our pthreads library within our toolchain (required for this library)
set(CMAKE_THREAD_PREFER_PTHREAD TRUE)
find_package(Threads REQUIRED)
# add all the *.c files as sources
FILE(GLOB SRC_FILES *.c)
# make this output a shared library (with .so output)
add_library (wiringPi SHARED ${SRC_FILES})
# be sure to include the current source directory for header files
target_include_directories (wiringPi PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
# add the following required libraries:
# Threads, Math, Crypt, and RealTime
target_link_libraries(wiringPi ${CMAKE_THREAD_LIBS_INIT} crypt m rt)

Following this, we should be able to build our library with these commands:

cmake . -DCMAKE_TOOLCHAIN_FILE=Toolchain-rpi.cmake
make

If everything is successful, you should see a new file called libwiringPi.so in your directory, ready for linking to your app. You should NOT be seeing any cmake warnings at this point (besides maybe a pointer size warning), so be very vigilant if you do come across one. When you get to building more libraries in larger projects, you may wish to have your build process place your build results in their own directory.

Note: It’s important to delete ALL the CMake generated build files and caches if you need to make a change to the Toolchain-rpi.cmake or CMakeLists.txt files, or else you’re gonna have a bad time.

Next up, we will use cmake to compile our main app. Following our previous methods, we again place a CMakeLists.txt into our base directory (I am assuming you have your main sources in a src/ directory, and any headers in an include/ directory below where you’ll run cmake).

cmake_minimum_required (VERSION 3.0)# Name our project
project (blink_example)
# Create a variable that holds the path to our libwiringPi.so file
set (WPI_PATH /home/alex/Development/wiringPi/wiringPi)
# Add the local ‘include’ directory and the wiringPi directory to grab headers
include_directories (include ${WPI_PATH})
# Actually find the wiringPi library object
find_library(WPI_LIB wiringPi HINTS ${WPI_PATH} NO_CMAKE_FIND_ROOT_PATH)
# Alert the user if we do not find it
#if(NOT WPI_LIB)
# message(FATAL_ERROR “wiringPi library not found”)
#endif()
# Add all the *.c files in our source directory to our executable output
FILE(GLOB SRC_FILES src/*.c)
add_executable(blink_example ${SRC_FILES})
# Link the pre-compiled wiringPi library to the executable we just declared
target_link_libraries(blink_example ${WPI_LIB})

One thing to note is that the order of statements in a cmake file is very important! If you do not place these correctly, you may end up searching for a solution to a problem you don’t have, (I may or may not have had personal experience with this at one point).

If everything went well again, you can now see your new executable blink_example in your directory. One way that you can test to be sure you’ve actually cross compiled on your main PC can be tried below:

alex@alex-VirtualBox:~/Development/blink_example$ ./blink_example 
bash: ./blink_example: cannot execute binary file: Exec format error

This is what you should be getting if everything went according to plan. Which makes sense — as you shouldn’t be able to run an ARM binary file on an Intel processor.

Final Words

The last step will be testing and actually running your new cross-compile app! Simply transferring your blink_example binary file to your RaspberryPi should be all that’s needed, (you will need to install wiringPi on the RaspberryPi itself as well if you haven’t already). After running with ./blink_example — our reward is a blinking LED!

Before writing this guide, I ran into so many issues trying to get even the simplest non-trivial example to work — segmentation faults, linking errors, missing libraries, and all sorts of compilation problems. But now, I no longer have my productivity tied down to the RaspberryPi’s speed and physical location. And most importantly…I can use my mechanical keyboard for development again!

If this guide has also helped save you time over the course of your development projects - I’d really appreciate sharing or recommending this article so other makers and coders can use it too! You can also follow me on twitter @au42 and on medium — the next part of this series will walk through the process of setting up gdbserver and remotely debugging your cross compiled code!

--

--

Alex C.U.

Denver-based software & hardware engineer. I work with the dark arts of radio waves, code, and electricity. We tricked rocks and lightning into thinking for us.