FreeRTOS vs Zephyr: How to Choose an RTOS for a Connected Sensor

FreeRTOS vs Zephyr: How to Choose an RTOS for a Connected Sensor

Flux, the AI hardware engineer, writes the firmware for this kind of device on either RTOS. Tell Flux to use FreeRTOS or Zephyr, and it sets up the tasks, queues, timers, and interrupts, maps each pin and peripheral to the nets in your schematic, builds the firmware, and reports flash and RAM use. It also sets up signed OTA updates with two firmware slots and rollback. The firmware stays in step with the schematic, the PCB, and the enclosure in the same project.

Start Free Trial

FreeRTOS vs Zephyr comes down to how much of the system you want the RTOS to supply. FreeRTOS is a small, MIT-licensed scheduler kernel: tasks, queues, semaphores, and software timers. You add it to the vendor SDK of your microcontroller, and the SDK supplies the drivers and the radio stack. Zephyr is a complete RTOS from the Linux Foundation, under the Apache 2.0 license. It brings its own drivers, devicetree hardware description, Bluetooth and IP networking, a build system, and an OTA bootloader.

So the hardware decides. If the chip vendor's SDK is built on FreeRTOS, use FreeRTOS. If the chip vendor's SDK is built on Zephyr, or the product needs Bluetooth, networking, and signed updates across more than one board, use Zephyr. Below, one connected sensor is worked through both ways so you can see where each choice comes from.

The example: a battery-powered environmental sensor

The device is a wall-mounted sensor for a building-monitoring product:

  • A BME280 temperature, humidity, and pressure sensor on I2C, read once every 30 seconds.
  • An accelerometer with an interrupt pin that wakes the device when someone removes it from the wall.
  • A Bluetooth Low Energy link to a gateway. A second product variant uses Wi-Fi.
  • Two AA cells, with a battery-life target of two years.
  • Signed over-the-air (OTA) updates with rollback, because nobody opens 2,000 enclosures to reflash them.
  • A molded enclosure with a PCB antenna that needs a keep-out zone at the board edge.

The firmware has the same three jobs on either RTOS:

  1. Sensor task: wake on a timer, read the BME280, and put the reading in a queue.
  2. Radio task: take readings from the queue and send them, then let the radio sleep.
  3. Tamper interrupt: wake on the accelerometer pin and send an alarm at once.

FreeRTOS vs Zephyr at a glance

Area FreeRTOS Zephyr
Scope Scheduler kernel. Libraries such as FreeRTOS-Plus-TCP and coreMQTT are separate add-ons. Kernel plus drivers, sensor API, Bluetooth host and controller, IP stack, file systems, logging, shell.
License MIT Apache 2.0
Drivers From the vendor SDK (ESP-IDF, STM32Cube, MCUXpresso) or your own code. In-tree drivers behind common APIs, selected by devicetree.
Hardware description C headers and the vendor's configuration tool. Devicetree files and overlays for each board.
Configuration FreeRTOSConfig.h Kconfig (prj.conf)
Build system Whatever the SDK uses: CMake, Make, or an IDE project. CMake with the west meta-tool.
Flash footprint Kernel alone is typically 6 to 12 KB. Larger, because the image includes the drivers and subsystems you enable.
OTA updates Vendor OTA component or an add-on library. MCUboot bootloader with signed images and two slots, integrated in the build.
Vendor SDKs built on it Espressif ESP-IDF, many STM32 and NXP examples. Nordic nRF Connect SDK, plus upstream support for many STM32, NXP, and ESP32 parts.

The sensor on FreeRTOS

With FreeRTOS, you write the task structure yourself and call the vendor SDK for the I2C driver and the radio. The sensor task uses vTaskDelayUntil() so the 30-second period does not drift, and it hands each reading to the radio task through a queue:

typedef struct { bool tamper; int32_t temp_mc; uint32_t hum_mpct; uint32_t press_pa; } reading_t;
static QueueHandle_t readings;

static void sensor_task(void *arg) {
    TickType_t last = xTaskGetTickCount();
    for (;;) {
        reading_t r = { .tamper = false };
        if (bme280_read(&r) == 0) {          /* your driver, on the SDK's I2C API */
            xQueueSend(readings, &r, 0);
        }
        vTaskDelayUntil(&last, pdMS_TO_TICKS(30000));
    }
}

static void radio_task(void *arg) {
    reading_t r;
    for (;;) {
        if (xQueueReceive(readings, &r, portMAX_DELAY) == pdPASS) {
            radio_send_reading(&r);            /* vendor BLE or Wi-Fi stack */
        }
    }
}

void app_main(void) {
    readings = xQueueCreate(8, sizeof(reading_t));
    xTaskCreate(sensor_task, "sensor", 2048, NULL, 5, NULL);
    xTaskCreate(radio_task,  "radio",  4096, NULL, 4, NULL);
}

The tamper interrupt puts a message with tamper = true at the front of the same queue with xQueueSendToFrontFromISR(). The radio task wakes and sends the alarm before the next reading. For low power, you enable tickless idle with configUSE_TICKLESS_IDLE so the core sleeps between ticks, and the vendor port puts the chip in its deepest sleep mode that keeps the timer running.

This is the shortest path on the Wi-Fi variant. The ESP32 family's ESP-IDF already runs FreeRTOS, its Wi-Fi stack and OTA component run as FreeRTOS tasks, and the code above is what an ESP-IDF application looks like. The cost: the BME280 driver, the pin assignments, and the update flow are your code, tied to one vendor's SDK. If the next product uses a different chip, you rewrite those parts.

The sensor on Zephyr

With Zephyr, you describe the hardware and enable the features, and the RTOS supplies the driver. The BME280 goes in a devicetree overlay for the board:

&i2c0 {
    status = "okay";
    bme280: bme280@76 {
        compatible = "bosch,bme280";
        reg = <0x76>;
    };
};

The features go in prj.conf:

CONFIG_I2C=y
CONFIG_SENSOR=y
CONFIG_BT=y
CONFIG_BT_PERIPHERAL=y
CONFIG_PM=y

The bootloader goes in sysbuild.conf:

SB_CONFIG_BOOTLOADER_MCUBOOT=y

The application reads the sensor through the generic sensor API, so the same code works with another supported sensor:

K_MSGQ_DEFINE(readings, sizeof(struct sensor_value) * 3, 8, 4);
static const struct device *bme = DEVICE_DT_GET(DT_NODELABEL(bme280));

static void sensor_thread(void *p1, void *p2, void *p3) {
    struct sensor_value v[3];
    for (;;) {
        if (sensor_sample_fetch(bme) == 0) {
            sensor_channel_get(bme, SENSOR_CHAN_AMBIENT_TEMP, &v[0]);
            sensor_channel_get(bme, SENSOR_CHAN_HUMIDITY, &v[1]);
            sensor_channel_get(bme, SENSOR_CHAN_PRESS, &v[2]);
            k_msgq_put(&readings, v, K_NO_WAIT);
        }
        k_sleep(K_SECONDS(30));
    }
}
K_THREAD_DEFINE(sensor_tid, 1024, sensor_thread, NULL, NULL, NULL, 5, 0, 0);

The radio thread takes values from readings and updates a GATT characteristic. When every thread sleeps, the power management subsystem puts the SoC in its low-power state. With sysbuild, west build --sysbuild builds MCUboot with the application, signs the application image, and puts two image slots in the board's flash partitions. After an update, the new image confirms itself with boot_write_img_confirmed(). If it does not, MCUboot goes back to the previous image at the next reset.

This is the shortest path on the BLE variant with a Nordic nRF52840 or nRF54 part. The nRF Connect SDK is built on Zephyr, so the Bluetooth stack, power management, and update flow come from the same tree. The cost is the learning curve: devicetree, Kconfig, and west are three new tools for an engineer who knows only a vendor IDE, and the image is larger than a bare FreeRTOS kernel.

Compare memory with a real build, not a datasheet

The FreeRTOS kernel is small, but the kernel is not the firmware. A connected sensor also carries a radio stack, a TLS or BLE security layer, a sensor driver, and a bootloader. On both RTOSes, the radio stack usually uses most of the flash and RAM. Compare the two choices on the same features:

  • Build both versions for the target with BLE or Wi-Fi, the sensor, low power, and OTA enabled.
  • Read the flash and RAM totals from the linker map, and list the largest symbols.
  • Check the result against the part's flash after you reserve the second OTA slot. Two slots halve the space for the application.
  • Check the stack size of each task under the worst case, for example during a BLE connection and a sensor read at the same time.

Zephyr gives you west build -t rom_report and ram_report for this. On FreeRTOS, read the map file from the SDK build and check stack use with uxTaskGetStackHighWaterMark().

How the hardware picks the RTOS

If your hardware is Choose Why
ESP32-class Wi-Fi sensor FreeRTOS ESP-IDF, its Wi-Fi stack, and its OTA component already run on FreeRTOS.
Nordic nRF BLE sensor Zephyr The nRF Connect SDK, its Bluetooth stack, and MCUboot are built on Zephyr.
STM32 with an existing STM32Cube codebase FreeRTOS Keep the HAL drivers and the team's code. Add FreeRTOS through the vendor middleware.
One product line on several MCU families Zephyr Devicetree and the common driver APIs keep the application code the same across boards.
Small MCU with tight flash and no radio FreeRTOS The kernel alone fits where a full RTOS image does not.

How Flux writes the firmware for either RTOS

The RTOS choice depends on the schematic, and the schematic depends on the enclosure, the antenna, and the battery. In Flux, all of them are in one project, and you ask the agent for the firmware with the hardware it runs on:

  • Tasks, queues, and timers on either RTOS. Tell Flux to set up FreeRTOS or Zephyr, or bare-metal timer interrupts, with the task priorities and data flow you need. It writes the firmware starter document with the microcontroller, framework, build system, and libraries.
  • A pin map from the schematic. Flux maps the I2C bus, the interrupt pin, and the boot pins from the schematic nets, and names each firmware define after its net. When you move a pin, it updates the map and the source.
  • Real builds and memory reports. Flux compiles for ARM, RISC-V, ESP32-class, nRF, RP2040, AVR, and STM32 targets. It returns .elf, .bin, .hex, and .map files, reports flash and RAM use, and lists the largest symbols. That is the memory comparison above, in minutes.
  • Host unit tests before the board arrives. Flux runs tests for the sensor conversion and the message format without hardware, and writes a CI workflow that builds and tests every change.
  • OTA with rollback. Flux adds a bootloader with two firmware slots, signed images, rollback, and the update flow in the app or the cloud.
  • Battery life from the power budget. Flux sets the sleep mode, the wake sources, and the duty cycle, then estimates battery life from the power budget.
  • Mechanical values in firmware. Flux reads the sensor position and the enclosure dimensions from the mechanical model and adds them as named firmware constants. When the enclosure changes, it updates them.

Prompts like these do the work in a Flux project:

  • “Use Zephyr for this nRF52840 sensor. Read the BME280 every 30 seconds, send it over BLE, wake on the accelerometer interrupt, and add MCUboot OTA with rollback.”
  • “Set up FreeRTOS tasks on this ESP32-S3 board for sensor sampling and Wi-Fi, with the sensor readings in a queue. Build it and report flash and RAM use.”
  • “Estimate battery life on two AA cells for this firmware. Show the active time, sleep current, and wake sources.”
  • “Move the antenna keep-out to the top edge of the enclosure, update the board outline, and keep the firmware pin map in step with the new schematic.”

Without Flux, an RTOS evaluation takes a week or two: one engineer ports the sensor to two SDKs, another changes the board, and the enclosure waits. In Flux, the agent builds both versions, reports the numbers, and keeps the schematic, PCB, enclosure, and firmware in the same project in a few hours. Read more about electronics design in Flux and the firmware docs. For the chip choice that comes before the RTOS, read STM32 vs ESP32.

Open a project and tell Flux what the sensor must do. It writes the firmware for the board and the enclosure you design with it.

Start Free Trial

FAQs

About the author

Nico Tzovanis

Nico is a professional electronics and PCB design engineer at Flux. Find him on Flux @nico

Let’s start building.

Let’s build your idea together. All you need to do is describe what you want to make, or what problem you want to solve. Flux will work with you to make it a reality.

Try it Free
Close-up of a Flux-designed printed circuit board