Skip to main content
Version: 0.8.0

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​

  1. 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
  1. Setup and activate a virtual environment
python3 -m venv .venv
source .venv/bin/activate
  1. 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

  1. Get the Zephyr source code
west update
  1. Export CMake packages for Zephyr
west zephyr-export
  1. 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:

  1. prj.conf, see Setting symbols in Configuration systems (Kconfig)
  2. Kconfig, see Configuration system (Kconfig)
  3. 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.

  1. Compile HelloWorld.lf for qemu_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.

  1. In one terminal, start qemu as a debug server waiting for a local connection from gdb
ninja -C build debugserver
  1. 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.