Build System Generators¶
lbuild module: modm:build
This parent module defines a common set of functionality that is independent of the specific build system generator implementation. This includes straight-forward options like project name and build path but also more complicated configuration for programming your target via AvrDude or OpenOCD and debugging via GDB.
Note that this module does not compile your project, you will need to choose
the modm:build:scons
or modm:build:cmake
submodule for that, or provide
your own build system.
Compiler Options¶
We maintain a common set of compiler options for all build system generator, so that they all have feature parity. We currently only support compiling modm with GCC for AVR, ARM Cortex-M and x86/posix with the options mentioned in the offical GCC options documentation.
You can find all compiler options inside the generated build script for your project, the options presented here are only the most important ones.
Shared for C and C++¶
-W{all, extra}
: a basic set of warnings.-Werror={format, maybe-uninitialized, overflow, sign-compare}
: these warnings are treated as errors.-f{data, function}-sections
: puts data and functions into their own linker section.-funsigned-{char, bitfields}
: modm tries to usestdint.h
types everywhere, but just in case.-fwrapv
: integer overflows wrap around according to 2s complement.
For release builds:
-Os
: optimize for smaller size.
For debug builds:
-Og
: optimize for debugging experience.MODM_DEBUG_BUILD
: this macro is only defined in debug profile. You can use it with#ifdef MODM_DEBUG_BUILD
to enable debug code.
Only C¶
-std=gnu2x
: use C23 with GNU extensions (forasm volatile
).
Only C++¶
-std=c++23
: use C++23
For exception and RTTI flags, see modm:stdc++
module.
Linker¶
--gc-section
: garbage collecting sections throws out a lot of unused data/code.-L{linkdir} -Tlinkerscript.ld
: modm uses a custom linkerscript.
For target specific flags, see the modm:platform:core
and related modules.
Configuration¶
This module generates a common set of configuration files that are used by the common tooling. Please note that these files are the foundation of more extensive tooling available as Python scripts which are then again wrapped by your chosen build system for convenience.
OpenOCD¶
For accessing your ARM Cortex-M based device, we use OpenOCD by default and
generate a modm/openocd.cfg
file with the target specific configuration:
- Search directories passed via the
path.openocd
collector. - User configuration files passed via the
openocd.source
collector. Your custommodm:build:openocd.cfg
is added here too.
You need to start openocd with this configuration file:
openocd -f modm/openocd.cfg
Be careful attaching to a running target
The OpenOCD implementation halts the target at least while the device's debug peripheral is initialized. Only connect to systems that cannot create any damage while being halted! For example halting motor controllers may damage motors!!
AvrDude¶
Unfortunately AvrDude does not support a project-specific configuration file
like OpenOCD does (only a undocumented user config in ~/.avrduderc
), so there
is no convenient one-line command to issue. You have to use the wrapper support
of the specific build system or simply call AvrDude yourself via its command
line.
GDB¶
A few commands are provided for convenience via the modm/gdbinit
configuration:
reset
: resets the device and halts.rerun
: resets the device and continues execution.modm_coredump
: Dumps all volatile memories into acoredump.txt
file. See themodm:platform:fault
module for details.modm_build_id
: Finds and prints the GNU build id of the firmware.
GDB continues running the target after attaching, but does not load an ELF file! Please pass the ELF file as a command line argument.
You can start your GDB session like so:
arm-none-eabi-gdb -x modm/gdbinit path/to/project.elf
Generic Python Tools¶
We have written a number of pure Python tools to provide common functionality that get wrapped by the build system.
Here is a selection of tools that have a command line interface, so you can call
them even without build system support in case you have a special setup.
Note that there are even more tools that can be called in Python only, so have
a look in your generated modm/modm_tools
folder.
Add modm_tools
to your Python path
To call modm_tools
via module syntax, you need to add the generated modm
folder to your Python path: export PYTHONPATH=path/to/generated/modm
.
You can also use the module in this repository directly.
AvrDude¶
This tool simply wraps the avrdude
command to provide two features:
- guessing the serial port when a baudrate was set.
- inspecting the ELF file to decide whether EEPROM needs to be programmed.
python3 -m modm_tools.avrdude -p m328p -c arduino -P auto -b 57600 \\
path/to/project.elf
Fuses stored in the ELF file
can be programmed by passing --fuse
arguments:
python3 -m modm_tools.avrdude -p m328p -c stk500v2 path/to/project.elf \\
--fuse hfuse --fuse lfuse --fuse efuse
(* only AVR targets)
OpenOCD¶
Simply wraps OpenOCD and issues the right command to program the target.
python3 -m modm_tools.openocd -f modm/openocd.cfg path/to/project.elf
You can also reset the target:
python3 -m modm_tools.openocd -f modm/openocd.cfg --reset
You can use a different OpenOCD binary by setting the MODM_OPENOCD_BINARY
environment variable before calling this script. This can be useful when
using a custom OpenOCD build for specific targets.
export MODM_OPENOCD_BINARY=/path/to/other/openocd
(* only ARM Cortex-M targets)
Black Magic Probe¶
This tool wraps GDB to program an ELF file onto a target connected to a BMP. You can explictly pass the serial port, or let the tool guess it.
python3 -m modm_tools.bmp path/to/project.elf
# or choose the port explicitly
python3 -m modm_tools.bmp path/to/project.elf -p /dev/tty.usbserial-123
You can also reset the target:
python3 -m modm_tools.bmp --reset
(* only ARM Cortex-M targets)
GDB¶
For debugging your program on ARM Cortex-M device, this Python tool wraps
arm-none-eabi-gdb
and connects it to a number of programmers running in the
background or remotely.
The tool can be called from the command line. Here is a typical use-case using the openocd backend with the common configuration files:
python3 -m modm_tools.gdb --elf path/to/project.elf --ui=tui \
-x modm/gdbinit -x modm/openocd_gdbinit \
openocd -f modm/openocd.cfg
Or you can call the Python API directly:
import sys
sys.path.append("modm")
from modm_tools import gdb
from modm_tools.openocd import OpenOcdBackend
backend = OpenOcdBackend(config="modm/openocd.cfg")
gdb.call(source="path/to/project.elf", backend=backend,
config=["modm/gdbinit", "modm/openocd_gdbinit"], ui="tui")
This configuration starts the OpenOCD process in the background for you,
however, if you want to connect to an already running process, you can use the
remote backend with the --host={ip or hostname}
via the command line:
# Extended-Remote running remotely
python3 -m modm_tools.gdb --elf path/to/project.elf -x modm/gdbinit --ui=tui \
remote --host 123.45.67.89
Note that you can use different programmer backends to GDB, for example the Black Magic Probe:
# Black Magic Probe
python3 -m modm_tools.gdb --elf path/to/project.elf -x modm/gdbinit --ui=tui \
bmp --port /dev/tty.usbserial-123
To analyze a core dump, you can use the CrashDebug
GDB backend.
See the modm:crashcatcher
module for details.
# Using CrashDebug for Post-Mortem debugging
python3 -m modm_tools.gdb --elf path/to/project.elf -x modm/gdbinit --ui=tui \
crashdebug --dump coredump.txt
(* only ARM Cortex-M targets)
Currently three UIs are implemented for debugging:
--ui=cmd
: No UI, only the GDB command shell.--ui=tui
: Text-based UI in your shell.--ui=gdbgui
: Web-based UI in your browser, based on gdbgui.
Text UI¶
This UI is builtin to GDB and is therefore always available.
┌——main.cpp———————————————————————————————————————————————————————┐
>│194 DRAW(x+1, y+3); │
│195 DRAW(x+2, y+3); │
│196 #else │
│197 DRAW(x , y ); │
│198 #endif │
│199 #undef DRAW │
│200 } │
│201 │
│202 static inline void drawScreen(framebuffer_t before, frame│
└—————————————————————————————————————————————————————————————————┘
>│0x80017a0 <game_of_life()+1692> strh.w r3, [r4, r12, lsl #1] │
│0x80017a4 <game_of_life()+1696> add r0, lr │
│0x80017a6 <game_of_life()+1698> ldr r2, [r2, #0] │
│0x80017a8 <game_of_life()+1700> strh.w r3, [r2, r0, lsl #1] │
│0x80017ac <game_of_life()+1704> ldr r3, [sp, #12] │
│0x80017ae <game_of_life()+1706> ldr r2, [sp, #0] │
│0x80017b0 <game_of_life()+1708> add r2, r3 │
│0x80017b2 <game_of_life()+1710> ldrb r3, [r7, r1] │
│0x80017b4 <game_of_life()+1712> strb r3, [r2, r1] │
└—————————————————————————————————————————————————————————————————┘
extended-r Remote target In: game_of_life L194 PC: 0x80017a0
Program received signal SIGINT, Interrupt.
0x080017a0 in drawPixel (color=<optimized out>, y=42, x=578) at main.c
(gdb)
GDB can change terminal configuration
Sometimes GDB quits uncleanly and a part of the TUI configuration is not
reset, in particular, the terminal may not display carriage returns
correctly anymore. In this case, calling stty sane
can reset the terminal
to its correct rendering state.
Web UI¶
This UI simply uses the gdbgui project and works very well as an advanced IDE-independent debugging solution.
Size Report¶
Inspects the ELF file and generates a size report of the static usage of the device's memories. You must pass the available memory segments as a Python dictionary:
python3 -m modm_tools.size path/to/project.elf \\
"[{'name': 'flash', 'access': 'rx', 'start': 134217728, 'size': 65536}, \\
{'name': 'sram1', 'access': 'rwx', 'start': 536870912, 'size': 20480}]"
Program: 1.4 KiB / 64.0 KiB (2.2% used)
(.build_id + .fastcode + .fastdata + .hardware_init + .rodata +
.table.copy.intern + .table.heap + .table.zero.intern + .text + .vector_rom)
Data: 3.0 KiB / 20.0 KiB (15.1% used) = 20 B static (0.1%) + 3072 B stack (15.0%)
(.bss + .fastdata + .stack)
Heap: 17.0 KiB / 20.0 KiB (84.9% available)
(.heap1)
(* only ARM Cortex-M targets)
Information Tool¶
This tool generates a set of source files containing information about the repository state.
You can use the --check-rebuild
flag to only write the output file when the
information changed. This prevents unnecessary rebuilding and relinking.
Respect developers privacy
This information is placed into the firmware in cleartext, so it will be trivial to extract from a memory dump. Consider this information public as soon as it is uploaded to your target. Make sure you only use the information you absolutely need!
Git Information¶
python3 -m modm_tools.info -o git_info.c -t git [--check-rebuild]
python3 -m modm_tools.info -o git_info.c -t git_status [--check-rebuild]
The git_info(directory)
function returns a dictionary with these values:
MODM_GIT_SHA
: commit hash:%H
.MODM_GIT_SHA_ABBR
: short commit hash:%h
.MODM_GIT_SUBJECT
: commit subject as text:%s
.MODM_GIT_AUTHOR
: author name:%an
.MODM_GIT_AUTHOR_EMAIL
: author email:%ae
.MODM_GIT_AUTHOR_DATE
: authoring date:%ad
.MODM_GIT_AUTHOR_DATE_TIMESTAMP
: authoring date as Unix timestamp:%at
.MODM_GIT_COMMITTER
: committer name:%cn
.MODM_GIT_COMMITTER_EMAIL
: committer email:%ce
.MODM_GIT_COMMITTER_DATE
: committer date:%cd
.MODM_GIT_COMMITTER_DATE_TIMESTAMP
: committer das as Unix timestamp:%ct
.MODM_GIT_CONFIG_USER_NAME
: local user name:user.name
.MODM_GIT_CONFIG_USER_EMAIL
: local user email:user.email
.
The git_info(directory, with_status=True)
function returns these additional values:
MODM_GIT_MODIFIED
: number of modified files:M
.MODM_GIT_ADDED
: number of added files:A
.MODM_GIT_DELETED
: number of deleted files:D
.MODM_GIT_RENAMED
: number of renamed files:R
.MODM_GIT_COPIED
: number of copied files:C
.MODM_GIT_UNTRACKED
: number of untracked files:?
.
This example project is showing an unclean repository state with uncommitted changes. This can give you a few hints as to where a firmware came from and help you pinpoint the source of a bug or feature.
Local Git User:
Name: Name Surname
Email: name.surname@example.com
Last Commit:
SHA: 1b5a9a642857182161a615039c92907e59881614
Abbreviated SHA: 1b5a9a642
Subject: wip
Author:
Name: Name Surname
Email: name.surname@example.com
Date: Tue Jul 17 22:23:20 2018 +0200
Timestamp: 1531859000
Committer:
Name: Name Surname
Email: name.surname@example.com
Date: Tue Jul 17 22:23:20 2018 +0200
Timestamp: 1531859000
File Status:
Modified: 10
Added: 0
Deleted: 0
Renamed: 0
Copied: 0
Untracked: 6
Build Information¶
python3 -m modm_tools.info -o build_info.c -t build --compiler=gcc [--check-rebuild]
Generates a files with these values defined as const char *
strings:
MODM_BUILD_PROJECT_NAME
: as defined in themodm:build:project.name
option.MODM_BUILD_MACHINE
: machine information.MODM_BUILD_USER
: user information.MODM_BUILD_OS
: OS version string (best effort, may not be useful!).MODM_BUILD_COMPILER
: compiler information.
Example output on macOS:
Project: build_info
Machine: name.local
User: user
OS: macOS 10.14.6 (x86_64)
Compiler: g++-10 10.2.0
JLink¶
Simply wraps JLinkGDBServer and issues the right command to program the target.
python3 -m modm_tools.jlink -device STM32F469NI path/to/project.elf
You can also reset the target:
python3 -m modm_tools.jlink -device STM32F469NI --reset
You can set the MODM_JLINK_BINARY
environment variable to point this script to
a specific JLinkGDBServer
binary:
export MODM_JLINK_BINARY=/path/to/other/JLinkGDBServer
(* only ARM Cortex-M targets)
Unittest¶
This tools scans a directory for files ending in _test.hpp
, extracts their
test cases and generates a source file containing the test runner.
python3 -m modm_tools.unit_test path/containing/tests path/to/generated_runner.cpp
Note that the files containing unittests must contain one class that inherits
from the unittest::TestSuite
class, and test case names must begin with
test
:
class TestClass : public unittest::TestSuite
{
public:
void testCase1();
}
Logging via Single-Wire Output (SWO)¶
Logging using the SWO protocol is supported by the modm:platform:itm
module.
You can use OpenOCD to receive the output, but you must manually supply the
CPU frequency:
python3 -m modm_tools.itm openocd -f modm/openocd.cfg --fcpu 48000000
JLink is also supported and can determine the CPU frequency automatically:
python3 -m modm_tools.itm jlink -device STM32F469NI
(* only ARM Cortex-M targets)
Logging via Real-Time Transport (RTT)¶
Logging using the RTT protocol is supported by the modm:platform:rtt
module.
You can use OpenOCD to send and receive on a channel of your choice using the
built-in Python telnet client:
python3 -m modm_tools.rtt --channel 0 openocd -f modm/openocd.cfg
JLink is also supported and may have faster transfer rates:
python3 -m modm_tools.rtt --channel 0 jlink -device STM32F469NI
(* only ARM Cortex-M targets)
GNU Build-ID¶
To extract the build ID from an ELF file:
python3 -m modm_tools.build_id path/to/project.elf
fa8918e6971ed783b25bff0ad11a0541be47a933
To copy the ELF file to a artifact cache:
python3 -m modm_tools.build_id path/to/project.elf --cache path/to/folder
# copies to `path/to/folder/fa8918e6971ed783b25bff0ad11a0541be47a933.elf`
(* only ARM Cortex-M targets)
Bitmap¶
This tool can convert P1 .pbm
files into C++ source code.
python3 -m modm_tools.bitmap image.pbm --outpath .
# creates `image.hpp` and `image.cpp` in the CWD
UF2 Converter¶
UF2 is a Microsoft file format to pass to a on-device bootloader.
python3 -m modm_tools.elf2uf2 firmware.elf -o firmware.uf2 --target rp2040 \
--range 0x10000000:0x15000000:CONTENTS \
--range 0x20000000:0x20042000:NO_CONTENTS
(* only ARM Cortex-M targets)
Options¶
avrdude.baudrate¶
AvrDude programmer baudrate
This option is only available for avr.
Default: 0
Inputs: [0 ... +Inf]
avrdude.options¶
AvrDude programmer options
This option is only available for avr.
Default: ""
Inputs: [String]
avrdude.port¶
AvrDude programmer port
This option is only available for avr.
Default: ""
Inputs: [String]
avrdude.programmer¶
AvrDude programmer
This option is only available for avr.
Default: ""
Inputs: [String]
build.path¶
Build Path
The build path is defaulted to build/{modm:build:project.name}
.
If you have a lot of embedded projects, you may want to change the build path
to a common directory so that you don't have build/
folders everywhere.
Remember to add your build path to your .gitignore
.
You should use a relative path instead of an absolute one, so that this option still works for other developers.
Default: build/modm
Inputs: [Path]
image.source¶
Path to directory containing .pbm files
Default: []
Inputs: [Path]
info.build¶
Generate build state information
Default: no
Inputs: [yes, no]
info.git¶
Generate git repository state information
Info
: generates information about the last commit.Info+Status
: likeInfo
plus git file status.
Default: Disabled
Inputs: [Disabled, Info, Info+Status]
openocd.cfg¶
Path to a custom OpenOCD configuration file
If you have a custom configuration file for your target, it will get included
by the generated modm/openocd.cfg
.
This is useful for not having to duplicate your config if you have several projects using the same target (like small bring-up and test projects).
Do not execute commands by default
When providing your own config file, wrap your specific commands into functions
and do not execute them by default. A stray init
or similar in your script
will mess with modm's ability to program and debug a device correctly.
This option is only available for rp, sam, stm32.
Default: []
Inputs: [Path]
project.name¶
Project Name
The project name defaults to the folder name you're calling lbuild from.
It's used by your build system to name the executable and it may also be passed to your application via a string constant or CPP define.
Default: modm
Inputs: [String]
unittest.source¶
Path to directory containing unittests
When this path is declared, the generated build script will compile only the unittests, not your application source code! You must use separate project configurations for compiling your unittest and application!
Default: []
Inputs: [Path]
Collectors¶
archflags¶
Compiler flags related to the target architecture
Flags must start with '-'! See Machine-Dependent Options
Inputs: [String]
archflags.debug¶
Compiler flags related to the target architecture (debug profile)
Flags must start with '-'! See Machine-Dependent Options
Inputs: [String]
archflags.release¶
Compiler flags related to the target architecture (release profile)
Flags must start with '-'! See Machine-Dependent Options
Inputs: [String]
asflags¶
Assembler flags
Flags must start with '-'! See Assembler Options
Inputs: [String]
asflags.debug¶
Assembler flags (debug profile)
Flags must start with '-'! See Assembler Options
Inputs: [String]
asflags.release¶
Assembler flags (release profile)
Flags must start with '-'! See Assembler Options
Inputs: [String]
bossac.options¶
Additional BOSSAc options
This collector is only available for sam.
Inputs: [String]
ccflags¶
Compiler flags for both C and C++ sources
Flags must start with '-'! See Options that Control Optimization See Options to Request or Suppress Warnings See Options for Debugging Your Program
Inputs: [String]
ccflags.debug¶
Compiler flags for both C and C++ sources (debug profile)
Flags must start with '-'! See Options that Control Optimization See Options to Request or Suppress Warnings See Options for Debugging Your Program
Inputs: [String]
ccflags.release¶
Compiler flags for both C and C++ sources (release profile)
Flags must start with '-'! See Options that Control Optimization See Options to Request or Suppress Warnings See Options for Debugging Your Program
Inputs: [String]
cflags¶
Compiler flags only for C sources
Flags must start with '-'! See Options Controlling C Dialect
Inputs: [String]
cflags.debug¶
Compiler flags only for C sources (debug profile)
Flags must start with '-'! See Options Controlling C Dialect
Inputs: [String]
cflags.release¶
Compiler flags only for C sources (release profile)
Flags must start with '-'! See Options Controlling C Dialect
Inputs: [String]
cppdefines¶
Preprocessor definitions
Accepted values are NAME
or NAME=DEFINITION
.
See -D name=definition
in Preprocessor Options
Inputs: [String]
cppdefines.debug¶
Preprocessor definitions (debug profile)
Accepted values are NAME
or NAME=DEFINITION
.
See -D name=definition
in Preprocessor Options
Inputs: [String]
cppdefines.release¶
Preprocessor definitions (release profile)
Accepted values are NAME
or NAME=DEFINITION
.
See -D name=definition
in Preprocessor Options
Inputs: [String]
cxxflags¶
Compiler flags only for C++ sources
Flags must start with '-'! See Options Controlling C++ Dialect
Inputs: [String]
cxxflags.debug¶
Compiler flags only for C++ sources (debug profile)
Flags must start with '-'! See Options Controlling C++ Dialect
Inputs: [String]
cxxflags.release¶
Compiler flags only for C++ sources (release profile)
Flags must start with '-'! See Options Controlling C++ Dialect
Inputs: [String]
default.avrdude.baudrate¶
Default AvrDude baudrate
This collector is only available for avr.
Inputs: [0 ... +Inf]
default.avrdude.options¶
Default AvrDude options
This collector is only available for avr.
Inputs: [String]
default.avrdude.port¶
Default AvrDude port
This collector is only available for avr.
Inputs: [String]
default.avrdude.programmer¶
Default AvrDude programmer
This collector is only available for avr.
Inputs: [String]
gitignore¶
Generated files that need to be ignored by Git
Inputs: [Path]
library¶
Libraries to link against
Inputs: [String]
linkflags¶
Linker flags
Flags must start with '-'! See Options for Linking
Inputs: [String]
linkflags.debug¶
Linker flags (debug profile)
Flags must start with '-'! See Options for Linking
Inputs: [String]
linkflags.release¶
Linker flags (release profile)
Flags must start with '-'! See Options for Linking
Inputs: [String]
openocd.source¶
Additional OpenOCD source files
You can add multiple source files that will get included by the generated
modm/openocd.cfg
to provide a default config for targets and boards.
You can add source files that are shipped with OpenOCD, for example,
board/stm32f469discovery.cfg
, or custom source files from your own repository.
To avoid name clashes with the built-in config files, you should copy your own source files into a separate folder and add it as a search path:
def build(env):
# Add a custom folder to the OpenOCD search paths
env.collect("modm:build:path.openocd", "repo/src/openocd/")
# Namespace this folder with your repository name to prevent name clashes
env.outbasepath = "repo/src/openocd/repo/board"
env.copy("board.cfg", "name.cfg")
# Now use a *relative* path to the source file inside this folder
env.collect("modm:build:openocd.source", "repo/board/name.cfg")
# Alternatively for a target config
env.outbasepath = "repo/src/openocd/repo/target"
env.copy("target.cfg", "name.cfg")
env.collect("modm:build:openocd.source", "repo/target/name.cfg")
This collector is only available for rp, sam, stm32.
Inputs: [Path]
path.include¶
Search path for header files
Inputs: [Path]
path.library¶
Search path for static libraries
Inputs: [Path]
path.openocd¶
Search path for OpenOCD configuration files.
This collector is only available for rp, sam, stm32.
Inputs: [Path]
pkg-config¶
Packages to configure against
Inputs: [String]
Queries¶
avrdude_options¶
Merges the default AvrDude options with the user options (* post-build only):
avrdude_programmer
avrdude_port
avrdude_baudrate
avrdude_options
:returns: options dictionary
collect_flags¶
Scans the collections for module compile flags.
Converts them into SCons-compatible names and places them into a dictionary
of the form: flags[filename][name][profile] = list(values)
(* post-build only).
:param env: the post_build step env :param scope_filter: the collection scope filter :returns: compile flags dictionary
device¶
Extracts common properties from a modm:target device:
- platform
- family
- partname
- core
- mcu (AVR only)
:returns: a dictionary of common properties.
memories¶
Extracts the memory map of the device. A memory region is a dictionary containing:
name
of regionstart
address of regionsize
of regionaccess
of region
:returns: a list of memory regions.
source_files¶
Builds a list of files that need to be compiled per repository (* post-build only).
:returns: a dictionary of sorted lists of filenames, keyed by repository.