【Zephyr|ESP32-S3】基础学习:使用LOG系统完成 "Hello World!"打印

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

上一篇完成了 Hello World 的编译烧录,用 printf 每秒打印一次系统运行时间。跑起来没问题,但你有没有注意到——串口输出里只有你自己写的文本内容,没有任何时间戳、模块名、日志等级这些信息。工程小的时候无所谓,等代码模块一多,满屏 printf 根本分不清哪条是谁打的。

Zephyr 自带一套生产级的日志子系统 LOG,支持时间戳、模块名、日志分级过滤,用起来也不比 printf 复杂多少。今天就把它替换上去。

改了哪些东西

在上一篇的 hello_world 工程上直接改,一共 2 个文件:

文件 改了什么
prj.conf 新增 CONFIG_LOG=y + 日志级别
src/main.c 添加 LOG 头文件和模块注册,printf → LOG_INF

为什么要用 LOG?

先看 Zephyr 下三种打印方式的区别:

方案 输出效果 适用场景
printf Hello World! (uptime: 1000 ms) 纯文本,无时间戳无模块信息
printk [00:00:01.000,000] <inf> Hello World 内核早期启动、底层调试
LOG系统 [00:00:01.000,000] <inf> main: Hello World! (board: esp32s3_devkitc, uptime: 1000 ms) 带时间戳+模块名+等级标签

简单说:

  • printf:最通用,但输出是"裸文本",什么上下文都没有
  • printk:内核自带,开销极低,适合 Boot 阶段 LOG 子系统还没就绪时用
  • LOG:功能最全,支持按模块名、日志等级过滤,多模块项目必备

💡 实际开发中,printf 直接淘汰,printk 只在极少数底层场景出现,LOG 覆盖 95% 以上的打印需求。

启用 LOG

LOG 子系统需要通过 Kconfig 启用。在 prj.conf 里加两行:

#--- Enable log system ---#
CONFIG_LOG=y
# 指定默认日志级别为INFO
CONFIG_LOG_DEFAULT_LEVEL=3

CONFIG_LOG=y 会让构建系统自动编译 LOG 核心,不需要额外配置硬件驱动——LOG 底层复用的是串口输出,串口在第 2 篇就已经能用了。

注册 LOG 模块

打开 src/main.c,添加头文件和模块注册:

#include <zephyr/logging/log.h>

LOG_MODULE_REGISTER(main, CONFIG_LOG_DEFAULT_LEVEL);

LOG_MODULE_REGISTER(name, level) 是每个用到 LOG 的 .c 文件都必须写的一行:

  • name — 模块名称,会出现在日志输出的前缀里(比如上面写的 main,输出就是 main: 开头)
  • level — 此模块的编译时日志级别,一般直接引用 CONFIG_LOG_DEFAULT_LEVEL

日志等级

Zephyr 的 LOG 有 5 个等级,级别从低到高:

等级 含义 对应函数
LOG_LEVEL_NONE 0 禁用
LOG_LEVEL_ERR 1 仅错误 LOG_ERR()
LOG_LEVEL_WRN 2 警告+错误 LOG_WRN()
LOG_LEVEL_INF 3 信息+警告+错误 LOG_INF()
LOG_LEVEL_DBG 4 全部输出 LOG_DBG()

等级是过滤门槛:设置为 3(INFO),则 LOG_INFLOG_WRNLOG_ERR 都会输出,LOG_DBG 在编译期直接被裁掉,零运行开销。

这样你调试时把级别改成 4 就能看到所有日志,发布时改回 3 只保留关键信息,一行配置切换,不用逐条注释代码。

替换 printf

把 main 函数里的 printf 全部换成 LOG_INF

int main(void)
{
    LOG_INF("Hello World from Log! (board: %s)", CONFIG_BOARD_TARGET);

    for(;;) {
        LOG_INF("Hello World from Log! (uptime: %u ms)", k_uptime_get_32());
        k_sleep(K_SECONDS(1));
    }
    return 0;
}

用法和 printf 一样,支持 %s%d%u 等格式化占位符。

改完后在 Workbench 中重新编译:

编译

烧录后打开串口助手,可以看到 LOG 系统的输出效果——每条日志都带有时间戳、日志等级标签和模块名前缀:

串口输出

对比上一篇的 printf 输出,信息量直接上了一个台阶:哪个模块、什么时间、什么级别,一目了然。

常见问题

Q1:编译报错 undefined reference to 'logX'

• 解决:prj.conf 漏了 CONFIG_LOG=y,或者忘了在 .c 文件里写 LOG_MODULE_REGISTER

Q2:LOG 输出没有时间戳

• 解决:需要同时开启 CONFIG_LOG_TIMESTAMP_24HR=yCONFIG_LOG_TIMESTAMP_64BIT=y(根据平台选择),一般 CONFIG_LOG=y 会自动拉取。

Q3:想临时看调试日志,不想改 prj.conf

• 解决:编译时通过 menuconfig 或 CMake 参数 -DCONFIG_LOG_DEFAULT_LEVEL=4 临时覆盖,不用修改源文件。

从这篇开始,后面所有代码都会用 LOG 代替 printf,LOG 也是 Zephyr 官方示例和驱动代码里最主流的打印方式。

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

Logo

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

更多推荐