- Git: https://github.com/riot-os/RIOT
- Supported Boards: https://www.riot-os.org/boards.html
- Documentation: https://doc.riot-os.org/
- Adafruit page: https://learn.adafruit.com/adafruit-feather-sense
- RIOT docs for board: https://api.riot-os.org/group__boards__adafruit-feather-nrf52840-sense.html
This is a tutorial for Lingua Franca applications running on RIOT OS with the Adafruit Feather Sense board. It uses reactor-uc, the "micro C" target for Lingua Franca.
| OS | Support Level |
|---|---|
| Linux (Ubuntu/Debian) | Fully supported (recommended) |
| NixOS / Nix | Fully supported |
| macOS | Supported with caveats |
Note: For detailed RIOT OS setup instructions, see the official RIOT Getting Started Guide.
# Clone via HTTPS
git clone https://github.com/lf-lang/reactor-uc.git --recurse-submodules
# Or clone via SSH
git clone git@github.com:lf-lang/reactor-uc.git --recurse-submodules
# Set the environment variable (add to ~/.bashrc for persistence)
export REACTOR_UC_PATH=$(pwd)/reactor-ucChoose the setup method for your operating system:
Most RIOT OS developers use Linux, providing the most streamlined experience. Ubuntu is recommended for newcomers.
Install required packages:
sudo apt update
sudo apt install git openjdk-17-jdk openjdk-17-jre cmake build-essential \
python3 python3-serial gcc-arm-none-eabi gdb-multiarch openocdThis is the easiest setup method. The repository includes a shell.nix / flake.nix that provisions all dependencies automatically.
If you don't have Nix installed:
curl -L https://nixos.org/nix/install | shOtherwise use your package manager to install it.
Enter the development environment:
nix developThis creates a shell with all dependencies (cross-compiler, Java, etc.) pre-installed. No manual package installation required.
Important: Run
nix developeach time you open a new terminal session for this project.
Native macOS development is supported but requires additional setup. macOS ships with an older version of make, so you must install GNU Make 4.0+.
Install required packages via Homebrew:
brew install git cmake openjdk@17 make
pip3 install pyserialInstall the ARM cross-compiler:
brew install --cask gcc-arm-embeddedWarning: Do not install the
arm-none-eabi-gccformula. If you did, uninstall it first:brew uninstall arm-none-eabi-gcc brew install --cask gcc-arm-embedded
Important: On macOS,
makeis installed asgmake. Usegmakeinstead ofmakein all commands below.
Check that the required tools are available:
# Check cross-compiler
which arm-none-eabi-gcc
# Check Java version (should be 17+)
java -version
# Check make version (should be 4.0+)
make --version # or gmake --version on macOSThe RIOT OS sources are provided as a submodule of the repository. Fetch them with:
cd reactor-uc-tutorial
git submodule update --init --recursiveThe repository has a Makefile that governs the build. By default, it compiles the LF program in src/HelloUc.lf. To compile a different program, edit the Makefile to set LF_MAIN to your program and BOARD to your board.
LF_MAIN ?= HelloUc
BOARD ?= adafruit-feather-nrf52840-senseAlternatively, you can override the board on the command line. For example:
make LF_MAIN=HelloUc allmake allOr override the Makefile configuration with parameters:
make LF_MAIN=HelloUc BOARD=adafruit-feather-nrf52840-sense allmake flashOr override the Makefile configuration with parameters:
make LF_MAIN=HelloUc BOARD=adafruit-feather-nrf52840-sense flashYou can open a terminal that interacts with stdin and stdout of your program as follows:
make termThis will display any output your program generates using, for example, printf.
You can also get debug output from the reactor-uc runtime by changing the following line in the Makefile:
CFLAGS += -DLF_LOG_LEVEL_ALL=LF_LOG_LEVEL_ERROR
to
CFLAGS += -DLF_LOG_LEVEL_ALL=LF_LOG_LEVEL_DEBUG
Modify src/LedController.lf — a reactor that controls the on-board LED.
Lingua Franca docs: lf-lang.org/docs
Task: Add an input port named toggle.
Task: Implement a reaction that reacts on the toggle input port. This reaction should check the state variable self->num to toggle either LED 0 or LED 1.
Use the RIOT macros from led.h: LED0_TOGGLE and LED1_TOGGLE
Edit src/HelloUc.lf to use your LedController with a periodic timer.
You can import reactor from other files like this:
import LedController from "./LedController.lf"
The file already imports and instantiates the LedController as led, and includes a startup reaction that sets the LED on initially.
Task: Add a timer.
Task: Add a reaction that is triggered by the timer and toggles the led.toggle port.
| Flashing Procedure | LF Diagram |
|---|---|
![]() |
Add the following lines to your Makefile to enable I2C support in RIOT:
# so i2c and printf support for floats is compiled into the riot kernel
USEMODULE += periph_i2c
USEMODULE += printf_floatThe Adafruit Feather Sense includes many sensors. We'll focus on the LSM6DS33 accelerometer and gyro. See the full sensor list for details.
The file LSM6DS33.lf has a section for reading sensor values that you need to complete. Refer to the RIOT I2C documentation and read register OUTX_L_G (see the datasheet for details).
To test your sensor implementation, compile and run Sensor.lf (which includes the sensor reactor). This command compiles, flashes, and opens the serial console:
make LF_MAIN=Sensor BOARD=adafruit-feather-nrf52840-sense all flash termMake the LED blink faster or slower based on the device's orientation. When flat on a table (angle ≈ 0), use a 1-second LED toggle period.
uint32_t time_speed_up = MAX(ROTATION_TO_TIME * total_angle_x) + BASE_BLINKING_PERIOD, MIN_BLINKING_PERIOD);
This produces our PERIOD in milliseconds.
Task: Add a reaction that gets triggered based on the self->blinking_speed period. Use the logical action blink for this. This reaction should also toggle the LED.
Flash the program and rotate the device around its longest axis to see the LED blink rate change. See the video below.
| Flashing Procedure | LF Diagram |
|---|---|
![]() |
reactor-uc supports many annotations; see the full list. In this scenario, we'll add a timeout and configure a buffer size for actions.
- Timeout: add the
@timeout(<time_value>)annotation to the main reactor. - Action Buffer Size: add the
@max_pending_event(<number>)before the action declaration.
Now recompile your program.
You can validate if the code generator correctly adjusted the action buffer size by opening src-gen/Sensor/Sensor/Sensor.h file and searching for the LF_DEFINE_ACTION_STRUCT macro.
The timeout property can be found inside the src-gen/Sensor/lf_start.c file inside the DynamicScheduler_ctor.
Before we go federated it is good to look into the @buffer annotation, which can be added to delayed connections to increase the associated buffer for storing the values. If you have a timer with a high frequency it is very easy to run out of space inside the connection.
Task: The DelayedConn.lf program has a high-frequency source (1ms timer) with a 500ms delayed connection. Experiment with different @buffer values to find the minimum buffer size that prevents dropped values.
Compile and Flash your program, then observe the output and adjust the @buffer annotation until no values are dropped.
Temporarily add the following to your Makefile:
USEMODULE += gnrc_netif
USEMODULE += gnrc_ipv6_default
USEMODULE += ipv6_addr
USEMODULE += netdev_default
USEMODULE += gnrc_netif_ieee802154
USEMODULE += gnrc_ipv6_default
USEMODULE += auto_init_gnrc_netif
USEMODULE += auto_init
Then compile and run the src/Ipv6LinkLocal.lf program. This program will print the IPv6 Link Local address of this board. Copy and save this address.
You need to tell reactor-uc to add the COAP network channel to the compilation unit:
CFLAGS += -DNETWORK_CHANNEL_COAP_RIOTIn reactor-uc, configure the network channels by adding annotations:
@interface_coap(name="if1", address="<Paste Your Link Local Address Here>")
Task: Coordinate with your neighbor, agree on which federate your board will run, and exchange the IPv6 link local addresses accordingly. Also, make sure your programs have the same structure.
This creates a CoAP network channel named if1 with the specified IPv6 address. The @link annotation specifies which network channel interface to use for a connection.
The compilation command also changes because you now need to specify which federate to compile and flash using the LF_FED variable:
make LF_MAIN=SimpleCoapFederated LF_FED=r1 BOARD=adafruit-feather-nrf52840-sense all flash term
make LF_MAIN=SimpleCoapFederated LF_FED=r2 BOARD=adafruit-feather-nrf52840-sense all flash termIf successful, you should see in the serial output that the two federates are communicating.
| Flashing Procedure | LF Diagram |
|---|---|
![]() |
You made it to the final level.
Open the the src/FederatedBlinking.lf file now we want to combine everything learned and
here we want to let the local Blink faster if neighbors microcontroller is turned.
Then make sure all the necessary modules are added inside your Makefile. Additionally, make sure you flash the correct version onto the correct board (otherwise the addresses won't match).








