eltonlaw sundries

Building a Custom Raspberry Pi Image: Part 1

Motivated by Jay Carlson's Amp Hour podcast and reading his blog post "So You Want to Build an Embedded Linux System?". The following was done on a raspberry pi 3B.

The build system used is Buildroot. The only other alternative looked into was Yocto but a cursory investigation seemed to indicate that people found Buildroot easier to start up with. Having used Buildroot a little for this project, the process does seem rather straightforward. Your final file image is entirely configured by a single .config file and adding packages is done by adding new directories, following certain conventions to relevant subdirectories (whether that be package, fs, toolchain or so on).

The first goal was to build and flash something working:

$ git clone git@github.com:buildroot/buildroot.git
$ cd buildroot
$ make raspberrypi3_qt5we_defconfig
$ make
$ dd bs=5M if=output/images/sdcard.img of=/dev/sdX conv=fsync

The third line make raspberrypi3_qt5we_defconfig creates the .config file in the project root, being a copy of a predefined raspberry pi3 config with support for QT, defined in the master tree. This is pretty nice as it's something in the mainline that is guaranteed to work for the board.

If you followed that link, the .config syntax is pretty manageable, it's a key-value format and describes things such as architecture, which SoC, root filesystem etc. This is merged with some default config file, I think, to fill in all the gaps.

$ wc -l .config
3416 .config
$ head -10 .config
#
# Automatically generated file; DO NOT EDIT.
# Buildroot 2020.11-rc3-dirty Configuration
#
BR2_HAVE_DOT_CONFIG=y
BR2_HOST_GCC_AT_LEAST_4_9=y
BR2_HOST_GCC_AT_LEAST_5=y
BR2_HOST_GCC_AT_LEAST_6=y
BR2_HOST_GCC_AT_LEAST_7=y
BR2_HOST_GCC_AT_LEAST_8=y

From there, make is called to build the image and a bunch of files/directories are generated in output/. The fit-for-consumption product is located at output/images/sdcard.img which can be copied onto a FAT32 formatted sd card.

Running that, the Pi boots up into a bare bones tty. It works, hooray,

...
Welcome to Buildroot
buildroot login: root
Password: 
# pwd
/root
# ls /
bin dev etc lib lib32 linuxrc lost+found media mnt opt proc root run sbin sys tmp usr var

Adding a custom binary to the image

To add a binary to the final image, I started with a simple executable:

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
       printf("Goobily doo\n");
       return 0;
}

As mentioned above, as far as I can tell, there are two main steps: 1) creating the package and 2) enabling the package. The package creation has helper tools for different types of builds: cmake-package, python-package, luarocks-package and so on, which are all extensions (I think) of a generic-package macro. There doesn't seem to be a process for just compiling a single C file but it's simple to hook into the build command with something like this:

define PI_MAIN_CONTROLLER_BUILD_CMDS
   $(TARGET_CC) -o $(@D)/pi-main-controller $(@D)/main.c
endef

$(TARGET_CC) and $(@D) are special variables defined by Buildroot, representing the cross compiler and the build directory respectively. Very neat. About the build directory, before this <package_name>_BUILD_CMDS is run, an earlier step copies everything in package/<package_name>/ to output/build/<package_name>-<version>/.

There's another hook for after the build process to install to the target. Since this is an executable, the following sets the binary as executable and moves it to output/target/usr/bin/pi-main-controller.

define PI_MAIN_CONTROLLER_INSTALL_TARGET_CMDS
   $(INSTALL) -D -m 0755 $(@D)/pi-main-controller $(TARGET_DIR)/usr/bin/pi-main-controller
endef

Running make again to rebuild:

make 

If it worked correctly:

$ ls output/target/usr/bin/pi-main-controller
output/target/usr/bin/pi-main-controller

From there, flashing the new image onto the sd card with that same dd command:

dd bs=5M if=output/images/sdcard.img of=/dev/sdX conv=fsync

...then plugging that in and logging in:

...
Welcome to Buildroot
buildroot login: root
Password: 
# pwd
/root
# pi-main-controller
Goobily doo
# 

There's some working code implementing the above in this commit e24ddae. Before reading it though, some notes:

Adding QT support

I found a basic hello world QT application here

#include <QApplication>
#include <QPushButton>

int main(int argc, char* argv[])
{
    QApplication app(argc, argv);
    QPushButton hello("Hello world!");
    hello.resize(100,30);
    hello.show();
    return app.exec();
}

and to modify Buildroot's cross compilation toolchain to compile this, some settings need to be set. Unfortunately, I'm not familliar with it and have no clue what settings need to be set, leaving whatever make raspberrypi3_qt5we_defconfig created:

BR2_PACKAGE_QT5_GL_AVAILABLE=y
BR2_PACKAGE_QT5_JSCORE_AVAILABLE=y
BR2_PACKAGE_QT5=y
BR2_PACKAGE_QT5BASE=y
BR2_PACKAGE_QT5BASE_CUSTOM_CONF_OPTS=""
BR2_PACKAGE_QT5BASE_CONFIG_FILE=""
BR2_PACKAGE_QT5BASE_EXAMPLES=y
BR2_PACKAGE_QT5BASE_NETWORK=y
BR2_PACKAGE_QT5BASE_SQL=y
BR2_PACKAGE_QT5BASE_SQLITE_NONE=y
BR2_PACKAGE_QT5BASE_TEST=y
BR2_PACKAGE_QT5BASE_XML=y
BR2_PACKAGE_QT5BASE_GUI=y
BR2_PACKAGE_QT5BASE_WIDGETS=y
BR2_PACKAGE_QT5BASE_OPENGL=y
BR2_PACKAGE_QT5BASE_OPENGL_ES2=y

BR2_PACKAGE_QT5BASE_EGLFS=y
BR2_PACKAGE_QT5BASE_DEFAULT_QPA=""
BR2_PACKAGE_QT5BASE_PRINTSUPPORT=y
BR2_PACKAGE_QT5BASE_FONTCONFIG=y
BR2_PACKAGE_QT5BASE_GIF=y
BR2_PACKAGE_QT5BASE_JPEG=y
BR2_PACKAGE_QT5BASE_PNG=y
BR2_PACKAGE_QT5BASE_DBUS=y
BR2_PACKAGE_QT5BASE_ICU=y

BR2_PACKAGE_QT5DECLARATIVE=y
BR2_PACKAGE_QT5DECLARATIVE_QUICK=y
BR2_PACKAGE_QT5QUICKCONTROLS=y
BR2_PACKAGE_QT5QUICKCONTROLS2=y
BR2_PACKAGE_QT5SVG=y
BR2_PACKAGE_QT5WEBCHANNEL=y
BR2_PACKAGE_QT5WEBENGINE_ARCH_SUPPORTS=y
BR2_PACKAGE_QT5WEBENGINE=y
BR2_PACKAGE_QT5WEBENGINE_PROPRIETARY_CODECS=y
BR2_PACKAGE_QT5WEBSOCKETS=y

Apparently the build process for a QT application is involved, I see alot of apps using qmake or cmake. Here's the full change swapping from a single C file app to using cmake 2b1df29. Swapping to cmake is easy. CMakeLists.txt is put into packages/pi-main-controller which will get unpacked into $(@D) like every other cmake-packages project in the mainline. The *_BUILD_CMDS function is predefined so that can be removed. Last, generic-package is replaced with cmake-package. Flash it with dd and done. Running pi-main-controller opens up Hello World! in the center with a white background.