Getting Started with Zephyr OS on Arduino đȘ
Learn how to integrate Zephyr Real-Time Operating System into your Arduino IoT projects
Zephyr OS is a lightweight, open-source real-time operating system (RTOS) maintained by the Linux Foundation. Designed for resource-constrained devices, it offers scalability, modularity, and real-time capabilities, making it an attractive choice for embedded developers.
Compared to FreeRTOS (currently one of the most widely used open-source RTOS, developed by Amazon Web Services Open Source), Zephyr uses a microkernel architecture, offering more modularity and flexibility. FreeRTOS, with its monolithic kernel, is more straightforward but less flexible, focusing on ease of integration.
Why Zephyr on Arduino?
Arduino boards are widely used in prototyping and embedded applications, and Zephyr brings an additional layer of robustness and flexibility.
But how does Zephyr OS fit into the Arduino ecosystem?
In this getting started guide, weâll explore Zephyrâs direct support for Arduino boards and how to integrate it into the traditional Arduino ecosystem development flow.
Zephyr OS and Arduino: Compatibility Overview
There are two main approaches to use Zephyr OS with Arduino:
- Direct Zephyr OS Support for Arduino Boards: Some Arduino boards, like the Portenta H7 and Nano 33 BLE, have direct Zephyr support. In this approach, Zephyr fully controls the board, bypassing the traditional Arduino ecosystem, and developers work directly with Zephyrâs features.
- ArduinoCore Integration with Zephyr: In this approach, Zephyr is integrated transparently into the classic Arduino experience. Zephyr runs in the background, providing real-time capabilities, power management, and other advanced features, while still allowing developers to use the familiar Arduino IDE and libraries.
In the following sections, we will explore how to program an Arduino board using both approaches.
Direct Support: Setting Up Zephyr for Arduino Boards
To set up Zephyr for Arduino boards, you need to install the Zephyr development environment, which includes prerequisites like Python, CMake, and West (Zephyr Meta Tool). Youâll also need to install the Zephyr SDK and toolchain, and clone the Zephyr repository.
For detailed steps on setting up the Zephyr development environment, refer to the Zephyr Getting Started Guide.
For configuring Zephyr with an Arduino board, we will be working on the Arduino UNO R4 WiFi board. You can select it, configure it using the west tool, and build a sample application. Flashing and debugging can then be done directly using the west tool.
For more details on configuring Zephyr for the Arduino UNO R4 WiFi, check the Arduino Uno R4 WiFi Zephyr Documentation.
First Zephyr Application on Arduino
Zephyr comes equipped with a variety of samples and demos that you can use as starting points for your projects. These examples cover different aspects of Zephyrâs functionality and can help you get up and running quickly. You can explore all available samples in the Zephyr Samples Documentation.
For simplicity, we will start with the Blinky sample, which is a basic example where the boardâs built-in LED blinks forever using the GPIO peripheral. In addition to controlling the LED, this example also prints the LEDâs state to the serial console, making it a great way to familiarize yourself with the basic functionalities of GPIO and UART.
You can build and flash an application using the west tool. Hereâs an example for building and flashing the Blinky application on the Arduino UNO R4 WiFi:
# From the root of the Zephyr repository
west build -b arduino_uno_r4_wifi samples/basic/blinky
west flashOnce the application is flashed, the built-in LED will start blinking with a frequency of 1 second, and the LEDâs state will be shown on the serial interface. However, since Zephyr does not use the typical USB serial port for output (like the Arduino ecosystem does), you will need to use an FTDI USB-to-UART device connected to pins D0 and D1 of the board. The serial output will display the LED state as LED state: ON and LED state: OFF.
To read the serial print, connect the FTDI device to your computer and open a serial logger with a baud rate of 115200. For example, you can use Minicom. Use the following command to open the serial connection on device /dev/ttyACM0 (adjust the device name based on your system):
minicom -D /dev/ttyACM0 -b 115200The Blinky program
Here is the Blinky program with comments on the main parts. The application uses key Zephyr APIs to interact with the GPIO and Console.
#include <stdio.h>
#include <zephyr/kernel.h>
#include <zephyr/drivers/gpio.h>
/* 1000 msec = 1 sec */
#define SLEEP_TIME_MS 1000
/* The devicetree node identifier for the "led0" alias */
#define LED0_NODE DT_ALIAS(led0)
/* Retrieve the GPIO device specification for the "led0" alias from the device tree */
static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(LED0_NODE, gpios);
int main(void)
{
int ret;
bool led_state = true;
/* Check if the GPIO device is ready for use */
if (!gpio_is_ready_dt(&led)) {
return 0;
}
/* Configure the GPIO pin as an output and set it to active (LED ON) */
ret = gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE);
if (ret < 0) {
return 0;
}
/* Toggle the LED state in an infinite loop */
while (1) {
/* Toggle the LED (ON -> OFF or OFF -> ON) */
ret = gpio_pin_toggle_dt(&led);
if (ret < 0) {
return 0;
}
/* Update the LED state and print it to the serial console */
led_state = !led_state;
printf("LED state: %s\n", led_state ? "ON" : "OFF");
/* Sleep for 1 second before toggling the LED again */
k_msleep(SLEEP_TIME_MS);
}
return 0;
}Itâs easy to see that the program is very generic and works on all compatible boards without modifications.
But how do I know the hardware implementation for led0 and the UART used by printf? This is discovered through the Device Tree.
The Device Tree
The Device Tree is a data structure that describes the hardware configuration of a system, including details about GPIO pins, UART interfaces, and other peripherals. It is defined in a set of files that specify how hardware resources are mapped, allowing Zephyr to correctly configure and interact with them.
The Device Tree files for the Arduino UNO R4 WiFi board can be found here.
In the device tree file arduino_uno_r4_wifi.dts, the led0 is defined in the led node with the following configuration:
leds {
compatible = "gpio-leds";
led: led {
gpios = <&ioport1 2 GPIO_ACTIVE_HIGH>;
};
};
// ...
arduino_header: connector {
// ...
gpio-map = <0 0 &ioport0 14 0>, /* A0 */
<1 0 &ioport0 0 0>, /* A1 */
<2 0 &ioport0 1 0>, /* A2 */
<3 0 &ioport0 2 0>, /* A3 */
// ...
};The LED is associated with a GPIO pin located on ioport1, pin number 2, (Connector Pin A3) and it is set to be active high.
In the aliases section, led0 is defined as a reference to this LED:
aliases {
led0 = &led;
};As for the UART used for the console, the configuration can be found in the arduino_uno_r4_common.dtsi file, where the serial console is mapped to uart2 with a baud rate of 115200:
/ {
model = "Arduino Uno R4 Board";
compatible = "renesas,ra4m1", "renesas,ra";
chosen {
zephyr,console = &uart2; // <-- UART used for Console
zephyr,shell-uart = &uart2;
zephyr,sram = &sram0;
zephyr,flash = &flash0;
zephyr,code-partition = &code_partition;
};
};
&sci2 {
status = "okay";
pinctrl-0 = <&sci2_default>;
pinctrl-names = "default";
interrupts = <4 1>, <5 1>, <6 1>, <7 1>;
interrupt-names = "rxi", "txi", "tei", "eri";
uart2: uart {
current-speed = <115200>; // <-- UART2 Baudate
status = "okay";
};
};Using Zephyr with the Arduino Ecosystem
The ArduinoCore for ZephyrOS is an implementation of the Arduino core that leverages the full potential of ZephyrOS. This integration allows developers to take advantage of Zephyrâs advanced features while maintaining the simplicity and ease of use of the Arduino development process.
Installation and Board Setup
âïž To get started, install the core and toolchain via the Board Manager in the Arduino IDE or CLI. For detailed installation instructions, refer to the official README.
đïž After installation, before loading your first sketch, use the Tools â Burn Bootloader option in the IDE or CLI. However, itâs important to note that this process doesnât install a traditional Arduino bootloader. Instead, what youâre installing is the Zephyr loader, a special component that manages the interaction between your sketch and the ZephyrOS.
How Zephyr Sketches Work
Unlike traditional Arduino sketches, Zephyr sketches are compiled into ELF files and dynamically loaded by the precompiled Zephyr loader firmware. This approach leads to faster compilation times and smaller binary files, as only the user-specific code and libraries are compiled, while ZephyrOS itself remains precompiled.
The Blink program
Now, letâs test our first sketch using the classic Blink program found in the File â Examples â Basics, without any modifications.
/*
Blink
Turns an LED on for one second, then off for one second, repeatedly.
https://www.arduino.cc/en/Tutorial/BuiltInExamples/Blink
*/
// the setup function runs once when you press reset or power the board
void setup() {
// initialize digital pin LED_BUILTIN as an output.
pinMode(LED_BUILTIN, OUTPUT);
}
// the loop function runs over and over again forever
void loop() {
digitalWrite(LED_BUILTIN, HIGH); // turn the LED on (HIGH is the voltage level)
delay(1000); // wait for a second
digitalWrite(LED_BUILTIN, LOW); // turn the LED off by making the voltage LOW
delay(1000); // wait for a second
}As we can see, the process doesnât change from the typical Arduino flow. You simply verify and upload the sketch to the board using the usual procedure.
However, what we can observe is the efficiency of the sketch when compiled with Zephyr compared to the traditional board core.
For instance, compiling the Blink sketch for an Arduino GIGA R1 board, we get the following results:
Compiled with Zephyr
Sketch uses 2860 bytes (0%) of program storage space. Maximum is 786432 bytes.
Global variables use 3312 bytes (0%) of dynamic memory, leaving 520312 bytes for local variables. Maximum is 523624 bytes.Compiled with the traditional board core (ArduinoCore-mbed)
Sketch uses 110736 bytes (5%) of program storage space. Maximum is 1966080 bytes.
Global variables use 47520 bytes (9%) of dynamic memory, leaving 476104 bytes for local variables. Maximum is 523624 bytes.As seen from the comparison, compiling with Zephyr results in much smaller binary files (2860 bytes vs 110736 bytes), reflecting its efficient handling of the code. This is one of the key advantages of using Zephyr for Arduino, offering faster compilation times and smaller program sizes while retaining the simplicity of Arduino development.
Expanding Your Zephyr Knowledge
Zephyr OS brings real-time capabilities and scalability to the Arduino world, enabling developers to create more complex and efficient embedded applications. Whether youâre transitioning from the traditional Arduino workflow or diving straight into RTOS-based development, Zephyr offers a powerful tool for your IoT projects.
For more in-depth information, check out these resources:
