Adding custom applications to Buildroot - a simple introduction
Photo Allan Jones

Adding custom applications to Buildroot - a simple introduction

Code examples are at link.

In a previous article I covered building a simple Linux OS for the Raspberry Pi 5 (Buildroot introduction). In this article I'll cover adding a custom application to the image using an external tree.

In the great tradition of embedded programming, the application will be to flash an LED - the 'Hello, World' of embedded! While this is a very simple example is does demonstrate the basic requirements for using buildroot to setup an embedded Linux system.

Why external trees?

The standard buildroot files exist under a single directory, which contains all of the packages, tools and output files. When adding a custom application it can be useful to keep all of its files separate from buildroot so it can be maintained and versioned separately. The external tree system allows custom packages to be added to a buildroot build. These packages can be existing libraries not currently available in buildroot or they can be project specific code.

In the case of case of general-purpose packages it makes sense to try to contribute these back to the buildroot project, but for closed-source projects, or niche, application-specific code and external tree is ideal.

A simple example

The first stage will be to add a very simple executable to the Linux image. The program we add will print out the text "hello" when it is run.

Directory Structure

In these examples I have used a simple directory structure;

work -- buildroot
      |
      |
      -- buildroot-external-demo        

The buildroot directory and my external tree are at the same level in my work directory. If you use a different structure then changes the paths accordingly.

Docker

I am using a docker container to run buildroot so as to isolate its dependencies from my host machine. See the article linked in the introduction.

External tree files

The files for this example are at link.

An external tree consists of a directory containing some top-level files and a package directory that contains the files for each package. The external tree can have one or more packages. It is possible to have more than one external tree.

  • Config.in This gives the paths to the packages.
  • external.desc This gives the name and description of the tree.
  • package The directory containing the packages.

Within the package directory, each package is contained within its own directory. At the top of each packages file are two configuration files.

Config.in

This contains the config string that identifies the package to buildroot and some help text. The help text is used within the buildroot GUI.

config BR2_PACKAGE_HELLO

    bool "hello"

    help

        Hello world package.

        http://example.com        

The second file has a name with must match the name of the package - in this case hello.mk

####################################################################
#
# hello
#
####################################################################

HELLO_VERSION = 1.0

HELLO_SITE = $(BR2_EXTERNAL_DEMO_PATH)/package/hello/src

HELLO_SITE_METHOD = local

$(eval $(cmake-package))        

This file contains the location of the package (in this case a local directory) and the build method (in this case CMake). Full descriptions of these files is given in the buildroot documentation.

The 'src' directory contains the source and build files.

CMakefiles.txt

cmake_minimum_required(VERSION 3.10)

project(hello LANGUAGES C)

add_executable(${PROJECT_NAME} "hello.c")

target_link_libraries(${PROJECT_NAME}

	)

install(TARGETS ${PROJECT_NAME} DESTINATION bin)        

This contains build instructions for our single-file project. Note that the install stage specifies where the executable is to be installed. Buildroot will install the compiled executable to this location in the operating system image.

hello.c

#include <stdio.h>

int main(void) {

    puts("hello");

    return 0;

}        

Running buildroot

First we'll set up a default raspberry Pi 5 configuration. The docker image buildroot-env was created in the previous article. Run from the buildroot directory.docker run -it --rm -v $(pwd):/buildroot buildroot-env make raspberrypi5_defconfig

Next we can customize this image.

docker run -it --rm -v $(pwd):/buildroot -v $(pwd)/../buildroot-external-demo:/external buildroot-env make BR2_EXTERNAL=/external menuconfig        

Note here we are mapping the buildroot external tree as /external inside the docker container. We are then passing the variable BR2_EXTERNAL to buildroot, pointing to the external tree.

From the "System Configuration" page,

  • set a suitable root password
  • set the init system to systemd (we'll need this later)

Article content
System Configuration page

Return to the top-level menu and select "External options". This should show the 'hello' package as an option.

Article content
External options

Select the 'hello' package and save the configuration before exiting.

Build the system;

docker run -it --rm -v $(pwd):/buildroot -v $(pwd)/../buildroot-external-demo:/external buildroot-env make BR2_EXTERNAL=/external        

Once built, copy the image on to an sd card and boot the Raspberry Pi.

sudo dd of=/dev/mmcblk0 if=output/images/sdcard.img status=progress        

Log into the Pi and run the hello command - which will print the text "hello".

A more interesting example

This next example adds an executable that flashes an LED. The code for this is at link.

Hardware setup

Connect an LED with suitable resistor from Pin 11 (GPIO 17) on the Pi to ground.

Article content
Hardware setup

Led-flash package

In addition to the hello package, the external tree now contains the led-flash package. The Config.in file has been modified to add this new package.

The package contains the Config.in and led-flash.mk files, similar to the 'hello' package.

In the source directory we have some simple code to flash the LED and a CMakeLists.txt to build and install the application.

cmake_minimum_required(VERSION 3.10)

project(led-flash LANGUAGES C)

add_executable(${PROJECT_NAME} "led-flash.c")

target_link_libraries(${PROJECT_NAME}
		gpiod
	)

install(TARGETS ${PROJECT_NAME} DESTINATION bin)        

Note that this code relies on and additional library, libgpiod - we will need to ensure that is included in the buildroot configuration.

Running buildroot

Run the configuration GUI.

docker run -it --rm -v $(pwd):/buildroot -v $(pwd)/../buildroot-external-demo:/external buildroot-env make BR2_EXTERNAL=/external menuconfig        

From the External options page add led-flash

Article content
Add led-flash

We will need to add libgpiod, so return to the top-level menu and got to Target packages -> Libraries -> Hardware handling and select gpiod (and optionally the tools package).

Article content
Add libgpiod

Build the system;

docker run -it --rm -v $(pwd):/buildroot -v $(pwd)/../buildroot-external-demo:/external buildroot-env make BR2_EXTERNAL=/external        

Transfer the completed image to an SD card and boot the Pi. Log in as root and run led-flash - the LED should flash!

Adding a service

The code for this example is at link.

For the final example we'll get led-flash to run as a systemd service, so it will start as soon as the system boots. To do this we'll add a post build script that will create the service file. The location of this file is not too important, but it makes sense to keep it with the rest of the external tree.

post-build.sh

#!/bin/sh

set -u
set -e


echo "[Unit]\n\
  Description=LedFlash\n\
\n\

  [Service]\n\
  Type=simple\n\
  ExecStart=/usr/bin/led-flash\n\
\n\

  [Install]\n\

  WantedBy=multi-user.target\n\

\n" > "${TARGET_DIR}/etc/systemd/system/LedFlash.service"        

This service file starts the led-flash executable.

Running buildroot

Start the GUI as before;

docker run -it --rm -v $(pwd):/buildroot -v $(pwd)/../buildroot-external-demo:/external buildroot-env make BR2_EXTERNAL=/external menuconfig        

From the System configuration page, select "Custom scripts to run before creating filesystem images" and add the path to the post-build.sh script. Note this is a space-separated list.

Note the path will be the one inside the docker container! Save and exit the GUI as normal.

Article content
Add post-build script.

At this point a rebuild may be needed this can be forced by running

docker run -it --rm -v $(pwd):/buildroot -v $(pwd)/../buildroot-external-demo:/external buildroot-env make BR2_EXTERNAL=/external clean         

before building.

As normal, transfer the image to an SD card and boot the Pi. The LED should start to flash once the Pi boots.

Conclusion

This rather trivial example has demonstrated the main aspects of setting up a custom operating system with an application-specific package, and having the executable start as a service. These are some of the fundamental parts of setting up a real embedded Linux system.




Buildroot typically doesn’t allow for package mangers, now I saw a setting in the menu configuration to allow RPM, it came with a lot of warnings have you tried a package management with Buildroot?

To view or add a comment, sign in

More articles by Allan Jones

Explore content categories