Zephyr Kconfig 配置系统保姆级详解:语法 + menuconfig 实战 + 固件裁剪 + 排错(含完整可运行工程)

📅 2026/6/25 13:52:17 👤 编程新知 🏷️ 技术资讯
Zephyr Kconfig 配置系统保姆级详解:语法 + menuconfig 实战 + 固件裁剪 + 排错(含完整可运行工程) 本文是「Zephyr 内核从入门到精通」系列第 05 篇。上一篇讲透了设备树本篇讲它的黄金搭档 Kconfig——它和设备树什么关系、prj.conf 写在哪、menuconfig 怎么一步步操作、开关一个功能固件体积怎么变、配置不生效怎么排查。全文保姆级每一步都标清「在哪做、怎么做、应该看到什么」配一个完整可编译的小工程照着抄就能跑。建议先点赞收藏照着做一遍。目录一、Kconfig vs 设备树一句话分清二、Kconfig 基本语法符号类型 依赖 select三、配置从哪来、谁说了算优先级四、固件裁剪的原理配置怎么变成代码五、完整实战工程开关 LOG / SHELL看固件体积怎么变六、menuconfig / guiconfig 一步步操作保姆级图解七、多配置文件 代码内条件编译八、强化版高频报错排查表13 条九、总结一、Kconfig vs 设备树一句话分清很多新手把 Kconfig 和设备树搞混因为两者都在「配置」。但分工非常明确设备树回答「硬件长什么样」Kconfig 回答「这次启用哪些功能、给多少资源」。用装修类比设备树是装修图纸房子结构固定不变Kconfig 是功能清单装不装空调、要不要地暖按预算勾选。问题归属LED 接在哪个引脚设备树I2C 总线上挂了哪个传感器、地址多少设备树是否启用蓝牙 / 日志 / ShellKconfig主线程栈大小Kconfig日志默认等级Kconfig判断窍门问「硬件本来长什么样」找设备树问「这次要不要、给多少」找 Kconfig。注意启用一个驱动通常需要两边配合——设备树里有对应节点且status okayKconfig 里开对应子系统如CONFIG_I2Cy。缺一个都跑不起来这是新手最容易踩的坑。二、Kconfig 基本语法2.1 你每天写的 prj.conf 长这样日常 99% 的配置都写在工程目录下的prj.conf# bool布尔开关y 开 / n 关 CONFIG_GPIOy CONFIG_BTn # int整数 CONFIG_MAIN_STACK_SIZE2048 CONFIG_LOG_DEFAULT_LEVEL3 # hex十六进制 CONFIG_SRAM_BASE_ADDRESS0x20000000 # string字符串带引号 CONFIG_BT_DEVICE_NAMEMyDevice四种符号类型记住即可类型取值例子booly/nCONFIG_LOGyint整数CONFIG_MAIN_STACK_SIZE2048hex0x 开头CONFIG_SRAM_BASE_ADDRESS0x20000000string双引号包裹CONFIG_BT_DEVICE_NAMEMyDev每个CONFIG_xxx都是某个Kconfig文件里config xxx定义出来的注意定义里没有 CONFIG_ 前缀引用时才加。定义里包含类型、默认值、依赖、帮助文本。2.2 depends on依赖新手必懂排错第一坑Kconfig 符号之间有依赖关系。看一个 Zephyr 源码里的真实例子config BT_PERIPHERAL bool Peripheral Role support depends on BT # 依赖 BT必须先 BTy如果你没开CONFIG_BT却写了CONFIG_BT_PERIPHERALy结果是这一行无效——不是「写错了」而是「依赖未满足系统直接忽略你的选择」。这种情况在 menuconfig 里表现为该项灰色、无法用空格勾选。这是「我明明y了却没生效」最常见的原因没有之一。2.3 select强制选中反向开依赖select是depends on的反方向——开 A 时自动把 B 也打开config BT_PERIPHERAL bool Peripheral Role support select BT_BROADCASTER # 开我自动也开 BT_BROADCASTER小心select会强制打开被选项哪怕被选项自己的依赖没满足可能导致诡异冲突。新手日常基本不用手写select知道它存在、能看懂报错即可。三、配置从哪来、谁说了算优先级板子有出厂默认配置你又写了 prj.conf还可能用 menuconfig 临时改到底谁说了算优先级从低到高越靠近「本次构建」的越大模块默认值(default) 板级 board_defconfig prj.conf 额外 conf(EXTRA_CONF_FILE) menuconfig 临时改动 最低 最高模块默认值各子系统Kconfig文件里的default最底层兜底。板级 defconfigboards/.../board_defconfig这块板子的出厂默认比如默认开了哪个串口。prj.conf你的工程配置日常主战场。额外 conf通过-DEXTRA_CONF_FILExxx.conf叠加的文件会覆盖 prj.conf。menuconfig构建时手动改的临时值优先级最高但只存在于构建目录pristine 重建就丢。记忆口诀离「本次构建」越近优先级越高。不管来源多复杂所有配置最终合并成一份文件build/zephyr/.config。排错只认它。四、固件裁剪的原理配置怎么变成代码这是 Kconfig 的核心价值也是 Zephyr 能同时覆盖几十 KB 小 MCU 和高端芯片的根本原因。完整链条prj.conf / defconfig / menuconfig │ Kconfig 系统求解依赖、合并所有来源 ▼ build/zephyr/.config ← 「真相之源」合并后的最终配置 │ 生成 ▼ build/zephyr/include/generated/.../autoconf.h ← #define CONFIG_xxx 1 │ 编译期被代码包含 ▼ 你的代码#if defined(CONFIG_xxx) 条件编译关键配置在编译期生效。当CONFIG_BTn时蓝牙相关代码根本不会被编译进固件关闭的功能 0 行代码、0 字节 RAM、0 字节 Flash 开销。所以「裁剪固件」不是运行时省内存而是编译时就不把不需要的东西放进去。下面的实战工程会让你亲眼看到固件体积随配置变化。五、完整实战工程开关 LOG / SHELL看固件体积怎么变下面这个小工程完整可编译目标开/关CONFIG_LOG和CONFIG_SHELL对比固件 FLASH/RAM 用量亲手感受裁剪效果。本文以nrf52840dk/nrf52840为例无板子也能 build 看体积。换成你自己的板子把-b后面替换即可。5.1 工程目录结构prj.conf 到底放哪在任意目录新建工程文件夹结构如下。prj.conf 就放在工程根目录和 CMakeLists.txt 平级kconfig_demo/ ← 工程根目录 ├── CMakeLists.txt ← 构建脚本 ├── prj.conf ← 主配置必须在这里名字固定 ├── debug.conf ← 额外调试配置可选叠加 └── src/ └── main.c ← 应用代码5.2 CMakeLists.txt# CMakeLists.txt cmake_minimum_required(VERSION 3.20.0) find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) project(kconfig_demo) target_sources(app PRIVATE src/main.c)5.3 prj.conf默认精简版LOG/SHELL 都关# prj.conf —— 工程根目录名字固定不能改 CONFIG_GPIOy # 先全部关掉作为「精简基线」 CONFIG_LOGn CONFIG_SHELLn5.4 debug.conf额外叠加打开 LOG 和 SHELL# debug.conf —— 开发调试时叠加开日志 Shell CONFIG_LOGy CONFIG_LOG_DEFAULT_LEVEL4 CONFIG_SHELLy CONFIG_THREAD_NAMEy5.5 src/main.c同一份代码跟着配置自动裁剪/* src/main.c */#includezephyr/kernel.h/* 只有 CONFIG_LOGy 时这段日志代码才会被编译进固件 */#ifdefined(CONFIG_LOG)#includezephyr/logging/log.hLOG_MODULE_REGISTER(demo,LOG_LEVEL_INF);#endifintmain(void){#ifdefined(CONFIG_LOG)/* CONFIG_LOGn 时这两行连同 LOG 子系统都不会进固件 */LOG_INF(Hello from demo, LOG is ON);LOG_INF(main stack size %d bytes,CONFIG_MAIN_STACK_SIZE);#else/* CONFIG_LOGn 时走这里用最朴素的 printk */printk(LOG is OFF, this is printk\n);#endifwhile(1){k_msleep(1000);}return0;}注意两个用法#if defined(CONFIG_LOG)判断某个 bool 配置是否为 y做条件编译。CONFIG_MAIN_STACK_SIZE直接当普通宏整数用在代码里。5.6 第一次构建精简版LOG/SHELL 关在工程根目录打开终端执行-p always表示干净重建避免缓存干扰cdkconfig_demo west build-palways-bnrf52840dk/nrf52840.构建结束终端最后会打印Memory region 用量表这就是固件体积类似Memory region Used Size Region Size %age Used FLASH: 21456 B 1 MB 2.05% RAM: 6240 B 256 KB 2.38%记下这两个数字FLASH ≈ 21 KBRAM ≈ 6 KB数值随版本/板子不同以你本地为准。5.7 第二次构建叠加 debug.conf开 LOG SHELL同样在工程根目录执行用-DEXTRA_CONF_FILE把debug.conf叠加上去west build-palways-bnrf52840dk/nrf52840.---DEXTRA_CONF_FILEdebug.conf多个额外配置文件用分号分隔-DEXTRA_CONF_FILEdebug.conf;extra.confWindows PowerShell 下整体加引号。构建完看体积会明显变大类似Memory region Used Size Region Size %age Used FLASH: 58320 B 1 MB 5.56% RAM: 12480 B 256 KB 4.76%【 截图位west build 终端体积表叠加 debug.conf 后明显变大】5.8 体积对比亲眼看到裁剪效果配置FLASHRAM精简版LOGn, SHELLn≈ 21 KB≈ 6 KB调试版LOGy, SHELLy≈ 58 KB≈ 12 KB差值37 KB6 KB仅仅两个开关固件就差了几十 KB。对几十/几百 KB 的小 MCU 来说这就是能不能塞得下的区别。这就是 Kconfig 裁剪的威力。5.9 去哪看最终值真相之源不确定某个配置最终到底是 y 还是 n打开这个文件build/zephyr/.config里面是合并后所有配置的最终结果。用编辑器搜CONFIG_LOG/CONFIG_SHELL对比两次构建会看到精简版构建里# CONFIG_LOG is not set # CONFIG_SHELL is not set叠加 debug.conf 后CONFIG_LOGy CONFIG_LOG_DEFAULT_LEVEL4 CONFIG_SHELLy关注一个细节被关掉的 bool 配置写法是# CONFIG_XXX is not set注释形式不是CONFIG_XXXn。在.config里看到这行就代表「关」。六、menuconfig / guiconfig 一步步操作保姆级图解不想背配置名用图形菜单边逛边改。6.1 进入 menuconfig前提必须先成功 build 过一次生成构建目录然后在工程根目录执行# 文本界面菜单在终端里跑最常用无图形环境也能用west build-tmenuconfig# 图形窗口版需要桌面图形环境west build-tguiconfig6.2 界面长什么样示意┌──────────────── Zephyr Kernel Configuration ────────────────┐ │ Arrow keys navigate. Enter selects submenus --- │ │ Press Y to include, N to exclude, / for Search │ │ │ │ [ ] Bluetooth │ │ [*] Logging --- │ │ [ ] Shell --- │ │ (2048) Size of stack for the main thread │ │ │ │ [Space/Enter] Toggle [/] Search [?] Help [ESC] Back │ └──────────────────────────────────────────────────────────────┘[*]表示开y[ ]表示关n灰色项表示依赖未满足、不可改。6.3 五个关键操作背下来就够用操作按键说明上下移动方向键 ↑↓在选项间移动光标进入子菜单回车带---的项可进入开/关一个 bool空格或y/n[ ]与[*]切换搜索符号/然后输入名字最实用输LOG直接定位看帮助?显示该项的依赖、默认值、定义它的文件路径退出并保存ESC退到顶层再ESC提示时选Yes保存到构建目录的 .config操作演示按/→ 输入SHELL→ 回车 → 列表里看到CONFIG_SHELL光标移到它 → 按空格切到[*]→ 一路 ESC → 提示保存选Yes。6.4 重要警告menuconfig 的改动会丢⚠️ menuconfig 改的是构建目录里的临时.config。下次你用west build -p alwayspristine 干净重建时构建目录被清空改动全部丢失正确做法menuconfig 里试出想要的配置后把对应的CONFIG_XXXy手动抄回prj.conf这样才永久生效。menuconfig 当「探索 试验」工具用prj.conf 才是「永久落地」的地方。七、多配置文件 代码内条件编译7.1 多配置文件分离开发 / 发布两套把调试相关配置抽到debug.conf前面工程已演示好处是prj.conf保持干净发布时不带 debug.conf 即可# 开发带调试配置west build-palways-bboard.---DEXTRA_CONF_FILEdebug.conf# 发布不带自动是精简版west build-palways-bboard.7.2 板级专属配置多板项目针对某块板的额外配置放在工程的boards/board.conf如boards/nrf52840dk_nrf52840.conf构建该板时自动叠加不用手动指定适合一个工程支持多块板。7.3 代码内条件编译应用层也能跟着裁剪/* 方式 1bool 配置做条件编译关掉时这段代码不进固件 */#ifdefined(CONFIG_BT)bt_enable(NULL);#endif/* 方式 2int / hex / string 配置直接当宏用 */LOG_INF(main stack %d,CONFIG_MAIN_STACK_SIZE);7.4 想知道某个 CONFIG 是干嘛的在 menuconfig 里按/搜再按?看帮助和定义文件路径或在 Zephyr 源码里搜定义grep -r config SHELL zephyr/搜的是不带 CONFIG_ 前缀的config SHELL。八、强化版高频报错排查表13 条#现象原因解决办法1配了CONFIG_Xy却没生效depends on依赖未满足被静默忽略menuconfig 看该项是否灰色先把依赖项打开如先CONFIG_BTy2改了 prj.conf 完全没反应构建用了旧缓存用west build -p always干净重建3menuconfig 改完重建就丢只改了临时 .config没写回 prj.conf把CONFIG_Xy抄进 prj.conf4不知道最终值是 y 还是 n多来源合并看 prj.conf 看不准打开build/zephyr/.config搜对应符号5.config里找不到CONFIG_Xn关闭的 bool 写法是注释找# CONFIG_X is not set这行即可那就是「关」6提示undefined symbol CONFIG_X之类拼错名字 / 该符号在当前板未定义menuconfig 按/搜确认真实名字与是否存在7代码里CONFIG_X报未定义该配置为 n宏没被定义用#if defined(CONFIG_X)包裹别直接裸用8固件太大、塞不下开了用不到的子系统LOG/SHELL/BT 等关掉不需要的CONFIG_*对照.config核实看体积表9加了配置但功能没起来只开了 Kconfig设备树节点没okayKconfig 与设备树两边都要配如 I2CCONFIG_I2Cy 节点statusokay10EXTRA_CONF_FILE多文件不生效分隔符写错或没整体加引号用分号分隔并整体引号-DEXTRA_CONF_FILEa.conf;b.conf11menuconfig 里某项是灰色改不动它依赖的上层符号没开先按?看它依赖谁去把依赖打开它才可选12改了int配置如栈大小没变仍是缓存 / 改错了文件-p always重建并在.config里确认CONFIG_X新值13两个配置互相冲突报 warning一个select强行打开了另一个看构建警告里的符号名调整 prj.conf避免硬冲突核心排错习惯配置出问题第一时间打开build/zephyr/.config——它是所有来源合并后的「真相之源」比盯着 prj.conf 猜靠谱一百倍。九、总结分工设备树管「硬件长什么样」Kconfig 管「启用哪些功能、给多少资源」启用驱动通常两边配合。语法日常写prj.conf工程根目录四种类型 bool/int/hex/string重点理解depends on和select。优先级模块默认 板级 defconfig prj.conf 额外 conf menuconfig越靠近本次构建越大最终都汇进build/zephyr/.config。原理prj.conf → .config → autoconf.h → #if defined(CONFIG_xxx)条件编译编译期生效关闭功能零开销。实战用-DEXTRA_CONF_FILE分离调试/发布配置menuconfig 当探索工具但记得把改动抄回 prj.conf-p always干净重建体积看 build 终端的 Memory region 表最终值看.config。下一篇《基础篇总结》把架构、设备树、Kconfig 串成完整主线用一个例子打通「设备树 Kconfig 代码」三件套建立 Zephyr 开发心智模型随后正式进入内核篇。如果帮到你点赞 收藏 关注三连。配置相关报错欢迎贴评论区最好附上.config里相关几行我帮你看。