Out Of Tree Zephyr RTOS module (driver)
Sometimes it’s inconvenient to develop a driver for some peripheral or sensor in-tree. As far as I was able to research (bare with me, I’m relatively new to Zephyr), the Zephyr RTOS modules are the way to go in such situations. First, a bunch of links that pointed me in the right direction:
- A thread stating that “it should not pose a problem, Nordic is doing just that”
- The nRF Connect SDK: sdk-nrf the above link is referring to.
- Stack overflow thread where a guy replied to himself. Even though this link covers the topic pretty much, I still stumbled upon some problems. See below.
- Another out of tree approach but overall, this thread is inconclusive.
- ZEPHYR_EXTRA_MODULES is mentioned here.
- My module this post is about.
- Acompanying test application to try things out.
My test module contains a driver for LSM6DS3 accelermoeter and gyroscope. This is an older version of LSM6DSL that is already supported by Zephyr, which makes my implementation as easy as changing one macro.
The current version of Zephyr as of today is 2.6.
The module
Explains what has been copied and what modified. Create the directory structure:
+drivers
+sensor
+dts
+bindings
+sensor
+zephyr
CMakeLists.txt
Kconfig
README.md
Copy zephyr/drivers/sensor/lsm6dsl
from the main Zephyr repo to drivers/sensor/lsm6ds3
in our directory.
Names
Change all the function, macro and file names from lsm6dsl to lsm6ds3. Remember about Kconfig
and CMakeLists.txt
Change the macro LSM6DSL_VAL_WHO_AM_I
from 0x6A to 0x69. This is the only “true” modification in the code.
Build system
Update / provide build system information. A module has to have a Kconfig and CMakeLists.txt in its root directory, so provide one. In my module, these files are as simple as:
Kconfig
:
menu "My super zephyr module"
rsource "drivers/Kconfig"
endmenu
CMakeLists.txt
:
add_subdirectory(drivers)
In the above files I point to the drivers
directory, so there have to be Kconfig and CMakeLists.txt there as well:
drivers
directory contains:
CMakeLists.txt : add_subdirectory_ifdef(CONFIG_SENSOR sensor)
Kconfig rsource "sensor/Kconfig"
sensor
directory contains:
CMakeLists.txt : add_subdirectory_ifdef(CONFIG_LSM6DS3 lsm6ds3)
Kconfig : rsource "lsm6ds3/Kconfig"
You see the pattern.
Bindings
Copy st,lsm6dsl-i2c.yaml
and st,lsm6dsl-spi.yaml
bindings from Zephyr repo to our dts/bindings/sensor
, and change the file names accordingly (don’t forget about the contents). Binding files are found automatically during the build.
module.yml
Lastly, provide the module description adding the zephyr/module.yml
:
build:
cmake: .
kconfig: Kconfig
settings:
dts_root: .
Problems I’ve had
Zephyr build system isn’t very verbose (nor precise) when problems occur, so if you ever encounter errors like this:
cmake -B build -G Ninja -DBOARD=nucleo_h743zi -DBOARD_ROOT=. -DZEPHYR_EXTRA_MODULES=/home/iwasz/workspace/zephyr-modbus/my-zephyr-module .
Including boilerplate (Zephyr base): /home/iwasz/workspace/zephyr-modbus/zephyrproject/zephyr/cmake/app/boilerplate.cmake
-- Application: /home/iwasz/workspace/zephyr-modbus/zephyr-inclinometer
-- Zephyr version: 2.6.0-rc3 (/home/iwasz/workspace/zephyr-modbus/zephyrproject/zephyr), build: v2.6.0-rc3
-- Found Python3: /usr/bin/python3.9 (found suitable exact version "3.9.4") found components: Interpreter
-- Found west (found suitable version "0.8.0", minimum required is "0.7.1")
CMake Error at /home/iwasz/workspace/zephyr-modbus/zephyrproject/zephyr/cmake/zephyr_module.cmake:61 (message):
/home/iwasz/workspace/zephyr-modbus/my-zephyr-module, given in
ZEPHYR_EXTRA_MODULES, is not a valid zephyr module
Call Stack (most recent call first):
/home/iwasz/workspace/zephyr-modbus/zephyrproject/zephyr/cmake/app/boilerplate.cmake:183 (include)
/home/iwasz/workspace/zephyr-modbus/zephyrproject/zephyr/share/zephyr-package/cmake/ZephyrConfig.cmake:24 (include)
/home/iwasz/workspace/zephyr-modbus/zephyrproject/zephyr/share/zephyr-package/cmake/ZephyrConfig.cmake:35 (include_boilerplate)
CMakeLists.txt:5 (find_package)
-- Configuring incomplete, errors occurred!
It could mean that the module.yml
is named incorrectly. In my case, I used yaml
extension instead of yml
.
After correcting the problem with module.yml
, the app started and the analyzer showed that it does something on the I²C, but then it failed in drivers/sensor/lsm6ds3/lsm6ds3_trigger.c
in the lsm6ds3_trigger_set
function. The problematic code was:
if (!drv_data->gpio) {
LOG_ERR ("triggers not supported");
return -ENOTSUP;
}
Clearly, a problem with my overlay, but why? It seems the Zephyr build system did not see my binding files. As stated here the module.yml
has to point at the binding files. The solution is to add:
settings:
dts_root: .
The test application
Is here. The application is straightforward. I copied zephyr/samples/sensor/lsm6dsl
. I added my custom overlay in the boards
directory to configure what is connected and to which pins:
&i2c2 {
pinctrl-0 = <&i2c2_scl_pb10 &i2c2_sda_pb11>;
status = "okay";
clock-frequency = <I2C_BITRATE_FAST>;
lsm6ds3@6b {
compatible = "st,lsm6ds3";
reg = <0x6b>;
irq-gpios = <&gpiod 11 GPIO_ACTIVE_HIGH>;
label = "LSM6DS3";
};
};
I modified the prj.conf
to enable my implementation instead of theirs (again I changed from LSM6DSL to LSM6DS3).
Finally I changed some (but not all) names in the C code from LSM6DSL to LSM6DS3:
device_get_binding (DT_LABEL (DT_INST (0, st_lsm6ds3)))
CONFIG_LSM6DS3_TRIGGER
Build
After these modification I was left with:
- The Zephyr itself (usual directory
zephyrproject
+ SDK) - My application in
my-zephyr-module-app
. - My module in
my-zephyr-module
To build this i simply have to :
cd my-zephyr-module-app
cmake -B build -G Ninja -DBOARD=nucleo_h743zi -DBOARD_ROOT=. -DZEPHYR_EXTRA_MODULES=/home/iwasz/workspace/zephyr-modbus/my-zephyr-module .
ninja -C build/
west flash
A follow-up
Sometimes it is convenient to have both the module and the application using it in one place. Thankfully there is an example in the Zephyr showing just that in zephyr/samples/application_development/out_of_tree_driver
. Interestingly enough, when I tried to incorporate this example into one of my projects, I got an error during build:
[...]
In declaration of void hello_world_print
Traceback (most recent call last):
File "/home/iwasz/workspace/zephyrproject/zephyr/scripts/gen_syscalls.py", line 450, in <module>
main()
File "/home/iwasz/workspace/zephyrproject/zephyr/scripts/gen_syscalls.py", line 379, in main
handler, inv, mrsh, sys_id, entry = analyze_fn(match_group)
File "/home/iwasz/workspace/zephyrproject/zephyr/scripts/gen_syscalls.py", line 324, in analyze_fn
func_type, func_name = typename_split(func)
File "/home/iwasz/workspace/zephyrproject/zephyr/scripts/gen_syscalls.py", line 143, in typename_split
raise SyscallParseException("Malformed system call invocation")
__main__.SyscallParseException: Malformed system call invocation
[6/178] Generating include/generated/kobj-types-enum.h, include/generated/otype-to-str.h, include/generated/otype-to-size.h
ninja: build stopped: subcommand failed.
The cause was that I modified the hello_world_driver.h
, and saved it, which in turn fired the clang-format
tool (which uses my system-wide configuration). And this action alone broke the build completely (namely the lack of a space character after the function name and before the opening bracket is causing the problem). The solution is either to surround the problematic bit in /* clang-format on/off */
guards:
/* clang-format off */
__syscall void hello_world_print(const struct device *dev);
static inline void z_impl_hello_world_print(const struct device *dev)
/* clang-format on */
Or to use the .clang-format file provided by Zephyr. I went with the second option and copied the mentioned file to the directory where my modules are. This way I can use my system-wide (and preferred) formatting for the user-space C++ code, and make the Zephyr build system happy at the same time.
TODO
Make vscode happy. The IDE is overwhelmed by the project split into 3 separate directories or I don’t know how to configure it. Let me know in the comments you whereabouts on this. Also I do not know (an I’ve tried) how to have the module included inside the application directory.