第一章:Zephyr与设备树概述

Zephyr RTOS简介

先聊聊Zephyr RTOS吧。说实话,我第一次接触Zephyr是在一个物联网项目里,当时被它「麻雀虽小五脏俱全」的特性吸引了。

Zephyr是一个面向资源受限设备的实时操作系统。它由Linux基金会维护,开源、免费。我个人的感觉是,它特别适合那些需要「小而精」的嵌入式场景——比如智能传感器、可穿戴设备、工业控制器这些。

它的核心特点我列一下:

  • 模块化设计:你可以按需裁剪,不需要的功能就不编译进去
  • 丰富的驱动支持:从GPIO到蓝牙、WiFi,覆盖面很广
  • 安全性:内置了安全启动、加密等机制
  • 跨平台:支持ARM、RISC-V、x86等多种架构

我在项目中遇到过一个问题:用FreeRTOS时,每次换硬件平台都要重写底层驱动,特别痛苦。Zephyr的设备树机制帮我解决了这个痛点——后面我会详细讲。

小提示:Zephyr的版本迭代很快,建议用最新的LTS版本做项目,稳定性和社区支持都更好。

设备树(DTS)的起源与作用

设备树这个概念,最早是从Linux内核那边来的。你想想看,以前嵌入式开发是怎么描述硬件的?

传统做法是:在代码里硬编码各种寄存器地址、中断号、时钟频率。比如这样:

/* 传统方式:硬编码硬件信息 */
#define UART0_BASE 0x40001000
#define UART0_IRQ  33
#define UART0_CLK  16000000

这种做法有什么问题?我举个例子。有一次我接手一个项目,硬件从STM32F4换成了STM32F7。好家伙,所有外设地址全变了。我得一个个去翻数据手册,改代码里的宏定义。改完还担心漏掉哪个。

设备树就是来解决这个问题的。它用一套结构化的文本描述硬件,把「硬件是什么」和「软件怎么用」分离开。

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

/ {
    uart0: serial@40001000 {
        compatible = "st,stm32-uart";
        reg = <0x40001000 0x400>;
        interrupts = <33 0>;
        clocks = <&clk_hse 16000000>;
        status = "okay";
    };
};

你看,地址、中断、时钟都写在这里了。换硬件时,只需要改这个文件,驱动代码基本不用动。

核心思想:设备树把硬件描述从C代码中剥离出来,变成独立的数据结构。驱动通过API读取这些数据,而不是直接访问硬编码的地址。

为什么Zephyr选择设备树

这个问题我琢磨过很久。Zephyr团队为什么不用其他方案,偏偏选了设备树?

原因其实很实在:

  1. 复用Linux生态:Linux内核已经用设备树很多年了,积累了大量的硬件描述文件。Zephyr可以直接借鉴,省了很多事。
  2. 跨平台统一:Zephyr支持几十种开发板。没有设备树的话,每个板子都得写一套独立的配置代码,维护成本太高了。
  3. 编译期静态配置:Zephyr是静态编译的,不像Linux那样动态加载。设备树在编译时就被解析成C结构体,运行时没有额外开销。

我记得有一次,客户要求把产品从NXP的i.MX RT系列换成Nordic的nRF系列。要是以前,这得重写大半驱动。但用Zephyr+设备树,我只需要换一个设备树文件,再调整一下Kconfig配置,两天就搞定了。

注意:设备树虽然好用,但学习曲线有点陡。尤其是刚开始接触时,那些#address-cells#size-cellsphandle这些概念,容易让人头晕。别急,后面几章我会手把手带你过一遍。

设备树在Zephyr中的工作流程

简单说说设备树在Zephyr里是怎么跑起来的:

步骤 做什么 生成什么
1. 编写DTS文件 描述硬件:外设地址、中断、时钟等 .dts / .dtsi 文件
2. 编译时解析 dtc工具将DTS编译为DTB二进制 .dtb 文件
3. 生成头文件 Zephyr脚本从DTB生成C宏定义 devicetree.h
4. 驱动使用 驱动代码通过宏访问硬件信息 编译进固件

说白了,设备树就是硬件和驱动之间的「翻译官」。你告诉它硬件长什么样,它帮你生成驱动需要的配置数据。

我的习惯:每次新建项目,我第一件事就是打开boards/目录下的DTS文件,看看这个板子有哪些外设、地址怎么分配的。这比看原理图还快。

避坑指南

最后分享几个我踩过的坑:

  • 兼容字符串别写错compatible = "st,stm32-uart"compatible = "st,stm32-uart1" 是两回事。驱动匹配不上时,先检查这个。
  • status属性别漏了:默认是disabled,你得显式写成okay才能用。我曾经因为这个排查了半天。
  • 地址和大小要对应reg = <0x40001000 0x400> 表示起始地址0x40001000,长度0x400字节。写错了驱动可能访问到错误的内存区域。

嗯,第一章就到这里。设备树的概念先有个印象,后面我们会一步步深入。下一章我会带你实际动手,写一个完整的设备树文件,并把它跑在开发板上。