Zephyr RTOS skill for Codex
Devicetree and Kconfig instead of hard-coded pins. Codex keeps Zephyr apps portable by putting pins and peripherals in devicetree overlays and features in Kconfig, then checks the merged zephyr.dts and final .config instead of guessing why a driver is not ready. Logic is tested with ztest and Twister on native_sim, and boards use the current hardware model v2 names.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- zephyr-rtos Installs to ~/.agents/skills/zephyr-rtos/ for every project on this machine.
curl -fsSL codexskills.dev/cli.mjs -o codex-skills.mjsnode codex-skills.mjs install zephyr-rtos The CLI also searches, updates and removes skills. Run it with no arguments to pick from a list. Needs Node 20.12+.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- --project zephyr-rtos Installs to .agents/skills/zephyr-rtos/ in the current repository. Commit it and everyone who runs Codex there gets it.
curl -fsSLO codexskills.dev/downloads/zephyr-rtos.zipunzip -o zephyr-rtos.zip -d ~/.agents/skills/ The archive holds the skill folder with its generated agents/openai.yaml. Unzip into .agents/skills/ to scope it to a repo.
& ([scriptblock]::Create((irm codexskills.dev/install.ps1))) zephyr-rtos PowerShell 5.1 or 7. Installs to %USERPROFILE%\.agents\skills\. Add -Project for the current repo.
104-line POSIX script. No sudo, no telemetry. Read install.sh
Use it in Codex
Start a prompt with $zephyr-rtos, or pick it from /skills.
You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.
What Codex reads before loading it
Builds Zephyr RTOS applications with west - board targets in the HWMv2 naming, prj.conf and Kconfig fragments, devicetree overlays and the DT_/GPIO_DT_SPEC_GET macros, threads, message queues and work queues, logging, Twister tests on native_sim, and sysbuild with MCUboot. Use when a Zephyr build fails on devicetree or Kconfig errors, a driver reports not ready, porting an app to a new board, adding a sensor or peripheral, or setting up tests and CI for a Zephyr or nRF Connect SDK project.
Use it when
- A Zephyr or nRF Connect SDK build fails on devicetree or Kconfig
- Adding a sensor, bus or GPIO to a Zephyr board through an overlay
- Porting a Zephyr application to another board
- Setting up ztest and Twister for host-side tests in CI
Use something else when
- The project uses bare FreeRTOS with a vendor HAL: use freertos
- You need MCUboot signing keys, slot layout and rollout policy: use firmware-ota
How to check the result
Do a pristine build for every supported board, confirm the merged zephyr.dts and final .config show the intended nodes and symbols, and run Twister on native_sim.
The mistake it prevents
Setting a Kconfig symbol whose dependencies are unmet: Zephyr drops it with only a warning, so the feature silently stays off.
Done well means: hardware is described in devicetree and features in Kconfig, never
hard-coded in C; the app builds for every board it claims to support from a pinned west
manifest; and logic is tested on native_sim before it touches hardware.
Inspect first
- Read
AGENTS.md, thenwest.yml(or the workspace manifest) for the Zephyr or nRF Connect SDK revision. APIs and board names change between releases; match the pinned one. - App layout:
CMakeLists.txt,prj.conf,boards/*.overlay,boards/*.conf,src/, optionalsysbuild.conf,Kconfig,sample.yamlortestcase.yaml. - Build once unchanged and keep the log; generated files are the ground truth.
west build -b nrf52840dk/nrf52840 app -p auto
west build -t menuconfig # interactive Kconfig for the current build dir
west build -t rom_report # also ram_report
west flash # user runs this with the board attached
Setting up a workspace (west init, west update, Python requirements, the Zephyr SDK)
needs network and a large download; ask before doing it in the sandbox. If the user’s
workspace exists, point Codex at it instead of creating another.
Board names
Zephyr 3.7+ uses hardware model v2: board/soc[/cpucluster][/variant], for example
nrf5340dk/nrf5340/cpuapp, nrf52840dk/nrf52840, nucleo_f401re, rpi_pico/rp2040,
esp32s3_devkitc/esp32s3/procpu, native_sim. Board overlay and conf files replace /
with _: boards/nrf5340dk_nrf5340_cpuapp.overlay. west boards | grep nucleo lists them.
Where a change belongs
| You want to | Put it in |
|---|---|
| Enable a subsystem or driver (I2C, logging, BLE, shell) | prj.conf (CONFIG_I2C=y) |
| Board-specific Kconfig | boards/<board>.conf |
| Pin, bus, address, interrupt line, sensor instance | Devicetree overlay |
| An app option users can tune | App Kconfig with source "Kconfig.zephyr" at the end |
| Build-variant config (debug vs release) | -DEXTRA_CONF_FILE=debug.conf |
| A second overlay for a shield or variant | -DEXTRA_DTC_OVERLAY_FILE=sensor.overlay or --shield |
Devicetree
Overlay that enables I2C and adds a sensor, plus an alias the code can use:
/* boards/nrf52840dk_nrf52840.overlay */
&i2c0 {
status = "okay";
bme280: bme280@76 {
compatible = "bosch,bme280";
reg = <0x76>;
};
};
/ {
aliases {
status-led = &led1;
};
};
#include <zephyr/kernel.h>
#include <zephyr/device.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("hardware 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));
}
}
- In C, devicetree names use underscores: alias
status-ledbecomesDT_ALIAS(status_led). device_is_ready()false means the driver did not initialise: check the node hasstatus = "okay", the matchingCONFIG_for the driver is set (most sensor drivers default on whenCONFIG_SENSOR=yand an enabled node exists), and the bus came up.- After a build, read
build/zephyr/zephyr.dts(the merged tree) andbuild/zephyr/.config(final Kconfig) before guessing. With sysbuild the app lives inbuild/<app-name>/zephyr/. - Bindings live in
zephyr/dts/bindings; the binding defines which properties are required. Read it when DTC errors mention missing properties.
Kconfig traps
- A symbol set in
prj.confwith unmet dependencies is silently dropped, with only a warning in the build log. Grep the final.configto confirm the value. - Disable with
CONFIG_FOO=ninprj.conf. Never editbuild/zephyr/.configby hand; it is regenerated, and menuconfig changes vanish on a pristine build unless copied back. - Symbols without a prompt cannot be set from
prj.confat all; they follow theirdefaultandselectstatements.menuconfigshows why a symbol has its value. - Stack sizes are Kconfig (
CONFIG_MAIN_STACK_SIZE,CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE). Turn onCONFIG_THREAD_ANALYZER=yduring development to see real usage.
Concurrency
| Need | Primitive |
|---|---|
| Fixed thread at boot | K_THREAD_DEFINE(name, stack, fn, p1, p2, p3, prio, 0, 0) |
| Pass fixed-size messages | K_MSGQ_DEFINE with k_msgq_put/k_msgq_get |
| Defer work from an ISR | k_work_submit on the system work queue; own queue if it blocks |
| Periodic work | k_work_delayable or k_timer handing off to work |
| Protect shared data between threads | k_mutex (priority inheritance); k_spinlock for ISR-shared |
Cooperative threads (negative priority) are never preempted by other threads; use them
only for short, deliberate sections. Never call blocking APIs or k_sleep from an ISR.
Tests with Twister
west build -b native_sim tests/unit -p auto && ./build/zephyr/zephyr.exe
west twister -T tests -p native_sim --inline-logs
Write tests with the ztest API (ZTEST_SUITE, ZTEST, zassert_equal) and declare
platforms and tags in testcase.yaml. Use emulated devices (zephyr,i2c-emul-controller
and sensor emulators) for driver-facing code. native_sim builds need a Linux host; on
macOS run them in the project’s container or CI.
Bootloader and updates
west build -b nrf52840dk/nrf52840 app --sysbuild -p auto -- -DSB_CONFIG_BOOTLOADER_MCUBOOT=y
MCUboot signs images with a development key by default; that key is public. Production
signing, slot layout and confirming images belong to firmware-ota.
Verification
- Clean pristine build (
-p always) for every board insample.yamlor the README - No Kconfig warnings about unmet dependencies in the log
-
zephyr.dtsshows the intended pins andstatus = "okay"on used nodes - Twister passes on
native_sim; hardware log (west flashthen RTT or UART output) pasted by the user shows the expected boot sequence -
rom_report/ram_reportcompared against the previous build
Report
**Workspace:** Zephyr v4.1.0 via west.yml; boards nrf52840dk/nrf52840, native_sim
**Change:** BME280 moved to devicetree overlay; LED via alias; sensor thread -> k_work_delayable
**Checks:** pristine builds pass; twister 12/12 on native_sim; .config shows CONFIG_BME280=y
**Size:** flash 61.2 -> 63.0 KB, RAM 18.4 -> 18.9 KB
**Open:** production MCUboot key not configured (dev key in use) What the zip contains
SKILL.md plus the interface file below. Nothing runs on install.
agents/openai.yaml
Gives the Codex app a display name, a one-line description and a starter prompt for this skill.
interface:
display_name: "Zephyr RTOS"
short_description: "Devicetree and Kconfig instead of hard-coded pins"
default_prompt: "$zephyr-rtos device_is_ready returns false for my BME280 on Zephyr, figure out why" About this skill
Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.