Overview
Lingua Franca's C-runtime supports the Zephyr RTOS. This enables developing and
programming hundreds
of resource-constrained microcontrollers. In this guide we will see how LF
programs can be built, programmed and debugged both in emulation and on real
hardware. When developing LF programs for Zephyr we use a west
-centric
approach. Using west
, which is the preferred build tool for Zephyr projects,
requires structuring the code base and development flow as expected by west
.
We use a T3 Forest
Topology
for our workspace. This means that we create a workspace where multiple
different LF Zephyr projects can be hosted together with a single copy of the
Zephyr RTOS sources.
Prerequisites​
- Linux or macOS development system. (The guide is written for Linux)
- nrf52 Development Kit (optional)
Getting started
The first step is to set up a proper Zephyr development environment. Follow the steps in the Install dependencies and Install Zephyr SDK sections of the official Zephyr Getting Started Guide. Do not perform the steps under Get Zephyr and install Python dependencies. These steps will be performed inside the LF Zephyr workspace we are going to create next.
Setting up the LF Zephyr workspace​
- Clone the template repository into a workspace directory of Zephyr projects
git clone https://github.com/lf-lang/lf-west-template lf-zephyr-workspace && cd lf-zephyr-workspace
- Setup and activate a virtual environment
python3 -m venv .venv
source .venv/bin/activate
- Install
west
pip3 install west
Now west
is installed within a virtual environment. This environment has to
be activated every time you want to use west with LF
- Get the Zephyr source code
west update
- Export CMake packages for Zephyr
west zephyr-export
- Install Python dependencies
pip install -r deps/zephyr/scripts/requirements.txt
Workspace organization​
Now you should have the following installed:
-west
; Verify with west boards
- Zephyr SDK located at
/opt/zephyr-sdk-VERSION
- Zephyr RTOS pulled down to
deps/zephyr
- A few example applications under
apps/
This workspace is meant to house all of your different LF Zephyr apps, as long as they are using the same version of Zephyr. Each app has to contain the following:
<app>
├── app.overlay
├── prj.conf
├── Kconfig
└── src
└── MyApplication.lf
Our custom west-extension will invoke lfc
and create a src-gen
directory
structured as a Zephyr
application.
This generated project can then be built, emulated or flashed by west
.
Hello World!​
You should now be able to build and emulate a simple "Hello World" LF program:
cd apps/HelloWorld
lfc src/HelloWorld.lf -n
west build src-gen/HelloWorld -t run
HelloWorld.lf
has the target properties platform: "Zephyr"
and threading: false
. This tells lfc
to create a Zephyr-compatible CMake project. Then we
use west
to build the generated CMake project and to start an emulation.
To enable west-centric development we have added a custom west command,
west lfc
. It is a wrapper around the original lfc but also copies the files
prj.conf
and Kconfig
into to generated project. It can also invoke west build
directly. The above example can be compiled and emulated with the
following command:
west lfc src/HelloWorld.lf --build "-t run"
The string within the quotation marks after --build
is passed on to west build
.
Nrf52 blinky​
In this example we will program a simple Blinky program onto an nrf52dk. This
requires an actual nrf52 board and also the nrfjprog
utility is installed. See
the following installation guide
here.
cd apps/NrfBlinky
west lfc src/NrfBlinky.lf --build "-p always -b nrf52dk_nrf52832"
west flash
In this example we use the -p always
to tell west to do a clean build and -b nrf52dk_nrf52832
to target the nrf52dk. These parameters are west-specific so
refer to west documentation for more info. west flash
is used to interact with
nrfjprog
and flash the application into the dev-board.
Bare-bones Zephyr app​
We also have a simple example of a bare-bones Zephyr app. This requires a physical board.
cd apps/HelloZephyr
west build -b nrf52dk_nrf52832 -p always
west flash
Zephyr configuration options
The Lingua Franca Zephyr platform depends on some specific Zephyr Kernel
configurations.
For instance, the Counter drivers must be linked with the application to provide
hi-resolution timing. These required configurations are stored in a file called
prj_lf.conf
which lfc
generates into the src-gen
folder. You can provide
your own configurations through the following three files that west lfc
expects to find at the root of each app:
prj.conf
, see Setting symbols in Configuration systems (Kconfig)Kconfig
, see Configuration system (Kconfig)app.overlay
, see Devicetree
The config options provided by you will be merged with those provided by lfc
and default settings for the board. It is very useful to inspect the final
version of the Kconfig and devicetree. After compilation, these are found in
build/zephyr/.config
and build/zephyr/zephyr.dts
. You can also inspect the
Kconfig options and devicetree by calling:
west build -t menuconfig
west build -t guiconfig
These are very powerful tools that give you a lot of insight into the application you have just built.
The west lfc
command
The custom lfc
west command has already been used in previous sections. It can
be inspected in scripts/lfc.py
. It invokes lfc
on the provided LF
source file. It also copies app.overlay
,prj.conf
and Kconfig
into the
src-gen directory before it, optionally, calls west build
on the resulting
project.
Please see west lfc --help
for more information and the scripts/lfc.py
.
LFC-centric development
In this guide we have shown how LF Zephyr apps can be developed in a
west
-centric manner. It is also possible to target Zephyr in a lfc
-centric
approach. When you give lfc
a LF program with the platform
target property
set to Zephyr
, it will generate a Zephyr project that can be built with
west
. For this to work, the environment variable ZEPHYR_BASE
must be set to
point to the Zephyr RTOS sources. To demonstrate this, create a simple example
program:
cat >> LfcCentricZephyr.lf << 'END'
target C {
platform: "Zephyr"
}
main reactor{
reaction(startup) {=
lf_print("Hello World!");
=}
}
END
If west
is installed in a virtual environment, activate it, and set up the
environment. Assuming that the template is located at /home/lf-zephyr-workspace
source /home/lf-zephyr-workspace/.venv/bin/activate
export ZEPHYR_BASE=/home/lf-zephyr-workspace/deps/zephyr
lfc LfcCentricZephyr.lf
This will create a Zephyr project at src-gen/LfcCentricZephyr
and invoke
the Zephyr toolchain to compile it. Since we have not specified any board,
the default, which is qemu_cortex_m3
, is used. We can run an emulation of
the program with:
cd src-gen/LfcCentricZephyr
west build -t run
Timing-precision
There exist two different Zephyr implementations of the LF timing API. The default is based on the Zephyr Kernel Timing. It is simple and supported on all boards with Zephyr support. However it is not that precise. E.g. for the nRF52 boards it is based on a 32KHz timer, while for many other boards is a 10Khz timer. This limits the timing-precision to 10s-100s of microseconds.
If this is not sufficient, the LF timing API based on the Zephyr Counter can be used. This is an abstraction of a generic timer peripheral which is found on many MCUs. The Counter API can give time-precision in the order of 10s of nanoseconds. Not all boards have implemented the Counter API, and not all implementations includes all the features that is needed to implement the LF timing API. Currently we have only tested with nRF52, iMXRT1170 and ESP32.
To compile with the Counter API, we must enable the counter device in the prj.conf
file:
CONFIG_COUNTER=y
and pass an extra flag to CMake:
cd apps/NrfBlinky
west lfc src/NrfBlinky.lf --build "-p always -b nrf52dk_nrf52832 -- -DLF_ZEPHYR_CLOCK_COUNTER=1"
C libraries
Zephyr has support for three C library implementations.
- Newlib (Default library used by LF)
- Picolibc (Supported by LF)
- Minimal libc (Not supported yet by LF)
For LF programs targeting Zephyr, Newlib is the default C library. You can
inspect the file lf_prj.conf
which will be copied into the src-gen
by lfc
when compiling a LF program with Zephyr as the target platform. Here you will find:"
CONFIG_NEWLIB_LIBC=y
CONFIG_NEWLIB_LIBC_FLOAT_PRINTF=y
This setting can be overridden in the user-written prj.conf
file. If you wish
to use Picolibc instead put the following in your prj.conf
file:
CONFIG_NEWLIB_LIBC=n
CONFIG_NEWLIB_LIBC_FLOAT_PRINTF=n
CONFIG_PICOLIBC=y
CONFIG_PICOLIBC_IO_FLOAT=y
With some additional work we could also get the LF runtime working with Minimal libc. It should reduce the code size considerably.
Debugging LF Zephyr programs using QEMU and GDB
In this section we will see how a LF program can be debugged while running in QEMU emulation.
- Compile
HelloWorld.lf
forqemu_cortex_m3
cd apps/HelloWorld
west lfc src/HelloWorld.lf --build "-b qemu_cortex_m3 -p always"
Note that we here, unlike the very first example, explicitly tell lfc
that we
are targeting a qemu_cortex_m3
platform. This is the default platform which is
used unless another is specified. It is added here for clarity.
- In one terminal, start qemu as a debug server waiting for a local connection
from
gdb
ninja -C build debugserver
- In another terminal start
gdb
and connect to the qemu server. Load the application image and run until main.
/ZEPHYR_SDK_INSTALL_DIR/arm-zephyr-eabi/bin/arm-zephyr-eabi-gdb
(gdb) target remote localhost:1234
(gdb) file build/zephyr/zephyr.elf
(gdb) b main
(gdb) c
From here you can step through the LF program. To get a more visual interface you can try:
(gdb) tui enable
Timing in QEMU emulations​
The QEMU emulation is not cycle-accurate and implements optimizations such that
if the system goes to sleep, like when the last active thread in the program
calls k_sleep()
, then the emulator fast-forwards time. This causes the
emulation of threaded programs to appear as if the fast
target property was
set to true
.
Troubleshooting​
ESP32​
Several users have reported problems with using ESP32 and Newlib, which is the default C library used by LF programs targeting Zephyr. A workaround is to use Picolibc instead. See the chapter on "C libraries" for a description of how to change C library implementation.
Multiple Zephyr installations​
If the following warning is shown when invoking west lfc
or any other west
command:
WARNING: ZEPHYR_BASE=/path/to/zephyr in the calling environment will be used,
but the zephyr.base config option in /path/to/lf-west-template is "deps/zephyr"
which implies a different ZEPHYR_BASE=/path/to/lf-west-template/deps/zephyr
To disable this warning in the future, execute 'west config --global zephyr.base-prefer env'
Then it means that you have multiple Zephyr repositories installed. We do not
recommend this as west
will link the application with the Zephyr found in the
CMake package registry. Please refer to the Getting Started section to purge the
system of old Zephyr installations.