Getting Started¶
The easiest way for you to learn about modm's APIs is to look at and experiment with our examples, especially if you have a development board that modm supports out-of-box.
Make sure you've installed all tools required for building modm.
TL;DR¶
To compile any example:
cd modm/examples/generic/blinky # cd into the example
lbuild build # generate modm library (call only once)
scons program # compile and upload to your development board
To debug with GDB in TUI mode:
scons program profile=debug # compile and upload debug profile
scons debug profile=debug # launch OpenOCD and GDB for debugging
To generate your target specific Doxygen documentation:
(cd modm/docs && doxygen doxyfile.cfg) # may take a minute or two
# open modm/docs/html/index.html
To remove it all:
scons -c # removes build artifacts
lbuild clean # removes generated files
Quickstart¶
To generate the modm library for the specific example target, call
lbuild build
You can then look at the generated modm code in the local modm/src/modm
folder.
Most of our examples compile with SCons by default, however you can generate a CMake build script by including the CMake build script generator module.
lbuild build -m "::cmake"
To compile the example and modm, call SCons or CMake:
scons
# or for CMake
make cmake # call just once
make build-release
To upload the example to your development board:
scons program
# or for CMake
make upload-release
You can also debug your examples. Make sure you've compiled and uploaded the debug profile first, because debugging a release profile is annoying:
scons program profile=debug
# or for CMake
make upload-debug
Then just do this to open up GDB in TUI mode:
scons gdb profile=debug
# or for CMake
make openocd
# open another shell to this location
make gdb
Interesting Examples¶
We have hundreds of examples but here are some of our favorite ones for our supported development boards:
- Arduino Uno: Blinky, Button & Serial, Analog & Serial.
- NUCLEO-F031K6: Blinky & Serial.
- NUCLEO-F103RB: Blinky & Serial, Debugging hard faults.
- STM32F072 Discovery: Blinky, CAN, Gyroscope.
- STM32F3 Discovery: Blinky, CAN, Accelerometer, Gyroscope.
- STM32F4 Discovery: Blinky, CAN, Accelerometer, Timer & LED Animations.
- STM32F469 Discovery: Blinky, Drawing on display, Touchscreen inputs, Multi-heap with external 16MB memory, Game of Life in Color with Multitouch
- STM32F769 Discovery: FPU with double precision
Here are some additional examples of displays and sensors we like:
- SSD1306 OLED display: Draws text and graphics onto I2C display.
- BMP085/BMP180 barometer: Reads atmospheric pressure and temperature from I2C sensor.
- BMP180/BME280 barometer: Reads atmospheric pressure and temperature from multiple I2C sensors.
- VL6180 time-of-flight distance sensor: Reads distance and ambient light from I2C sensor.
- VL53L0 time-of-flight distance sensor: Much improved version of the VL6180 sensor.
- ADNS9800 motion sensor: Reads 2D motion from SPI sensor used in gaming mice.
- TCS3414 color sensor: Reads RGB color from I2C sensor.
- HD44780 over I2C-GPIO expander: Draws text via native GPIO port or I2C-GPIO expander port onto character display.
Copy Carefully¶
When copying from our examples make sure to set the repository path correctly!
All example modm/examples/**/project.xml
files are missing this path, since we
set it in the inherited base modm/examples/lbuild.xml
configuration.
<library>
<repositories>
<repository><path>path/to/modm/repo.lb</path></repository>
</repositories>
</library>
Your own Project¶
To generate a modm library for your own project, you need to define a
project.xml
file, which contains the path to where modm is, as well as
repository and module options and of course which modules you want to have
generated. Even though modm will generate a library that is self-contained,
we still recommend adding modm as a git submodule for reproducibility.
This guide assumes the following project layout:
$ tree
.
├── modm
│ ├── repo.lb
│ └── {a lot of stuff}
└── project_name
├── main.cpp
└── project.xml
Place your applications into their own folder!
All modm build systems search recursively for application sources inside the current folder. If you place the modm library repository into your application folder you will see build errors related to building sources twice:
scons: *** Multiple ways to build the same target were specified for: ...
Using a Board Support Package¶
To build on a BSP, inherit from an existing project configuration using the
<extends>
element. You can discover the available configuration aliases using
lbuild:
$ lbuild --repository ../modm/repo.lb discover
Parser(lbuild)
╰── Repository(modm @ ../modm) modm: a barebone embedded library generator
├── Configuration(modm:arduino-uno) Arduino UNO
├── Configuration(modm:blue-pill) Blue Pill
├── Configuration(modm:disco-f469ni) STM32F469IDISCOVERY
├── Configuration(modm:nucleo-f401re) NUCLEO-F401RE
├── Configuration(modm:olimexino-stm32) Olimexino STM32
╰── Configuration(modm:stm32f030_demo) STM32F030 Demo Board
Our BSPs declare a minimal set of modules as dependencies as well as pre-define several important options for this board. You can then add all the modules you need and configure them as you want.
<library>
<repositories>
<!-- path to modm repository -->
<repository>
<path>../modm/repo.lb</path>
</repository>
</repositories>
<!-- extend this board configuration -->
<extends>modm:disco-f469ni</extends>
<options>
<!-- give this project a custom name -->
<option name="modm:build:project.name">test</option>
</options>
<modules>
<!-- include the SCons build module -->
<module>modm:build:scons</module>
</modules>
</library>
Choose a build system
Our BSPs do not specify a build system generator, so you need to add the module yourself if you want. Here we use the SCons build system generator, but you can choose others as well.
Our board support packages provide their configuration in the Board
namespace,
which you can use to initialize the target and several board subsystems.
If a serial connection is available on the board, you can directly use the modm
logging functions.
#include <modm/board.hpp>
int main()
{
Board::initialize();
Board::Leds::setOutput();
while (true)
{
Board::Leds::toggle();
modm::delayMilliseconds(Board::Button::read() ? 250 : 500);
#ifdef MODM_BOARD_HAS_LOGGER
static uint32_t counter(0);
MODM_LOG_INFO << "Loop counter: " << (counter++) << modm::endl;
#endif
}
return 0;
}
Discovering modm¶
To generate your custom library, modm uses the Library Builder, which is the interface to discover available modules and their configuration options.
$ lbuild --repository ../modm/repo.lb discover
Parser(lbuild)
╰── Repository(modm @ ../modm) modm: a barebone embedded library generator
╰── EnumerationOption(target) = REQUIRED in [at90can128, at90can32, at90can64, ...
This gives you an overview of the repositories and their options. In this case
the modm:target
repository option is required, so let's check that out:
$ lbuild -r ../modm/repo.lb discover-options
modm:target = REQUIRED in [at90can128, at90can32, at90can64, at90pwm1, at90pwm161, at90pwm2,
... a really long list ...
stm32l4s9vit, stm32l4s9zij, stm32l4s9zit, stm32l4s9ziy]
Meta-HAL target device
You can then choose this repository option and discover the available modules for this specific repository option:
$ lbuild -r ../modm/repo.lb --option modm:target=stm32f407vgt discover
Parser(lbuild)
╰── Repository(modm @ ../modm) modm: a barebone embedded library generator
├── EnumerationOption(target) = stm32f407vgt in [at90can128, at90can32, at90can64, ...]
├── Configuration(modm:disco-f407vg) STM32F4DISCOVERY
├── Module(modm:board) Board Support Packages
│ ╰── Module(modm:board:disco-f407vg) STM32F4DISCOVERY
├── Module(modm:build) Build System Generators
│ ├── Option(build.path) = build/parent-folder in [String]
│ ├── Option(project.name) = parent-folder in [String]
│ ╰── Module(modm:build:scons) SCons Build Script Generator
│ ├── BooleanOption(info.build) = False in [True, False]
│ ╰── EnumerationOption(info.git) = Disabled in [Disabled, Info, Info+Status]
├── Module(modm:platform) Platform HAL
│ ├── Module(modm:platform:can) Controller Area Network (CAN)
│ │ ╰── Module(modm:platform:can:1) Instance 1
│ │ ├── NumericOption(buffer.rx) = 32 in [1 .. 32 .. 65534]
│ │ ╰── NumericOption(buffer.tx) = 32 in [1 .. 32 .. 65534]
│ ├── Module(modm:platform:core) ARM Cortex-M Core
│ │ ├── EnumerationOption(allocator) = newlib in [block, newlib, tlsf]
│ │ ├── NumericOption(main_stack_size) = 3040 in [256 .. 3040 .. 65536]
│ │ ╰── EnumerationOption(vector_table_location) = rom in [ram, rom]
You can now discover all module options in more detail:
$ lbuild -r ../modm/repo.lb -D modm:target=stm32f407vgt discover-options
modm:target = stm32f407vgt in [at90can128, at90can32, at90can64, ...]
Meta-HAL target device
modm:build:build.path = build/parent-folder in [String]
Path to the build folder
modm:build:project.name = parent-folder in [String]
Project name for executable
Or check out specific module and option descriptions:
$ lbuild -r ../modm/repo.lb -D modm:target=stm32f407vgt discover -n :build
>> modm:build [Module]
# Build System Generators
This parent module defines a common set of functionality that is independent of
the specific build system generator implementation.
>>>> modm:build:project.name [StringOption]
# Project Name
The project name defaults to the folder name you're calling lbuild from.
Value: parent-folder
Inputs: [String]
>>>> modm:build:build.path [StringOption]
# Build Path
The build path is defaulted to `build/{modm:build:project.name}`.
Value: build/parent-folder
Inputs: [String]
The complete lbuild command line interface is available with lbuild -h
.
Options are checked
lbuild
checks all your project options against the possible values in the
module and outputs an error if they are incorrect.
Visualize your dependencies
Create a dependency graph with lbuild dependencies -m "modm:module" | dot -Tsvg -Grankdir=BT -o dependencies.svg
to help you understand what code is pulled in when you generate your library.
Custom Configuration¶
In case modm doesn't have a BSP for your board or the BSP uses the hardware in ways you don't like, you can define your own completely custom configuration. Here a completely minimal library is generated for a STM32F469NIH device only with the Cortex-M, GPIO and time modules and their dependencies. Since no build system generator module is specified, you will only get the raw source code.
<library>
<repositories>
<repository><path>../modm/repo.lb</path></repository>
</repositories>
<options>
<option name="modm:target">stm32f469nih</option>
</options>
<modules>
<module>modm:platform:clock</module>
<module>modm:platform:core</module>
<module>modm:platform:gpio</module>
<module>modm:architecture:delay</module>
</modules>
</library>
Check your repository path
Make sure you've set the correct path to the modm repo.lb
file, otherwise
lbuild cannot help you much. This is especially important when copying
from our examples, which have the repository path set in the inherited
common configuration file located in modm/examples/lbuild.xml
!
A minimal main.cpp
for this configuration would look like this:
#include <modm/platform.hpp>
using namespace modm::platform;
int main()
{
GpioA0::setOutput();
while (true) {
GpioA0::toggle();
modm::delayMilliseconds(500);
}
}
We recommend to start your custom projects with a known-good configuration from one of our examples and then work your way into your specialization.
Generate and Compile¶
Once you have your project.xml
set up, you can call lbuild build
, which
generates the target and configuration specific library from modm.
This will create a few files and folders:
$ ls
main.cpp project.xml
$ lbuild build
$ ls
SConstruct main.cpp modm project.xml project.xml.log
You can add these folders and files to your .gitignore
file, however, we
recommend eventually committing these files (yes, all these files) into your
project repository so that you don't have issues reproducing the build.
Generate custom documentation
Include the modm:docs
module (or execute lbuild build --module "modm:docs"
),
then call doxygen doxyfile
inside the generated modm/docs
folder.
The documentation for your target and configuration will then be available
in modm/docs/html/index.html
.
For this project we included the modm:build:scons
generator, so we can just
call scons build size
, which will compile the entire source code and output
the resource consumption:
$ scons build size
scons: Reading SConscript files ...
scons: done reading SConscript files.
scons: Building targets ...
Compiling C++·· build/main.o
...
Indexing······· build/libmodm.a
Linking········ build/project.elf
Memory usage··· build/project.elf
Program: 3612B (0.3% used)
(.fastcode + .fastdata + .hardware_init + .reset + .rodata + .table.copy.intern +
.table.section_heap + .table.zero.intern + .text)
Data: 3184B (1.6% used) = 144B static (0.1%) + 3040B stack (1.5%)
(.bss + .fastdata + .stack)
Heap: 197520B (98.4% available)
(.heap0 + .heap1 + .heap2 + .heap5)
You can program your target by calling scons program
. Additional tools are
documented in SCons module documentation.
If you have any questions, open an issue or ping @salkinium.