跳到主要内容

第1章 从 example-application 认识 Zephyr 工程

准备阶段已经在 VS Code 中打开 RA6M5 工程,并确认了开发工具和串口连接。接下来要确定应用怎样进入 Zephyr 构建:源文件放在哪里,驱动在哪里启用,板卡引脚又从哪里取得?

Zephyr 官方的 example-application 把应用、板卡、自定义驱动和 Binding 放在同一个工程中,并提供接入 Zephyr 的构建入口。沿着这份参考结构,可以把应用源码、软件配置和硬件描述对应到当前 RA6M5 工程。

应用怎样使用 Zephyr 和硬件​

以板载 LED 为例,应用调用 GPIO API 控制亮灭,GPIO 驱动负责操作控制器,设备树保存连接关系。构建系统再将应用、选中的驱动与硬件描述组合成固件。

内容负责什么LED 程序中的例子
应用决定程序执行的功能和顺序打印启动信息,每隔一段时间翻转 LED
Zephyr 内核提供线程运行、等待和调度等机制k_sleep() 让当前线程等待
驱动实现对应硬件的操作,并提供设备接口GPIO 驱动配置和切换输出电平
设备树(Devicetree)描述设备、连接与参数LED 所属控制器、引脚编号和有效电平
Binding规定一类设备节点的属性含义与要求规定 gpio-leds 子节点使用 gpios 描述连接

观察下图的上下两部分:电脑构建时,软件配置确定启用哪些功能,设备树与 Binding 转成代码可用的硬件参数,再结合选中的应用、内核和驱动源码生成固件。烧录后,开发板 CPU 执行这些代码,应用通过 GPIO API 调用驱动,由驱动操作 GPIO 控制器。

构建期的文件组合与运行期的驱动调用

图 1:LED 程序的构建输入与运行调用。上半部分表示配置、硬件描述和源码怎样参与构建;下半部分区分 CPU 执行的代码与实际硬件,P400 的输出电平控制 D12。

这份分工决定了修改位置:闪烁间隔写在应用中,LED 接线写在设备树中,是否启用 GPIO 功能由配置决定。图中的“设备树生成头文件”保存构建过程生成的宏,代码通过这些宏取得连接信息;运行时使用的是编译进固件的参数和驱动,不会重新读取 DTS 或 Binding。后续编写 main.c 时会用到这条关系。

example-application 提供的参考结构​

官方仓库演示了工作区应用、自定义板卡、树外驱动、Binding 和 Zephyr 模块。与当前课程有关的目录如下:

example-application/
├─ app/ # 可以独立构建的应用
├─ boards/ # 工程提供的板卡描述
├─ drivers/ # 工程提供的驱动
├─ dts/bindings/ # 工程提供的设备树属性规则
├─ include/app/ # 应用或驱动共用的头文件
├─ lib/ # 可复用的库代码
├─ zephyr/module.yml # 声明工程怎样接入 Zephyr 构建
├─ CMakeLists.txt # 共享代码的编译入口
├─ Kconfig # 共享代码的配置入口
└─ west.yml # 工作区依赖清单

应用和扩展代码放在 Zephyr 源码仓库之外,因此常称为“树外”应用或驱动。它们仍然使用 Zephyr 的 API、配置和构建机制。官方的 Application Development:Using a Reference Workspace Application 将这个仓库作为工作区应用的参考。

example-application 是一种组织参考,不要求所有工程都只能有一个 app/。RA6M5 工程把应用放在 apps/<应用名>/ 中,让多个示例各有自己的配置和源文件,并共用同一套板卡与驱动。

在 RA6M5 工程中找到对应目录​

在 VS Code 中打开 RA6M5/ 根目录。这里的根目录是同时包含 west.yml、apps/ 和 scripts/ 的目录。后续命令均从这里执行。

RA6M5/
├─ .west/config
├─ west.yml
├─ apps/ # 各应用独立构建
├─ boards/dshan/dshan_ra6m5/ # 本板的硬件描述与默认配置
├─ drivers/ # 工程内的驱动实现
├─ dts/bindings/ # 工程内的 Binding
├─ include/ # 公共头文件
├─ lib/ # 公共库代码
├─ CMakeLists.txt # 工程模块的编译入口
├─ Kconfig # 工程模块的配置入口
├─ zephyr/ # Zephyr 源码
│ └─ module.yml # 当前工程的模块描述文件
├─ modules/ # west 管理的外部依赖
├─ sdk_env/ # 工程配套工具
├─ scripts/dev.ps1 # 工程提供的开发辅助脚本
└─ build/ # 生成的构建结果

应用建立后,apps/ 与 build/ 中相同的应用名表示来源和结果。例如下一章创建的 apps/board_bringup/ 是应用输入,编译后生成的 build/board_bringup/ 是它的构建输出。修改源文件应进入 apps/;查看最终设备树、配置和固件应进入 build/。

不要仅凭文件名判断作用。当前工程有两类 CMakeLists.txt:根目录的文件组织共享代码,应用目录的文件决定本次要构建哪个应用。它们在同一次构建中都可能被读取,但负责的内容不同。

west.yml 确定工作区使用哪些源码​

west 是工作区管理和开发命令工具。先查看它在当前工程中使用的清单位置:

Get-Content .\.west\config

关注其中的两项:

[manifest]
path = .
file = west.yml

path = . 指向工作区根目录,file = west.yml 指定清单文件。因此应阅读根目录的 west.yml。其中 Zephyr 项目的关键字段为:

remotes:
- name: zephyrproject-rtos
url-base: https://github.com/zephyrproject-rtos

projects:
- name: zephyr
remote: zephyrproject-rtos
revision: v4.4.2

这是清单的节选。remote 与 name 对应到 Zephyr 官方仓库,revision 指定本工程清单选择的版本;后面的 import 再从 Zephyr 清单引入需要的模块。课程命令使用已经准备好的这套工作区,不需要为每个应用重新下载 Zephyr。

清单解决的是源码仓库及其版本问题。LED 连接到哪个引脚、应用要编译哪个 main.c,分别由板卡描述和应用构建文件决定,不写入 west.yml。

module.yml 把共享代码接入构建​

工程根目录下有 drivers/,并不意味着这个目录中的所有源文件都会自动编译。Zephyr 需要先知道当前工程提供了哪些构建入口,再根据配置选择具体源码。

打开 zephyr/module.yml。这里的路径是 Zephyr 约定的模块描述文件位置;文件里的 . 相对于当前模块的根目录,即 RA6M5/。关键内容为:

build:
kconfig: Kconfig
cmake: .
settings:
board_root: .
dts_root: .
soc_root: .

这些字段把当前工程接入 Zephyr 的构建搜索范围。kconfig 指向根目录的配置入口,cmake 指向包含根 CMakeLists.txt 的目录;board_root、dts_root 和 soc_root 分别补充 Board、设备树与 SoC 文件的查找位置。Zephyr 的 Modules:Module yaml file description 说明了这些模块字段。

继续打开根目录的 CMakeLists.txt,可以看到共享内容怎样进入编译:

zephyr_include_directories(include)

add_subdirectory(drivers)
add_subdirectory(lib)

第一行向构建系统提供公共头文件目录,后两行进入驱动与库的构建入口。根目录 Kconfig 则继续读取配置定义:

rsource "drivers/Kconfig"
rsource "lib/Kconfig"

Kconfig 定义可选功能及依赖,CMake 按配置选择源文件。应用通过自己的 prj.conf 选择共享驱动;驱动实现仍由工程模块的构建规则管理。第4、5章会沿着这两个入口接入新的驱动。

一个应用需要哪些文件​

将注意力放到 apps/<应用名>/。应用至少要给构建系统三个输入:编译入口、软件配置和程序源文件。

文件谁读取它当前应用要表达的内容
CMakeLists.txtCMake,在构建配置阶段读取接入 Zephyr,声明要编译的源文件
prj.confKconfig 配置过程读取为本应用选择功能,例如启用 printk 输出
src/main.cC 编译器编译,固件启动后由应用线程执行 main()打印内容、调用设备 API、安排运行顺序
app.overlay 或应用下的板卡 overlay设备树处理过程读取,应用需要时添加为该应用补充或调整硬件描述

prj.conf 使用 CONFIG_选项名=值 为本次构建选择已有选项,target_sources(app PRIVATE src/main.c) 则把源文件加入应用目标。这两个输入分别回答“开启哪些功能”和“编译哪些代码”。修改配置后,是否生效应查看生成的 .config;修改源码列表后,可查看 compile_commands.json 中是否出现对应文件。

板载 LED 已经在 boards/dshan/dshan_ra6m5/dshan_ra6m5.dts 中描述。下一章使用这份描述,所以应用无需再创建一份 LED overlay。只有应用确实需要补充硬件连接或改变节点配置时,才增加 overlay。

从文件位置判断应改哪里​

在 VS Code 中分别找到下面的文件,并核对对应内容:

要确认的内容打开的文件或目录应找到什么
工作区清单入口.west/configpath = . 与 file = west.yml
Zephyr 依赖west.ymlname: zephyr、对应仓库及 revision
共享代码接入方式zephyr/module.yml、根 CMakeLists.txt模块入口与 drivers/、lib/ 的引用
板载 LED 描述boards/dshan/dshan_ra6m5/dshan_ra6m5.dtsled0 别名和 user_led 节点
自己的程序apps/每个应用单独保存构建入口、配置和源码

能够找到这些位置,就可以开始增加应用:在 apps/board_bringup/ 中安排打印和闪灯逻辑,复用工程已有的板卡描述和 Zephyr GPIO 驱动。

下一章:编写第一个 Zephyr 应用