<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Errata Hunter</title>
    <description>The latest articles on DEV Community by Errata Hunter (@erratahunter).</description>
    <link>https://dev.to/erratahunter</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3797510%2F78826cc2-a514-4c4b-8e2f-a575c16e80f4.png</url>
      <title>DEV Community: Errata Hunter</title>
      <link>https://dev.to/erratahunter</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/erratahunter"/>
    <language>en</language>
    <item>
      <title>Embedded Firmware Development with Claude Code — Devicetree, Kconfig, and Debugging</title>
      <dc:creator>Errata Hunter</dc:creator>
      <pubDate>Wed, 25 Mar 2026 21:34:06 +0000</pubDate>
      <link>https://dev.to/erratahunter/embedded-firmware-development-with-claude-code-devicetree-kconfig-and-debugging-4jl7</link>
      <guid>https://dev.to/erratahunter/embedded-firmware-development-with-claude-code-devicetree-kconfig-and-debugging-4jl7</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;75% of Zephyr firmware development time goes to Kconfig/devicetree configuration and build error interpretation, not writing code — Claude Code can intervene in that 75% directly from the terminal.&lt;/li&gt;
&lt;li&gt;Kconfig hallucination (AI inventing nonexistent symbols) is structurally preventable by combining a shell script that greps &lt;code&gt;build/zephyr/.config&lt;/code&gt; and Kconfig source files with Claude Code's skill and hook system.&lt;/li&gt;
&lt;li&gt;Feeding all three files (&lt;code&gt;.dts&lt;/code&gt;, &lt;code&gt;.dtsi&lt;/code&gt;, binding YAML) as context via the &lt;code&gt;@&lt;/code&gt; syntax prevents &lt;code&gt;compatible&lt;/code&gt; string hallucination in devicetree overlays.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;AI coding tools still feel distant for embedded firmware developers. "Can AI even understand devicetree?" "What if it invents Kconfig symbols?" — reasonable doubts. I had them too.&lt;/p&gt;

&lt;p&gt;I deployed &lt;a href="https://code.claude.com/" rel="noopener noreferrer"&gt;Claude Code&lt;/a&gt; on production &lt;a href="https://docs.zephyrproject.org/latest/" rel="noopener noreferrer"&gt;Zephyr&lt;/a&gt;/NCS firmware projects at my day job. Kconfig hallucination broke builds. I built safeguards using Claude Code's skill and hook system to fix it. This post covers the methodology I developed. I can't share proprietary code, but I can explain where things break when you hand devicetree and Kconfig to AI, and how to structurally prevent it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why I Left the GUI IDE for the Terminal
&lt;/h2&gt;

&lt;p&gt;A typical day for a Zephyr firmware developer breaks down like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Run &lt;code&gt;west build&lt;/code&gt; → interpret CMake/Kconfig/devicetree errors&lt;/li&gt;
&lt;li&gt;  Edit &lt;code&gt;prj.conf&lt;/code&gt; → dig through &lt;code&gt;menuconfig&lt;/code&gt; to find the right &lt;code&gt;CONFIG_*&lt;/code&gt; symbols&lt;/li&gt;
&lt;li&gt;  Write devicetree overlays → cross-reference &lt;code&gt;.dts&lt;/code&gt;, &lt;code&gt;.dtsi&lt;/code&gt;, and binding YAML simultaneously&lt;/li&gt;
&lt;li&gt;  Flash and check serial logs → track down bugs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Actual C code writing accounts for roughly 25% of the day. The remaining 75% is spent fighting configuration files and error logs.&lt;/p&gt;

&lt;p&gt;GUI IDE AI features focus on code autocompletion — predicting function signatures, suggesting next lines. They help with the 25%. Claude Code runs in the terminal, so it can execute &lt;code&gt;west build&lt;/code&gt; directly, read the entire build log, interpret errors, and suggest next actions. It greps &lt;code&gt;.config&lt;/code&gt; files and traces Kconfig dependencies back to source. &lt;strong&gt;It can intervene in the other 75%&lt;/strong&gt; — and that's why a terminal-based AI agent has an edge in embedded.&lt;/p&gt;

&lt;p&gt;Dedicated embedded AI tools are emerging. &lt;a href="https://embedder.com/" rel="noopener noreferrer"&gt;Embedder&lt;/a&gt; (YC S25) generates driver code from uploaded PDF datasheets and is preparing serial console and GDB integration. If you work within supported chipsets (STM32, ESP32) using standard workflows, these packaged tools deliver real productivity gains.&lt;/p&gt;

&lt;p&gt;I chose Claude Code instead. The reason comes down to what software engineering calls &lt;strong&gt;the double-edged sword of opinionated design&lt;/strong&gt;. Packaged tools are productive within the Golden Path their creators designed, but friction increases the moment you step outside it. When the tool's assumed build pipeline doesn't match your project structure, you end up contorting your workflow to fit the tool. When the tool's baked-in "best practices" conflict with your hardware's nonstandard constraints, AI suggestions derail rather than help. Embedded development — where hardware configuration, SDK structure, and build pipelines vary project to project — makes this especially pronounced.&lt;/p&gt;

&lt;p&gt;It's a tradeoff between convenience and control. I deal with Zephyr/NCS &lt;a href="https://dev.to/erratahunter/ncs-project-management-guide-ditching-global-install-to-reclaim-control-j39"&gt;west workspace structures&lt;/a&gt; and per-project build configurations, so I chose to tune a general-purpose tool to fit my pipeline directly.&lt;/p&gt;




&lt;h2&gt;
  
  
  Devicetree: Teaching Hardware to AI
&lt;/h2&gt;

&lt;p&gt;Zephyr's &lt;a href="https://docs.zephyrproject.org/latest/build/dts/index.html" rel="noopener noreferrer"&gt;devicetree system&lt;/a&gt; has three layers. SoC &lt;code&gt;.dtsi&lt;/code&gt; files define base hardware. Board &lt;code&gt;.dts&lt;/code&gt; files declare pin mappings. &lt;a href="https://docs.zephyrproject.org/latest/build/dts/bindings.html" rel="noopener noreferrer"&gt;Binding YAML&lt;/a&gt; files specify valid properties for each node. Developers must cross-reference all three layers when writing overlays.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F19h0jsf0tjzx6l6mvduw.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F19h0jsf0tjzx6l6mvduw.webp" alt="Zephyr devicetree three-layer system — SoC dtsi, board dts, and binding YAML flow through overlay to final generated header" width="800" height="174"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To get accurate overlays from AI, you need to feed all three files as context&lt;/p&gt;

&lt;h3&gt;
  
  
  No Context Means Hallucination
&lt;/h3&gt;

&lt;p&gt;Tell Claude Code "add SPI flash to this board" without context and you'll get a plausible but wrong overlay. The &lt;code&gt;compatible&lt;/code&gt; string won't match any actual binding, or it'll reference a nonexistent node label.&lt;/p&gt;

&lt;p&gt;For accurate results, feed all three files explicitly:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Read @boards/arm/nrf52840dk_nrf52840.dts and
@zephyr/dts/arm/nordic/nrf52840.dtsi, then reference
@zephyr/dts/bindings/spi/spi-device.yaml to write
an overlay adding W25Q128 flash to SPI1."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;With the &lt;code&gt;@&lt;/code&gt; syntax pointing to specific files, Claude Code correctly references existing SPI node labels from the board and doesn't miss required properties (&lt;code&gt;reg&lt;/code&gt;, &lt;code&gt;spi-max-frequency&lt;/code&gt;) defined in the binding YAML.&lt;/p&gt;

&lt;h3&gt;
  
  
  Three Patterns Where AI Gets Devicetree Wrong
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;&lt;code&gt;compatible&lt;/code&gt; string hallucination&lt;/strong&gt; — invents a compatible that doesn't exist in any binding. Feeding the binding YAML as context prevents this.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Node address collision&lt;/strong&gt; — &lt;code&gt;@0&lt;/code&gt; must match &lt;code&gt;reg = &amp;lt;0&amp;gt;&lt;/code&gt;, but AI assigns addresses that duplicate existing nodes. Feeding the board &lt;code&gt;.dts&lt;/code&gt; lets it check existing assignments.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Ignoring overlay detection rules&lt;/strong&gt; — Zephyr's build system auto-detects overlays in this order when &lt;code&gt;DTC_OVERLAY_FILE&lt;/code&gt; is unset: &lt;code&gt;socs/&amp;lt;SOC&amp;gt;.overlay&lt;/code&gt; → &lt;code&gt;boards/&amp;lt;BOARD&amp;gt;.overlay&lt;/code&gt; → &lt;code&gt;&amp;lt;BOARD&amp;gt;.overlay&lt;/code&gt; → &lt;code&gt;app.overlay&lt;/code&gt;. If AI creates an overlay with an arbitrary filename, the build system ignores it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The third problem is solved by adding one line to CLAUDE.md:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;## Devicetree
- Overlay files must be named `app.overlay` or `boards/&amp;lt;BOARD&amp;gt;.overlay`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;AI's role in devicetree work isn't "writing overlays for you." It's &lt;strong&gt;reducing the overhead of cross-referencing three files&lt;/strong&gt; — checking available nodes in &lt;code&gt;.dtsi&lt;/code&gt;, pulling required properties from binding YAML, and combining settings that don't conflict with existing &lt;code&gt;.dts&lt;/code&gt;. The question is whether you do this manually by switching between three files, or hand all three to AI and get it in one pass.&lt;/p&gt;




&lt;h2&gt;
  
  
  Kconfig: Building a Skill to Prevent AI Hallucination
&lt;/h2&gt;

&lt;p&gt;Zephyr's &lt;a href="https://docs.zephyrproject.org/latest/build/kconfig/index.html" rel="noopener noreferrer"&gt;Kconfig system&lt;/a&gt; has thousands of symbols in a tree structure. Enabling &lt;code&gt;CONFIG_BT&lt;/code&gt; auto-activates &lt;code&gt;NET_BUF&lt;/code&gt;. Choosing &lt;code&gt;CONFIG_BT_HCI&lt;/code&gt; under &lt;code&gt;BT_STACK_SELECTION&lt;/code&gt; triggers another dependency chain. Symbols forced on by &lt;code&gt;select&lt;/code&gt;, conditionally enabled by &lt;code&gt;depends on&lt;/code&gt;, and suggested by &lt;code&gt;imply&lt;/code&gt; are intertwined. The Zephyr project itself &lt;a href="https://github.com/zephyrproject-rtos/zephyr/issues/52575" rel="noopener noreferrer"&gt;acknowledges excessive &lt;code&gt;select&lt;/code&gt; usage&lt;/a&gt; and is migrating to &lt;code&gt;depends on&lt;/code&gt; — that's how complex the dependency structure is.&lt;/p&gt;

&lt;p&gt;Ask AI to "create a minimal prj.conf for BLE Central scan only" and you get a plausible result. But it might invent nonexistent symbols or miss required dependencies.&lt;/p&gt;

&lt;p&gt;When I hit this problem at work, I solved it by making incremental requests to Claude Code. I didn't start with "build me a Kconfig hallucination prevention skill." I asked questions one at a time, and automation emerged naturally.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Conversation Flow
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;"Where does the final Kconfig output go after &lt;code&gt;west build&lt;/code&gt;?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;build/zephyr/.config&lt;/code&gt; contains the fully resolved symbol list — thousands of &lt;code&gt;CONFIG_*=y/n&lt;/code&gt; lines. You can also check via &lt;code&gt;menuconfig&lt;/code&gt;/&lt;code&gt;guiconfig&lt;/code&gt;, but this file is the ground truth the build system actually uses.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Find the .config file in this project's build output."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Claude Code locates &lt;code&gt;build/zephyr/.config&lt;/code&gt; and shows its contents. A follow-up question:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Does this project have a separate kernel source? Is there a separate kernel .config like in Linux?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Unlike the Linux kernel, Zephyr builds the app and RTOS kernel into a single binary. &lt;code&gt;build/zephyr/.config&lt;/code&gt; is the entire system's configuration. There's no separate kernel .config.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Find all the Kconfig source files in the west workspace."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;zephyr/Kconfig&lt;/code&gt;, &lt;code&gt;zephyr/subsys/bluetooth/Kconfig&lt;/code&gt;, Kconfig files in each driver directory — organized into a tree. These source files contain each symbol's &lt;code&gt;depends on&lt;/code&gt;, &lt;code&gt;select&lt;/code&gt;, and &lt;code&gt;help&lt;/code&gt; text. If AI references these when modifying &lt;code&gt;prj.conf&lt;/code&gt;, it can't fabricate nonexistent symbols.&lt;/p&gt;

&lt;p&gt;Here's where a problem arises. The &lt;code&gt;.config&lt;/code&gt; file is thousands of lines. Kconfig source files are scattered across the entire west workspace. Reading everything every time wastes tokens.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"I want to reference these files when modifying .conf, but minimize token usage. How can I query only the relevant symbols?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Claude Code proposes a &lt;strong&gt;shell script + skill combination&lt;/strong&gt;. A script that greps for relevant symbols, called by a skill.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Build that skill."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A Kconfig reference skill appears under &lt;code&gt;.claude/skills/&lt;/code&gt;. When a &lt;code&gt;.conf&lt;/code&gt; modification is requested:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Grep the built &lt;code&gt;.config&lt;/code&gt; for the current state of relevant symbols&lt;/li&gt;
&lt;li&gt; Extract the &lt;code&gt;depends on&lt;/code&gt;, &lt;code&gt;select&lt;/code&gt;, &lt;code&gt;help&lt;/code&gt; text from Kconfig source&lt;/li&gt;
&lt;li&gt; Use this as context when modifying &lt;code&gt;.conf&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Instead of reading thousands of lines, it extracts only the needed symbols and their dependencies.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"I want this skill to trigger only when modifying .conf files. Check what needs to change in .claude/."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Claude Code's &lt;a href="https://code.claude.com/docs/en/hooks" rel="noopener noreferrer"&gt;hook system&lt;/a&gt; handles this. Set &lt;code&gt;PostToolUse&lt;/code&gt; with &lt;code&gt;matcher: "Write|Edit"&lt;/code&gt; in &lt;code&gt;.claude/settings.json&lt;/code&gt;, extract the file path from stdin JSON, and conditionally trigger only for &lt;code&gt;.conf&lt;/code&gt; files:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FILE_PATH=$(jq -r '.tool_input.file_path' &amp;lt; /dev/stdin)
if [[ ! "$FILE_PATH" =~ \.conf$ ]]; then
  exit 0  # ignore non-.conf files
fi
## Run Kconfig reference logic
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;CLAUDE.md instructions are advisory — AI can ignore them. Hooks are deterministic. They execute without exception. Making Kconfig source reference automatic on every &lt;code&gt;.conf&lt;/code&gt; edit structurally prevents AI from inventing nonexistent symbols.&lt;/p&gt;

&lt;h3&gt;
  
  
  What This Flow Reveals
&lt;/h3&gt;

&lt;p&gt;Six conversations produced a hallucination prevention skill. Asking "build me a Kconfig hallucination prevention skill" upfront wouldn't have worked — you can't design the solution without knowing &lt;code&gt;.config&lt;/code&gt; exists.&lt;/p&gt;

&lt;p&gt;The pattern here is &lt;strong&gt;discovering AI's limitation, then using the same AI to build a tool that fills the gap&lt;/strong&gt;. Don't try to turn Claude Code into an embedded engineer. Instead, as the engineer, identify AI's weak spots and co-build compensating tools. That approach works.&lt;/p&gt;




&lt;h2&gt;
  
  
  Build Error Debugging — Where AI Delivers the Highest ROI
&lt;/h2&gt;

&lt;p&gt;When &lt;code&gt;west build&lt;/code&gt; fails, the terminal floods with errors from CMake, Kconfig, devicetree, the C compiler, and the linker all mixed together. Even experienced engineers spend time just figuring out whether an error is a devicetree problem or a Kconfig problem.&lt;/p&gt;

&lt;p&gt;Feed the entire build log to Claude Code and it classifies errors by category, then traces root causes. Embedded build errors fall into three categories, each with different AI utility.&lt;/p&gt;

&lt;h3&gt;
  
  
  Devicetree Binding Mismatch
&lt;/h3&gt;

&lt;p&gt;When a &lt;code&gt;compatible&lt;/code&gt; string doesn't match any binding YAML, the build system throws an error. The error message is clear enough to solve without AI, but Claude Code finds the correct binding YAML across hundreds of directories under &lt;code&gt;zephyr/dts/bindings/&lt;/code&gt; and proposes a fix in one step. Manually searching takes time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Kconfig Dependency Failure — "Silent Failure"
&lt;/h3&gt;

&lt;p&gt;This one is trickier. Set &lt;code&gt;CONFIG_X=y&lt;/code&gt; in &lt;code&gt;prj.conf&lt;/code&gt;, but if &lt;code&gt;CONFIG_X&lt;/code&gt; has &lt;code&gt;depends on CONFIG_Y&lt;/code&gt; and &lt;code&gt;CONFIG_Y=n&lt;/code&gt;, the Kconfig system silently ignores &lt;code&gt;CONFIG_X&lt;/code&gt;. The build succeeds, but the intended feature doesn't work.&lt;/p&gt;

&lt;p&gt;Have Claude Code compare &lt;code&gt;prj.conf&lt;/code&gt; against &lt;code&gt;build/zephyr/.config&lt;/code&gt; and it finds symbols present in &lt;code&gt;prj.conf&lt;/code&gt; but missing from &lt;code&gt;.config&lt;/code&gt;. Tracing the unmet dependency requires Kconfig source — and the Kconfig reference skill from earlier connects here too.&lt;/p&gt;

&lt;h3&gt;
  
  
  Linker Errors
&lt;/h3&gt;

&lt;p&gt;Embedded linker errors are typically RAM/Flash overflow or duplicate symbol definitions. Claude Code reads the linker script (&lt;code&gt;.ld&lt;/code&gt;) and &lt;code&gt;build.map&lt;/code&gt; to identify which object files conflict. For memory overflow, it extracts each section's size from &lt;code&gt;build.map&lt;/code&gt; and suggests reduction priorities.&lt;/p&gt;

&lt;p&gt;Build error debugging is where Claude Code's ROI is highest. Error messages are text, root-cause tracing requires cross-referencing multiple files, and resolution patterns are relatively well-defined.&lt;/p&gt;




&lt;h2&gt;
  
  
  Runtime Debugging: Log Analysis and GDB
&lt;/h2&gt;

&lt;p&gt;After a successful build and flash, a different kind of debugging begins.&lt;/p&gt;

&lt;h3&gt;
  
  
  Serial Log Pattern Analysis
&lt;/h3&gt;

&lt;p&gt;Capture &lt;code&gt;printk&lt;/code&gt; or Zephyr &lt;code&gt;LOG_MODULE&lt;/code&gt; output over serial and feed it to Claude Code. It identifies timestamp intervals, repeating error code patterns, and state changes preceding specific events. Faster than scrolling through hundreds of lines manually.&lt;/p&gt;

&lt;p&gt;Automating the copy-paste step is possible too. &lt;a href="https://lib.rs/crates/serial-mcp-server" rel="noopener noreferrer"&gt;&lt;code&gt;serial-mcp-server&lt;/code&gt;&lt;/a&gt; is a Rust-based &lt;a href="https://code.claude.com/docs/en/mcp" rel="noopener noreferrer"&gt;MCP&lt;/a&gt; server that exposes UART communication as Claude Code tools — &lt;code&gt;list_ports&lt;/code&gt;, &lt;code&gt;open&lt;/code&gt;, &lt;code&gt;read&lt;/code&gt;, &lt;code&gt;write&lt;/code&gt;, &lt;code&gt;close&lt;/code&gt;. It supports STM32, ESP32, and USB-serial converters like CH340 and FTDI. With MCP configured, you can say "open the serial port and read the log" mid-conversation.&lt;/p&gt;

&lt;h3&gt;
  
  
  HardFault Analysis
&lt;/h3&gt;

&lt;p&gt;When a HardFault occurs during J-Link + GDB debugging, feed the call stack and register dump to Claude Code. It interprets Cortex-M &lt;a href="https://developer.arm.com/documentation/dui0552/latest/cortex-m3-peripherals/system-control-block/configurable-fault-status-register" rel="noopener noreferrer"&gt;CFSR (Configurable Fault Status Register)&lt;/a&gt; bits and traces the faulting function to build a cause hypothesis.&lt;/p&gt;

&lt;p&gt;Stack overflows, null pointer dereferences, and unaligned memory access — common patterns — are caught accurately. Nondeterministic bugs like timing issues between DMA completion interrupts and the main loop are harder, since logs alone can't reproduce them. AI help has limits there.&lt;/p&gt;




&lt;h2&gt;
  
  
  Limitations and Workarounds
&lt;/h2&gt;

&lt;p&gt;Areas where Claude Code struggles in embedded:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Binary protocol parsing&lt;/strong&gt; — for byte-packed data like BLE custom profiles or proprietary sensor protocols, AI makes frequent errors in bit shifting and endianness handling. Packed struct field offset calculations vary by compiler and target architecture, and AI overlooks these differences.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Timing-critical interrupt logic&lt;/strong&gt; — when ISR execution time is constrained to microseconds, AI generates functionally correct code but doesn't optimize for execution time. &lt;code&gt;volatile&lt;/code&gt; access ordering, cache line alignment, and compiler barrier insertion remain the engineer's domain.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hardware register maps&lt;/strong&gt; — don't expect AI to know your SoC's register map accurately. It gets the general structure right, but hallucinates on specific bit reset values and reserved bit handling.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mitigation Strategies
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Keep hardware specs in CLAUDE.md, but keep it short.&lt;/strong&gt; &lt;a href="https://code.claude.com/docs/en/best-practices" rel="noopener noreferrer"&gt;Claude Code's official best practices&lt;/a&gt; call this "Progressive Disclosure" — don't dump all information, tell AI how to find it. Instead of pasting the entire register map, write "refer to &lt;code&gt;nrf52840.svd&lt;/code&gt; for nRF52840 register details." Long CLAUDE.md files cause AI to ignore instructions.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;## Hardware
- SoC: [nRF52840](https://www.nordicsemi.com/Products/nRF52840) (Cortex-M4F, 256KB RAM, 1MB Flash)
- Board: nRF52840-DK (PCA10056)
- NCS SDK: v2.9.0
- Register reference: nrf52840.svd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Always provide verification.&lt;/strong&gt; Have AI write code, then run &lt;code&gt;west build&lt;/code&gt; and check the result — it catches its own mistakes. According to Claude Code's official docs, "run tests after every change — this alone increases output quality 2–3x." Unit testing is often impractical in embedded, but &lt;code&gt;west build&lt;/code&gt; pass/fail serves as a first-order verification.&lt;/p&gt;




&lt;h2&gt;
  
  
  Claude Code Configuration Strategy for Embedded Projects
&lt;/h2&gt;

&lt;p&gt;Effective Claude Code configuration for embedded requires separating the roles of CLAUDE.md, skills, and hooks.&lt;/p&gt;

&lt;p&gt;Component&lt;/p&gt;

&lt;p&gt;Purpose&lt;/p&gt;

&lt;p&gt;Execution&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CLAUDE.md&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Per-session context (board, SDK, build commands)&lt;/p&gt;

&lt;p&gt;Auto-loaded (advisory)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Skills&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Domain knowledge, workflows (Kconfig reference, etc.)&lt;/p&gt;

&lt;p&gt;On-demand&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hooks&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Non-negotiable rules (.conf validation, etc.)&lt;/p&gt;

&lt;p&gt;Auto-executed (deterministic)&lt;/p&gt;

&lt;h3&gt;
  
  
  CLAUDE.md: Minimal Context Only
&lt;/h3&gt;

&lt;p&gt;CLAUDE.md loads every session. Exclude what AI can infer from code; include only what it can't. Board pin maps, SoC specs, NCS SDK version, and build commands are typical entries.&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;## Build

&lt;ul&gt;
&lt;li&gt;west build -b nrf52840dk/nrf52840 -- -DOVERLAY_CONFIG=overlay-debug.conf&lt;/li&gt;
&lt;li&gt;west flash --runner jlink&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Conventions
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Overlay filenames: app.overlay or boards/&amp;lt;BOARD&amp;gt;.overlay&lt;/li&gt;
&lt;li&gt;Kconfig: prj.conf (shared), boards/&amp;lt;BOARD&amp;gt;.conf (board-specific)&lt;/li&gt;
&lt;li&gt;After .conf edits, verify against build/zephyr/.config
&lt;/li&gt;
&lt;/ul&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;


Skills: Isolate Domain Knowledge
&lt;/h3&gt;


&lt;p&gt;Knowledge needed only in specific workflows — like the Kconfig reference skill — belongs in &lt;a href="https://code.claude.com/docs/en/skills" rel="noopener noreferrer"&gt;&lt;code&gt;.claude/skills/&lt;/code&gt;&lt;/a&gt;. Putting everything in CLAUDE.md wastes the context window, and as it grows longer, AI starts ignoring instructions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hooks: Advisory vs. Deterministic
&lt;/h3&gt;

&lt;p&gt;Writing "always reference &lt;code&gt;.config&lt;/code&gt; when editing &lt;code&gt;.conf&lt;/code&gt;" in CLAUDE.md doesn't guarantee compliance — AI can skip it. If a rule must execute without exception, implement it as a hook. Hooks run shell scripts before or after Claude Code's tool usage, independent of AI judgment.&lt;/p&gt;

&lt;h3&gt;
  
  
  Build-Debug Cycle
&lt;/h3&gt;

&lt;p&gt;When these components combine, the embedded build-debug cycle looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqu9qaess6w6hsd2qfb7t.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqu9qaess6w6hsd2qfb7t.webp" alt="Zephyr firmware build-debug cycle — three feedback loops (west build error loop, serial log runtime loop, Kconfig hook validation) converging on a single edit point, with Claude Code intervening at error classification and pattern analysis stages" width="800" height="1192"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Three feedback loops converge on a single edit point — Claude Code intervenes at the red and navy nodes&lt;/p&gt;

&lt;p&gt;Hooks auto-validate Kconfig on &lt;code&gt;.conf&lt;/code&gt; edits. AI classifies and traces build errors. AI analyzes serial log patterns. The 75% outside of code writing — that's where AI intervenes.&lt;/p&gt;




&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;Applying AI to embedded development isn't about how well AI writes code. It's about reducing friction in the other 75% — build configuration, error interpretation, log analysis.&lt;/p&gt;

&lt;p&gt;Claude Code can intervene in that 75%. But embedded's domain specifics (Kconfig hallucination, inaccurate hardware register maps) mean you can't use it out of the box. You need skills and hooks as compensating mechanisms. After building and deploying these at my day job, the loop of using AI to compensate for AI's own limitations works.&lt;/p&gt;

&lt;p&gt;I wrote this post methodology-first because I can't share proprietary code. When I start a personal side project, I plan to apply the same approach and share devicetree overlay sessions, the Kconfig skill in action, and build error debugging logs — with actual code.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>firmware</category>
      <category>zephyr</category>
      <category>claudecode</category>
    </item>
    <item>
      <title>NCS Project Management Guide: Ditching Global Install to Reclaim Control</title>
      <dc:creator>Errata Hunter</dc:creator>
      <pubDate>Sun, 15 Mar 2026 21:37:25 +0000</pubDate>
      <link>https://dev.to/erratahunter/ncs-project-management-guide-ditching-global-install-to-reclaim-control-j39</link>
      <guid>https://dev.to/erratahunter/ncs-project-management-guide-ditching-global-install-to-reclaim-control-j39</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Patching SDK internals in an NCS global install silently breaks every other project on your machine.&lt;/li&gt;
&lt;li&gt;Freestanding (manual &lt;code&gt;ZEPHYR_BASE&lt;/code&gt; binding) → T2 (app owns &lt;code&gt;west.yml&lt;/code&gt; manifest) → T3 (separate manifest repo managing multiple apps and SDK) gives you progressive isolation.&lt;/li&gt;
&lt;li&gt;Start with Freestanding. Move to T2 or T3 only when reproducibility or multi-product needs demand it.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;When you first pick up the &lt;a href="https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/nrf/index.html" rel="noopener noreferrer"&gt;nRF Connect SDK (NCS)&lt;/a&gt;, the natural move is to follow Nordic's Toolchain Manager or VS Code Extension wizard. &lt;a href="https://dev.to/erratahunter/zephyr-cmake-hell-is-dead-why-i-let-google-antigravity-write-my-firmware-2j73"&gt;When I set up my Zephyr dev environment with Antigravity IDE&lt;/a&gt;, I did exactly that. A few button clicks and the &lt;a href="https://zephyrproject.org/" rel="noopener noreferrer"&gt;Zephyr RTOS&lt;/a&gt; core, libraries, and compiler land neatly under &lt;code&gt;C:\ncs\toolchains&lt;/code&gt; or a &lt;code&gt;v2.x.x&lt;/code&gt; folder.&lt;/p&gt;

&lt;p&gt;The problem: &lt;strong&gt;this convenient global install becomes an unmanageable swamp as projects multiply.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Zephyr's ecosystem is well designed. Need to remap board pins? Use an &lt;code&gt;app.overlay&lt;/code&gt; (&lt;a href="https://docs.zephyrproject.org/latest/build/dts/howtos.html" rel="noopener noreferrer"&gt;Devicetree Overlay&lt;/a&gt;). Want to tweak system configuration? Edit &lt;code&gt;prj.conf&lt;/code&gt; (&lt;a href="https://docs.zephyrproject.org/latest/build/kconfig/index.html" rel="noopener noreferrer"&gt;Kconfig&lt;/a&gt;) and override without touching the original source.&lt;/p&gt;

&lt;p&gt;But production firmware development never follows the textbook. Sometimes you have to reach deep into the HAL (Hardware Abstraction Layer) to dodge a chipset errata. Sometimes the only way forward is a monkey patch inside Nordic's &lt;code&gt;nrfxlib&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In a global install, that single hack &lt;strong&gt;silently breaks every other NCS project on your machine&lt;/strong&gt;. On top of that, every new SDK release meant downloading gigabytes onto the main drive all over again.&lt;/p&gt;

&lt;p&gt;Under the banner of convenience, I had lost control of my build environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deep Dive: True Isolation and the Essence of Freestanding
&lt;/h2&gt;

&lt;p&gt;Breaking this cycle required flipping the NCS paradigm. Instead of "the Toolchain Manager owns my SDK," the goal was &lt;strong&gt;"my code picks and isolates its own SDK."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The first concept I ran into was the &lt;strong&gt;&lt;a href="https://docs.zephyrproject.org/latest/develop/application/index.html" rel="noopener noreferrer"&gt;Freestanding Application&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In the Zephyr ecosystem, a &lt;strong&gt;manifest (&lt;code&gt;west.yml&lt;/code&gt;)&lt;/strong&gt; is a dependency recipe file that declares "this project needs Zephyr version X, nRF module version Y." Think of it as the equivalent of Node.js's &lt;code&gt;package.json&lt;/code&gt; or Python's &lt;code&gt;requirements.txt&lt;/code&gt;. One &lt;code&gt;west update&lt;/code&gt; command pulls every source at the exact pinned version.&lt;/p&gt;

&lt;p&gt;Freestanding skips the manifest and any complex topology. It is the most intuitive way to isolate an SDK on a local dev machine. My app lives at &lt;code&gt;D:\Workspace\my_app\&lt;/code&gt;, the SDK sits in a completely separate location, and I bind &lt;code&gt;ZEPHYR_BASE&lt;/code&gt; via an environment variable only in the terminal session where I build.&lt;/p&gt;

&lt;p&gt;This is like mounting just the volumes you need into a Docker container — clean and minimal. It is the lightest starting point for physically separating your project from the SDK.&lt;/p&gt;

&lt;p&gt;But as projects grow, Freestanding hits its limits. That is where Zephyr's official &lt;a href="https://docs.zephyrproject.org/latest/develop/west/workspaces.html" rel="noopener noreferrer"&gt;West Workspace Topology&lt;/a&gt; comes in. Zephyr defines three topologies based on who owns the manifest repository:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;T1 (Zephyr-centric Star):&lt;/strong&gt; Zephyr itself is the manifest. This is what you get from a default &lt;code&gt;west init&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;T2 (App-centric Star):&lt;/strong&gt; Your app is the manifest. The cleanest layout for a single product.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;T3 (Forest):&lt;/strong&gt; A dedicated manifest repo manages multiple apps and the SDK as siblings. Built for multi-product teams.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This post walks through the progression from Freestanding to T2, then T3.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fps0q2ek3vg8iktjltncl.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fps0q2ek3vg8iktjltncl.webp" alt="NCS project isolation progression — from global install to Freestanding, T2 Star, and T3 Forest topology" width="800" height="170"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Step-by-step progression from global install to Freestanding, T2, and T3&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation: Building a Controlled Environment from Scratch
&lt;/h2&gt;

&lt;p&gt;Here is the step-by-step journey from the simplest Freestanding setup, through T2, to T3 Topology.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Pure Freestanding — The Lightest Isolation
&lt;/h3&gt;

&lt;p&gt;First, I stepped out of the Toolchain Manager's shadow and installed the toolchain and &lt;a href="https://docs.zephyrproject.org/latest/develop/west/index.html" rel="noopener noreferrer"&gt;West (Zephyr's meta-tool)&lt;/a&gt; directly inside a Python virtual environment (venv).&lt;/p&gt;

&lt;p&gt;Open a terminal at the app directory and manually bind the SDK dependency. On Windows, a single script call does it:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;:: Temporarily bind the system NCS (Zephyr) directory to this session only
&amp;gt; C:\ncs\v2.x.x\zephyr\zephyr-env.cmd

:: Now the build command targets the SDK on the C drive
&amp;gt; west build -b nrf52840dk_nrf52840
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;For a quick library test or a lightweight side project, this is enough. The binding lives only inside the virtual environment and leaves everything else untouched.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: T2 Topology — Let the App Own Its Dependencies
&lt;/h3&gt;

&lt;p&gt;Freestanding relies on your memory or documentation to track the SDK version. Once you start building a real product, that weakness shows. A teammate should be able to &lt;code&gt;git clone&lt;/code&gt; and reproduce the exact build environment — telling them over Slack which &lt;code&gt;ZEPHYR_BASE&lt;/code&gt; to set is an accident waiting to happen.&lt;/p&gt;

&lt;p&gt;T2 is what Zephyr's official docs call the &lt;strong&gt;Star topology (application is the manifest repository)&lt;/strong&gt;. You place a &lt;code&gt;west.yml&lt;/code&gt; manifest directly inside your app repo, making the app itself the owner of its SDK dependencies.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;my_product/              # App repo IS the manifest repo (T2)
├── .west/               # Auto-generated by west init
├── app/                 # Application source
│   ├── CMakeLists.txt
│   ├── prj.conf
│   └── src/main.c
├── west.yml             # ★ Pins Zephyr, NRF, and module versions
├── zephyr/              # Pulled by west update
└── modules/             # nrf, hal_nordic, nrfxlib, etc.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Pin a specific Zephyr tag or commit hash in &lt;code&gt;west.yml&lt;/code&gt;, and anyone who runs &lt;code&gt;west init -l . &amp;amp;&amp;amp; west update&lt;/code&gt; anywhere gets the exact same SDK version. The reproducibility that Freestanding lacked is now baked in.&lt;/p&gt;

&lt;p&gt;Zephyr's &lt;a href="https://docs.zephyrproject.org/latest/develop/west/manifest.html" rel="noopener noreferrer"&gt;Manifest Imports&lt;/a&gt; feature simplifies &lt;code&gt;west.yml&lt;/code&gt; authoring considerably. Instead of listing dozens of module versions by hand, you import the Zephyr manifest wholesale and only override what you need.&lt;/p&gt;

&lt;p&gt;The caveat: T2 assumes &lt;strong&gt;one app = one manifest&lt;/strong&gt;. For a single product it is hard to beat, but the moment you need to develop Product A and Product B on the same hardware platform, the model starts cracking.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: T3 Topology — Forest Structure for Multiple Apps
&lt;/h3&gt;

&lt;p&gt;The moment arrived when I had to develop "Terminal A" and "Terminal B" on the same platform hardware simultaneously. Creating separate T2 workspaces for each meant duplicating multi-gigabyte SDK folders per product.&lt;/p&gt;

&lt;p&gt;The answer was &lt;strong&gt;T3 Topology&lt;/strong&gt;. Zephyr's docs call it the &lt;strong&gt;Forest topology&lt;/strong&gt; — a dedicated manifest repository arranges multiple apps and the SDK as siblings at the same directory level.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;my_workspace/            # Workspace root (T3 anchor)
├── .west/               # West metadata
├── manifest_repo/       # Single repo holding west.yml (dependency hub)
├── app_product_a/       # Application 1
├── app_product_b/       # Application 2
├── zephyr/              # Zephyr core pulled by West
└── modules/             # nrf, hal, nrfxlib, etc.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The decisive difference from T2: &lt;strong&gt;the manifest lives outside any app&lt;/strong&gt;. A single &lt;code&gt;manifest_repo/west.yml&lt;/code&gt; governs the Zephyr version, module versions, and even the Git revisions of every app. &lt;code&gt;app_product_a&lt;/code&gt; and &lt;code&gt;app_product_b&lt;/code&gt; remain fully decoupled, yet at build time they safely share the same verified SDK within &lt;code&gt;my_workspace&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;T3 also pays off when setting up CI/CD pipelines (e.g., GitHub Actions). The CI server clones &lt;code&gt;manifest_repo&lt;/code&gt;, runs &lt;code&gt;west update&lt;/code&gt;, and every app plus its dependencies land at the correct version in one shot.&lt;/p&gt;

&lt;h3&gt;
  
  
  Troubleshooting: The Curse of Windows Long Paths (260-char Limit)
&lt;/h3&gt;

&lt;p&gt;The moment I moved to a West Workspace structure (T2 or T3) and wired up CI, builds started failing:&lt;br&gt;&lt;br&gt;
&lt;strong&gt;"No such file or directory"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;NCS and Zephyr have deeply nested directory hierarchies. When CMake and Ninja generate build artifacts on top of that, the path easily blows past the Windows MAX_PATH limit of 260 characters. Unless you are using third-party tools that handle long paths natively, you will hit this.&lt;/p&gt;

&lt;p&gt;If you are building this structure on Windows, open an admin PowerShell and run this before anything else:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Disable the Windows 10/11 long path restriction (reboot recommended after)
New-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem" -Name "LongPathsEnabled" -Value 1 -PropertyType DWORD -Force
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;I missed this one setting and spent half a day chasing phantom CMakeLists.txt errors.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9qi24vn9tz8hgvuux93k.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9qi24vn9tz8hgvuux93k.webp" alt="Freestanding vs T2 Star vs T3 Forest topology comparison — manifest ownership, app count, and SDK sharing differences" width="800" height="446"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Key differences between the three topology options at a glance&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaway: Which Structure Should You Pick?
&lt;/h2&gt;

&lt;p&gt;After ditching the Toolchain Manager's global install, we gained three weapons for NCS project management.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;There is no silver bullet.&lt;/strong&gt; Each structure carries clear trade-offs, and the right choice depends on your project's expected lifetime and team size.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Freestanding&lt;/th&gt;
&lt;th&gt;T2 (Star)&lt;/th&gt;
&lt;th&gt;T3 (Forest)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Manifest&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;App = Manifest&lt;/td&gt;
&lt;td&gt;Separate manifest repo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;App count&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Multiple&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SDK version pinning&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Manual (&lt;code&gt;ZEPHYR_BASE&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Pinned via &lt;code&gt;west.yml&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Pinned via &lt;code&gt;west.yml&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Reproducibility&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Initial setup cost&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Near zero&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Storage&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Minimal&lt;/td&gt;
&lt;td&gt;One SDK copy per app&lt;/td&gt;
&lt;td&gt;One shared SDK copy&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  1. Freestanding — When You Need Isolation Right Now
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Best for:&lt;/strong&gt; One-off side projects, quick sensor driver tests you plan to throw away, getting a build environment running in under 5 minutes on a personal machine.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Downside:&lt;/strong&gt; &lt;code&gt;west&lt;/code&gt; does not manage versions for you. You have to remember or document which SDK version you depended on, and manually bind the environment variable every time. Three months later — or when handing the project to a teammate — reproduction may fail.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. T2 (Star) — When You Are Building a Real Product
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Best for:&lt;/strong&gt; Serious single-product development where anyone should be able to &lt;code&gt;git clone&lt;/code&gt; and reproduce the exact build environment. A solo developer or small team focused on one firmware project.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Downside:&lt;/strong&gt; The entire SDK lives inside the app workspace, so adding a second product means duplicating the same Zephyr/NRF sources. Storage and &lt;code&gt;west update&lt;/code&gt; time scale linearly with product count.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. T3 (Forest) — When the Team Manages Multiple Products
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Best for:&lt;/strong&gt; Company-scale development with multiple products (A, B, C...) on the same hardware platform sharing common core logic. CI/CD pipeline integration is a must at this stage.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Downside:&lt;/strong&gt; Significant learning curve for the initial &lt;code&gt;manifest_repo/west.yml&lt;/code&gt; setup and directory structure conventions. A manifest maintainer must mediate version conflicts across products.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My own path: I started with Freestanding for rapid prototyping, moved to T2 once the product was greenlit, and switched to T3 when derivative products appeared on the same platform. You do not need to start at T3. Binding the SDK with a single &lt;code&gt;zephyr-env.cmd&lt;/code&gt; call via Freestanding is enough. That alone is the first step toward reclaiming control in the closed NCS ecosystem.&lt;/p&gt;

</description>
      <category>firmware</category>
      <category>ncs</category>
      <category>nrf</category>
      <category>zephyr</category>
    </item>
    <item>
      <title>Zephyr CMake Hell is Dead: Why I Let Google Antigravity Write My Firmware</title>
      <dc:creator>Errata Hunter</dc:creator>
      <pubDate>Wed, 11 Mar 2026 21:41:32 +0000</pubDate>
      <link>https://dev.to/erratahunter/zephyr-cmake-hell-is-dead-why-i-let-google-antigravity-write-my-firmware-2j73</link>
      <guid>https://dev.to/erratahunter/zephyr-cmake-hell-is-dead-why-i-let-google-antigravity-write-my-firmware-2j73</guid>
      <description>&lt;p&gt;I just burned another weekend chasing a ghost I2C timeout that wasn't even in the datasheet. Decided it was time to ditch the proprietary vendor lock-in and migrate to the modern Zephyr RTOS. My reward? Welcome to &lt;code&gt;west&lt;/code&gt; and &lt;code&gt;CMakeLists.txt&lt;/code&gt; hell.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F52c93rg0yg4yb5f3kxbd.WEBP" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F52c93rg0yg4yb5f3kxbd.WEBP" alt="Technical diagram of the Zephyr RTOS build pipeline illustrating how CMake processes Kconfig and Devicetree inputs to generate headers like autoconf.h and devicetree_generated.h before GCC compilation into zephyr.elf." width="800" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A high-level representation of Zephyr's build pipeline complexity. Before the HN pedants start arguing about exact CMake module dependencies in the comments—yes, this is simplified. The point is that you shouldn't need a PhD in build systems just to toggle a GPIO.&lt;/p&gt;

&lt;p&gt;Bare-metal firmware debugging is painful enough on its own. We shouldn't have to bleed over build system setups too. Web devs have AI agents scaffolding their entire async microservice architectures while we’re still grepping through 2010-era PDFs just to figure out a device tree path.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Why I Brought Antigravity into the Embedded World&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;I got sick of it. I decided to throw Google's new agent-centric IDE, Antigravity, at my firmware problems.&lt;/p&gt;

&lt;p&gt;The software world is moving at warp speed with AI, while the embedded industry remains a walled garden of bloated IDEs and slow iteration. We need to adapt. By offloading the massive barrier to entry—Kconfig labyrinths, linker scripts, and toolchain paths—to an AI agent, we can actually focus on what matters: the core logic and hardware interactions. I’m documenting this because we need to lower the barrier to entry in bare-metal engineering. Stop fighting the build system. Get your ideas out there.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Arming Antigravity: Mandatory &amp;amp; Recommended Extensions&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;It’s not some bloated enterprise tool. Setup is dead simple.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Download the OS-specific build from &lt;code&gt;[antigravity.google](https://antigravity.google/download)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt; It’s a VS Code fork under the hood, so your muscle memory for shortcuts and UI remains intact.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;But since we are forcing an AI agent to write firmware, we have to arm it with traditional weapons for hardware debugging. Go to the Extensions tab and install these:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Mandatory Extensions (The Core)&lt;/strong&gt; &lt;strong&gt;The nRF Extensions Quirk:&lt;/strong&gt; In standard VS Code, you'd just lazy-install the &lt;code&gt;nRF Connect for VS Code Extension Pack&lt;/code&gt; and call it a day. However, Antigravity’s extension search currently doesn't index the consolidated pack. Don't panic. You just have to manually search and install its four horsemen individually:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;nRF Connect for VS Code:&lt;/strong&gt; The absolute backbone for SDK, toolchain management, building, and debugging.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;nRF DeviceTree:&lt;/strong&gt; Visualizes and edits the nightmare that is device tree structures.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;nRF Kconfig:&lt;/strong&gt; GUI editor for project settings so you don't go blind reading Kconfig files.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;nRF Terminal:&lt;/strong&gt; Serial and RTT logging terminal.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2. Recommended Auxiliary Extensions&lt;/strong&gt; To actually read the code the agent spits out and survive debugging, you should install these for development efficiency:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Cortex-Debug (marus25.cortex-debug):&lt;/strong&gt; Provides ARM Cortex-M debugging capabilities. &lt;strong&gt;Do not skip this.&lt;/strong&gt; You can't debug bare-metal if you can't dump your hardware registers.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;C/C++ (Microsoft):&lt;/strong&gt; Essential for code compilation, IntelliSense (autocomplete), and debugging support.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;CMake Tools (Microsoft):&lt;/strong&gt; Manages the CMake build system, which is the beating heart of Zephyr.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The Antigravity Workflow: Navigating the nRF Connect Sidebar&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Once you have the extensions installed, you'll see the Nordic icon pop up on your left activity bar. Open it up, and you get three beautiful panels:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Welcome:&lt;/strong&gt; This is your entry point. You use this panel to manage your SDK versions, set up toolchains, and create new projects from templates.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Application:&lt;/strong&gt; This shows the structure of your loaded Zephyr projects. It’s where you manage not just your source code, but also keep an eye on your device tree overlays and Kconfig (&lt;code&gt;prj.conf&lt;/code&gt;) files.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Build:&lt;/strong&gt; The holy grail. You don't have to constantly wrestle with &lt;code&gt;west build&lt;/code&gt; commands in the terminal anymore. Once your board target is set, you just hit the &lt;strong&gt;"Build"&lt;/strong&gt; and &lt;strong&gt;"Flash"&lt;/strong&gt; buttons right here. Need to open the Kconfig GUI or start a Debug session? One click. It’s a completely frictionless workflow.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;What’s Next: NCS and Project Architecture&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The toolchain is ready, and the workflow is set. But before we unleash the AI to actually write our firmware, there’s a multi-gigabyte elephant in the room: installing the &lt;strong&gt;Nordic Connect SDK (NCS)&lt;/strong&gt; and structuring your repository.&lt;/p&gt;

&lt;p&gt;If you dump your code inside the vendor SDK folder ("In-tree") like beginner tutorials suggest, your Git history will become a dumpster fire.&lt;/p&gt;

&lt;p&gt;In Part 2, I’ll walk you through taming the massive NCS installation without breaking your python paths. More importantly, we’ll deep-dive into the architectural holy war of Zephyr project management: &lt;strong&gt;Freestanding Applications vs. T2 Topology (Workspace)&lt;/strong&gt;. I’ll break down their pros, cons, exact use cases, and how to set them up so you don't hate yourself later. Stay tuned.&lt;/p&gt;

</description>
      <category>antigravity</category>
      <category>nrf52840</category>
      <category>zephyrrtos</category>
      <category>firmware</category>
    </item>
  </channel>
</rss>
