Migrate from FreeRTOS to Zephyr with AI

By Embedder ·

Moving from FreeRTOS to Zephyr involves hardware configuration, kernel behavior, build integration, and runtime validation. An AI coding agent can help trace dependencies, make changes, and run checks. Engineers still need to define the requirements and review the results.

This article walks through that process with Embedder, from understanding the existing firmware to checking the migrated application on the board.

Understand the scope

The size of a migration depends on how the application is organized. Code behind a well-defined OS abstraction layer may carry over with relatively few changes. An application with FreeRTOS calls and vendor HAL initialization spread across its modules will need more work.

Most migrations touch four areas:

  • Hardware configuration and drivers. Describe the board through Zephyr’s devicetree, select software features and drivers through Kconfig, and retain runtime configuration where needed. Unsupported peripherals or missing driver features may require additional implementation. Zephyr’s configuration guide explains these boundaries.
  • Kernel APIs and behavior. Adapt tasks, queues, timers, synchronization, and interrupt handling while preserving the behavior their callers depend on.
  • The build. Integrate the application into Zephyr’s CMake build and configuration system. Projects commonly use west to manage the workspace and invoke build, flash, and debug operations. See Zephyr’s west documentation.
  • Runtime validation. Check timing, memory, power, peripheral behavior, and recovery against the application’s requirements.

A successful build is an early milestone. It leaves questions such as whether the sensor pipeline meets its deadline while the radio is busy, or whether a failed peripheral can recover without stalling other work.

Catch behavioral differences early

Several migration mistakes come from carrying familiar values or assumptions into an API with different semantics.

Task priorities run in opposite directions. FreeRTOS assigns higher task priority to larger numbers. Zephyr assigns higher thread priority to smaller numbers: 0 is the highest preemptible priority, and negative values select cooperative priorities. Review the intended ordering alongside system threads, time slicing, and startup behavior. See FreeRTOS task scheduling and Zephyr threads.

Stack sizes need an explicit conversion. Upstream FreeRTOS specifies xTaskCreate() stack depth in StackType_t elements, often called words. Zephyr’s K_THREAD_DEFINE() takes bytes. If StackType_t is four bytes, a depth of 512 represents 2,048 bytes. Check the source SDK first: ESP-IDF already specifies task stack sizes in bytes. After converting, measure stack usage again under representative load. See the FreeRTOS API, Zephyr thread API, and ESP-IDF documentation.

Timer callbacks change context. FreeRTOS software timer callbacks execute in the timer service task and must not block. Zephyr k_timer expiry callbacks execute in interrupt context. Processing that needs thread context must be deferred, with the resulting scheduling latency included in the design. See FreeRTOS software timers and Zephyr timers.

Deferred work may combine multiple events into one execution. Submitting a Zephyr work item that is already queued does not enqueue another copy. A shared workqueue can also delay processing behind other work. If every sample matters, retain the samples in a suitable queue or buffer and verify that the consumer keeps up. Zephyr’s workqueue documentation describes these behaviors.

A valid hardware configuration can still be wrong for the board. A pin assignment may be supported by the MCU but connected differently on the PCB. A configured bus speed may exceed a peripheral’s limits. Review the schematic and device documentation, then check the actual signals during bring-up.

These differences should become explicit decisions in the migration plan and checks in the test procedure.

Use Embedder through the migration

Embedder is an AI coding agent built for firmware. It can inspect the repository and hardware documentation, work with the configured build tools, and use supported debug probes and bench instruments to investigate runtime behavior. Its documented RTOS migration workflow connects those activities in a repeatable process.

1. Scope the migration and capture a baseline

Ask Embedder to inventory FreeRTOS dependencies in the application and accessible middleware. Include wrappers, build variants, interrupt handlers, and generated code that may hide dependencies.

Record the source SDK and RTOS versions, then choose the destination Zephyr release. Check support for the exact board and peripheral features the application uses. A driver’s presence does not establish that it supports the required DMA, asynchronous transfers, or power modes.

Before changing the firmware, run the existing tests and capture the behavior that must be preserved. Record workloads, measurement methods, and tolerances for timing, memory, power, and recovery.

A planning request might look like this:

Plan a FreeRTOS-to-Zephyr migration on the current board. Inventory RTOS
dependencies, including wrappers and middleware. Preserve the sensor API,
10 ms sampling period, sample ordering, and UART packet format. Identify
board and driver gaps for the selected Zephyr release. Propose stages,
affected files, and checks against the recorded baseline before editing.

Replace those example requirements with your own. Keep the original firmware artifact and a reproducible FreeRTOS build available for comparison.

2. Establish a minimal image and work incrementally

Start with a Zephyr image that builds, boots, and produces observable output. Confirm the board target, toolchain, dependencies, and flash procedure before moving application services.

Then work through one subsystem at a time: configure a peripheral, verify a transaction, bring up its consuming thread, and check the complete data path.

Each stage should have a defined result. For a sensor application, that could be one correctly decoded sample reaching the UART output before adding periodic acquisition, buffering, and concurrent workloads.

3. Port hardware configuration against the board

Give Embedder the existing initialization code, board revision, schematics, and relevant hardware documentation. Ask it to trace pin assignments, clocks, interrupt routing, and peripheral settings, then propose the necessary devicetree overlays, pinctrl configuration, Kconfig settings, and runtime code.

Account for what Zephyr’s existing board and SoC support already initialize. If retaining vendor HAL code, check that it does not compete with a Zephyr driver for the same peripheral, interrupt, DMA channel, or clock configuration.

Review the generated configuration as well as the source files. Zephyr’s devicetree guide explains how to inspect the resolved hardware description and diagnose configuration problems.

4. Convert kernel calls with their behavior documented

Ask Embedder to explain each proposed mapping: priority order, stack units, ownership, timeout behavior, event handling, return values, and callback context.

For example, a fixed-size queue using xQueueSendFromISR() may map to k_msgq_put() with K_NO_WAIT in a regular Zephyr ISR. The port still needs to preserve item ownership and handle a full queue. See Zephyr message queues.

That mapping does not extend to zero-latency interrupts. Zephyr treats kernel API calls from zero-latency interrupt context as undefined behavior. Follow the destination’s interrupt rules.

Review periodic work separately from ordinary delays. Sleeping for 10 ms after processing a sample adds processing time to each cycle; it may not preserve an existing delay-until schedule. Keep the intended deadline and overrun policy explicit. Zephyr’s timing documentation covers relative and absolute timeouts.

5. Build, run, and measure

With the toolchain and supported hardware connections configured, Embedder can run builds, investigate failures, flash the board, and collect serial output or instrument captures.

Repeat the baseline workload and check concrete requirements:

  • Does acquisition meet its period and jitter limits under expected peak load?
  • How long does it take from a sensor interrupt to processing by the consuming thread?
  • Are samples lost, reordered, or overwritten when queues fill?
  • Does the system recover from a peripheral timeout or stalled task?
  • Are stack usage, total memory, and power consumption within budget?

Choose measurements that answer those questions. A logic analyzer can capture bus transactions. An oscilloscope can reveal signal-quality problems. GPIO instrumentation or suitable trace can help measure execution latency. See Embedder’s supported debug tools and instruments.

Save the firmware version, configuration, test inputs, and captures with each result. Account for logging and debugger halts that can affect timing. Once individual subsystems work, test their interactions under load. Hard timing requirements may also need worst-case analysis beyond observed test runs.

Review the result before retiring the source build

Engineers decide which interfaces to preserve, where an OS abstraction layer is useful, which drivers to retain, and whether additional changes such as adopting MCUboot belong in the migration.

Embedder can help implement those decisions and repeat the build, flash, and measurement work. Review the resulting code alongside the evidence: what passed, what changed intentionally, and what remains unverified.

Keep the working FreeRTOS build until the Zephyr application passes the agreed checks. Finish with a reproducible destination build, updated project instructions, and regression tests that make the next change easier to verify.