<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Zephyr on Blog</title><link>https://b0weny-qwq.github.io/Blog/tags/zephyr/</link><description>Recent content in Zephyr on Blog</description><generator>Hugo -- gohugo.io</generator><language>zh-CN</language><lastBuildDate>Fri, 10 Jul 2026 00:40:00 +0800</lastBuildDate><atom:link href="https://b0weny-qwq.github.io/Blog/tags/zephyr/index.xml" rel="self" type="application/rss+xml"/><item><title>Zephyr 入门（二）：设备树是什么，应用怎样使用它</title><link>https://b0weny-qwq.github.io/Blog/p/zephyr-lesson-2-devicetree/</link><pubDate>Fri, 10 Jul 2026 00:40:00 +0800</pubDate><guid>https://b0weny-qwq.github.io/Blog/p/zephyr-lesson-2-devicetree/</guid><description>&lt;h1 id="lesson-2-设备树是什么应用怎样使用它"&gt;Lesson 2: 设备树是什么，应用怎样使用它
&lt;/h1&gt;&lt;p&gt;上一课的闪灯程序里有这样一行：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;GPIO_DT_SPEC_GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;DT_ALIAS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;led0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;gpios&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;它看起来没有写 GPIO 端口和引脚号，却能找到板上的 LED。这一课就把这条线拆开：&lt;code&gt;led0&lt;/code&gt; 从哪里来，它怎样变成具体硬件，以及为什么换一块兼容的板后，应用代码有机会不改。&lt;/p&gt;
&lt;h2 id="设备树是在替应用保管硬件细节"&gt;设备树是在替应用保管硬件细节
&lt;/h2&gt;&lt;p&gt;假设一块板把用户 LED 接在 &lt;code&gt;gpio0&lt;/code&gt; 的 12 号引脚，另一块板把它接在 &lt;code&gt;gpio1&lt;/code&gt; 的 7 号引脚。如果在应用中写死端口和引脚：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;HAL_GPIO_TogglePin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GPIOC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;GPIO_PIN_13&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;这段代码就把“闪灯逻辑”和“这块板的接线”绑在了一起。换板时，功能本身没变，仍然只是闪灯，却要回到业务代码里找引脚号。&lt;/p&gt;
&lt;p&gt;Zephyr 的做法是把接线关系放进设备树。应用只说“我想操作默认 LED”或“我想使用这个 I2C 传感器”；设备树负责回答它接在哪个控制器、占用哪个引脚、地址和中断是什么。可以把它看成开发板附带的一张硬件清单：应用不必记住仓库里每个零件的位置，只需要按清单取用。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;应用 C 代码
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 使用 led0、uart0、传感器节点等逻辑入口
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;设备树
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 记录控制器、引脚、总线、地址、中断和连接关系
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;驱动
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 根据这些参数操作实际外设
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;设备树不会凭空让所有程序跨板运行。目标板需要有对应硬件描述和驱动；但当两块板都提供相同的逻辑入口时，应用不再需要关心 LED 实际接到 P0.13 还是 P1.7。&lt;/p&gt;
&lt;h2 id="一份设备树从哪里拼出来"&gt;一份设备树从哪里拼出来
&lt;/h2&gt;&lt;p&gt;以一块 STM32 开发板为例，最终设备树通常不是单个文件，而是几层描述叠加的结果。以下路径都相对于 &lt;code&gt;zephyr/&lt;/code&gt; 源码根目录：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;boards/&amp;lt;厂商&amp;gt;/&amp;lt;开发板&amp;gt;/&amp;lt;开发板&amp;gt;.dts
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 板级硬件：板载 LED、按键、晶振、默认串口、外接器件等。
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dts/&amp;lt;架构&amp;gt;/&amp;lt;厂商&amp;gt;/&amp;lt;SoC 系列&amp;gt;/*.dtsi
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; SoC 硬件：片内 UART、GPIO、I2C、SPI、时钟、中断和内存地址。
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dts/bindings/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; binding YAML：说明 compatible 对应的设备类型、属性和参数格式。
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;可以这样理解它们的分工：SoC 的 &lt;code&gt;.dtsi&lt;/code&gt; 先说明“芯片里有几路 I2C、每路在哪个地址”；开发板的 &lt;code&gt;.dts&lt;/code&gt; 再说明“这块板把 LED 接到了哪个引脚”；应用 overlay 最后补充“这次项目要在 I2C0 上接一个传感器”。&lt;/p&gt;
&lt;p&gt;应用不应该直接改 Zephyr 自带的板级 DTS，否则升级 Zephyr 或切换版本时很难维护。通常在应用目录里添加 overlay：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;lt;application&amp;gt;/app.overlay
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;lt;application&amp;gt;/boards/&amp;lt;board&amp;gt;.overlay
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;构建后要看的不是某一层输入，而是最终合并结果：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;build/zephyr/zephyr.dts
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;build/zephyr/include/generated/devicetree_generated.h
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;如果你在 overlay 中改了 LED 引脚，实际却没有变化，先打开 &lt;code&gt;build/zephyr/zephyr.dts&lt;/code&gt; 搜索 &lt;code&gt;led0&lt;/code&gt; 或节点标签。它才是本次构建真正采用的硬件清单。&lt;/p&gt;
&lt;h2 id="先分清四个容易混淆的名字"&gt;先分清四个容易混淆的名字
&lt;/h2&gt;&lt;p&gt;同一个设备在 DTS 中可能有几种名字，刚开始很容易看花：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;节点名 DTS 树中的名字，例如 led_0、serial@4000xxxx。
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;节点标签 供 DTS 内部引用的名字，例如 led0:、uart0:，引用时写 &amp;amp;led0。
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;属性 节点的参数，例如 gpios、reg、status、compatible。
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;alias /aliases 下给应用的逻辑名字，例如 led0、sw0。
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;可以把节点标签理解为设备树内部的“书签”，而 alias 更像留给应用的快捷入口。两者名字可以相同，但不是同一个概念。尤其要记住：&lt;code&gt;DT_ALIAS(led0)&lt;/code&gt; 查的是 &lt;code&gt;/aliases&lt;/code&gt; 中的 &lt;code&gt;led0&lt;/code&gt;，不是节点名，也不是节点标签。&lt;/p&gt;
&lt;h2 id="例子从-led0-找到真实引脚"&gt;例子：从 &lt;code&gt;led0&lt;/code&gt; 找到真实引脚
&lt;/h2&gt;&lt;p&gt;下面是一段简化的板级 DTS。它把真实 LED 映射为通用别名：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-dts" data-lang="dts"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;/&lt;/span&gt;&lt;span class="cm"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;leds&lt;/span&gt;&lt;span class="cm"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;compatible&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;gpio-leds&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;user_led&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;led_0&lt;/span&gt;&lt;span class="cm"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;gpios&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nl"&gt;&amp;amp;gpio0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;GPIO_ACTIVE_HIGH&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;aliases&lt;/span&gt;&lt;span class="cm"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;led0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;&amp;amp;user_led&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;这里的 &lt;code&gt;user_led&lt;/code&gt; 是节点标签，&lt;code&gt;led_0&lt;/code&gt; 是节点名。真正描述接线的是 &lt;code&gt;gpios&lt;/code&gt;：LED 连到 &lt;code&gt;gpio0&lt;/code&gt; 的 12 号引脚，高电平有效。&lt;code&gt;aliases&lt;/code&gt; 则给它起了一个跨板更好使用的名字 &lt;code&gt;led0&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;应用只要取这个别名：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;zephyr/drivers/gpio.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define LED_NODE DT_ALIAS(led0)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;gpio_dt_spec&lt;/span&gt; &lt;span class="n"&gt;led&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;GPIO_DT_SPEC_GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LED_NODE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gpios&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;这段代码并不是运行时去读取一个配置文件。宏会在编译期一路展开：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;DT_ALIAS(led0)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; /aliases 中的 led0
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; user_led 节点
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; gpios 属性
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; GPIO 控制器、12 号引脚、有效电平
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; struct gpio_dt_spec
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;随后就可以调用 &lt;code&gt;gpio_pin_configure_dt()&lt;/code&gt;、&lt;code&gt;gpio_pin_set_dt()&lt;/code&gt; 或 &lt;code&gt;gpio_pin_toggle_dt()&lt;/code&gt;。若另一块板把默认 LED 定义为 &lt;code&gt;led0&lt;/code&gt;，它可以接在完全不同的引脚上，而这段应用逻辑不必知道。&lt;/p&gt;
&lt;h2 id="例子给项目接一个-i2c-传感器"&gt;例子：给项目接一个 I2C 传感器
&lt;/h2&gt;&lt;p&gt;再看一个更接近项目的场景：开发板已经有 &lt;code&gt;i2c0&lt;/code&gt;，但板级 DTS 没有写你的 BME280 传感器。此时不需要复制整份板级 DTS，只要在应用的 &lt;code&gt;app.overlay&lt;/code&gt; 中补上一段：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-dts" data-lang="dts"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nl"&gt;&amp;amp;i2c0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;status&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;okay&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;bme280&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;sensor&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="mi"&gt;76&lt;/span&gt;&lt;span class="cm"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;compatible&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;bosch,bme280&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;reg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mh"&gt;0x76&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;这段话可以顺着读：找到已有的 &lt;code&gt;i2c0&lt;/code&gt;；确保它启用；在这条总线上声明一个地址是 &lt;code&gt;0x76&lt;/code&gt; 的设备；它遵循 &lt;code&gt;bosch,bme280&lt;/code&gt; 这份 binding 的规则。&lt;code&gt;bme280:&lt;/code&gt; 是节点标签，因此 C 代码可以按标签取到设备：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;zephyr/device.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;device&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;sensor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;DEVICE_DT_GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;DT_NODELABEL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bme280&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;设备树到这里解决的是“传感器接在哪里、是什么类型”。它还不保证传感器一定可用：对应的驱动必须被 Kconfig 编译进固件，运行前也应该用 &lt;code&gt;device_is_ready(sensor)&lt;/code&gt; 检查设备是否准备好。下一课的 Kconfig 正好解释这个条件。&lt;/p&gt;
&lt;h2 id="dtsdtsioverlay-和-binding-各自负责什么"&gt;DTS、DTSI、overlay 和 binding 各自负责什么
&lt;/h2&gt;&lt;p&gt;回看上面的两个例子，这四类文件的边界就比较清楚了：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;SoC .dtsi
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 描述芯片内部已有的外设资源，好比芯片的数据手册。
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;板级 .dts
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 描述开发板怎样使用这些资源，例如 LED 和按键接在哪里。
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;应用 overlay
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 描述当前应用新增、启用或修改的硬件，例如接入自己的传感器。
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;binding YAML
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 定义某种 compatible 允许哪些属性、数据格式和约束，像一份填写规则。
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;构建系统把它们合并成 &lt;code&gt;build/zephyr/zephyr.dts&lt;/code&gt;，再生成 C 宏头文件。设备树不是运行时配置文件，所有宏都在编译期展开；改了 overlay 后必须重新构建，固件里的描述才会更新。&lt;/p&gt;
&lt;h2 id="本课结论"&gt;本课结论
&lt;/h2&gt;&lt;p&gt;设备树回答“硬件有什么、怎样连接、参数是什么”，应用代码再用设备树宏把这些信息交给驱动 API。读设备树时，先从板级 &lt;code&gt;.dts&lt;/code&gt; 看这块板怎样接线；需要了解片内资源时再追到 SoC &lt;code&gt;.dtsi&lt;/code&gt;；不清楚某个属性怎么写时查 binding；改完后始终以 &lt;code&gt;build/zephyr/zephyr.dts&lt;/code&gt; 为准。&lt;/p&gt;
&lt;p&gt;下一课会看 Kconfig：设备树已经说清“这里接着一颗 BME280”，Kconfig 还要决定“它的驱动是否真的进入这次固件”。&lt;/p&gt;</description></item><item><title>Zephyr 入门（一）：认识 Zephyr 工程</title><link>https://b0weny-qwq.github.io/Blog/p/zephyr-lesson-1-project-overview/</link><pubDate>Fri, 10 Jul 2026 00:30:00 +0800</pubDate><guid>https://b0weny-qwq.github.io/Blog/p/zephyr-lesson-1-project-overview/</guid><description>&lt;p&gt;这一系列想从 Zephyr 入门开始，补上之前一直想写却没动笔的内容。&lt;/p&gt;
&lt;h1 id="lesson-1-认识-zephyr-工程"&gt;Lesson 1: 认识 Zephyr 工程
&lt;/h1&gt;&lt;p&gt;Zephyr 是一个面向资源受限设备的开源实时操作系统（RTOS）。它有线程、调度和同步这些内核能力，但它想管的不只是“任务怎么调度”：驱动、硬件描述、配置、板级支持和构建流程也被放进了同一套体系。&lt;/p&gt;
&lt;p&gt;先想一个最常见的练习：每隔一秒翻转一次 LED。用 FreeRTOS 时，任务里的延时可以写成 &lt;code&gt;vTaskDelay()&lt;/code&gt;；但 LED 在哪个引脚、GPIO 时钟怎样开、引脚怎样初始化，仍要由 STM32 HAL、TI DriverLib 或其他厂商库处理。任务代码可以不变，换一块板后，&lt;code&gt;HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13)&lt;/code&gt; 这类外设代码和初始化往往还是得跟着改。&lt;/p&gt;
&lt;p&gt;Zephyr 希望把这部分差异挪到应用代码之外。应用更接近下面这种写法：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;gpio_dt_spec&lt;/span&gt; &lt;span class="n"&gt;led&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;GPIO_DT_SPEC_GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;DT_ALIAS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;led0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;gpios&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;gpio_pin_toggle_dt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;led&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;这里没有出现 &lt;code&gt;GPIOC&lt;/code&gt; 或 &lt;code&gt;PIN_13&lt;/code&gt;。应用只说“切换这块板的默认 LED”；&lt;code&gt;led0&lt;/code&gt; 到底接在哪个 GPIO 控制器、哪个引脚，以及应该选用哪一个 GPIO 驱动，由开发板的设备树和 Kconfig 配置决定。换到另一块已支持、也提供 &lt;code&gt;led0&lt;/code&gt; 的板时，这段闪灯逻辑可以保持不动，只需要重新选择目标板构建。&lt;/p&gt;
&lt;p&gt;这不是魔法，也不是硬件差异消失了。可以把设备树看成一张接线和设备清单：它把“LED 接在 P0.13”这样的事实放在板级描述里，而不是散落在应用业务代码中。DMA、低功耗、特殊时钟等功能仍然要面对具体芯片的能力和限制；只是普通应用不必反复触碰同一组寄存器和引脚号。&lt;/p&gt;
&lt;p&gt;这也是 Zephyr 值得学习、同时需要先熟悉工程体系的原因。它适合 MCU、传感器节点、可穿戴设备等需要实时性和较小资源占用的场景，也覆盖了从小型开发板到部分带 MMU 的平台。刚接触时，最容易困惑的往往不是 API，而是应用、配置、设备树和驱动究竟怎样配合。&lt;/p&gt;
&lt;p&gt;这一课先不急着读内核。先建立整体印象：一份闪灯应用从哪些文件得到“这块板有什么”，又如何变成可以烧录的固件。理解了这条线，再回头读线程和调度会顺得多。&lt;/p&gt;
&lt;h2 id="先看整个工作区"&gt;先看整个工作区
&lt;/h2&gt;&lt;p&gt;用 West 初始化后的工作区通常长这样：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;workspace/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── .west/ West 工作区配置
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── zephyr/ Zephyr RTOS 源码
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── modules/ 第三方模块，例如 CMSIS、芯片 HAL
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;└── my-application/ 自己的应用工程
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;可以把这个目录想成一个项目工地：&lt;code&gt;my-application&lt;/code&gt; 是你正在写的产品代码，&lt;code&gt;zephyr&lt;/code&gt; 是公共框架和基础设施，&lt;code&gt;modules&lt;/code&gt; 放着芯片厂商和第三方提供的零件。Zephyr SDK 不在工地里，它安装在开发电脑上，提供交叉编译器、链接器等工具；它不属于 Zephyr 源码，也不会被烧录进开发板。&lt;/p&gt;
&lt;p&gt;刚开始最值得盯住的是两处：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;my-application/ 应用代码、应用配置、设备树覆盖
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;zephyr/ RTOS、驱动、设备树、板级支持和构建规则
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;code&gt;west&lt;/code&gt;、SDK 和 &lt;code&gt;modules/hal/&amp;lt;vendor&amp;gt;/&lt;/code&gt; 当然都重要，但前期不需要急着读实现。它们的作用分别是管理多个仓库、提供工具链，以及承载厂商 HAL；应用通常会通过 Zephyr 的驱动间接使用这些 HAL。&lt;/p&gt;
&lt;h2 id="west-到底负责什么"&gt;West 到底负责什么
&lt;/h2&gt;&lt;p&gt;Git 管理单个仓库的历史，West 管理的是一组彼此有依赖关系的仓库。可以把它理解为项目的“装配清单”：&lt;code&gt;zephyr/west.yml&lt;/code&gt; 记录 CMSIS、芯片 HAL 等模块该放在哪里、要使用哪个版本。执行：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;west update
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;West 会把工作区同步到清单指定的状态。比如同一个 Zephyr 版本需要某个版本的 STM32 HAL，&lt;code&gt;west update&lt;/code&gt; 会按清单取回匹配版本，避免每个人手动拼一套不兼容的仓库。它还把构建、烧录和调试入口统一起来：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;west build -b &amp;lt;board&amp;gt; &amp;lt;application-directory&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;west flash
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;west debug
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;不过 West 本身不编译 C 代码。真正干活的是 Zephyr 的 CMake 构建系统、构建工具和交叉编译器；West 更像是把这些步骤组织到同一个命令入口下。&lt;/p&gt;
&lt;h2 id="一份应用怎样变成固件"&gt;一份应用怎样变成固件
&lt;/h2&gt;&lt;p&gt;还是以闪灯程序为例。&lt;code&gt;main.c&lt;/code&gt; 只写“翻转 LED”，但编译器还不知道这颗 LED 接在哪里、GPIO 驱动要不要编进去。应用代码并不是直接丢给编译器，而是先经过配置和硬件描述的合并：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;span class="lnt"&gt;23
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;src/main.c
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; v
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;CMakeLists.txt
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; find_package(Zephyr ...): 引入 Zephyr 构建系统
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; target_sources(...): 将应用源文件加入 app 目标
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; v
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;prj.conf
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 选择需要的 Kconfig 功能，例如 GPIO、日志、线程
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; v
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;板级 DTS + SoC DTSI + 应用 overlay
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 描述目标硬件，以及应用对硬件描述的修改
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; v
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;生成最终设备树、最终 Kconfig 和设备树 C 宏
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; v
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;交叉编译并链接
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; v
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;zephyr.elf / zephyr.bin
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;构建目录不是可以完全忽略的临时垃圾，它像是构建系统交卷前留下的草稿。比如你把 &lt;code&gt;led0&lt;/code&gt; 指到另一个引脚，却发现灯没有变化；这时先看最终合并出的设备树，而不是靠猜。遇到“明明改了配置却没生效”这类问题，也先来这里确认：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;build/zephyr/zephyr.dts
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 最终合并后的设备树。
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;build/zephyr/.config
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 最终生效的 Kconfig 配置。
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;build/zephyr/include/generated/devicetree_generated.h
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 设备树生成的 C 宏。
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2 id="zephyr-和-zephyr-sdk-别混在一起"&gt;Zephyr 和 Zephyr SDK 别混在一起
&lt;/h2&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Zephyr： 会被编译进目标设备的 RTOS、驱动和板级支持源码。
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Zephyr SDK： 在开发主机上执行编译、汇编、链接和二进制转换的工具链。
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;它们之间的关系可以简单理解为：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;应用源码 + Zephyr 源码
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; | Zephyr SDK 提供交叉编译器、链接器等工具
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; v
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;zephyr.elf / zephyr.bin
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; v
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;烧录到目标开发板并运行
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;所以日常开发主要修改应用源码、&lt;code&gt;prj.conf&lt;/code&gt; 和设备树；SDK 通常只在安装、升级或排查工具链问题时才需要关注。&lt;/p&gt;
&lt;h2 id="zephyr-目录该从哪儿看"&gt;&lt;code&gt;zephyr/&lt;/code&gt; 目录该从哪儿看
&lt;/h2&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kernel/ 线程、调度、定时器、同步机制
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;drivers/ GPIO、UART、I2C、SPI 等驱动框架
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;boards/ 开发板定义
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;soc/ 芯片和 SoC 支持
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dts/ 通用设备树定义与 bindings
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;include/zephyr/ 应用可包含的 API 头文件
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cmake/、scripts/ 构建和代码生成规则
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;第一次打开 &lt;code&gt;zephyr/&lt;/code&gt; 时很容易被目录数量劝退。不建议马上扎进 &lt;code&gt;kernel/&lt;/code&gt;：先从自己的 &lt;code&gt;main.c&lt;/code&gt; 出发，找到它使用的 &lt;code&gt;led0&lt;/code&gt;，再看它在最终设备树里落到了哪个 GPIO。等这条“代码如何落到硬件上”的路走通，再读内核时，看到的就不再是一堆孤立文件。&lt;/p&gt;
&lt;h2 id="zephyr-能支持哪些硬件"&gt;Zephyr 能支持哪些硬件
&lt;/h2&gt;&lt;p&gt;Zephyr 不是给任意 MCU 都能直接编译运行的通用库。把一个 STM32 的应用拿去给一颗新的 Cortex-M 芯片编译，并不因为两者都是 Cortex-M 就会成功。要支持一颗芯片，架构、SoC、开发板定义、设备树 binding、驱动和工具链必须一起就位：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;span class="lnt"&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;应用代码
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Zephyr 通用 API
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;驱动 + 设备树 binding
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;SoC 支持 + 启动代码 + 中断/时钟/内存配置
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;具体 MCU 硬件
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;这有点像两辆车都使用同一款发动机，并不代表仪表、线束和控制器可以直接互换。判断某个 MCU 是否受支持，不能只看它是不是 Cortex-M；外设、时钟、中断控制器、内存布局、启动方式和所需驱动是否完成适配，同样关键。&lt;/p&gt;
&lt;h3 id="移植一颗新-mcu-为什么费工夫"&gt;移植一颗新 MCU 为什么费工夫
&lt;/h3&gt;&lt;p&gt;移植不只是让 &lt;code&gt;main()&lt;/code&gt; 跑起来，通常还要补齐下面这些环节：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;CPU 架构层 异常入口、上下文切换、中断模型
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;启动与内存 启动代码、向量表、链接脚本、Flash/RAM 布局
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;SoC 基础设施 时钟、复位、中断控制器、系统定时器
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;硬件描述 DTS、pinmux/pinctrl、设备树 bindings
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;外设驱动 GPIO、UART、SPI、I2C、DMA 等驱动及其 Kconfig
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;构建与调试 交叉工具链、烧录、调试器配置
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;同一 CPU 架构的异常和上下文切换部分可能可以复用，但真正花时间的通常是 SoC 基础设施、设备树和外设驱动。厂商 HAL 也不能自动解决问题：它只是提供了操作硬件的底层能力，还需要接入 Zephyr 的驱动模型、Kconfig 和设备树体系，应用才能通过统一 API 使用它。&lt;/p&gt;
&lt;h2 id="zephyr-怎样把-api-接到硬件上"&gt;Zephyr 怎样把 API 接到硬件上
&lt;/h2&gt;&lt;p&gt;Zephyr 希望应用表达“我要控制这块板定义的 LED”，而不是直接写寄存器地址或厂商专属的 GPIO 端口。比如：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;gpio_pin_toggle_dt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;led&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;这句 API 最终能改变引脚电平，靠的是下面几层接力：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;应用： 使用 GPIO API
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;设备树： 指定 LED 对应哪个 GPIO 控制器和引脚
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;GPIO 驱动： 将 API 调用转换为该控制器的操作
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;SoC/厂商 HAL： 访问具体外设寄存器
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;硬件： 改变引脚电平
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;其中三件事尤其重要。统一 API 是应用看到的操作面板，例如 GPIO、UART、I2C、SPI API；设备树像硬件清单，描述“板上有什么、连在哪里”；Kconfig 像装配选项，决定哪些功能和驱动会被编译进固件。&lt;/p&gt;
&lt;p&gt;抽象并不意味着硬件差异消失。目标板已经有相应 DTS 描述和驱动时，同一份应用代码才可能少改甚至不改地迁移；DMA、低功耗、特殊时钟和厂商私有外设功能仍然需要结合具体 SoC 的限制来处理。&lt;/p&gt;
&lt;h2 id="与-freertos-的应用开发模型对比"&gt;与 FreeRTOS 的应用开发模型对比
&lt;/h2&gt;&lt;p&gt;Zephyr 和 FreeRTOS 都能做实时嵌入式应用，区别主要不在“有没有线程和队列”，而在于平台覆盖的范围。前者更像一套带标准接口和装配规则的平台，后者的 Kernel 则专注把实时内核做好：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;FreeRTOS Kernel
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 重点提供任务调度、队列、信号量、软件定时器等内核能力。
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Zephyr
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 除了内核，还把驱动模型、设备树、Kconfig、板级支持和构建流程
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 作为同一平台的一部分。
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;这并不代表 FreeRTOS 不能用驱动或厂商库。实际项目里，它常和厂商的启动代码、HAL、手写或厂商提供的外设驱动一起使用，只是这些组件不由 FreeRTOS Kernel 统一定义和管理。若项目只固定在一块板上，这种方式完全合理；当要支持多块板、维护多种芯片，或希望复用驱动和配置时，Zephyr 试图用一套接口、配置和硬件描述把这些部分串起来。&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;问题&lt;/th&gt;
 &lt;th&gt;FreeRTOS 项目中常见做法&lt;/th&gt;
 &lt;th&gt;Zephyr 项目中常见做法&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;创建任务/线程&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;xTaskCreate()&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;K_THREAD_DEFINE()&lt;/code&gt; 或 &lt;code&gt;k_thread_create()&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;延时&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;vTaskDelay()&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;k_sleep()&lt;/code&gt; 或 &lt;code&gt;k_msleep()&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;队列与同步&lt;/td&gt;
 &lt;td&gt;Queue、Semaphore、Event Group&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;k_msgq&lt;/code&gt;、&lt;code&gt;k_sem&lt;/code&gt;、&lt;code&gt;k_event&lt;/code&gt; 等对象&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;外设选择&lt;/td&gt;
 &lt;td&gt;厂商 HAL、手写寄存器配置或项目代码&lt;/td&gt;
 &lt;td&gt;通用驱动 API + DTS + Kconfig&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;引脚和外设实例&lt;/td&gt;
 &lt;td&gt;通常在 BSP、初始化代码或应用中配置&lt;/td&gt;
 &lt;td&gt;主要由设备树描述&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;编译开关&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;FreeRTOSConfig.h&lt;/code&gt; 加上工程配置&lt;/td&gt;
 &lt;td&gt;Kconfig，例如 &lt;code&gt;prj.conf&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;换一块板&lt;/td&gt;
 &lt;td&gt;常需要调整 BSP、时钟、引脚和驱动初始化&lt;/td&gt;
 &lt;td&gt;若目标板已被支持，通常通过板选择和 DTS/overlay 调整&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;API 名称不能机械地一一替换，两者的对象模型、内存管理方式和配置选项并不完全相同。学习入口也不同：FreeRTOS 往往从任务和调度开始；Zephyr 更适合先弄清应用如何经过配置、设备树和驱动连接到硬件，再进入内核。&lt;/p&gt;
&lt;h2 id="推荐的阅读顺序"&gt;推荐的阅读顺序
&lt;/h2&gt;&lt;p&gt;别从目录树里盲目挑文件。继续沿着“闪灯”这个小目标往下追，读完每一步都能马上验证：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;应用的 &lt;code&gt;CMakeLists.txt&lt;/code&gt;：应用如何进入 Zephyr 构建系统。&lt;/li&gt;
&lt;li&gt;应用的 &lt;code&gt;prj.conf&lt;/code&gt;：应用请求启用哪些功能。&lt;/li&gt;
&lt;li&gt;目标板的 &lt;code&gt;.dts&lt;/code&gt;：板上有哪些硬件资源。&lt;/li&gt;
&lt;li&gt;应用的 &lt;code&gt;src/main.c&lt;/code&gt;：应用如何调用 Zephyr API。&lt;/li&gt;
&lt;li&gt;构建生成的 &lt;code&gt;zephyr.dts&lt;/code&gt;、&lt;code&gt;.config&lt;/code&gt; 和 &lt;code&gt;devicetree_generated.h&lt;/code&gt;：这次构建最终做了什么。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;下一课会把 &lt;code&gt;led0&lt;/code&gt; 这条线拆开，看看它如何一路找到真实引脚：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;DT_ALIAS(led0) -&amp;gt; LED 节点 -&amp;gt; gpios 属性 -&amp;gt; GPIO 控制器与引脚 -&amp;gt; GPIO 驱动
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;顺着这条链路读下去，再进入驱动、初始化和内核，关系会清楚得多。&lt;/p&gt;</description></item><item><title>工具链搭建完成</title><link>https://b0weny-qwq.github.io/Blog/p/toolchain-bringup-stm32f1-mspm0/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://b0weny-qwq.github.io/Blog/p/toolchain-bringup-stm32f1-mspm0/</guid><description>&lt;h1 id="工具链搭建完成"&gt;工具链搭建完成
&lt;/h1&gt;&lt;p&gt;这段时间主要在折腾自己的嵌入式工具链。到 &lt;code&gt;5.18&lt;/code&gt; 这一天，基本可以算是阶段性搭建完成了。&lt;/p&gt;
&lt;p&gt;以前做嵌入式，很多流程其实是被厂商 &lt;code&gt;IDE&lt;/code&gt;、图形配置工具和各种零散脚本拖着走。能跑是能跑，但每次换平台、换板子、换环境，都像是在重新和工具链打一架。现在我更想把这些东西统一到一套可复现、可调用、可交给 &lt;code&gt;agent&lt;/code&gt; 执行的流程里：构建、烧录、检查、日志、错误定位，都尽量走明确的命令和工程结构。&lt;/p&gt;
&lt;p&gt;这次最重要的成果，是测试成功了两条 &lt;code&gt;ARM&lt;/code&gt; 架构相关的烧录链路。&lt;/p&gt;
&lt;p&gt;一条是经典的 &lt;code&gt;STM32F1&lt;/code&gt;。这基本算是我最熟悉的一类 &lt;code&gt;MCU&lt;/code&gt;，也是很多嵌入式开发者绕不开的起点。能把它从传统开发方式里抽出来，用现代一点的工程结构完成构建和烧录，对我来说像是把老朋友重新接进新的工作流。&lt;/p&gt;
&lt;p&gt;另一条是 &lt;code&gt;TI MSPM0&lt;/code&gt;。相比 &lt;code&gt;STM32F1&lt;/code&gt;，它更像是一个新的切入点：同样是 &lt;code&gt;ARM&lt;/code&gt; 生态，但厂商工具、SDK 组织方式、烧录链路和调试习惯都不完全一样。把 &lt;code&gt;MSPM0&lt;/code&gt; 也跑通，说明这套工具链不是只为某一个芯片临时拼出来的，而是有机会扩展成更通用的嵌入式开发底座。&lt;/p&gt;
&lt;p&gt;从结果看，&lt;code&gt;STM32F1&lt;/code&gt; 和 &lt;code&gt;MSPM0&lt;/code&gt; 两边都能完成烧录验证，这件事让我很兴奋。工程流开始从“某个项目能跑”变成“某类流程能复用”。只要构建、烧录、调试这些基础动作足够稳定，后面再接入更多芯片、板卡和模板，就不用每次都从一片混乱里重新开始。&lt;/p&gt;
&lt;h2 id="下一步认识-zephyr"&gt;下一步：认识 Zephyr
&lt;/h2&gt;&lt;p&gt;工具链跑通之后，我开始对 &lt;code&gt;Zephyr&lt;/code&gt; 这个 &lt;code&gt;RTOS&lt;/code&gt; 产生兴趣。&lt;/p&gt;
&lt;p&gt;以前接触 &lt;code&gt;RTOS&lt;/code&gt;，更多是从裸机工程往上加：任务、调度、信号量、队列、驱动抽象，一层一层把东西堆起来。这样学很扎实，但也容易停留在“我会移植一个系统”或者“我会用一套接口”的阶段。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Zephyr&lt;/code&gt; 给我的感觉不太一样。它不像只是一个单纯的内核，更像一整套面向现代嵌入式工程的系统：设备树、配置系统、构建系统、驱动模型、板级支持、跨平台抽象，这些东西组合在一起，才接近我想理解的“工程化嵌入式”。&lt;/p&gt;
&lt;p&gt;我想认识它，不只是为了会用一个新的 &lt;code&gt;RTOS&lt;/code&gt;，而是想看看成熟的嵌入式系统到底怎么组织复杂性。为什么它能支持那么多板子，为什么驱动可以被抽象成统一模型，为什么配置和构建能被系统化管理，这些问题都值得拆开看。&lt;/p&gt;
&lt;p&gt;工具链搭好只是第一步。烧录跑通代表入口打通了，有意思的事情还在后面：把底层硬件、构建系统、&lt;code&gt;RTOS&lt;/code&gt; 思想和 &lt;code&gt;agent&lt;/code&gt; 工作流接到一起，形成一套自己能长期维护、长期扩展的嵌入式工程体系。&lt;/p&gt;
&lt;p&gt;这是我接下来想继续推进的方向。&lt;/p&gt;</description></item></channel></rss>