系统架构
用裸机方式写程序时,你可能在 main 中初始化时钟、GPIO,再进入死循环。Zephyr 保留应用入口,但在调用 main 前已经完成内核、线程环境和一批设备初始化。应用不需要重新实现调度器或为每个外设手写寄存器操作。
软件分层
通常情况下,应用、被选中的内核功能和驱动会一起编译、链接成固件,烧录后运行在 MCU 上。它不是在 Linux 上启动一个普通进程,也不是把 C 源码复制到板上解释执行。
从圆屏示例看,各层分工如下:
| 层次 | 本例对应内容 | 你通常修改什么 |
|---|---|---|
| 应用 | 创建圆环、响应按钮、显示数值 | main.c 及自己的业务代码 |
| 库/子系统 | LVGL、日志、输入处理 | Kconfig 配置及调用方式 |
| 驱动 | GC9A01、CST8XX、SPI、GPIO | 优先使用 API;缺驱动才新增 |
| 板卡 | 屏幕连到哪条 SPI、哪个 GPIO 是背光 | DTS、pinctrl、板卡默认配置 |
| SoC / 架构 | ESP32-S3 寄存器、IRQ、Xtensa 上下文 | 普通板卡适配通常复用 |
| 内核 | 调度、线程、时间、同步 | 通过内核 API 使用 |
设备树与 Kconfig 参与构建,不是运行中的额外两个服务。设备树提供硬件实例信息,Kconfig 决定哪些实现参与构建。
源码目录
进入 main 工作区的 zephyr 目录:
cd ~/Projects/zephyr-main-workspace/zephyr
ls kernel drivers subsys soc arch boards include samples
不要一开始从头读完整个 kernel。先从自己应用用到的函数往下找,例如:
rg 'k_sleep' include/zephyr/kernel.h
rg 'gpio_pin_set_dt' include/zephyr/drivers/gpio.h
rg 'zephyr,display|zephyr,touch' boards/waveshare/esp32s3_touch_lcd_1_28
include/zephyr/ 是常用 API 的入口;drivers/ 是具体实现;boards/ 描述板级连接;工作区外层的 modules/ 是依赖仓库,不要误认为这里缺少 LVGL。
启动流程
对本板的 Simple boot 构建,可以先按下列顺序理解:
芯片 ROM 根据启动引脚决定下载或从 Flash 启动
→ 固件启动代码准备内存、CPU 和系统基础设施
→ Zephyr 初始化内核及按阶段安排的设备
→ main 线程执行应用 main()
→ 应用线程与系统线程由调度器运行
其他板卡或 MCUboot 构建还会有不同的启动链。看到 ROM 的 waiting for download,说明还没有运行应用;这时改 LVGL 或线程优先级不会解决问题。
驱动初始化可发生在 main 前,main 内仍要用 device_is_ready() 检查。设备实例存在不代表初始化成功。
配置与构建流程
板卡 DTS + SoC DTSI + 应用 overlay → zephyr.dts → 设备树生成头文件
板卡默认配置 + prj.conf + Kconfig 规则 → .config → autoconf.h
应用 C + 驱动 C + 内核 C + 库 → 编译/链接 → ELF/BIN
例如你想显示文字:硬件配置必须有可用的显示设备,软件配置必须启用 DISPLAY、LVGL 及所需控件,应用还必须创建文字并处理刷新。只打开一个开关不会自动生成界面。
练习
打开现有 LVGL 构建目录的 zephyr/.config,找到 DISPLAY 与 LVGL;打开 zephyr/zephyr.dts,找到 chosen 的 display 引用;回到 main.c 找到 DEVICE_DT_GET(DT_CHOSEN(zephyr_display))。把这三处连起来,才能知道程序操作的是哪块屏幕。
下一步:建立自己的应用。
参考:本机 include/zephyr/、kernel/init.c、板卡 DTS,以及 Zephyr 应用开发。