Zephyr RTOS skill for Claude Code
Devicetree and Kconfig without the guesswork. Claude sets up a pinned west workspace, describes hardware in board overlays instead of C constants, enables the minimal Kconfig set, and uses DT-spec driver APIs with readiness checks. When builds or devices fail it reads the generated zephyr.dts and .config first, then fixes compatibles, status, or dependencies. Logic is tested on native_sim with twister.
- name
zephyr-rtos - size243 lines · ≈2,250 tokens
- files1
- licenseMIT
- updated
What Claude sees before loading it
This description is the only part of the skill that sits in context all the time. Claude reads it to decide when to load the full playbook below.
description: Builds Zephyr RTOS applications with west — workspace manifests, board targets and qualifiers, devicetree overlays and bindings, Kconfig prj.conf fragments, DT-spec driver APIs (gpio_dt_spec, i2c_dt_spec), kernel primitives (threads, work queues, message queues), logging and shell, sysbuild with MCUboot, native_sim and twister tests. Use when starting or debugging a Zephyr app on nRF, STM32, ESP32, RP2040 or other boards, when devicetree or Kconfig errors appear, a device is not ready, a sample needs porting to custom hardware, or tests need to run in CI. Try asking
- Port the Zephyr blinky sample to our custom nRF52840 board with an overlay
- Build fails with undefined reference to __device_dts_ord — what did I miss?
- Add a BME280 over I2C to this Zephyr app and test it in CI with twister
Use it when
- Starting or structuring a Zephyr application and west workspace
- Devicetree, pinctrl, or Kconfig errors block the build
- A driver reports device not ready on real hardware
- Setting up twister tests or MCUboot via sysbuild
Skip it when
- Plain FreeRTOS task and ISR design — use freertos-tasks
- OTA rollout strategy, signing keys, and rollback policy — use firmware-ota
How to check the result
A pristine build succeeds for every target board, the generated zephyr.dts and .config contain the intended nodes and symbols, and twister suites pass on native_sim.
The common mistake it prevents
Adding a devicetree node whose compatible matches no binding or whose driver is disabled in Kconfig, so it silently does nothing until a cryptic linker error appears.
Done well means: the app lives in its own west workspace with a pinned Zephyr revision,
hardware is described in devicetree overlays (never hardcoded pin numbers), features are
enabled in prj.conf with the smallest set of symbols, every device is checked with
*_is_ready_dt() before use, and logic is tested on native_sim with twister.
Ground rules
- Read the generated files before guessing:
build/zephyr/zephyr.dts(final devicetree) andbuild/zephyr/.config(final Kconfig). Most “it is not working” problems are visible there. - Configure hardware in devicetree, software in Kconfig. A pin, bus, or address in C code is a bug waiting for the next board revision.
- Start from the closest upstream sample (
zephyr/samples/...) and change one thing at a time. - Pin the Zephyr revision in
west.yml. Upgrading Zephyr is a migration: read the release notes’ migration guide for each version crossed. - Check board voltage levels in the board docs and SoC datasheet before connecting peripherals; most Zephyr targets use 1.8 V or 3.3 V I/O that is not 5 V tolerant.
Workflow
- [ ] 1. Workspace: west init (app manifest), west update, SDK + host tools installed
- [ ] 2. Build an upstream sample (blinky) for the exact board; flash it
- [ ] 3. Create the app: CMakeLists.txt, prj.conf, src/main.c, boards/<board>.overlay
- [ ] 4. Describe hardware in the overlay; enable drivers in prj.conf
- [ ] 5. Use DT-spec APIs; check readiness; log with LOG_MODULE_REGISTER
- [ ] 6. Inspect zephyr.dts and .config when anything is off
- [ ] 7. Unit/integration tests on native_sim via twister; CI build for each board
1. Workspace (application-as-manifest)
# my-app/west.yml
manifest:
remotes:
- name: zephyrproject-rtos
url-base: https://github.com/zephyrproject-rtos
projects:
- name: zephyr
remote: zephyrproject-rtos
revision: v4.2.0 # pin a release tag; update deliberately
import:
name-allowlist: [cmsis, hal_nordic, hal_stm32, mcuboot, mbedtls] # only what you need
self:
path: my-app
python3 -m venv .venv && . .venv/bin/activate && pip install west
west init -l my-app # use the local manifest
west update
west zephyr-export
pip install -r zephyr/scripts/requirements.txt
west sdk install # Zephyr SDK toolchains (recent Zephyr versions)
The tag above is an example; use the release the project targets (LTS lines are the safer choice for products).
2–3. Build and flash
west build -b nrf52840dk/nrf52840 my-app -p auto # -p auto: pristine when needed
west build -b nucleo_f401re my-app -d build-nucleo # separate build dir per board
west flash # uses the board's runner (J-Link, OpenOCD, pyOCD...)
west debug # GDB session through the runner
west build -t menuconfig # interactive Kconfig for this build
west build -t rom_report # flash usage by symbol
Board names use hardware model v2: board/soc[/cpucluster][/variant], e.g.
nrf5340dk/nrf5340/cpuapp. List them with west boards.
App skeleton
# CMakeLists.txt
cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(my_app)
target_sources(app PRIVATE src/main.c)
# prj.conf
CONFIG_GPIO=y
CONFIG_I2C=y
CONFIG_LOG=y
CONFIG_SENSOR=y
CONFIG_MAIN_STACK_SIZE=2048
Board-specific Kconfig goes in boards/<board>.conf; overlays in
boards/<board>.overlay are picked up automatically (use the board name with /
replaced by _, e.g. nrf52840dk_nrf52840.overlay).
4. Devicetree overlays
/* boards/nrf52840dk_nrf52840.overlay */
/ {
aliases {
status-led = &led1;
};
};
&i2c0 {
status = "okay";
clock-frequency = <I2C_BITRATE_FAST>;
bme280: bme280@76 {
compatible = "bosch,bme280";
reg = <0x76>;
};
};
- Node
compatiblemust match a binding inzephyr/dts/bindings(or your owndts/bindings/*.yaml); otherwise the node is silently unused and no driver loads. status = "okay"enables a node; many SoC peripherals are disabled by default.- Pin multiplexing on nRF/STM32 uses
pinctrl-0/pinctrl-nameswith pinctrl nodes; copy the pattern from the board’s.dts. - Devicetree errors show as long macro failures like
__device_dts_ord_XX undefined: the node is disabled, has no matching driver enabled in Kconfig, or the compatible is wrong.
5. Driver code with DT specs
#include <zephyr/kernel.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(app, LOG_LEVEL_INF);
static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(DT_ALIAS(status_led), gpios);
static const struct device *const bme = DEVICE_DT_GET(DT_NODELABEL(bme280));
int main(void)
{
if (!gpio_is_ready_dt(&led) || !device_is_ready(bme)) {
LOG_ERR("device not ready");
return -ENODEV;
}
gpio_pin_configure_dt(&led, GPIO_OUTPUT_INACTIVE);
while (1) {
struct sensor_value t;
if (sensor_sample_fetch(bme) == 0 &&
sensor_channel_get(bme, SENSOR_CHAN_AMBIENT_TEMP, &t) == 0) {
LOG_INF("temp %d.%06d C", t.val1, t.val2);
}
gpio_pin_toggle_dt(&led);
k_sleep(K_SECONDS(1));
}
}
- Devicetree property names convert to C with
-→_(status-led→status_led). - Prefer
*_dt_specstructs (gpio_dt_spec,i2c_dt_spec,spi_dt_spec,adc_dt_spec,pwm_dt_spec) over raw device pointers plus pin numbers. - Every API returns negative errno on failure. Check it.
Kernel primitives
| Need | Use |
|---|---|
| Long-running activity | K_THREAD_DEFINE (static) with explicit stack size and priority |
| Deferred work from ISR or callback | k_work_submit on the system work queue (keep it short) or a dedicated k_work_q |
| Periodic work | k_work_delayable or k_timer (timer expiry runs in ISR context) |
| Pass fixed-size messages | k_msgq |
| Signal | k_sem, k_event, k_poll for several sources |
| Mutual exclusion | k_mutex (priority inheritance) |
Cooperative priorities are negative (never preempted by other threads); preemptible are ≥ 0. Lower number = higher priority. Blocking inside the system work queue stalls every driver that uses it.
Logging, shell, stacks
CONFIG_LOG_MODE_DEFERRED=y # default; keeps ISR logging cheap
CONFIG_SHELL=y # interactive commands over UART/RTT
CONFIG_THREAD_ANALYZER=y # stack usage report
CONFIG_THREAD_ANALYZER_AUTO=y
CONFIG_STACK_SENTINEL=y # or CONFIG_HW_STACK_PROTECTION=y where supported
CONFIG_ASSERT=y # development builds
Thread analyzer output shows each thread’s stack use; keep 25% headroom, then turn the analyzer off for release.
MCUboot and OTA
Use sysbuild to build MCUboot and the app together:
west build -b nrf52840dk/nrf52840 --sysbuild my-app -- -DSB_CONFIG_BOOTLOADER_MCUBOOT=y
Sign images with the project’s key (never ship the default dev key in bootloader/mcuboot),
and see firmware-ota for slot layout, confirmation, and rollback policy.
Testing
west build -b native_sim my-app && ./build/zephyr/zephyr.exe # run on the host
west twister -T tests/ -p native_sim # ztest suites
west twister -T my-app -p nrf52840dk/nrf52840 --build-only # CI build matrix
Use the ztest framework (ZTEST_SUITE, ZTEST) for unit tests; emulated buses and
sensors exist for many drivers so logic can be tested without hardware.
Debugging table
| Symptom | Check |
|---|---|
undefined reference to __device_dts_ord_N | Node disabled, driver Kconfig off, or wrong compatible |
| Device not ready at runtime | Driver init failed: wiring, address, power, pinctrl; enable driver log level DBG |
| Kconfig symbol ignored | .config shows its value; dependency unmet (menuconfig shows why) |
| Overlay has no effect | Wrong file name for the board; check build log “Found devicetree overlay” |
Hard fault, ***** MPU FAULT ***** | Stack overflow; thread analyzer, raise stack |
| Flash full | west build -t rom_report; disable unused subsystems, CONFIG_SIZE_OPTIMIZATIONS=y |
Verification
- Pristine build (
-p always) succeeds for every target board without warnings. zephyr.dtsand.configcontain the intended nodes and symbols (grep for them).- Twister suites pass on
native_simin CI; hardware smoke test passes on the board. - Thread analyzer shows ≥ 25% stack headroom per thread after exercising all features.
- For MCUboot builds: an image signed with the production key boots, an unsigned one is rejected.
About this skill
Written and maintained by the Claude Skills editors to the same editorial standard as every skill in the directory: a selective trigger, a real workflow, known pitfalls and a verification step. First published , last updated . MIT licensed.