This is a collection of build scripts providing unit testing, cross compilation for avr platforms as well as definitions of target platforms developed by the department. It is tested with bazel 0.26 on MacOS and Linux. Please note that we currently have issues with Windows, so we recommend using one of the former systems.
To use the scripts add these lines to your WORKSPACE
file
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
name = "EmbeddedSystemsBuildScripts",
strip_prefix = "EmbeddedSystemsBuildScripts-<version>",
urls = ["https://github.com/es-ude/EmbeddedSystemsBuildScripts/archive/v<version>.tar.gz"]
)
Where <version>
is the version number of the scripts, that you want to use.
For more detailed documentation see docs
To be able to build for avr microcontrollers you add the following lines to your WORKSPACE
:
load("@EmbeddedSystemsBuildScripts//Toolchains/Avr:avr.bzl", "avr_toolchain")
avr_toolchain()
This will generate an external Workspace, containing a toolchain definition for every supported microcontroller, as well as bazel constraints and config_settings to support creating new platforms and make build choices depending on different constraints chosen for the current build.
Use
$ bazel query 'kind(constraint_setting, @Toolchains/Avr//platforms/...)'
to retrieve a list of all defined constraint settings. These are dimensions from which you can choose values to define your own platform.
Note that the constraint_setting board_id
is used by the department to refer to development boards.
To see a list of possible values for constraint call for
$ bazel query 'attr(constraint_setting, <your_constraint_setting_name>, @AvrToolchain//platforms/...)'
You can then use these constraints to create your own platform definitions:
platform(
name = "MyPlatform",
constraint_values = [
"@AvrToolchain//platforms/mcu:atmega328p",
"@AvrToolchain//platforms/cpu_frequency:16mhz",
"@AvrToolchain//platforms/misc:hardware_usart",
],
parents = [":avr_common"],
)
The mcu
constraint is mandatory and used internally to choose the correct toolchain configuration.
To build a target for your platform use
$ bazel build //:myTarget --incompatible_enable_cc_toolchain_resolution=true --platforms //:MyPlatform
Additionally to creating your own platform you can use one of our predefined boards. To get a list call
$ bazel query 'kind(platform, @AvrToolchain//platforms/...)'
In most cases you'll want to apply various build flags to optimize the program size. Calling the build with the flag --compilation_mode opt
will apply a set of flags we found useful for that.
To treat warnings that most probably come from programming errors - e.g. missing return statement - into compiler errors apply the following flag to the bazel call
--features=treat_warnings_as_errors
By default, we compile with the feature called gnu99
, that adds --std=gnu99
to the build command. However, if you want to build with avr-g++ -std=gnu99
is an invalid flag and can be disabled by applying the following flag to the bazel call
--feature=-gnu99
Since v1.0
of the EmbeddedSystemsBuildScripts, the bazel run
command of the AvrToolchain requires a positional argument, which specifies the port of the programmer. Positional arguments are applied with two dashes, after the initial command, i.e.
bazel run //package:target_upload --platform=... -- /dev/ttyXYZ
To be able to build for arm cpu's you add the following lines to your WORKSPACE
:
load("@EmbeddedSystemsBuildScripts//Toolchains/Arm:arm.bzl", "arm_toolchain")
arm_toolchain()
And the following lines to your projects BUILD
file:
load("@ArmToolchain//:helpers.bzl", "generate_stm_upload_script")
generate_stm_upload_script(name = Name)
Additional information can be derived from the AvrToolchain
section above.
While there is no difference in how native and embedded cc_* targets are defined, actually being able to program a device involves more steps.
These are building the binary, converting it to a .hex
file and uploading it to the program memory.
The macro default_embedded_binary
does all that.
load("@AvrToolchain//:helpers.bzl", "default_embedded_binary")
load("@AvrToolchain//platforms/cpu_frequency:cpu_frequency.bzl", "cpu_frequency_flag")
default_embedded_binary(
name = "main",
copts = cpu_frequency_flag(),
srcs = ["main.c"],
deps = [":MyLib"],
)
The above call will create hex file with the target name "main"
, an elf file with the name "main_ELF"
and an upload script that receives the hex file as it's argument named "main_upload"
.
Additionally the cpu_frequency_flag
macro is loaded. It simply resolves the applied cpu frequency constraint to the matching symbol definition flag, accessible in the source files
by the c macro F_CPU
.
Additional copts
can be added by concatenating cpu_frequency_flag()
with a list of the desired copts
, i.e. copts = cpu_frequency_flag() + ["-DDEBUG=1"]
.
Since v.1.0
, the upload_script may need to be specified as an additional argument if you're using the default_embedded_binary
macro. Possible values for this parameter are:
upload_script = "@AvrToolchain//:dfu_upload_script"
or
upload_script = "@AvrToolchain//:avrdude_upload_script"
Building for an ARM target device can be done by using the default_arm_binary
macro.
load("@ArmToolchain//:helpers.bzl", "default_arm_binary", "generate_stm_upload_script")
generate_stm_upload_script(name = arm_upload)
default_arm_binary(
name = "main",
srcs = ["main.c"],
deps = [":MyLib"],
uploader = "arm_upload",
additional_linker_inputs = ["MY_LINKER_FILE.ld"]
linkopts = ["-T MY_LINKER_FILE.ld"]
)
Please be aware, that the upload script needs to be created manually beforehand, and passed into the default_arm_binary
macro as a parameter.
There are two macros unit tests. All can be found in the file
with the label "@EmbeddedSystemsBuildScripts//Unity:unity.bzl"
. First the unity_test
macro: It is responsible for creating a test runner, using the ruby shipped with unity. After generating the test runner it creates a cc_binary from that test runner and the provided source file containing the test functions.
unity_test(
file_name = "MyTest.c"
)
The test can then be called like so
$ bazel test //:MyTest
Attributes listed in the unity_test
call are passed to the internal cc_binary call.
With the help of the mock macro you can use CMock to create mock functions for a specified header file.
Currently the corresponding header file has to be exported with the exports_files
rule from the package that contains it.
exports_files(
srcs = ["Functions.h"],
visibility = ["//visibility:public"],
)
You can then define a mock library, containing the header to control and query the mocks state as well as the object file with the mock implementation by
mock(
name = "MyMock",
srcs = ["//lib:Functions.h"],
deps = ["//lib:FunctionsHdr"]
)
unity_test(
file_name = "MyTest.c",
deps = [":MyMock", "//lib:MyLib"],
)
Note the order of the targets listed in the deps attribute. This will make sure, that the definitions from MyMock are used instead of production code. Often however it will be better to build a small lib containing only the code under test. In order to use the mocked header file instead of the original, the include directive needs to follow this schema
#include "lib/MockFunctions.h"
avrdude
works with different programmers for different boards. Amongst other things, they provide the pin configuration. For more information run man avrdude
in your shell. Currently our build scripts only support the programmers arduino
, which is used for the ArduinoUno and wiring
, used for the ArduinoMega.