<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>I2C on Tech Snippets - 嵌入式技术笔记</title>
    <link>https://tech-snippets.xyz/tags/i2c/</link>
    <description>Recent content in I2C on Tech Snippets - 嵌入式技术笔记</description>
    <generator>Hugo</generator>
    <language>zh-cn</language>
    <lastBuildDate>Sun, 07 Jun 2026 19:00:00 +0800</lastBuildDate>
    <atom:link href="https://tech-snippets.xyz/tags/i2c/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Zephyr RTOS 设备树与驱动开发实战：从 Devicetree、Kconfig 到可复用传感器驱动</title>
      <link>https://tech-snippets.xyz/posts/zephyr-devicetree-driver-kconfig-practical-guide/</link>
      <pubDate>Sun, 07 Jun 2026 19:00:00 +0800</pubDate>
      <guid>https://tech-snippets.xyz/posts/zephyr-devicetree-driver-kconfig-practical-guide/</guid>
      <description>前言 如果你长期写 STM32、ESP32 或 Linux 驱动，第一次接触 Zephyr RTOS 时，最容易卡住的地方通常不是线程、信号量这些传统 RTOS 概念，而是三个看起来“有点绕”的基础设施：Devicetree、Kconfig 和驱动模型。很多人会问：为什么点一个 LED、读一个 I2C 传感器，不能像裸机工程那样直接写寄存器地址？为什么配置宏要分散在 prj.conf、Kconfig、*.overlay、*.yaml 和生成目录里？
原因很简单：Zephyr 面向的不是单个芯片或单块板子，而是“同一套应用可以在不同板级硬件上复用”。它把硬件描述、功能裁剪、驱动实例化、应用逻辑拆开，让应用尽量不关心底层板子的管脚、总线地址和外设差异。这个思路非常适合产品化嵌入式开发：今天样机用 nRF52，明天量产版换 STM32；今天传感器挂在 i2c0，明天板子改版挪到 i2c1；应用层最好只写一次。
本文不做“概念堆砌”，而是以一个虚构但贴近真实项目的 I2C 温湿度传感器 xyz123 为例，完整走一遍 Zephyr 驱动开发链路：如何写 Devicetree overlay，如何写 binding，如何通过 Kconfig 打开驱动，如何用 DEVICE_DT_INST_DEFINE() 生成设备实例，如何在应用中调用标准 sensor API，最后再给出调试和移植时最常见的坑。
一、为什么 Zephyr 要把硬件描述放到 Devicetree 在传统裸机工程里，硬件信息经常散落在 C 代码中：I2C 地址写成宏，GPIO 管脚写成宏，时钟频率写在 board.h，中断优先级写在初始化函数里。项目小的时候这很直观；项目一旦有多块板、多种传感器、多套 SKU，维护成本会很快上升。
Zephyr 借鉴 Linux 的 Devicetree 思路，把“板上有什么硬件、硬件挂在哪里、默认参数是什么”写成树状描述。比如一个 I2C 传感器可以描述为：它挂在 i2c1 控制器下面，地址是 0x44，中断脚连接到 gpio0 的第 12 脚，采样周期默认 1000 ms。驱动代码不直接写死这些值，而是在编译期从 Devicetree 生成的头文件中取。
这种方式有三个直接好处：</description>
      <content:encoded><![CDATA[<h2 id="前言">前言</h2>
<p>如果你长期写 STM32、ESP32 或 Linux 驱动，第一次接触 Zephyr RTOS 时，最容易卡住的地方通常不是线程、信号量这些传统 RTOS 概念，而是三个看起来“有点绕”的基础设施：Devicetree、Kconfig 和驱动模型。很多人会问：为什么点一个 LED、读一个 I2C 传感器，不能像裸机工程那样直接写寄存器地址？为什么配置宏要分散在 <code>prj.conf</code>、<code>Kconfig</code>、<code>*.overlay</code>、<code>*.yaml</code> 和生成目录里？</p>
<p>原因很简单：Zephyr 面向的不是单个芯片或单块板子，而是“同一套应用可以在不同板级硬件上复用”。它把硬件描述、功能裁剪、驱动实例化、应用逻辑拆开，让应用尽量不关心底层板子的管脚、总线地址和外设差异。这个思路非常适合产品化嵌入式开发：今天样机用 nRF52，明天量产版换 STM32；今天传感器挂在 <code>i2c0</code>，明天板子改版挪到 <code>i2c1</code>；应用层最好只写一次。</p>
<p>本文不做“概念堆砌”，而是以一个虚构但贴近真实项目的 I2C 温湿度传感器 <code>xyz123</code> 为例，完整走一遍 Zephyr 驱动开发链路：如何写 Devicetree overlay，如何写 binding，如何通过 Kconfig 打开驱动，如何用 <code>DEVICE_DT_INST_DEFINE()</code> 生成设备实例，如何在应用中调用标准 sensor API，最后再给出调试和移植时最常见的坑。</p>
<p><img alt="Zephyr 驱动构建与实例化流程" loading="lazy" src="/images/zephyr-driver-build-flow.svg"></p>
<h2 id="一为什么-zephyr-要把硬件描述放到-devicetree">一、为什么 Zephyr 要把硬件描述放到 Devicetree</h2>
<p>在传统裸机工程里，硬件信息经常散落在 C 代码中：I2C 地址写成宏，GPIO 管脚写成宏，时钟频率写在 <code>board.h</code>，中断优先级写在初始化函数里。项目小的时候这很直观；项目一旦有多块板、多种传感器、多套 SKU，维护成本会很快上升。</p>
<p>Zephyr 借鉴 Linux 的 Devicetree 思路，把“板上有什么硬件、硬件挂在哪里、默认参数是什么”写成树状描述。比如一个 I2C 传感器可以描述为：它挂在 <code>i2c1</code> 控制器下面，地址是 <code>0x44</code>，中断脚连接到 <code>gpio0</code> 的第 12 脚，采样周期默认 1000 ms。驱动代码不直接写死这些值，而是在编译期从 Devicetree 生成的头文件中取。</p>
<p>这种方式有三个直接好处：</p>
<ol>
<li><strong>应用和硬件解耦</strong>：应用只拿一个设备指针，不需要知道传感器到底在 <code>i2c0</code> 还是 <code>i2c1</code>。</li>
<li><strong>同一驱动支持多个实例</strong>：一块板上可以挂两个相同型号的传感器，只要 Devicetree 写两个节点即可。</li>
<li><strong>编译期发现错误</strong>：地址、管脚、兼容字符串写错，很多问题会在构建阶段暴露，而不是运行时才发现。</li>
</ol>
<p>一个最简 overlay 可能是这样：</p>
<pre tabindex="0"><code class="language-dts" data-lang="dts">&amp;i2c1 {
    status = &#34;okay&#34;;
    clock-frequency = &lt;I2C_BITRATE_STANDARD&gt;;

    xyz123@44 {
        compatible = &#34;demo,xyz123&#34;;
        reg = &lt;0x44&gt;;
        label = &#34;XYZ123&#34;;
        int-gpios = &lt;&amp;gpio0 12 GPIO_ACTIVE_LOW&gt;;
        sample-period-ms = &lt;1000&gt;;
    };
};
</code></pre><p>注意这里的 <code>compatible = &quot;demo,xyz123&quot;</code> 非常关键。Zephyr 不是靠节点名字 <code>xyz123@44</code> 找驱动，而是靠 compatible 字符串把 Devicetree 节点和驱动的实例化宏关联起来。</p>
<h2 id="二kconfig-解决的是要不要编译和编译哪些特性">二、Kconfig 解决的是“要不要编译”和“编译哪些特性”</h2>
<p>Devicetree 描述硬件“存在什么”，Kconfig 描述软件“启用什么”。两者经常一起出现，但职责不同。比如板子上可以有 <code>xyz123</code> 传感器节点，但你仍然可以通过 Kconfig 决定是否编译该驱动、是否打开日志、是否启用中断模式。</p>
<p>一个驱动的 Kconfig 通常长这样：</p>
<pre tabindex="0"><code class="language-kconfig" data-lang="kconfig">menuconfig XYZ123
    bool &#34;XYZ123 temperature and humidity sensor&#34;
    default y
    depends on DT_HAS_DEMO_XYZ123_ENABLED
    select I2C
    select SENSOR
    help
      Enable driver for the XYZ123 I2C temperature and humidity sensor.

if XYZ123

config XYZ123_TRIGGER
    bool &#34;Enable XYZ123 interrupt trigger support&#34;
    depends on GPIO
    help
      Enable data-ready interrupt support for XYZ123.

config XYZ123_INIT_PRIORITY
    int &#34;XYZ123 init priority&#34;
    default 90

endif
</code></pre><p>这里 <code>DT_HAS_DEMO_XYZ123_ENABLED</code> 是 Zephyr 根据 binding 和 Devicetree 自动生成的宏，含义是“是否存在 enabled 状态的 <code>demo,xyz123</code> 节点”。这样做的好处是：没有硬件节点时，驱动默认不会被莫名其妙编译进来；有硬件节点时，驱动可以自动打开。</p>
<p>在应用工程的 <code>prj.conf</code> 里，我们只需要写：</p>
<pre tabindex="0"><code class="language-conf" data-lang="conf">CONFIG_I2C=y
CONFIG_SENSOR=y
CONFIG_XYZ123=y
CONFIG_LOG=y
CONFIG_XYZ123_TRIGGER=n
</code></pre><p>实际项目里，我建议把 <code>prj.conf</code> 当作“产品功能配置”，不要把板级管脚、I2C 地址塞进去。管脚和地址属于 Devicetree；是否启用日志、是否启用 Shell、是否打开某个驱动特性，才属于 Kconfig。</p>
<p>（第一部分完，约2100字）</p>
<h2 id="三工程目录怎么组织才不容易乱">三、工程目录怎么组织才不容易乱</h2>
<p>Zephyr 支持多种工程组织方式：可以把驱动直接放在应用工程里，也可以作为 out-of-tree module 独立维护。初学阶段建议先放在应用工程里，确认流程跑通后再拆成模块。一个清晰的最小结构如下：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">zephyr-xyz123-demo/
</span></span><span class="line"><span class="cl">├── CMakeLists.txt
</span></span><span class="line"><span class="cl">├── prj.conf
</span></span><span class="line"><span class="cl">├── boards/
</span></span><span class="line"><span class="cl">│   └── nucleo_f446re.overlay
</span></span><span class="line"><span class="cl">├── dts/
</span></span><span class="line"><span class="cl">│   └── bindings/
</span></span><span class="line"><span class="cl">│       └── sensor/
</span></span><span class="line"><span class="cl">│           └── demo,xyz123.yaml
</span></span><span class="line"><span class="cl">├── drivers/
</span></span><span class="line"><span class="cl">│   └── sensor/
</span></span><span class="line"><span class="cl">│       └── xyz123/
</span></span><span class="line"><span class="cl">│           ├── CMakeLists.txt
</span></span><span class="line"><span class="cl">│           ├── Kconfig
</span></span><span class="line"><span class="cl">│           └── xyz123.c
</span></span><span class="line"><span class="cl">└── src/
</span></span><span class="line"><span class="cl">    └── main.c
</span></span></code></pre></div><p>顶层 <code>CMakeLists.txt</code> 负责把应用源文件和自定义驱动目录加入构建：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cmake" data-lang="cmake"><span class="line"><span class="cl"><span class="nb">cmake_minimum_required</span><span class="p">(</span><span class="s">VERSION</span> <span class="s">3.20.0</span><span class="p">)</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="nb">find_package</span><span class="p">(</span><span class="s">Zephyr</span> <span class="s">REQUIRED</span> <span class="s">HINTS</span> <span class="o">$ENV{</span><span class="nv">ZEPHYR_BASE</span><span class="o">}</span><span class="p">)</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="nb">project</span><span class="p">(</span><span class="s">zephyr_xyz123_demo</span><span class="p">)</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="nb">target_sources</span><span class="p">(</span><span class="s">app</span> <span class="s">PRIVATE</span> <span class="s">src/main.c</span><span class="p">)</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="nb">add_subdirectory</span><span class="p">(</span><span class="s">drivers/sensor/xyz123</span><span class="p">)</span><span class="err">
</span></span></span></code></pre></div><p>驱动目录中的 <code>CMakeLists.txt</code> 则根据 Kconfig 决定是否编译源文件：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cmake" data-lang="cmake"><span class="line"><span class="cl"><span class="nb">zephyr_library</span><span class="p">()</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="nb">zephyr_library_sources_ifdef</span><span class="p">(</span><span class="s">CONFIG_XYZ123</span> <span class="s">xyz123.c</span><span class="p">)</span><span class="err">
</span></span></span></code></pre></div><p>如果驱动未来会变成公司内部通用模块，就可以把 <code>drivers/</code>、<code>dts/bindings/</code> 和 <code>Kconfig</code> 抽出去，通过 <code>ZEPHYR_EXTRA_MODULES</code> 引入。不要一上来就追求“模块化最佳实践”，先把最小闭环跑通，定位问题会轻松很多。</p>
<h2 id="四binding-yaml让-devicetree-属性变成可校验的接口">四、binding YAML：让 Devicetree 属性变成可校验的接口</h2>
<p>Devicetree overlay 写了 <code>sample-period-ms</code>、<code>int-gpios</code> 等属性，Zephyr 需要知道这些属性的类型和含义，这就要靠 binding YAML。我们的 <code>demo,xyz123.yaml</code> 可以这样写：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">description</span><span class="p">:</span><span class="w"> </span><span class="l">Demo XYZ123 temperature and humidity sensor</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">compatible</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;demo,xyz123&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">include</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">sensor-device.yaml, i2c-device.yaml]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">properties</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">int-gpios</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">phandle-array</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">required</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">description</span><span class="p">:</span><span class="w"> </span><span class="l">Optional data-ready interrupt GPIO</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">sample-period-ms</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">int</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">default</span><span class="p">:</span><span class="w"> </span><span class="m">1000</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">description</span><span class="p">:</span><span class="w"> </span><span class="l">Default sampling period in milliseconds</span><span class="w">
</span></span></span></code></pre></div><p><code>include: i2c-device.yaml</code> 表示该设备遵循 I2C 子节点的基本约定，例如必须有 <code>reg</code> 属性。<code>sensor-device.yaml</code> 则让它符合 sensor 类设备的公共属性。这样写完后，如果 overlay 中把 <code>sample-period-ms</code> 写成字符串，构建阶段就会报错；如果把 compatible 写错，驱动实例也不会生成。</p>
<p>很多 Zephyr 新手会忽略 binding，直接在 C 代码里尝试 <code>DT_PROP()</code> 读取属性，然后发现宏不存在。排查这类问题时，第一步永远是确认 compatible 是否一致：</p>
<ul>
<li>overlay 中写的是 <code>compatible = &quot;demo,xyz123&quot;;</code></li>
<li>YAML 文件名通常写成 <code>demo,xyz123.yaml</code></li>
<li>驱动中使用的实例宏会转成 <code>DT_DRV_COMPAT demo_xyz123</code></li>
</ul>
<p>逗号和连字符在 C 宏里会转换为下划线，这是一个常见细节。</p>
<h2 id="五驱动核心configdata-与-device_dt_inst_define">五、驱动核心：config、data 与 DEVICE_DT_INST_DEFINE</h2>
<p>Zephyr 驱动通常把只读配置和运行时状态分开：</p>
<ul>
<li><code>config</code>：来自 Devicetree 的总线、地址、GPIO、默认参数，通常放 <code>const</code>。</li>
<li><code>data</code>：运行时采样值、锁、状态标志，通常每个设备实例一份。</li>
</ul>
<p>下面是一份可作为起点的 <code>xyz123.c</code>。真实传感器的寄存器可能不同，但结构和 Zephyr 驱动写法基本一致。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="cp">#define DT_DRV_COMPAT demo_xyz123
</span></span></span><span class="line"><span class="cl"><span class="cp"></span>
</span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;zephyr/device.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;zephyr/drivers/i2c.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;zephyr/drivers/sensor.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;zephyr/kernel.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;zephyr/logging/log.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp"></span>
</span></span><span class="line"><span class="cl"><span class="nf">LOG_MODULE_REGISTER</span><span class="p">(</span><span class="n">xyz123</span><span class="p">,</span> <span class="n">CONFIG_SENSOR_LOG_LEVEL</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cp">#define XYZ123_REG_TEMP_MSB   0x00
</span></span></span><span class="line"><span class="cl"><span class="cp">#define XYZ123_REG_HUM_MSB    0x02
</span></span></span><span class="line"><span class="cl"><span class="cp">#define XYZ123_REG_CONFIG     0x10
</span></span></span><span class="line"><span class="cl"><span class="cp">#define XYZ123_CMD_MEASURE    0xA5
</span></span></span><span class="line"><span class="cl"><span class="cp"></span>
</span></span><span class="line"><span class="cl"><span class="k">struct</span> <span class="n">xyz123_config</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">struct</span> <span class="n">i2c_dt_spec</span> <span class="n">bus</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint32_t</span> <span class="n">sample_period_ms</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">struct</span> <span class="n">xyz123_data</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">int32_t</span> <span class="n">temp_milli_c</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">int32_t</span> <span class="n">hum_milli_pct</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">struct</span> <span class="n">k_mutex</span> <span class="n">lock</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">static</span> <span class="kt">int</span> <span class="nf">xyz123_reg_read16</span><span class="p">(</span><span class="k">const</span> <span class="k">struct</span> <span class="n">device</span> <span class="o">*</span><span class="n">dev</span><span class="p">,</span> <span class="kt">uint8_t</span> <span class="n">reg</span><span class="p">,</span> <span class="kt">uint16_t</span> <span class="o">*</span><span class="n">val</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">const</span> <span class="k">struct</span> <span class="n">xyz123_config</span> <span class="o">*</span><span class="n">cfg</span> <span class="o">=</span> <span class="n">dev</span><span class="o">-&gt;</span><span class="n">config</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint8_t</span> <span class="n">buf</span><span class="p">[</span><span class="mi">2</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="kt">int</span> <span class="n">ret</span> <span class="o">=</span> <span class="nf">i2c_burst_read_dt</span><span class="p">(</span><span class="o">&amp;</span><span class="n">cfg</span><span class="o">-&gt;</span><span class="n">bus</span><span class="p">,</span> <span class="n">reg</span><span class="p">,</span> <span class="n">buf</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">buf</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">ret</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nf">LOG_ERR</span><span class="p">(</span><span class="s">&#34;read reg 0x%02x failed: %d&#34;</span><span class="p">,</span> <span class="n">reg</span><span class="p">,</span> <span class="n">ret</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">ret</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="o">*</span><span class="n">val</span> <span class="o">=</span> <span class="p">((</span><span class="kt">uint16_t</span><span class="p">)</span><span class="n">buf</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">&lt;&lt;</span> <span class="mi">8</span><span class="p">)</span> <span class="o">|</span> <span class="n">buf</span><span class="p">[</span><span class="mi">1</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">static</span> <span class="kt">int</span> <span class="nf">xyz123_sample_fetch</span><span class="p">(</span><span class="k">const</span> <span class="k">struct</span> <span class="n">device</span> <span class="o">*</span><span class="n">dev</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                               <span class="k">enum</span> <span class="n">sensor_channel</span> <span class="n">chan</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">const</span> <span class="k">struct</span> <span class="n">xyz123_config</span> <span class="o">*</span><span class="n">cfg</span> <span class="o">=</span> <span class="n">dev</span><span class="o">-&gt;</span><span class="n">config</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">struct</span> <span class="n">xyz123_data</span> <span class="o">*</span><span class="n">data</span> <span class="o">=</span> <span class="n">dev</span><span class="o">-&gt;</span><span class="n">data</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint16_t</span> <span class="n">raw_temp</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint16_t</span> <span class="n">raw_hum</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">int</span> <span class="n">ret</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">chan</span> <span class="o">!=</span> <span class="n">SENSOR_CHAN_ALL</span> <span class="o">&amp;&amp;</span>
</span></span><span class="line"><span class="cl">        <span class="n">chan</span> <span class="o">!=</span> <span class="n">SENSOR_CHAN_AMBIENT_TEMP</span> <span class="o">&amp;&amp;</span>
</span></span><span class="line"><span class="cl">        <span class="n">chan</span> <span class="o">!=</span> <span class="n">SENSOR_CHAN_HUMIDITY</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="o">-</span><span class="n">ENOTSUP</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nf">k_mutex_lock</span><span class="p">(</span><span class="o">&amp;</span><span class="n">data</span><span class="o">-&gt;</span><span class="n">lock</span><span class="p">,</span> <span class="n">K_FOREVER</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">ret</span> <span class="o">=</span> <span class="nf">i2c_reg_write_byte_dt</span><span class="p">(</span><span class="o">&amp;</span><span class="n">cfg</span><span class="o">-&gt;</span><span class="n">bus</span><span class="p">,</span> <span class="n">XYZ123_REG_CONFIG</span><span class="p">,</span> <span class="n">XYZ123_CMD_MEASURE</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">ret</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">goto</span> <span class="n">out</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nf">k_sleep</span><span class="p">(</span><span class="nf">K_MSEC</span><span class="p">(</span><span class="mi">20</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">ret</span> <span class="o">=</span> <span class="nf">xyz123_reg_read16</span><span class="p">(</span><span class="n">dev</span><span class="p">,</span> <span class="n">XYZ123_REG_TEMP_MSB</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">raw_temp</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">ret</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">goto</span> <span class="n">out</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">ret</span> <span class="o">=</span> <span class="nf">xyz123_reg_read16</span><span class="p">(</span><span class="n">dev</span><span class="p">,</span> <span class="n">XYZ123_REG_HUM_MSB</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">raw_hum</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">ret</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">goto</span> <span class="n">out</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">data</span><span class="o">-&gt;</span><span class="n">temp_milli_c</span> <span class="o">=</span> <span class="p">((</span><span class="kt">int32_t</span><span class="p">)</span><span class="n">raw_temp</span> <span class="o">*</span> <span class="mi">165000</span> <span class="o">/</span> <span class="mi">65535</span><span class="p">)</span> <span class="o">-</span> <span class="mi">40000</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">data</span><span class="o">-&gt;</span><span class="n">hum_milli_pct</span> <span class="o">=</span> <span class="p">(</span><span class="kt">int32_t</span><span class="p">)</span><span class="n">raw_hum</span> <span class="o">*</span> <span class="mi">100000</span> <span class="o">/</span> <span class="mi">65535</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nl">out</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="nf">k_mutex_unlock</span><span class="p">(</span><span class="o">&amp;</span><span class="n">data</span><span class="o">-&gt;</span><span class="n">lock</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">ret</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>这里用到了 <code>i2c_dt_spec</code>，它是 Zephyr 很推荐的写法。它把 I2C 控制器设备指针、从机地址等信息打包好，驱动里不需要自己解析 <code>reg</code> 或查找 bus device。</p>
<h2 id="六channel_get-与驱动-api-表">六、channel_get 与驱动 API 表</h2>
<p><code>sensor_sample_fetch()</code> 负责从硬件更新缓存，<code>sensor_channel_get()</code> 负责把缓存值转换成 Zephyr 的 <code>sensor_value</code>。继续补上后半部分：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="k">static</span> <span class="kt">int</span> <span class="nf">xyz123_channel_get</span><span class="p">(</span><span class="k">const</span> <span class="k">struct</span> <span class="n">device</span> <span class="o">*</span><span class="n">dev</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                              <span class="k">enum</span> <span class="n">sensor_channel</span> <span class="n">chan</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                              <span class="k">struct</span> <span class="n">sensor_value</span> <span class="o">*</span><span class="n">val</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">struct</span> <span class="n">xyz123_data</span> <span class="o">*</span><span class="n">data</span> <span class="o">=</span> <span class="n">dev</span><span class="o">-&gt;</span><span class="n">data</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">int32_t</span> <span class="n">milli</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nf">k_mutex_lock</span><span class="p">(</span><span class="o">&amp;</span><span class="n">data</span><span class="o">-&gt;</span><span class="n">lock</span><span class="p">,</span> <span class="n">K_FOREVER</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">switch</span> <span class="p">(</span><span class="n">chan</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">case</span> <span class="nl">SENSOR_CHAN_AMBIENT_TEMP</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">milli</span> <span class="o">=</span> <span class="n">data</span><span class="o">-&gt;</span><span class="n">temp_milli_c</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">break</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">case</span> <span class="nl">SENSOR_CHAN_HUMIDITY</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">milli</span> <span class="o">=</span> <span class="n">data</span><span class="o">-&gt;</span><span class="n">hum_milli_pct</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">break</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">default</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">        <span class="nf">k_mutex_unlock</span><span class="p">(</span><span class="o">&amp;</span><span class="n">data</span><span class="o">-&gt;</span><span class="n">lock</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="o">-</span><span class="n">ENOTSUP</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">val</span><span class="o">-&gt;</span><span class="n">val1</span> <span class="o">=</span> <span class="n">milli</span> <span class="o">/</span> <span class="mi">1000</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">val</span><span class="o">-&gt;</span><span class="n">val2</span> <span class="o">=</span> <span class="p">(</span><span class="n">milli</span> <span class="o">%</span> <span class="mi">1000</span><span class="p">)</span> <span class="o">*</span> <span class="mi">1000</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nf">k_mutex_unlock</span><span class="p">(</span><span class="o">&amp;</span><span class="n">data</span><span class="o">-&gt;</span><span class="n">lock</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">static</span> <span class="kt">int</span> <span class="nf">xyz123_init</span><span class="p">(</span><span class="k">const</span> <span class="k">struct</span> <span class="n">device</span> <span class="o">*</span><span class="n">dev</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">const</span> <span class="k">struct</span> <span class="n">xyz123_config</span> <span class="o">*</span><span class="n">cfg</span> <span class="o">=</span> <span class="n">dev</span><span class="o">-&gt;</span><span class="n">config</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">struct</span> <span class="n">xyz123_data</span> <span class="o">*</span><span class="n">data</span> <span class="o">=</span> <span class="n">dev</span><span class="o">-&gt;</span><span class="n">data</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nf">device_is_ready</span><span class="p">(</span><span class="n">cfg</span><span class="o">-&gt;</span><span class="n">bus</span><span class="p">.</span><span class="n">bus</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nf">LOG_ERR</span><span class="p">(</span><span class="s">&#34;I2C bus is not ready&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="o">-</span><span class="n">ENODEV</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nf">k_mutex_init</span><span class="p">(</span><span class="o">&amp;</span><span class="n">data</span><span class="o">-&gt;</span><span class="n">lock</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nf">LOG_INF</span><span class="p">(</span><span class="s">&#34;XYZ123 ready, period=%u ms&#34;</span><span class="p">,</span> <span class="n">cfg</span><span class="o">-&gt;</span><span class="n">sample_period_ms</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">static</span> <span class="k">const</span> <span class="k">struct</span> <span class="n">sensor_driver_api</span> <span class="n">xyz123_api</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="n">sample_fetch</span> <span class="o">=</span> <span class="n">xyz123_sample_fetch</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="n">channel_get</span> <span class="o">=</span> <span class="n">xyz123_channel_get</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><p>最后是实例化宏：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="cp">#define XYZ123_DEFINE(inst)                                                   \
</span></span></span><span class="line"><span class="cl"><span class="cp">    static struct xyz123_data xyz123_data_##inst;                             \
</span></span></span><span class="line"><span class="cl"><span class="cp">    static const struct xyz123_config xyz123_config_##inst = {                 \
</span></span></span><span class="line"><span class="cl"><span class="cp">        .bus = I2C_DT_SPEC_INST_GET(inst),                                     \
</span></span></span><span class="line"><span class="cl"><span class="cp">        .sample_period_ms = DT_INST_PROP_OR(inst, sample_period_ms, 1000),     \
</span></span></span><span class="line"><span class="cl"><span class="cp">    };                                                                         \
</span></span></span><span class="line"><span class="cl"><span class="cp">    SENSOR_DEVICE_DT_INST_DEFINE(inst,                                         \
</span></span></span><span class="line"><span class="cl"><span class="cp">        xyz123_init,                                                           \
</span></span></span><span class="line"><span class="cl"><span class="cp">        NULL,                                                                  \
</span></span></span><span class="line"><span class="cl"><span class="cp">        &amp;xyz123_data_##inst,                                                   \
</span></span></span><span class="line"><span class="cl"><span class="cp">        &amp;xyz123_config_##inst,                                                 \
</span></span></span><span class="line"><span class="cl"><span class="cp">        POST_KERNEL,                                                           \
</span></span></span><span class="line"><span class="cl"><span class="cp">        CONFIG_XYZ123_INIT_PRIORITY,                                           \
</span></span></span><span class="line"><span class="cl"><span class="cp">        &amp;xyz123_api);
</span></span></span><span class="line"><span class="cl"><span class="cp"></span>
</span></span><span class="line"><span class="cl"><span class="nf">DT_INST_FOREACH_STATUS_OKAY</span><span class="p">(</span><span class="n">XYZ123_DEFINE</span><span class="p">)</span>
</span></span></code></pre></div><p><code>DT_INST_FOREACH_STATUS_OKAY()</code> 会遍历所有 compatible 为 <code>demo,xyz123</code> 且 <code>status = &quot;okay&quot;</code> 的节点，并为每个节点展开一次 <code>XYZ123_DEFINE(inst)</code>。如果板子上有两个 <code>xyz123</code>，就会生成两个 Zephyr device。应用层无需改驱动，只要通过 alias、label 或节点标识拿到对应设备即可。</p>
<p>（第二部分完，约2500字）</p>
<h2 id="七应用层如何优雅地使用驱动">七、应用层如何优雅地使用驱动</h2>
<p>驱动写好后，应用层代码应该保持简洁。推荐在 overlay 中为传感器加一个 alias：</p>
<pre tabindex="0"><code class="language-dts" data-lang="dts">/ {
    aliases {
        env-sensor = &amp;xyz123_sensor;
    };
};

&amp;i2c1 {
    status = &#34;okay&#34;;

    xyz123_sensor: xyz123@44 {
        compatible = &#34;demo,xyz123&#34;;
        reg = &lt;0x44&gt;;
        sample-period-ms = &lt;1000&gt;;
    };
};
</code></pre><p>然后在 <code>main.c</code> 中使用：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;zephyr/kernel.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;zephyr/device.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;zephyr/drivers/sensor.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;zephyr/sys/printk.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp"></span>
</span></span><span class="line"><span class="cl"><span class="cp">#define ENV_SENSOR_NODE DT_ALIAS(env_sensor)
</span></span></span><span class="line"><span class="cl"><span class="cp"></span>
</span></span><span class="line"><span class="cl"><span class="cp">#if !DT_NODE_HAS_STATUS(ENV_SENSOR_NODE, okay)
</span></span></span><span class="line"><span class="cl"><span class="cp">#error &#34;env-sensor alias is not defined or not okay&#34;
</span></span></span><span class="line"><span class="cl"><span class="cp">#endif
</span></span></span><span class="line"><span class="cl"><span class="cp"></span>
</span></span><span class="line"><span class="cl"><span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">const</span> <span class="k">struct</span> <span class="n">device</span> <span class="o">*</span><span class="n">sensor</span> <span class="o">=</span> <span class="nf">DEVICE_DT_GET</span><span class="p">(</span><span class="n">ENV_SENSOR_NODE</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">struct</span> <span class="n">sensor_value</span> <span class="n">temp</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">struct</span> <span class="n">sensor_value</span> <span class="n">hum</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">int</span> <span class="n">ret</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nf">device_is_ready</span><span class="p">(</span><span class="n">sensor</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nf">printk</span><span class="p">(</span><span class="s">&#34;sensor device is not ready</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">while</span> <span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">ret</span> <span class="o">=</span> <span class="nf">sensor_sample_fetch</span><span class="p">(</span><span class="n">sensor</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">ret</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nf">printk</span><span class="p">(</span><span class="s">&#34;sample fetch failed: %d</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">,</span> <span class="n">ret</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="nf">k_sleep</span><span class="p">(</span><span class="nf">K_SECONDS</span><span class="p">(</span><span class="mi">1</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">            <span class="k">continue</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="nf">sensor_channel_get</span><span class="p">(</span><span class="n">sensor</span><span class="p">,</span> <span class="n">SENSOR_CHAN_AMBIENT_TEMP</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">temp</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nf">sensor_channel_get</span><span class="p">(</span><span class="n">sensor</span><span class="p">,</span> <span class="n">SENSOR_CHAN_HUMIDITY</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">hum</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="nf">printk</span><span class="p">(</span><span class="s">&#34;T=%d.%06d C, RH=%d.%06d %%</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">               <span class="n">temp</span><span class="p">.</span><span class="n">val1</span><span class="p">,</span> <span class="n">temp</span><span class="p">.</span><span class="n">val2</span><span class="p">,</span> <span class="n">hum</span><span class="p">.</span><span class="n">val1</span><span class="p">,</span> <span class="n">hum</span><span class="p">.</span><span class="n">val2</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="nf">k_sleep</span><span class="p">(</span><span class="nf">K_SECONDS</span><span class="p">(</span><span class="mi">1</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>这段代码有两个值得保留的习惯。第一，使用 <code>DT_ALIAS()</code> 让应用不依赖具体节点路径；第二，用 <code>DT_NODE_HAS_STATUS()</code> 在编译期检查 alias 是否存在。很多板级移植问题，越早在编译期发现越好。</p>
<h2 id="八构建查看生成文件与定位-devicetree-问题">八、构建、查看生成文件与定位 Devicetree 问题</h2>
<p>假设目标板是 <code>nucleo_f446re</code>，可以这样构建：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">west build -b nucleo_f446re -p always .
</span></span></code></pre></div><p>如果 Devicetree 或 binding 有问题，先不要急着改 C 代码。建议按下面顺序排查：</p>
<ol>
<li>查看最终合并后的 Devicetree：<code>build/zephyr/zephyr.dts</code>。</li>
<li>查看生成的宏：<code>build/zephyr/include/generated/zephyr/devicetree_generated.h</code>。</li>
<li>确认节点是否存在、状态是否为 <code>okay</code>、compatible 是否正确。</li>
<li>确认 binding 是否被加载，属性名是否从短横线转换成下划线。</li>
<li>确认 Kconfig 中 <code>CONFIG_XYZ123=y</code> 是否真的生效，可查看 <code>build/zephyr/.config</code>。</li>
</ol>
<p>举个例子，如果你在驱动里写了：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="cp">#define DT_DRV_COMPAT demo_xyz123
</span></span></span></code></pre></div><p>但 overlay 中写成：</p>
<pre tabindex="0"><code class="language-dts" data-lang="dts">compatible = &#34;demo,xyz-123&#34;;
</code></pre><p>那么 <code>DT_INST_FOREACH_STATUS_OKAY(XYZ123_DEFINE)</code> 不会展开任何实例，驱动编译了也不会生成设备。这个问题运行时很难看出来，但在 <code>zephyr.dts</code> 和 generated header 中一查就很明显。</p>
<h2 id="九把轮询模式升级为中断触发模式">九、把轮询模式升级为中断触发模式</h2>
<p>工业现场常见的传感器并不适合盲目轮询：有些数据更新慢，轮询浪费功耗；有些数据到达时间不固定，轮询会增加延迟。Zephyr 的 sensor API 支持 trigger 模式，可以让驱动在数据就绪中断到来时回调应用。</p>
<p>实现 trigger 时，建议分三层处理：</p>
<ul>
<li>GPIO 中断回调里只做最小工作，例如提交 <code>k_work</code>。</li>
<li><code>k_work</code> 中读取状态寄存器，确认确实是 data-ready。</li>
<li>再调用用户注册的 <code>sensor_trigger_handler_t</code>。</li>
</ul>
<p>伪代码如下：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="cp">#ifdef CONFIG_XYZ123_TRIGGER
</span></span></span><span class="line"><span class="cl"><span class="cp"></span><span class="k">struct</span> <span class="n">xyz123_data</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">int32_t</span> <span class="n">temp_milli_c</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">int32_t</span> <span class="n">hum_milli_pct</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">struct</span> <span class="n">k_mutex</span> <span class="n">lock</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">struct</span> <span class="n">gpio_callback</span> <span class="n">gpio_cb</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">struct</span> <span class="n">k_work</span> <span class="n">work</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">sensor_trigger_handler_t</span> <span class="n">handler</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">const</span> <span class="k">struct</span> <span class="n">sensor_trigger</span> <span class="o">*</span><span class="n">trigger</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">static</span> <span class="kt">void</span> <span class="nf">xyz123_gpio_callback</span><span class="p">(</span><span class="k">const</span> <span class="k">struct</span> <span class="n">device</span> <span class="o">*</span><span class="n">port</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                                 <span class="k">struct</span> <span class="n">gpio_callback</span> <span class="o">*</span><span class="n">cb</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                                 <span class="kt">uint32_t</span> <span class="n">pins</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">struct</span> <span class="n">xyz123_data</span> <span class="o">*</span><span class="n">data</span> <span class="o">=</span> <span class="nf">CONTAINER_OF</span><span class="p">(</span><span class="n">cb</span><span class="p">,</span> <span class="k">struct</span> <span class="n">xyz123_data</span><span class="p">,</span> <span class="n">gpio_cb</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nf">k_work_submit</span><span class="p">(</span><span class="o">&amp;</span><span class="n">data</span><span class="o">-&gt;</span><span class="n">work</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="cp">#endif
</span></span></span></code></pre></div><p>实际实现时，还要把 <code>int-gpios</code> 加入 config，并在 init 中配置 GPIO 输入和中断边沿。这里最容易犯的错是直接在 GPIO ISR 里读 I2C。大多数 I2C API 不是 ISR-safe，ISR 里做总线访问也会拖长中断关闭时间。用 workqueue 把慢操作挪到线程上下文，是更稳妥的写法。</p>
<h2 id="十可复用驱动的几个工程化细节">十、可复用驱动的几个工程化细节</h2>
<p>驱动能跑只是第一步，要在多个项目里复用，还需要注意一些“看似小、但会影响维护”的细节。</p>
<h3 id="1-不要在驱动里写板级策略">1. 不要在驱动里写板级策略</h3>
<p>驱动应该负责“如何和芯片通信”，不应该决定“产品多久采一次样、异常时重启几次、数据上传到哪里”。这些策略放在应用层或服务层。驱动最多提供配置属性和 API。</p>
<h3 id="2-错误码要保持-zephyr-风格">2. 错误码要保持 Zephyr 风格</h3>
<p>I2C 失败返回底层错误码，参数不支持返回 <code>-ENOTSUP</code>，设备不存在返回 <code>-ENODEV</code>，参数非法返回 <code>-EINVAL</code>。统一错误码后，应用层才能做通用处理。</p>
<h3 id="3-日志不要过度">3. 日志不要过度</h3>
<p>驱动 init 失败、总线读写失败可以打 error；每次采样成功不建议打 info，否则量产设备日志会被刷爆。必要时用 debug 等级，并让 Kconfig 控制日志级别。</p>
<h3 id="4-兼容字符串一旦发布就别随意改">4. 兼容字符串一旦发布就别随意改</h3>
<p><code>compatible</code> 相当于驱动和硬件描述之间的 ABI。公司内部项目也一样，改名会导致旧 overlay 失效。需要支持新版本芯片时，可以新增 compatible，例如 <code>demo,xyz123b</code>，而不是直接替换旧名称。</p>
<h2 id="十一常见问题清单">十一、常见问题清单</h2>
<p><strong>1. <code>DEVICE_DT_GET()</code> 能编译但运行时报 device not ready</strong></p>
<p>优先检查父总线是否 ready，例如 I2C 控制器的 <code>status</code> 是否为 <code>okay</code>，时钟和 pinctrl 是否配置正确。传感器设备 ready 依赖总线 ready。</p>
<p><strong>2. <code>DT_INST_PROP()</code> 报宏不存在</strong></p>
<p>通常是 binding 没加载、compatible 不匹配、属性名写错，或属性没有定义在 YAML 中。先看 <code>zephyr.dts</code>，再看 generated header。</p>
<p><strong>3. Kconfig 里已经 default y，为什么 CONFIG_XYZ123 还是没打开</strong></p>
<p>检查 <code>depends on DT_HAS_DEMO_XYZ123_ENABLED</code> 是否为真。如果 Devicetree 没有 enabled 节点，default y 也不会生效。</p>
<p><strong>4. 多实例驱动只有一个设备出现</strong></p>
<p>确认每个节点都有唯一 unit address，例如 <code>xyz123@44</code>、<code>xyz123@45</code>，并且 <code>reg</code> 地址不同。I2C 子节点的 unit address 应该与 <code>reg</code> 对应。</p>
<p><strong>5. overlay 文件没有生效</strong></p>
<p>板级 overlay 默认路径通常是 <code>boards/&lt;board&gt;.overlay</code>。也可以构建时显式指定：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">west build -b nucleo_f446re -- -DDTC_OVERLAY_FILE<span class="o">=</span>boards/nucleo_f446re.overlay
</span></span></code></pre></div><h2 id="十二调试建议先让链路透明再追性能">十二、调试建议：先让链路透明，再追性能</h2>
<p>很多 Zephyr 驱动问题不是算法问题，而是“我不知道构建系统最后生成了什么”。所以调试时要养成看中间产物的习惯。确认 Devicetree 合并结果，确认 <code>.config</code>，确认驱动源文件是否被编译，确认日志模块是否打开。等这些基础链路透明后，再去优化采样频率、功耗和总线吞吐。</p>
<p>对于 I2C 传感器，我通常会按下面顺序做 bring-up：</p>
<ol>
<li>用示波器或逻辑分析仪确认 SCL/SDA 有波形。</li>
<li>用 Zephyr 的 I2C scan sample 确认从机地址存在。</li>
<li>在驱动 init 中读取芯片 ID 寄存器。</li>
<li>再实现 sample_fetch 和 channel_get。</li>
<li>最后才加 trigger、低功耗和校准逻辑。</li>
</ol>
<p>这个顺序看起来慢，实际上最省时间。因为它把“硬件焊接问题”“Devicetree 配置问题”“驱动协议问题”“应用使用问题”分开了。</p>
<h2 id="十三从样机驱动走向量产代码">十三、从样机驱动走向量产代码</h2>
<p>样机阶段的 Zephyr 驱动往往只关注“能不能读到数据”，量产阶段则要多考虑几个维度：异常恢复、功耗状态、版本兼容和可测试性。以 I2C 传感器为例，现场环境中可能出现总线被拉低、传感器短暂掉电、热插拔连接器接触不良、外部干扰导致 CRC 错误等情况。如果驱动只在初始化阶段检查一次芯片 ID，后续读写失败就直接返回错误，应用层会很难判断是偶发总线错误还是设备永久失效。</p>
<p>比较稳妥的做法是给驱动内部增加有限状态，而不是把所有复杂性都暴露给应用。比如连续 3 次读写失败后，把设备标记为 <code>SUSPICIOUS</code>，下一次采样前先尝试软复位；软复位失败再返回 <code>-EIO</code> 或 <code>-ENODEV</code>。应用层仍然只调用 <code>sensor_sample_fetch()</code>，但可以根据错误码决定是否上报健康状态、降低采样频率或触发系统级恢复。注意这个状态机不要写得过重，驱动不是业务流程引擎，它只需要提供足够可靠的硬件访问边界。</p>
<p>功耗也是量产代码必须补上的部分。Zephyr 的设备电源管理可以让驱动实现 suspend / resume，在系统进入低功耗前关闭传感器测量，在唤醒后重新配置寄存器。对于电池供电设备，传感器本身的待机电流、I2C 上拉电阻、GPIO 中断唤醒方式都会影响整机续航。很多项目一开始只测 MCU 的 sleep 电流，忽略外设持续工作，最后发现整板电流比预期高一个数量级。驱动如果把 power mode 做成 Kconfig 或 Devicetree 属性，后续不同产品线复用时会方便很多。</p>
<p>还有一个容易被忽略的点是芯片批次和寄存器版本。真实供应链中，同一个型号可能有 A 版、B 版，甚至兼容厂商的替代料。建议驱动 init 阶段读取 chip id 和 revision，把关键信息打到 debug 日志里，并为已知差异留出表驱动配置。例如某个 revision 的湿度补偿公式不同，最好在驱动内部用 <code>quirks</code> 标志处理，而不是让应用层到处写 if。</p>
<h2 id="十四如何为-zephyr-驱动写测试">十四、如何为 Zephyr 驱动写测试</h2>
<p>驱动测试不能只依赖真板子。真板子当然必须测，但它不适合覆盖所有异常分支，也不适合在 CI 中频繁运行。Zephyr 自带的 <code>ztest</code> 和 native 模拟目标可以帮助我们把一部分逻辑拆出来做单元测试。对于 <code>xyz123</code> 这类传感器驱动，寄存器换算、错误码处理、状态机迁移、配置参数边界，都可以抽成纯函数或轻量 mock。</p>
<p>一种实用策略是把“协议解析”和“总线访问”分层。总线访问函数只负责 <code>i2c_burst_read_dt()</code>、<code>i2c_reg_write_byte_dt()</code> 这些硬件动作；协议层函数负责把 raw data 转换成毫摄氏度、毫百分比，负责检查状态位和 CRC。这样即使没有 I2C mock，也能对协议层做大量测试。比如温度原始值为 <code>0</code> 时应接近 <code>-40.000 C</code>，原始值为 <code>65535</code> 时应接近 <code>125.000 C</code>；湿度原始值超出合理范围时是否钳位，也可以明确写成测试用例。</p>
<p>CI 中可以至少跑三类检查。第一类是格式和静态检查，确保驱动代码符合项目风格；第二类是 Kconfig 与 Devicetree 构建矩阵，例如一个 overlay 开启中断、一个 overlay 关闭中断、一个 overlay 使用第二个 I2C 实例；第三类是 native 单元测试，覆盖换算、状态机和错误分支。这样做的收益在硬件改版时非常明显：当 overlay、binding 或 Kconfig 被别人改动，CI 能尽早告诉你哪个组合坏了。</p>
<p>真板测试则更关注时序和电气边界。建议保留一份 bring-up checklist：逻辑分析仪抓取 I2C 地址和 ACK，冷启动连续采样 24 小时，热插拔或模拟掉电恢复，低温高温环境下读取 chip id 和数据稳定性，中断触发下是否丢事件。驱动文档里把这些测试结果记录下来，比只留一段“已验证”更有价值。</p>
<h2 id="十五移植到公司内部平台时的落地建议">十五、移植到公司内部平台时的落地建议</h2>
<p>很多团队引入 Zephyr 不是从零开始，而是已有一套历史 BSP、HAL 和应用框架。迁移时不要试图一口气把所有驱动都改成 Zephyr 风格，最好选择一个边界清晰的外设作为试点，比如温湿度传感器、GPIO 扩展器或简单 ADC。试点目标不是证明 Zephyr 能跑，而是沉淀一套团队可复制的模板：目录结构、命名规范、binding 写法、日志级别、错误码约定、测试方式和 code review 清单。</p>
<p>命名规范建议尽早统一。compatible 可以采用公司域名前缀或产品前缀，例如 <code>acme,xyz123</code>；Kconfig 选项保持大写芯片名；驱动文件名和日志模块名保持小写。属性名尽量使用 Zephyr 社区常见表达，例如 <code>int-gpios</code>、<code>reset-gpios</code>、<code>supply-gpios</code>、<code>sample-period-ms</code>，不要每个团队自造一套叫法。统一命名会直接降低后续维护和搜索成本。</p>
<p>代码评审时，可以专门检查几个问题：C 代码里是否写死了 I2C 地址或 GPIO 编号；驱动是否支持多实例；binding 是否声明了所有自定义属性；Kconfig 是否错误地强制选择了不必要组件；应用层是否绕过 sensor API 直接访问驱动私有结构；错误路径是否释放锁；ISR 中是否调用了可能睡眠的 API。这些检查点比单纯看代码是否“能编译”更接近工程质量。</p>
<p>最后，文档要和模板一起交付。Zephyr 的学习曲线主要来自工具链和生成文件，如果团队里只有一两个人知道怎么查 <code>zephyr.dts</code> 和 <code>.config</code>，项目风险会很高。建议在仓库中放一份 <code>docs/driver-porting.md</code>，写清楚新增一个传感器需要改哪些文件、构建命令是什么、常见报错怎么查。长期看，这份文档会比一次性的口头培训更可靠。</p>
<h2 id="总结">总结</h2>
<p>Zephyr 的 Devicetree、Kconfig 和驱动模型一开始确实比裸机宏定义复杂，但它解决的是产品化阶段绕不开的问题：硬件差异如何管理，功能如何裁剪，驱动如何多实例复用，应用如何摆脱板级细节。掌握它的关键不是背宏，而是理解职责边界：Devicetree 描述硬件，Kconfig 描述软件选项，驱动用实例化宏把两者连接起来，应用层只面对标准 API。</p>
<p>本文用 <code>xyz123</code> I2C 传感器走了一遍完整链路：overlay、binding、Kconfig、CMake、驱动实现、应用调用、构建调试和工程化建议。把这套流程跑通后，再写 SPI Flash、GPIO 扩展器、ADC 采样芯片或自定义工业传感器，本质上都是同一套方法。真正值得长期保留的经验是：不要把板级信息写死在 C 代码里，不要跳过生成文件排查，不要在驱动里塞产品策略。这样写出来的 Zephyr 驱动，才有机会从一次性样机代码变成可复用的工程资产。</p>
<p>（全文完，约8800字）</p>
]]></content:encoded>
    </item>
  </channel>
</rss>
