This is sample and template code for Zephyr’s module system. This repository aims to support developers who want to integrate an existing code as a Zephyr module.
For anyone wanting to work with Zephyr modules, please read the official document first.
- This repository has a sample library, libsample, which we want to integrate to Zephyr as a module.
- An application using this sample is in a separate repository.
- libsample has two functions
- sample_pure() does not use any Zephyr facility at all. It does its own calculation and return. If this is the case for all functions in your code, it’s simple.
- sample_zephyr() uses Zephyr facility and you need to take care a few more bits.
- libsample is buildable for both Linux and Zephyr and we want to target both native_sim and qemu_cortex_m3.
- The build system for this repository is CMake. A Zephyr module must have a working CMake build script. If code is based on Meson, GNU Make, or any other build system, you must first port it to CMake.
- We use the T2 Star topology for our workspace.
Since it’s a very simple library, building it for Linux is easy:
git clone https://github.com/yashi/module-sample cd module-sample make -B builddir -G Ninja ninja -C builddir
At first, you should make libsample buildable by the Zephyr’s build system. You need to tell the build system libsample as a module, and tell how to build it.
This allows us to test building the library with your target compilers.
As noted, we are using T2 star topology. Run the following commands to setup a workspace. This assume you have Pipenv.
mkdir module-workspace cd module-workspace pipenv shell pip3 install west west init -m https://github.com/yashi/module-app west update --narrow pip3 install -r zephyr/scripts/requirements-base.txt
Once this is done, you can test build the application with the following command:
west build -b native_sim/native/64 module-app -t run
You need to C-c
to kill the application.
If you don’t know about West workspace, please read the Basic and the Workspaces.
To do so, you must add an entry about the library in the application’s
west.yml
.
- name: module-sample
url: https://github.com/yashi/module-sample
revision: main
path: modules/lib/sample
With this entry, West can now list module-sample as a module. You can
check it with west list
.
$ west list manifest module-app HEAD N/A zephyr zephyr main https://github.com/zephyrproject-rtos/zephyr cmsis modules/hal/cmsis b0612c97c1401feeb4160add6462c3627fe90fc7 https://github.com/zephyrproject-rtos/cmsis module-sample modules/lib/sample main https://github.com/yashi/module-sample
West now recognize it as a module but it doesn’t build it at all because it’s not told to do so.
Zephyr’s build system will load module’s CMakeLists.txt
, which is
zephyr/CMakeLists.txt
as the default. Please note that the build
system will search for zephyr/
directroy in a module directory. This
is a fixed location and hard coded in zephyr/scripts/zephyr_module.py
.
You can, on the other hand, change the location of the
zephyr/CMakeLists.txt
. We’ll talk about this in a later section.
If you just want to bulid the library, add the following line in the
zephyr/CMakeLists.txt
.
add_subdirectory(.. build)
This tells the build system
- To add the the library’s root directory (because we are in
zephyr/
directory) as a sub directory to build. - A
binary_dir
. Because..
is not a sub directory of the current directoryzephyr/
, CMake will complain if you omit the second binary directory parameter. So thisbuild
parameter must be set. The name of the binary directory doesn’t have to bebuild
but can be of your choise. If you omit it you get:CMake Error at .../module-workspace/modules/lib/sample/zephyr/CMakeLists.txt:1 (add_subdirectory): add_subdirectory not given a binary directory but the given source directory ".../module-workspace/modules/lib/sample" is not a subdirectory of ".../module-workspace/modules/lib/sample/zephyr". When specifying an out-of-tree source a binary directory must be explicitly specified.
With this line, you see that libsample is built when you build your application. You see the number of the build steps increased.
$ west build -b native_sim/native/64 module-app : [95/95] Linking C executable zephyr/zephyr.elf
$ west build -b native_sim/native/64 module-app : [97/97] Linking C executable zephyr/zephyr.elf
We just built libsample using the Zephyr build system but we want to
control when to build it or not just like any other features in Zephyr.
To do so, we’ll use if(CONFIG_LIBSAMPLE)
and Kconfig
constructs.
The default location of Kconfig
is zephyr/Kconfig
under a module
directory. You can change the location of Kconfig
as well as
CMakeLists.txt
. This will be discussed in the later section.
if(CONFIG_LIBSAMPLE)
add_subdirectory(.. build)
endif()
config LIBSAMPLE
bool "Enable libsample"
help
This option enables the libsample as a Zephyr module.
With these changes, libsample will show up in the menuconfig, you can
build it with -DCONFIG_LIBSAMPLE=y
, or you can control the build with
prj.conf
as usual.
Modules ---> *** Available modules. *** sample (.../module-workspace/modules/lib/sample) ---> [ ] Enable libsample
$ west build -b native_sim/native/64 module-app -- -DCONFIG_LIBSAMPLE=y
CONFIG_LIBSAMPLE=y
Now we can test buliding libsample with your target board and
target compilers. We’ll use qemu_cortex_m3
and native_sim/native/64
as examples, but you should make sure your library is built by your
configuraiton.
To see how the library is built, you should use -v
option to
west
command.
$ west -v build -b native_sim/native/64 module-app -- -DCONFIG_LIBSAMPLE=y : [2/135] ccache .../zephyr-sdk-0.13.1/arm-zephyr-eabi/bin/arm-zephyr-eabi-gcc -I.../module-workspace/modules/lib/sample/include -Wall -Wextra -std=gnu11 -MD -MT modules/sample/build/CMakeFiles/sample.dir/src/plain.c.obj -MF modules/sample/build/CMakeFiles/sample.dir/src/plain.c.obj.d -o modules/sample/build/CMakeFiles/sample.dir/src/plain.c.obj -c .../module-workspace/modules/lib/sample/src/plain.c [3/135] : && ccache /usr/bin/cmake -E rm -f modules/sample/build/libsample.a && ccache .../zephyr-sdk-0.13.1/arm-zephyr-eabi/bin/arm-zephyr-eabi-ar qc modules/sample/build/libsample.a modules/sample/build/CMakeFiles/sample.dir/src/plain.c.obj && ccache .../zephyr-sdk-0.13.1/arm-zephyr-eabi/bin/arm-zephyr-eabi-ranlib modules/sample/build/libsample.a && :
$ west -v build -b native_sim/native/64 module-app -- -DCONFIG_LIBSAMPLE=y [1/97] ccache /usr/lib/ccache/gcc -I.../module-workspace/modules/lib/sample/include -Wall -Wextra -std=gnu11 -MD -MT modules/sample/build/CMakeFiles/sample.dir/src/plain.c.obj -MF modules/sample/build/CMakeFiles/sample.dir/src/plain.c.obj.d -o modules/sample/build/CMakeFiles/sample.dir/src/plain.c.obj -c .../module-workspace/modules/lib/sample/src/plain.c [2/97] cd .../module-workspace/build/zephyr && /usr/bin/cmake -E echo [3/97] : && ccache /usr/bin/cmake -E rm -f modules/sample/build/libsample.a && ccache /usr/bin/ar qc modules/sample/build/libsample.a modules/sample/build/CMakeFiles/sample.dir/src/plain.c.obj && ccache /usr/bin/ranlib modules/sample/build/libsample.a && :
An experienced user might notice that the built timing is way too early, before the essential builds in the build system. This will be a problem if your library depends on Zephyr proper. We’ll cover that later.
Make sure your library is built with compiler options you want to use. You should also make sure that your library is not using any compiler options and flags a Zephyr application would normally built with. This is because we haven’t tell to do so. If your library doesn’t depend on Zephyr, you don’t need any compiler option from Zephyr. If it uses and depends on Zephyr, that is your library uses Zephyr semaphore or logging subsystem, you must tell additional flags while building your library. We’ll cover this later.
A header-only library is a rare but does exists. If you want to integrate such a library, you have to tell the bulid system how to find your header file. Usually, your application is the one to use the header file.
We’ll use the following line to integrate libsample to the application.
#include <libsample.h>
Just adding this line to your Zephyr application yeilds
.../module-workspace/module-app/src/main.c:2:10: fatal error: libsample.h: No such file or directory 2 | #include <libsample.h> | ^~~~~~~~~~~~~ compilation terminated. ninja: build stopped: subcommand failed.
If you see the compilation with -v
it’s obvious that compiler
doesn’t specify libsample’s include directroy.
To tell include directory with CMake? It’s
target_include_directories
. This function tells include
directries to the given target. But we want to tell our application
the libsample include directroy.
We have to ways to do so.
One way to do so is to use zephyr_interface
, which is a target
Zephyr’s build system has. This target collects all compiler
options the build system needs.
“zephyr_interface” is a source-less library that encapsulates all the global compiler options needed by all source files. All zephyr libraries, including the library named “zephyr” link with this library to obtain these flags.
All you have to do is to add the following line in your library’s
zephyr/CMakeLists.txt
.
zephyr_include_directories(../include)
This does get job done. But if you check the build commands, you will see that almost all the compilations gets the libsample’s include directory.
-I.../module-workspace/modules/lib/sample/zephyr/../include
This is needed only if Zephyr proper depends on a library, such as the CMSIS module. However, that is not our case.
Another way to specify is to use ZEPHYR_INTERFACE_LIBS
. It has a
similar name with zephyr_interface
, but these two are different.
In fact, ZEPHYR_INTERFACE_LIBS
is only used by
zephyr_interface_library_named()
as of this writing. The macro
is defined in zephyr/cmake/extensions.cmake
.
It’d be easier if we could use zephyr_interface_library_named()
in our libsample but if you do you get the following error:
CMake Error at .../module-workspace/zephyr/cmake/extensions.cmake:619 (add_library): add_library cannot create target "sample" because another target with the same name already exists. The existing target is a static library created in source directory ".../module-workspace/modules/lib/sample". See documentation for policy CMP0002 for more details.
It’s obvious if you see how the macro is defined.
macro(zephyr_interface_library_named name)
add_library(${name} INTERFACE)
set_property(GLOBAL APPEND PROPERTY ZEPHYR_INTERFACE_LIBS ${name})
endmacro()
libsample already declare it as sample
by calling
add_library(sample)
in the top level CMakeLists.txt
and you are
now trying to re-declare sample
with this macro and CMake doesn’t
like it.
If libsample is only for Zephyr, it’s easier to just use this macro
in the top level CMakeLists.txt
and done with it. It’s also
possible to do it with a separete branch, overwriting the top level
CMakeLists.txt
.
But here we want to keep as much the original CMake build system
for libsample as possible and keep the Zephyr module construct in a
separate zephyr/
directory. So, we’ll use
ZEPHYR_INTERFACE_LIBS
directly. Our zephyr/CMakeLists.txt
will
become this:
add_subdirectory(.. build)
set_property(GLOBAL APPEND PROPERTY ZEPHYR_INTERFACE_LIBS sample)
We also need to change our zephyr/Kconfig
:
config APP_LINK_WITH_SAMPLE
bool "Make libsample header file available to application"
default y
depends on LIBSAMPLE
We need this because the Zephyr build system has the following
check in zephyr/cmake/app/boilerplate.cmake
:
target_link_libraries_ifdef(
CONFIG_APP_LINK_WITH_${boilerplate_lib_upper_case}
app
PUBLIC
${boilerplate_lib}
)
This also explains why the name of the option is APP_LINK_WITH_SAMPLE
.
You might ask, “We are talking about include directories, why does it use
target_link_libraries_ifdef
, which uses =target_link_libraries=, instead
of target_include_directories_ifdef
or =target_include_directories=?”
With CMake, if a library already knows include directories for
applications, your application can just link against it with
target_link_libraries()
.
You can learn about this in more detail in the CMake Tutorial, the step 2 and step 3 are the ones you should check.
- [ ] Support autoconf.h
- [ ] Support -std=gnu11
- [ ] Support its own cflags