How to do C/C++ Development on Torizon
When you develop native C/C++ applications for an embedded target you need a toolchain that provides a compiler and all the libraries you need to convert your code into native code, for a specific target platform.
Development for Torizon is not an exception. Using containers you may not target Torizon OS itself, but the distribution you plan to run inside your application’s container. In this article, we will provide some generic information and some instructions targeting the Debian-based containers we provide as a base to develop your own apps.
This article complies with the Typographic Conventions for Torizon Documentation.
- Learn the basics about Torizon and read the TorizonCore Technical Overview.
- Have a minimal practical hands-on experience with Torizon by going through our Quickstart Guide.
Cross-compilation vs Native Compilation
Usually, applications, running on a PC/server, can be built directly on the target system, installing the compiler and other required components locally. This approach can be very efficient when your system has a good amount of processing power and also the capability of running all the tools needed to edit and debug your code. Many embedded systems are not so powerful and, anyway, it would not be so easy to integrate them with the server-based build system commonly used in CI/CD environments.
In short consider the following between cross/native-compilation:
- Cross-compiling tool-chains can be run on host development machines
- Not all software was meant to be cross-compiled readily so native may be easier for initial evaluation
- More powerful embedded modules in conjunction with an external USB (to limit writes to internal flash) can be fit for native compilation.
Another option is to use emulation to run an ARM32 or ARM64 container on your development PC. In this case, you should expect build times that are one hundred or more times those you can expect with a cross-compilation or when building code natively on the same machine.
Cross-compilation and containers
Cross-compilation within a container environment provides notable advantages for example:
- Isolated build environment with correct version of build tools that don't conflict with the host.
- Build time is roughly the same as a native build.
- Containers consume less overhead resources compared to a full virtual machine.
Cross-compilation and Debian
Debian provides very good support for cross-compilation. It provides pre-packaged cross-compiling toolchains for all the architectures it supports and multi-architecture support.
Multi-arch support allows you to install Debian packages for different architectures on top of a running Debian system. This will allow you, for example, to install armhf or arm64 development packages on an x64 system. Such a feature greatly simplifies building complex codebases with multiple dependencies and, specifically for Torizon where the application can run in a Debian container also on the target, it allows you to quickly generate an image for the target (with the non-dev native packages) where your application can run.
On a Debian system you can add support for a different architecture (armhf in this sample) by running dpkg:
$ dpkg --add-architecture armhf
$ apt-get update
After having installed the new architecture and updated the packages list (this will download up to date package lists also for the new architecture) you can install packages using the standard package-management tools:
$ apt-get install libzip-dev:armhf
You can specify the package required architecture by adding the architecture id after the package name with a colon as the divider.
Torizon Containerized Workflow
To simplify development of applications targeting our standard base containers, Toradex provides some Torizon base SDK containers with the basic toolchains required to target our modules already pre-installed and configured. These are also tagged in such a way to make the choice of which container to use simple.
There are two versions of each architecture-specific SDK. One providing just the build tools, another one that works as an SSH server. The latter can be used for tools, like Visual Studio, that supports Linux development on a remote machine.
SDKs are named
Currently, supported ARCH values are armhf (for 32 bit containers) and arm64 (for 64 bit containers).
How to find the right packages
If you want to use a specific library - or any other build or runtime dependency - in your code, or need to build code that references a specific library to run in a Torizon container, you’ll have to find which Debian packages provide your library for usage at runtime and during development. You will have to install the development package in the build/SDK container and the runtime package in the container you plan to run on the device.
Debian provides hundreds of ready-to-use packages, most of the time available for all the architectures they support. The best place to search for those packages is packages.debian.org. Toradex also provides packages and you can manually browse the Toradex Debian feeds on feeds.toradex.com/debian/, for instance.
Another way to search is to run a Debian Container for Torizon in interactive mode and use
apt-cache search together with
grep to search and filter packages. This approach lists both packages from the official Debian feeds and the Toradex feed. See the example below where we search for QtQuick 2 QML module:
## apt update
## apt-cache search qtquick2 | grep qml
qml-module-qtquick2 - Qt 5 Qt Quick 2 QML module
qml-module-qtquick2-vivante - Qt 5 Qt Quick 2 QML module
qml-module-qtquick2-vivante-dbgsym - debug symbols for qml-module-qtquick2-vivante
As you can see above, some of the packages are
-vivante packages built by Toradex to support hardware acceleration on platforms where we use the downstream NXP kernel, currently all NXP i.MX 8-based SoMs.
As an example of how to add the packages to the containers, consider the following case where we want to build code requiring libzip:
- First search for libzip on the Debian packages site.
- libzip4 is the runtime package that is required by the application to run.
- libzip-dev is the build package that is required to build the application.
If you are not sure about what development package matches a specific runtime and vice-versa you can go on the package’s property page and check the source package. A source package contains the codebase used to build one or more packages and for libraries, this means at least a runtime one and a
Once you found the development package, you can install it in your build/SDK container:
$$ apt-get install libzip-dev:armhf
And install the runtime one in the target container:
## apt-get install libzip4
Notice that for the build container you’ll have to use the architecture-specific suffix.
Docker multi-stage builds can be used to build multiple containers in sequence. This is very useful if you want to automate the build of Torizon applications.
You can use our base SDK container, add the development libraries you need and the code you have to build, build it and then copy the newly built executable in a target container where you also install the runtime libraries.
# take SDK container as base
FROM torizon/debian-cross-toolchain-armhf:2-bullseye AS build
# install all required development packages
RUN apt-get install libzip-dev:armhf ....
# get code you need to build
RUN git clone ...
# build code
RUN cd myapp && make/cmake/etc.
# create 2nd container for target
# install runtime libraries
RUN apt-get install libzip4 ...
# take application executable from build container
COPY /myapp/out/myappexecutable /usr/bin/
# configure container to run your application at startup
It is often the case that an application may require other processes or services that interact with it. However it is generally a best practice to only have 1 main process running per container. That being said you need not limit your containerized solution to just one container.
It is quite common to have a solution that utilizes multiple containers running in unison. For this purpose Docker provides their "docker-compose" tool. This tool allows the orchestration of multi-container solutions. For more information about working with multiple containers and how this can be of benefit please refer to our article about multi-container solutions here.
Import an Existing C/C++ Application
If you have an existing application and want to migrate to Torizon, please read the article How to Import a C/C++ Application to Torizon as a starting point.
Torizon Workflow Examples
This section provides few examples on doing C/C++ application development on Torizon.
Example Introduction: InfluxDB, Grafana and OpenWeatherMap
To get the most from the upcoming sections and test things in practice, we recommend that you clone the torizon-samples repository to your computer:
$ cd ~
$ git clone https://github.com/toradex/torizon-samples.git
$ cd torizon-samples/weather
For this example we will be creating the following:
- A Weatherboard using InfluxDB, Grafana, and OpenWeatherMap.
- Current weather displayed in Grafana and forecast displayed via graphs.
- Current weather will be fetched twice every minute with the forecast fetched once at the start.
For our sample app we will use the free plan which allows for 60 API calls per minute.
If you want to actually run this application yourself, an account needs to be created on openweathermap.org. This is required in order to get the API key needed to make calls to the openweathermap servers. The registration process and how to obtain key is described here. The key is referred to as 'appid' in the requests.
For an actual working application you'll need to update the API key in the demo code. This value is currently hard-coded and needs to be replaced at the following functions in the weather.cpp file.
You can also modify the geographic coordinates you want by modifying these functions:
C++ Cross-compilation using Visual Studio Workflow Example
Specifically for those who have Windows as a development environment, Toradex provides a Visual Studio extension that makes C/C++ development for Torizon simple:
- First, learn how to setup the Visual Studio Extension For Torizon.
- Then, learn how to use the extension following the C/C++ Development and Debugging on Torizon Using Visual Studio.
C++ Cross-compilation using CMake Workflow Example
This section builds on the Multi-stage builds section and shows how to build a C++ application with CMake. The workflow here will be mostly manual using command line utilities to build your application. If you seek a more automated IDE approach to development please consider our IDE integrations mentioned above.
CMake is an open-source, cross-platform family of tools designed to build, test and package software.
Take note of the src directory which contains the main source code files for the application.
To compile this application with cmake we need a couple of files namely armhf-toolchain.cmake and CMakeLists.txt:
- armhf-toolchain.cmake, defines the architecture for cross compilation and the build tools required for cross compiling.
- For more details on toolchain files please refer toe the CMake documentation here.
- CMakelists.txt specifies the C++ standard, linked libraries and build time flags.
- Also makes sure that we have the dependencies available before building the code.
Finally we use the Dockerfile to build a containerized application. This Dockerfile is derived from the information available in the Multi-stage builds section. The Dockerfile also needs to be updated with correct architecture details during build-time when building for a different architecture.
Now that you're familiar with the files we can begin building the container. The following command will create a container image on your development PC.
# for armhf
$ docker-compose build
# for arm64
$ docker-compose build --build-arg IMAGE_ARCH=linux/arm64 --build-arg TOOLCHAIN_ARCH=aarch64 --build-arg PKG_ARCH=arm64
Now that we've built the application container image you'll need to deploy it to your target device. For methods to deploy the container image to the target please see the article here.
For this application, we'll need a total of 3 containers.
- First is our application container we built above.
- Next is a container for Grafana.
- Last is the InfluxDB container.
We'll set up all 3 as shown in this docker-compose.yml file. This will also create the networks and volumes we need, link the containers, and expose Grafana on port 3000. You'll need to copy this file to your target TorizonCore device.
Finally we can start our application using
docker-compose. Make sure you've properly deployed the built container image, and to execute the below command from the same directory you copied
# docker-compose up -d
With the application up and running, you can now point your browser to http://X.X.X.X:3000, where X.X.X.X is the IP Address of the target device. Login with the default credentials (user: admin, password: admin).
Setup data source
- Click add data source
- Select InfluxDB as a data source type
- Set URL to http://influxdb:8086 under HTTP (we're using a docker link, so we can access the InfluxDBcontainer by the container name we set up in our docker-compose file.)
- Under InfluxDB Details, set the Database to "Weather", the database name we created in code.
- Finally, set Min time interval to 3h.
- The forecast API provides data points at 3h intervals. If this value is set to anything under 3h, the forecast graphs will show as dots instead of connected lines/curves.
- Now, click the Save & Test button, it should say "Data source is working."
Import Weather Dashboard
Go to the Dashboards menu and select Manage. To set up the dashboard, click Import then Upload and browse for the
Weather-<timestamp>.json file from the weather sample directory. (You can also choose to paste the JSON.) Finally, under Options, enter Weather in the Name field and click import. The Dashboard is now ready.