【Zephyr|ESP32-S3】基础学习:完成第一个应用 “Hello World!”

哈喽,我是余火,一个普通的牛马打工人,目前正在学如何使用Zephyr RTOS。

上篇完成了 VS Code + Workbench for Zephyr 开发环境的搭建,环境跑通之后,自然要创建第一个工程来验证整条工具链——编译能不能通过、固件能不能烧进去、串口能不能看到输出。

对于嵌入式开发来说,第一个程序永远是 “Hello World”。这个程序本身很简单,但它能帮你验证开发环境的每一环是否正常工作:CMake 构建系统能不能正确解析项目文件、交叉编译工具链能不能生成目标平台的固件、烧录工具能不能把固件写入芯片、串口通信能不能正常收发数据。这些环节后续每篇文章都会用到,所以先花时间确保它们都能跑通是值得的。

这次用 Workbench 从零创建工程,跑通编译烧录,再改代码加上循环打印,顺便认识 Zephyr 的几个基础 API。

改了哪些东西

Workbench 创建 hello_world 工程时会自动生成以下文件。后续所有 Zephyr 工程都遵循同样的目录结构:

文件 作用 能否删除
src/main.c 应用主入口main() 是 Zephyr 内核启动后创建的默认线程 ❌ 必需
CMakeLists.txt CMake 构建文件,告诉构建系统如何编译这个应用 ❌ 必需
prj.conf Kconfig 配置文件,控制内核和应用的功能开关 ⚠️ 可空(使用默认配置)
README.rst 应用说明文档,reStructuredText 格式 ✅ 不影响编译

💡 Zephyr 的 main() 和裸机 main() 不一样:在裸机开发中,main() 是整个程序的入口,执行完就结束。在 Zephyr 里,main() 是内核启动后创建的第一个线程,有独立的栈空间和优先级。main() 返回后线程退出,但内核和其他线程继续运行——不会像裸机那样"程序结束"。

CMakeLists.txt 和 prj.conf 详解

这两个文件是 Zephyr 工程的构建核心,搞清楚它们你就搞清了 Zephyr 的编译流程和配置体系。

CMakeLists.txt

每个 Zephyr 应用都必须有这个文件,它是 CMake 构建系统的入口。Workbench 自动生成的模板非常标准,总共就四行有效代码:

# 最低 CMake 版本要求
cmake_minimum_required(VERSION 3.20.0)

# 引入 Zephyr 构建框架
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})

# 项目名称(会出现在构建日志中)
project(hello_world)

# 指定应用源文件
target_sources(app PRIVATE src/main.c)

逐行解释:cmake_minimum_required 指定 CMake 最低版本;find_package(Zephyr) 是最关键的一行,它引入 Zephyr 的构建框架,包括交叉编译配置、链接脚本、板级配置等;project() 声明项目名称,这个名字会出现在编译输出的信息里;target_sources() 指定这个应用包含哪些源文件。后续如果工程有多个 .c 文件,在这里追加即可。

prj.conf

prj.conf 使用 Kconfig 语法。Zephyr 内核的功能通过 CONFIG_ 宏开关控制,在这个文件里写一行等价于在代码里 #define。Workbench 创建工程时会自动添加调试相关的配置:

# 优化等级设为 -Og(调试友好,保留符号信息)
CONFIG_DEBUG_OPTIMIZATIONS=y
# 线程感知支持(调试器能显示线程列表)
CONFIG_DEBUG_THREAD_INFO=y
# 生成每个函数的栈使用报告(编译后查看栈占用)
CONFIG_STACK_USAGE=y
# 输出 HEX 格式固件(部分烧录器需要)
CONFIG_BUILD_OUTPUT_HEX=y
# 打印内存使用统计(RAM/Flash 占用信息)
CONFIG_OUTPUT_PRINT_MEMORY_USAGE=y

目前你不需要逐行理解这些配置的含义,只需要知道:后续如果编译报错说某个功能没开启,大概率是在 prj.conf 里少写了一行 CONFIG_XXX=y。到时候会具体讲。

💡 Kconfig 是 Zephyr 的配置核心,类似 Linux 内核的 menuconfig。CONFIG_ 前缀的宏控制内核的方方面面——串口驱动、蓝牙协议栈、文件系统、日志等级等。你可以用终端执行 west build -t menuconfig 打开图形化配置界面浏览所有可用选项。

创建 Hello World 工程

点击 VS Code 左侧 Workbench 面板中的 Add Application,进入应用创建界面。新版本 Workbench 合并了"新建"和"导入"两个入口,旧版插件需要点击 Create New Application

Add Application

在弹出的界面中选择 Create new application,找到 hello_world 示例(Zephyr 官方自带的入门例程,位于 samples/hello_world),点击 Create。如果你之前没用过 Zephyr 的模板系统,不用担心——Workbench 会自动处理所有依赖关系,从模板复制文件到你的 workspace 目录中。

Create or Import

点击 Create 后,Workbench 在 workspace 的 applications 文件夹下创建工程目录。此时 VS Code 左侧资源管理器中可以看到新项目,展开后能看到前面提到的四个文件——src/main.cCMakeLists.txtprj.confREADME.rst

编译工程

在 VS Code 的 Workbench 面板中点击 Build,或者打开终端手动执行:

west build -b esp32s3_devkitc

Build

-b esp32s3_devkitc 指定目标板名称。如果你的开发板型号不同,需要换成对应的板名(在 zephyr/boards/ 目录下查找)。

第一次编译时间较长,因为需要编译整个 Zephyr 内核和所有驱动。好消息是 Zephyr 默认使用增量编译——后续只修改了 src/main.c,重新 Build 时只会重新编译这一个文件然后重新链接,通常几秒钟就完成。

编译完成后,build/ 目录下会生成以下输出文件:

输出文件 格式 用途
zephyr.bin 原始二进制 west flash 默认烧录使用这个文件
zephyr.hex Intel HEX 部分第三方烧录器需要这个格式
zephyr.elf ELF(含调试符号) GDB / OpenOCD 调试时使用
zephyr.map 内存映射文本 查看 RAM / Flash 占用分布

💡 清理构建缓存:如果编译出现奇怪的链接错误(比如改了头文件但新代码没生效),试试 west build -b esp32s3_devkitc --pristine,它会清除所有缓存后从头编译,相当于裸机开发中的 make clean && make

烧录到 ESP32-S3

用 USB 数据线连接 ESP32-S3-DevKitC-1 开发板和电脑。在 Workbench 面板点击 Flash,或使用终端命令:

west flash

Flash

west flash 会自动检测已连接的开发板并烧录 build/zephyr.bin。如果电脑上连接了多块开发板,可以通过 -r 参数指定烧录器(如 -r idvid)。正常情况下,终端会显示烧录进度和最终的 Done 提示。

打开串口助手,波特率设为 115200(ESP32-S3 的默认串口波特率),选择正确的 COM 口。按一下开发板上的 RST 复位键触发重启,可以看到 ESP32-S3 的启动日志和应用的打印输出。默认模板只打印一行信息就结束了,串口不会再有新内容。

修改代码:增加循环打印

默认模板只打印一行就结束,验证一下编译烧录链路没问题后,现在改代码让程序持续运行——每秒打印一次系统运行时间,方便后续观察程序是否正常工作。

本节用到的 Zephyr 内核 API:

API 头文件 作用
k_uptime_get_32() <zephyr/kernel.h> 返回系统启动后的毫秒数(uint32_t,约 49 天溢出回零)
k_sleep(timeout) <zephyr/kernel.h> 让当前线程休眠指定时间,休眠期间 CPU 可执行其他线程
K_SECONDS(n) <zephyr/kernel.h> 秒 → 内核 tick 的时间转换宏
K_MSEC(n) <zephyr/kernel.h> 毫秒 → 内核 tick 的时间转换宏

修改后的完整 src/main.c

#include <stdio.h>
#include <zephyr/kernel.h>

int main(void)
{
    /* 开机打印板名,CONFIG_BOARD_TARGET 由构建系统自动注入 */
    printf("hello renyuan %s\n", CONFIG_BOARD_TARGET);

    /* 无限循环:每秒打印一次运行时间 */
    for(;;) {
        printf("hello renyuan (uptime: %u ms)\n", k_uptime_get_32());
        k_sleep(K_SECONDS(1));
    }
    return 0;
}

逐行说明:

  • #include <stdio.h>:提供 printf 函数,标准 C 库头文件
  • #include <zephyr/kernel.h>:Zephyr 内核服务头文件,提供 k_sleepk_uptime_get_32K_SECONDS 等 API
  • CONFIG_BOARD_TARGET:Zephyr 构建系统自动注入的宏,展开为当前目标板名称(如 esp32s3_devkitc),不需要手动 #define
  • for(;;):经典无限循环写法,main() 线程永不退出
  • k_uptime_get_32():获取系统启动至今的毫秒数,用来验证程序在持续运行
  • K_SECONDS(1):Zephyr 提供的时间宏,将 1 秒转为内核时钟周期数,比手写 1000 更安全

💡 Zephyr 的时间宏为什么比手写数值更安全K_SECONDS(n)K_MSEC(n) 会根据 CONFIG_SYS_CLOCK_TICKS_PER_SEC(内核 tick 频率)自动计算对应的 tick 数。ESP32-S3 的 tick 频率是 1000 Hz,所以 K_SECONDS(1) 恰好等于 1000。但如果换到 nRF52832 等平台,默认 tick 频率是 32768 Hz,K_SECONDS(1) 就不等于 1000 了。直接写毫秒数值在这种平台上会出现精度偏差。

修改保存后再次 Build → Flash,串口助手可以看到每秒一条打印,uptime 数值持续递增:

串口输出

到这里,创建→编译→烧录→串口验证这条完整的开发链路就全部跑通了,后续每篇文章都会重复这套流程。

printk 和 printf 的区别

Zephyr 提供了自己的打印函数 printk,和标准 C 库的 printf 有区别:

特性 printk printf
头文件 <zephyr/kernel.h> <stdio.h>
依赖 仅依赖内核,不依赖标准 C 库 需要启用 CONFIG_NEWLIB_LIBC
格式化能力 支持 %d%s%x 等常用格式 完整的 C 标准格式化
ISR 中可用 ✅ 可以(内核直接实现) ❌ 不行(依赖库可能阻塞)
推荐场景 日志输出、调试打印 需要复杂格式化时

本篇用的是 printf,因为它更符合大多数开发者的习惯。后续文章会逐步切换到 printk,因为它不依赖标准库、更轻量,而且在 ISR 上下文中也能安全调用。对初学者来说,两者语法基本一致,用哪个都能跑通,知道区别即可。

常见问题

Q:Build 报错 undefined reference to 'printf'

A:printf 来自标准 C 库,确认 src/main.c 顶部包含了 #include <stdio.h>。如果 prj.conf 中没有启用标准库(CONFIG_NEWLIB_LIBC=y),printf 可能不可用,改用 Zephyr 原生的 printk 即可——只需 #include <zephyr/kernel.h>,不需要额外配置。

Q:串口助手看不到任何输出?

A:检查三个点:① 波特率是否设为 115200;② 首次烧录后需要按一下 RST 复位键触发重启;③ COM 口是否选对——在 Windows 设备管理器的"端口"分类中确认 ESP32-S3 对应的端口号。

Q:west flash 提示找不到串口设备?

A:ESP32-S3 开发板上通常使用 CP2102CH343 芯片做 USB 转串口。Windows 下需要安装对应驱动:CP2102 去 Silicon Labs 官网下载,CH343 去 WCH 官网下载。安装成功后设备管理器中会出现 COM 口设备。

Q:增量编译后烧录,串口输出还是旧代码的结果?

A:确认 Build 日志中没有 error,并且最后一行显示编译成功。如果编译成功但输出没变化,可能是烧录失败——检查 USB 连接是否稳定,或者尝试手动执行 west flash 查看烧录过程的详细日志。按 RST 复位后观察是否有 *** Booting Zephyr OS *** 开机日志(每次复位都会打印)。

总结

本篇用 Workbench 从零创建了 hello_world 工程,跑通了创建→编译→烧录→串口输出的完整开发链路,并在此基础上加了 k_sleep + k_uptime_get_32 实现每秒打印运行时间。编译烧录这套流程后续每篇都会反复用到。


希望我的笔记能对你有一点点点的帮助!欢迎关注一起学习👇

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐