As part of the training, you were given a BeagleBone Black. We will be using this board to run all the labs, and also just to have fun with it. By the end you should be able to fully customize all the software that is running on the board, and we really mean all of it. But for now, we will start by making sure we can power up this hardware and see some output coming out of it.
You were also given access to a virtual machine image. It is not strictly necessary to use it, though. If you have a computer with a native Linux-based operating system then you can use that. It should also be possible to use WSL but for the moment we will not provide support for this. Feel free to be adventurous, though!
- Start by powering up the virtual machine and logging in.
- Open a terminal. You can press the Windows key and then start typing “terminal”, you should see the appropriate application pop up in the search results.
- Download the following two files:
- https://files.beagle.cc/file/beagleboard-public-2021/images/am335x-debian-11.7-iot-armhf-2023-09-02-4gb.img.xz
- https://github.com/balena-io/etcher/releases/download/v1.19.21/balenaEtcher-linux-x64-1.19.21.zip
- You can use something like
wget
to do this, e.g.,$ wget <file-url>
.
- You can use something like
- The first one is an official Linux-based operating system image created by the BeagleBoard community. The second one is a graphical utility that will allow us to copy the previous image to the micro-SD card used as storage for the BeagleBone Black.
- Be sure to uncompress the ZIP file:
$ unzip balenaEtcher-linux-x64-1.19.21.zip
- Connect the micro-SD card to your computer. If you are using a virtual machine you may need to enable the device so that it is visible to the former.
- In Virtual Box you can click on Devices/USB and then select the entry that corresponds to the card reader.
- Execute
balenaEtcher
and follow the instructions to copy the OS image to the card.If you want to do this from the terminal, make sure you change the working directory to the one that contains the executable:
$ cd balenaEtcher-linux-x64
$ ./balena-etcher
- Eject the micro-SD card from the host computer and insert it in the appropriate slot on the BeagleBone Black board.
- Connect the serial cable to the board and an appropriate USB port on the host computer.
- Connect the power cable to the board and a powerful enough USB port. This will power up the device and you should see some blue LEDs blinking.
- Wait for a bit and then go to Devices/USB in your virtual machine’s menu at the top. You should see a new entry that corresponds to the BeagleBone Black. Enable it.
- Make sure that a new device node called
/dev/ttyACM0
is available. It is possible that the name is different if you are using another environment.
$ file /dev/ttyACM0
/dev/ttyACM0: character special (166/0)
- Open a serial connection to this node using a suitable program (for example,
screen
).
$ sudo apt install screen
$ sudo screen /dev/ttyACM0 115200
- Log in to the board using the default user (
debian
) and password (temppwd
).If everything went well, you should now have access to a shell session running on the BeagleBone Black. Feel free to play around!
Cross-development environments
Embedded development generally begins (and often remains) on a host computer that is a general-purpose computing platform, with a CPU type that is usually quite different than that of the target system. There are a few reasons for this:
- The target system could simply not be powerful enough to compile a sufficiently-enough large program on it.
- Even if it were, you do not need to ship a compiler with your embedded system, so having one there in the first place does not make much sense.
- The storage technology on the target system usually does not lend itself to the usage patterns of a program like a compiler, which will be performing a lot of file I/O during the build process.
For these reasons, it is almost always the case that one of the first things you will need to do when developing for an embedded platform is to prepare a cross-toolchain, that is, a toolchain that can be executed on the host platform but generates code for the target platform.
The BeagleBone Black uses an ARM-based CPU. You will most likely be using a system based on the so-called x64 architecture. These two are incompatible with each other (i.e., you cannot execute a program using x64 instructions on an ARM-based CPU), and therefore the need for a cross-toolchain is pretty clear.
There are many options when it comes to choosing a cross-toolchain. If you are working with an embedded system that already includes such a utility (usually in the form of an SDK of some sort) then simply use that.
However, since one of the goals of this training is to understand how most of these things work under the hood, we will proceed to prepare a cross-compilation toolchain by ourselves.
Further reading: Introduction to cross-compiling for Linux
As mentioned before, most programmers only use a native compiler, that is, one which generates code for the host machine. However, in embedded development, one is usually developing for target hardware which is not the same as the host machine. Therefore, cross-compilers have the concept of target platform which is designated by a so-called triplet. The latter defines information required by the compiler in order to generate code for the target hardware. Confusingly enough, the triplet can consist of 2, 3 or 4 separate pieces. The reason for this is that often there are parts of the triplet which can be deemed irrelevant.
You have probably seen triplets before. They usually follow one of the following patterns:
arch-vendor-os
arch-vendor-kernel-runtime
arch-os
arch-kernel-runtime
This is defined as follows:
arch
: The target CPU architecture. (Mandatory)vendor
: Typically, who built the compiler. Sometimesnone
, sometimesunknown
.os
: Specifies whether the compiler is meant for bare-metal or for a particular operating system. (Mandatory)kernel
: If not a bare-metal compiler, it indicates the kernel on which the code is meant to run.runtime
: This is a somewhat complex entry. It is supposed to indicate the runtime or C library which is being used, e.g.,gnu
ormusl
. However, details of the ABI (such as the calling conventions) and floating-point handling sometimes are also lumped into it.
Reading triplets is often a game on its own:
arm-eabi
: 32-bit ARM-based bare-metal compiler using the EABI calling convention.arm-none-eabi
: Same as above.aarch64-linux-gnueabihf
: 64-bit ARM-based compiler for Linux, using glibc and the EABI calling convention and hard float instructions.
Strictly speaking, there are three triplets involved when building something:Build platform: This triplet describes the machine on which you are building the compiler itself.Host platform: This triplet describes the machine on which you are executing the compiler.Target platform: This triplet describes the machine on which the code generated by the compiler will run.
In principle we could just then grab a source distribution of, say, GCC, and then go through the configuration steps required to build a version of it that can generate code for ARM-based CPUs. This is a perfectly valid approach, and you are encouraged to do it at least once in your life. In the interest of time, though, we will use a third-party tool that will automate some of this process, but not all.
Such tool is called crosstool-NG
. It is very simple to configure and use, and it will give you a feel for the kind of environment we will be dealing with during the training.
There are other tools that can be used to build a cross-toolchain. This is a non-exhaustive list:Buildroot: http://buildroot.uclibc.org/OpenEmbedded: https://www.openembedded.org/wiki/Main_PageYocto Project: https://www.yoctoproject.org/
Keep in mind that these tools do more than just building a cross-toolchain, though. But more often than not you will end up using one of them if you are working on an embedded project.
- Download a copy of the source code of
crosstool-NG
.
$ wget http://crosstool-ng.org/download/crosstool-ng/crosstool-ng-1.26.0.tar.xz
- Uncompress the archive and navigate to the appropriate directory.
$ tar -xf crosstool-ng-1.26.0.tar.xz
$ cd crosstool-ng-1.26.0
- Install some build prerequisites:
$ sudo apt install flex texinfo help2man gawk libtool-bin bison libncurses-dev
- Execute the
configure
script and pass$HOME/cross-toolchain/ct-ng
as the installation prefix.
$ ./configure --prefix=$HOME/cross-toolchain/ct-ng
- Build and install
crosstool-NG
$ make && make install
- After this, you should see the resulting installation in
$HOME/cross-toolchain/ct-ng
. - Change the working directory to
$HOME/cross-toolchain
and execute./ct-ng/bin/ct-ng menuconfig
.
$ cd $HOME/cross-toolchain/ct-ng
$ ./ct-ng/bin/ct-ng menuconfig
- You should see an interface like the following:
- Walk through all the menus and make (or confirm) the following changes:
Paths and misc options
- Set
Local tarballs directory
to${HOME}/cross-toolchain/tarballs
. - Set
Save new tarballs
to yes (i.e., activate the option). - Set
Prefix directory
to${HOME}/cross-toolchain/${CT_TARGET}
.
- Set
Target options
- Set
Target architecture
toarm
. - Set
Floating point
tohardware (FPU)
.
- Set
Operating system
- Set
Target OS
tolinux
.
- Set
Binary utilities
- Enable
binutils libraries for the target
.
- Enable
C-library
- Set
C library
toglibc
. - Set
Version of glibc
to2.25
.
- Set
C compiler
- Unset
Link stdlibc++ statically into the gcc binary
.
- Unset
Debug facilities
- Enable
ltrace
andstrace
.
- Enable
- Exit the configuration interface (be sure to save the configuration!)
- Build the cross-toolchain as configured in the previous step. This will take a while!
$ ./ct-ng/bin/ct-ng build
Once this finishes you will have a custom-built cross-toolchain that can be used to compile programs for the BeagleBone Black. To make sure that it is indeed capable of this, let us try and build a simple program. Remember that we will do this on the host platform (i.e., your computer) and then transfer the resulting program to the target platform.
- Write a simple C program. Anything will work here, but preferably write something to standard output.
- Compile it using the cross-toolchain generated in the previous steps.
$ ~/cross-toolchain/arm-unknown-linux-gnueabihf/bin/arm-unknown-linux-gnueabihf-gcc program.c -o app
- Confirm that the resulting executable was indeed built for an ARM-based CPU.
$ ./app
bash: ./app: cannot execute binary file: Exec format error
$ file app
app: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-armhf.so.3, for GNU/Linux 5.2.17, with debug_info, not stripped
- Power off the BeagleBoard Black and remove the micro-SD card.
- Insert the micro-SD card into the host platform and make sure that it is visible from the virtual machine.
- Copy the executable generated in the previous step to the micro-SD card.
- By default, the operating system on the virtual machine should mount the micro-SD card. The path could be something like
/media/training/rootfs
.
- By default, the operating system on the virtual machine should mount the micro-SD card. The path could be something like
$ cp app /media/training/rootfs/home/debian
- Unmount the micro-SD card, remove it from the host platform and insert it into the appropriate slot in the BeagleBone Black board.
- Follow the steps described in a previous section to log in to the board through a serial connection.
- Once you are there, execute the program that was copied from the host platform.
$ ./app
- You should see whatever it is that you specified the program to write.
Closing thoughts
In the next module we will start looking into the components that make up a Linux-based system and focus on one of them: the bootloader. We will use the toolchain generated earlier to build our own version of the bootloader and then make the BeagleBone Black boot from it. You will notice that the procedure is remarkably similar to what we had to do when configuring and building the toolchain.