ARM Mbed is a powerful embedded framework that supports many modern devices and allows creating complex firmware (like USB devices or Bluetooth LE beacons) using easy high-level C++ API. Although mbed comes with a powerful build system that makes sense of the numerous targets and features, it is optimized for the online compiler, so building the projects offline (and debugging them) could be tricky.

So we decided to make a tool that integrates into the Python-based mbed build system, polls it for supported targets, features and libraries and stores the recovered information in a structured XML file that can be used to create Visual Studio projects for various mbed targets.

Mbed build system

The mbed build system is a part of the mbed Github repository (located in the tools folder). It is pretty sophisticated, so I will show you how to properly hook into it in order to get the structured information about the build process.

In order to build mbed for a certain target (or to create a project doing that), you essentially need the following information:

The exact list of source files (many of them are platform-specific, so just building everything won’t work)

The list of preprocessor macros that the mbed code would expect for that target (like DEVICE_SPI=1)

The list of directories to search for header files

The linker script used to produce the final executable

It is also nice to have a list of configurable options (like UART baud rate) and to be able to change them via GUI.

Mbed internally determines those settings from a combination of JSON files and directory naming rules, so it could be tricky to try replicating that logic outside mbed. Thankfully, the mbed build system is based on Python and provides a relatively easy API for querying that information. E.g. the following Python script will query and display basic build settings for all mbed targets:

import sys from copy import deepcopy from os.path import join, dirname, basename import tools.build_api from tools.export import EXPORTERS from tools.libraries import LIBRARIES from tools.paths import MBED_HEADER from tools.settings import ROOT for target in EXPORTERS['gcc_arm'].TARGETS: toolchain = tools.build_api.prepare_toolchain([ROOT], "", target, 'GCC_ARM', silent=True) fullRes = toolchain.scan_resources(ROOT) print fullRes 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import sys from copy import deepcopy from os . path import join , dirname , basename import tools . build_api from tools . export import EXPORTERS from tools . libraries import LIBRARIES from tools . paths import MBED_HEADER from tools . settings import ROOT for target in EXPORTERS [ 'gcc_arm' ] . TARGETS : toolchain = tools . build_api . prepare_toolchain ( [ ROOT ] , "" , target , 'GCC_ARM' , silent = True ) fullRes = toolchain . scan_resources ( ROOT ) print fullRes

If you run this script under debugger, you will notice that calling toolchain.scan_resources() makes mbed search every subdirectory of the mbed source tree, locate the files that apply to the current target and store them in a convenient ‘resources’ object:

The major problem, however, is that the resources object will also include sources and settings from many auxiliary libraries that are not needed for many projects and will slow down the build if included. Luckily, there is an easy way to scan them separately from the main sources. Each library has a corresponding ‘mbed_lib.json’ file listed in the ‘json_files’ field of the resources, object so doing another scan with the library directories excluded will get the absolute minimum build environment for the selected target:

mbed_library_dirs = [os.path.dirname(j) for j in fullRes.json_files if os.path.basename(j) == 'mbed_lib.json'] mbed_library_dirs = [lib for lib in mbed_library_dirs if lib != os.path.join(ROOT, 'platform') and not os.path.basename(lib).startswith("TARGET_")] minimal_res = toolchain.scan_resources(ROOT, exclude_paths = [os.path.join(ROOT, 'features')] + mbed_library_dirs) 1 2 3 mbed_library_dirs = [ os . path . dirname ( j ) for j in fullRes . json_files if os . path . basename ( j ) == 'mbed_lib.json' ] mbed_library_dirs = [ lib for lib in mbed_library_dirs if lib ! = os . path . join ( ROOT , 'platform' ) and not os . path . basename ( lib ) . startswith ( "TARGET_" ) ] minimal_res = toolchain . scan_resources ( ROOT , exclude_paths = [ os . path . join ( ROOT , 'features' ) ] + mbed_library_dirs )

Features

The mbed framework has a concept of features. An mbed feature is a collection of source files and preprocessor macros that can be added to a project to support certain functionality (like Bluetooth LE). You can easily get a list of features for each target by querying the resources.features dictionary:

for feature in fullRes.features: featureRes = fullRes.features[feature] print featureRes 1 2 3 for feature in fullRes . features : featureRes = fullRes . features [ feature ] print featureRes

The keys in the dictionary are feature names; the values are ‘resources’ object equivalent to the ones created for the targets.

Libraries

In addition to features and the implicit libraries, mbed also defines 8 libraries (via the tools.libraries.LIBRARIES object) that extend your projects with functionality like USB host/device or RTOS. Getting structured information about them is also as simple as calling toolchain.scan_resources():

for lib in LIBRARIES: sourceDirs = lib['source_dir'] if isinstance(sourceDirs, str): sourceDirs = [sourceDirs] for srcDir in sourceDirs: libToolchain = deepcopy(toolchain) libRes = libToolchain.scan_resources(srcDir) 1 2 3 4 5 6 7 8 for lib in LIBRARIES : sourceDirs = lib [ 'source_dir' ] if isinstance ( sourceDirs , str ) : sourceDirs = [ sourceDirs ] for srcDir in sourceDirs : libToolchain = deepcopy ( toolchain ) libRes = libToolchain . scan_resources ( srcDir )

Configurable Properties

Each target, feature or library can define configurable properties that will affect the behavior of the project. They can be queried by first loading the resources via toolchain.config.load_resources() and then calling toolchain.config.get_config_data():

for feature in fullRes.features: featureToolchain = deepcopy(toolchain) featureRes = fullRes.features[feature] featureToolchain.config.load_resources(featureRes) (settings, macros) = featureToolchain.config.get_config_data() print settings 1 2 3 4 5 6 for feature in fullRes . features : featureToolchain = deepcopy ( toolchain ) featureRes = fullRes . features [ feature ] featureToolchain . config . load_resources ( featureRes ) ( settings , macros ) = featureToolchain . config . get_config_data ( ) print settings

The settings are often annotated, so you can easily see what each of them does:

Putting it all together

So having extracted the detailed build settings for each mbed target, we made a simple tool that compiled 3 basic projects on each of them using the extracted settings:

The simplest “Blinking LED”

A multi-threaded version of “Blinking LED” using the RTOS included with mbed

A virtual COM port over USB (CDC device class)

The results were not bad at all: LEDBlink built successfully on 142 out of 152 targets (the ones that failed mostly had minor bugs in the target drivers). The virtual COM port succeeded on all supported targets except EFM32 that seems to have a mismatch between the USB driver and the underlying low-level driver. The multi-threaded blinking LED was too large to fit into the smallest devices, but otherwise built successfully.

Trying it out

You can download the source code for the tool that extracts mbed build settings from our repository on Gihtub. Or if you prefer things that work out-of-the-box, give VisualGDB a try and simply create a buildable and debuggable mbed-based Visual Studio project using its wizard:

We are always interested in your feedback, so feel free to drop us a line via support@sysprogs.com if you have questions or suggestions regarding our mbed integration.