Zephyr RTOS gives you a modern, permissively-licensed real-time operating system with a huge board and driver ecosystem, built and configured through its own west/CMake toolchain. Arm Keil MDK gives you µVision’s source-level debugger, RTX-class breakpoints and watch windows, and — through ULINK2, ULINKplus, ULINKpro and DSTREAM-ST — instruction trace, code coverage and execution profiling that Zephyr’s own command-line debug tooling does not provide. The two are not mutually exclusive: you can build a Zephyr application entirely with Zephyr’s own build system, then hand the resulting ELF straight to µVision for debugging, without ever creating a Keil build project. This guide walks through exactly how.
What Is Zephyr RTOS?
Zephyr is an open-source, real-time operating system hosted by the Linux Foundation, designed for resource-constrained and embedded devices. It supports a very wide range of Arm Cortex-M, Cortex-A/R and other architectures, and is built with west (Zephyr’s meta-tool) driving a CMake + Ninja/Make build.
What Is Keil MDK?
Arm Keil MDK is Arm’s own microcontroller development kit — the µVision IDE, Arm Compiler, and CMSIS software packs — paired with Arm Keil ULINK and DSTREAM-ST debug probes. MDK is not tied to any single RTOS or build system: µVision can debug any ELF/AXF image that carries standard debug information, regardless of which toolchain produced it.
Why Debug a Zephyr Application with Keil MDK?
Zephyr’s own west debug and west debugserver commands drive GDB through runners such as pyOCD, J-Link or OpenOCD — Arm’s own ULINK and DSTREAM-ST probes are not among the supported runners. If your team already owns Arm debug hardware, or needs capabilities Zephyr’s default debug flow does not offer — instruction trace, code coverage, execution profiling, or Arm Development Studio/µVision’s debug UI — the practical path is to build with Zephyr as usual, then load the resulting image directly into µVision as a “debug-only” session.
Prerequisites
- A working Zephyr development environment (Zephyr SDK,
west, and Python dependencies installed per the official Getting Started Guide) - Keil MDK (µVision 5) installed, with the CMSIS device pack for your target microcontroller
- An Arm Keil ULINK2, ULINKplus, ULINKpro or DSTREAM-ST probe, or a CMSIS-DAP adapter, wired to your board’s JTAG/SWD header
Step 1 — Build Your Zephyr Application
Build normally with west, targeting your board:
west build -b <your_board> path/to/your/appBy default Zephyr’s CMake build compiles with debug information enabled, which is what makes source-level debugging in µVision possible afterwards. The resulting debug image is written to build/zephyr/zephyr.elf (Zephyr also produces zephyr.hex and zephyr.bin, but only the ELF carries the symbol and line-number information µVision needs for source-level debug).
Step 2 (Optional) — Build with the Arm Compiler 6 Toolchain
Zephyr defaults to the GNU Arm Embedded toolchain, but it also supports Arm Compiler 6 (armclang) — the same compiler used by Keil MDK — as a third-party toolchain. To use it, set before building:
export ZEPHYR_TOOLCHAIN_VARIANT=armclang
export ARMCLANG_TOOLCHAIN_PATH=/path/to/your/arm/compiler
export ARMLMD_LICENSE_FILE=/path/to/license_armds.datThis requires a valid Arm Compiler license (e.g. from an MDK-Professional or Arm Development Studio installation). It is not required to debug in µVision — a GNU-built ELF debugs in µVision perfectly well — but keeping build and debug on the same compiler family avoids any codegen differences between what you build and what you step through.
Step 3 — Create a Debug-Only µVision Project
Rather than trying to recreate Zephyr’s build inside µVision, create a minimal project whose only job is to load and debug the Zephyr-built ELF:
- In µVision, Project › New µVision Project and select the exact microcontroller your Zephyr board targets (matching device is what supplies the correct memory map and flash algorithm).
- When prompted for startup/runtime files, you can decline adding them — this project will not compile any source, it only hosts debug settings.
- Open Project › Options for Target › Debug, select your debug adapter (ULINK2 / ULINKplus / ULINKpro / DSTREAM-ST / CMSIS-DAP) from the dropdown, and configure it under Settings (port SW, correct clock speed for your target).
- Still on the Debug tab, tick Load Application at Startup and Run to main(), and untick Update Target before Debugging under the Utilities tab if present, so µVision does not try to (re)build anything before launching the debug session.
Step 4 — Point µVision at the Zephyr ELF
In Options for Target › Debug, browse to your build output as the image to load: build/zephyr/zephyr.elf. µVision’s debugger reads the ELF’s DWARF debug information directly, so source files, line numbers, function names, global and static variables all resolve normally — you can also drive this via the LOAD command from the command window, e.g. LOAD build\zephyr\zephyr.elf, if you prefer to load images ad hoc without changing project settings.
Step 5 — Start Debugging
Click Debug › Start/Stop Debug Session. µVision resets the target, downloads the Zephyr image over your probe, and stops at main() (or wherever your entry breakpoint is set). From here you have full source-level debugging of your Zephyr application: breakpoints, step/step-over/step-into, local and global variable watch, memory and register views, and — if you’re using ULINKpro or DSTREAM-ST — instruction trace, code coverage and execution profiling against the running Zephyr image, the same as any other Cortex-M target.
Tips for a Clean Debug Session
- Keep optimization at
-O0or-Ogin your Zephyr build (via your application’sCMakeLists.txtor a debug build type) for reliable source-line stepping — higher optimization levels reorder and inline code in ways that make step-by-step debugging confusing regardless of which debugger you use. - Double-check the device selected in your µVision project matches your Zephyr board’s exact part number, so the flash algorithm and memory map line up.
- µVision does not ship a dedicated Zephyr thread-awareness view the way it does for Keil RTX — you get full Cortex-M/CoreSight-level debugging (registers, memory, call stack, trace), but not a built-in “list of Zephyr threads” panel. Zephyr’s thread structures can still be inspected manually via the Watch/Memory windows if needed.
- Re-run
west buildand reload the ELF (or restart the debug session) after every source change — this workflow does not auto-rebuild from µVision.
Frequently Asked Questions
Can ‘west debug’ drive a Keil ULINK or DSTREAM-ST probe directly?
No. Zephyr’s west debug/west debugserver commands support runners such as pyOCD, J-Link and OpenOCD, not Arm’s ULINK/DSTREAM-ST family. To use those probes, build with west as usual and load the resulting ELF into a µVision debug session instead, as described above.
Do I need an Arm Compiler license to debug a Zephyr build in µVision?
No. µVision can debug an ELF built with the default GNU Arm Embedded toolchain with no extra license — an Arm Compiler 6 (armclang) license is only needed if you choose to build with ZEPHYR_TOOLCHAIN_VARIANT=armclang instead.
Does this give me Zephyr thread-aware debugging in µVision?
Not out of the box. µVision provides full Cortex-M source-level debugging, memory/register access, and — with ULINKpro or DSTREAM-ST — instruction trace and code coverage against the Zephyr image, but it does not include a built-in Zephyr-specific thread-list view the way it does for Keil RTX.
Pertech supplies the full Arm Keil MDK toolchain and the ULINK2, ULINKplus, ULINKpro and DSTREAM-ST debug probes used in this workflow, with local stock and technical support in Israel. Contact us if you need help wiring up Zephyr and Keil MDK for your project.







