第一章:Zephyr与驱动开发概述

大家好,我是你们的讲师。今天咱们聊聊Zephyr RTOS和驱动开发的那些事儿。

说实话,我刚开始接触嵌入式实时系统时,也踩过不少坑。Zephyr这个系统,我用了快五年了。它给我的感觉就是——灵活,但学习曲线有点陡。不过别担心,这一章我会把最核心的东西掰开揉碎了讲给你听。

1.1 Zephyr RTOS简介

Zephyr是个什么玩意儿?说白了,它是一个轻量级的实时操作系统。专门为资源受限的嵌入式设备设计的。我最早接触它是在一个智能穿戴项目上,当时需要在只有几百KB Flash的芯片上跑蓝牙协议栈。FreeRTOS太简陋,Linux又太重。Zephyr刚好卡在中间——既有RTOS的实时性,又有Linux那样的驱动框架。

它的几个核心特点,我列一下:

  • 模块化内核:你可以按需裁剪。不需要的模块直接砍掉,省资源。
  • 丰富的协议栈:蓝牙、Wi-Fi、Thread、Zigbee,基本你能想到的无线协议它都支持。
  • 跨平台支持:ARM、RISC-V、x86、Xtensa,主流架构全覆盖。
  • 设备树驱动模型:这个咱们后面重点讲,它是Zephyr的灵魂。

我记得有一次,一个同事问我:「Zephyr和FreeRTOS到底选哪个?」我的回答是:如果你只需要跑几个任务、用个UART,FreeRTOS就够了。但如果你要做复杂的外设驱动开发,或者需要蓝牙协议栈,Zephyr是更好的选择。它把驱动和硬件描述分开了,维护起来舒服很多。

1.2 外设驱动框架

Zephyr的驱动框架,我称之为「三层结构」:

  1. API层:给应用开发者用的。比如 spi_transceive()、i2c_write()。你不需要关心底层怎么实现。
  2. 驱动层:每个外设的具体实现。比如 stm32_spi.c、nrfx_i2c.c。这部分是芯片厂商或社区写的。
  3. 设备树层:描述硬件连接关系。比如「SPI1挂在了GPIOB的CS引脚上」。这部分是开发者自己配的。

为什么要这么分层?你想想看,如果我要把一块SPI Flash从STM32移植到NXP芯片上,传统做法是重写整个驱动。但在Zephyr里,我只需要改设备树文件,驱动代码几乎不用动。这就是框架的魅力。

核心思想:驱动代码与硬件描述分离。驱动只关心「怎么操作寄存器」,设备树告诉驱动「操作哪个寄存器」。这样代码复用率极高。

我见过很多初学者,一上来就扎进驱动源码里看。结果看了三天,越看越晕。我的建议是:先理解框架,再读代码。框架就像地图,代码就像路上的风景。没有地图,你很容易迷路。

1.3 设备树(DTS)基础

设备树,英文叫Device Tree。很多从Linux转过来的朋友应该不陌生。Zephyr的设备树语法和Linux基本一致,但更精简。

一个典型的设备树节点长这样:

/ {
    soc {
        spi1: spi@40013000 {
            compatible = "st,stm32-spi";
            reg = <0x40013000 0x400>;
            clocks = <&rcc STM32_CLOCK_BUS_APB2 0x00001000>;
            #address-cells = <1>;
            #size-cells = <0>;
            status = "disabled";
        };
    };
};

这里我解释几个关键字段:

字段 含义 我的经验
compatible 驱动匹配字符串 驱动里用DEVICE_DT_DEFINE宏注册时,必须和这个字符串一致。我吃过这个亏,拼写错了查了两天。
reg 寄存器基地址和大小 注意大小端问题。有些芯片的寄存器地址是64位的,别写错了。
status 设备状态 默认是disabled,需要你在板级文件里改成okay才能用。

小技巧:调试设备树时,可以用 west build -t menuconfig 查看最终生成的设备树。Zephyr会把所有.dts文件合并成一个,你可以在 build/zephyr/zephyr.dts 里看到最终结果。

设备树里还有一个概念叫「绑定」(binding)。它定义了设备树节点的属性类型。比如某个属性是整型还是字符串。Zephyr在编译时会检查绑定文件,确保你的设备树写对了。我曾经在一个项目里把时钟频率的单位写错了,结果SPI通信速率不对。后来查了绑定文件才发现,人家要求的是kHz,我写成了Hz。

1.4 驱动开发环境搭建

环境搭建这块,我建议你按这个步骤来:

  1. 安装依赖:Python 3.8+、CMake、Ninja、Git。这些是基础。
  2. 获取Zephyr源码:用west工具。west init + west update,搞定。
  3. 安装工具链:Zephyr SDK或者你芯片厂商的GCC。我个人推荐Zephyr SDK,省心。
  4. 配置环境变量:ZEPHYR_BASE要指向你的Zephyr源码目录。

具体命令我就不贴了,官方文档写得很清楚。我重点说说容易踩的坑:

避坑指南:我曾经在Windows上用WSL搭建环境,结果USB设备透传搞了半天。后来干脆换成了Ubuntu虚拟机,省事多了。如果你用Windows,建议直接用Windows版Zephyr SDK,别折腾WSL。

还有一个常见问题:编译时提示找不到头文件。这通常是因为你的应用目录结构不对。Zephyr要求应用目录下必须有CMakeLists.txt和prj.conf。prj.conf里至少要配置一个外设,比如:

CONFIG_SPI=y
CONFIG_I2C=y
CONFIG_UART_INTERRUPT_DRIVEN=y

这些配置项决定了哪些驱动会被编译进去。我刚开始学的时候,经常忘了配prj.conf,结果编译出来的固件啥外设都用不了。

最后,我建议你准备一块开发板。STM32F4系列或者nRF52840都行。别用模拟器,模拟器跑不出真实的外设行为。我见过太多人在模拟器上跑通了,一上板子就翻车。

好了,第一章就到这里。下一章咱们开始写第一个SPI驱动。到时候我会手把手带你从设备树配置到驱动代码实现,走一遍完整的流程。