Starting a new Project¶
The modm library explicitly generates and copies its files from inside the modm
repository into your project using the lbuild code generator. It is intended and
very strongly recommended to commit all generated files from lbuild build
into your repository. This will make any changes very clear when you run lbuild
build
again with an updated version of modm!
We recommend adding modm into your repository as a Git submodule so that you
control exactly what version of modm you're using. We recommend you place the
modm repository (and all other external code) into a folder called ext/
or
similiar:
$ git submodule add https://github.com/modm-io/modm.git ext/modm
$ tree
.
└── ext
└── modm
├── repo.lb
...
Next you want to create a folder with a project.xml
and a main.cpp
file. We
recommend you place that in a separate app/
folder so that all your firmwares
are in the same place:
$ tree
.
├── ext
│ └── modm
│ ├── repo.lb
│ ...
└── app
└── 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: ...
The project.xml
must contain the (relative) path to modm's repo.lb
file.
Note that the project name is inferred from the project folder, but you can
easily change it. modm also does not add a build system by default, so you
want to choose one at this point. Add the
modm:docs
module to generate custom API docs:
<library>
<repositories>
<repository><path>../../ext/modm/repo.lb</path></repository>
</repositories>
<options>
<option name="modm:build:project.name">custom_name</option>
</options>
<modules>
<module>modm:build:scons</module>
<module>modm:docs</module>
</modules>
</library>
You should now be able to discover modm from inside your project folder:
$ cd app/project_name
$ lbuild discover
Parser(lbuild)
╰── Repository(modm @ ../../ext/modm) modm: a barebone embedded library generator
├── Option(target) = REQUIRED in [stm32f469ngh6, stm32f469nih6, stm32f469vet6, ...
...
Note that we've not yet declared the modm:target
option, so you cannot
discover all of modm yet. You can now choose from two levels of customization:
-
Inherit and extend a board support package from modm. This is particularly useful if your project is based on an existing development board.
-
Define your own
modm:target
option and required modules. This is necessary for using modm on a custom hardware design.
Using a Board Support Package¶
Use lbuild to discover the specific BSP configuration you want to use. Some board configurations support different hardware revisions, so check your hardware to select the right one. For this example, we are going to use the DISCO-F469NI Rev B-03 board config.
$ lbuild discover
Parser(lbuild)
╰── Repository(modm @ ../../ext/modm) modm: a barebone embedded library generator
├── Option(target) = REQUIRED in [stm32f469ngh6, stm32f469nih6, stm32f469vet6, ...
├── Config(modm:disco-f469ni:b-03) in [b-01, b-03] STM32F469IDISCOVERY
...
Then inherit from this configuration in your project.xml
. This will add the
modm:target
option and give you a set of modules that the BSP depends on.
Note that you can overwrite any options you don't agree with, however, you
cannot remove any inherited modules.
<library>
<repositories>...</repositories>
<extends>modm:disco-f469ni:b-03</extends>
<options>...</options>
<modules>...</modules>
</library>
You can now lbuild discover
all of modm. Note that the selected modules are
now marked underlined as a subtle hint.
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.
A minimal main.cpp
can look like this:
#include <modm/board.hpp>
int main()
{
Board::initialize();
Board::Leds::setOutput();
while (true)
{
Board::Leds::toggle();
modm::delay(Board::Button::read() ? 250ms : 500ms);
#ifdef MODM_BOARD_HAS_LOGGER
static uint32_t counter(0);
MODM_LOG_INFO << "Loop counter: " << (counter++) << modm::endl;
#endif
}
return 0;
}
You can find more details about the BSPs capabilities on docs.modm.io which contains the full API documentation for all BSPs. Alternatively you can generate your own API docs locally:
(cd modm/docs && doxypress doxypress.json)
# open modm/docs/html/index.html
Please note that the BSPs contain an opinionated set of pre-defined functionality and settings. This is great for getting started quickly, however, if you want to change some hardcoded things (like which UART to use for logging), it's better to use your own BSP:
-
Generate the BSP closest to your custom hardware, then copy the files from
modm/src/modm/board/{name}
to your own project and modify them. -
In your
project.xml
remove the board config inheritance (<extends>
) and instead copy the pre-defined options into your own config. -
Check what modm modules you need to depend on and add them to your own project (check for
module.depends(...)
in the BSPsmodule.lb
). -
You may need to manually add the pre-defined collector values to your project configuration (check for
env.collect(...)
in the BSPsmodule.lb
).
Custom Configuration¶
For completely custom hardware, you start by defining the modm:target
option
and then adding the modules with their options as needed. For example, a basic
configuration for the STM32F469NIH6 with startup, GPIO and clock modules looks
like this:
<library>
<repositories>
<repository><path>../../ext/modm/repo.lb</path></repository>
</repositories>
<options>
<option name="modm:target">stm32f469nih6</option>
</options>
<modules>
<module>modm:platform:clock</module>
<module>modm:platform:core</module>
<module>modm:platform:gpio</module>
<module>modm:architecture:delay</module>
<module>modm:build:scons</module>
<module>modm:docs</module>
</modules>
</library>
Remember to include the modm:docs
module, since you need to generate your own
API docs from your specific configuration:
(cd modm/docs && doxypress doxypress.json)
# open modm/docs/html/index.html
A minimal main.cpp
for this configuration can look like this. Since there is
no BSP, you need to #include <modm/platform.hpp>
and add the modm::platform
namespace manually.
#include <modm/platform.hpp>
using namespace modm::platform;
using namespace std::chrono_literals;
int main()
{
GpioA0::setOutput();
while (true)
{
GpioA0::toggle();
modm::delay(0.5s);
}
}
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.
Visualize your dependencies
You can create a dependency graph with
lbuild dependencies | dot -Tsvg -Grankdir=BT -o dependencies.svg
to help you understand what code is pulled in when you generate your library.
Generate, Compile and Upload¶
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:
$ cd app/project_name
$ ls
main.cpp project.xml
$ lbuild build
$ ls
SConstruct main.cpp modm project.xml project.xml.log
For this project we included the modm:build:scons
generator, so we can just
call scons build
, which will compile the entire source code and output the
resource consumption:
$ scons build -j8
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 upload this firmware to your target by calling scons program
.
Additional tools are documented in the SCons module documentation.
Enable parallel builds
Remember to add the -j8
flag to your scons build
call to enable parallel
builds, which are significantly faster than sequential builds. To make this
permanent you can add export SCONSFLAGS="-j8"
or export MAKEFLAGS="-j8"
to your .bashrc
!
If you have any questions, open an issue or ping @salkinium.