
接手别人的IAR工程或者把自己电脑上的工程拷给同事一编译满屏的Error[Pe169]: cannot open source file stm32f1xx_hal.h。这种场景很多嵌入式工程师都遇到过问题十有八九出在 Options 里那些写死的绝对路径上C:\Users\zhang\Desktop\xxxx\Drivers\CMSIS\Include。换台电脑、换个目录路径一失效整个工程的头文件全部“蒸发”。这篇文章只讲一件事在 IAR 的 Additional include directories 里用相对路径替代绝对路径并且把$PROJ_DIR$这个内置变量用明白。搞定了这个工程在团队里传、在 Git 上拉、从一台电脑搬到另一台电脑只要内部目录结构不变编译配置就永远不用再动。适合刚接触 IAR 的入门用户也适合被路径问题折磨了很久、想一次性把工程配置规范起来的老手。1. 为什么要折腾相对路径和 $PROJ_DIR$1.1 绝对路径的坑换台电脑就“翻车”我见过太多这样的场景新同事从代码仓库拉下一个工程打开 IAR 编译瞬间几十个 include 相关的报错。检查了一圈发现 Options 里的 Additional include directories 填的全是C:\Users\laowang\Desktop\STM32_Project\Drivers\CMSIS\Include这种路径。问题很明显这是在老王电脑上能用的路径换一个人、换一个目录就完全失效。IAR 在编译时会按这些路径去找头文件找不到就直接报cannot open source file。绝对路径的“可移植性”为零尤其团队合作时每个人存放代码的习惯都不一样有人放 D 盘有人放桌面有人用中文目录路径分分钟全炸。还有一个隐蔽的坑很多人把工程压缩包发给别人时会顺手解压到一个名字很长的目录比如STM32_Project_final_v2_20250101。如果配置里是全路径IAR 还能按原路径找到吗大概率不能。结果就是拿到工程的人第一件事不是看代码而是花半小时帮别人修路径。1.2 相对路径到底“相对”什么先说结论IAR 里写相对路径默认基准是“当前工程文件.ewp所在的目录”也就是$PROJ_DIR$。你写的每一段相对路径IAR 都会先替换成$PROJ_DIR$对应的绝对路径再去拼接后面的子目录。这里可以打个比方。绝对路径像“湖北省武汉市洪山区某某大道 123 号”不管你在哪都能按这个地址找到。相对路径像“从我家出门左转走到红绿灯再右转”关键在于“我家”在哪。如果你搬家了但描述里“我家”的位置还指老地方同样会走错。所以相对路径的前提是工程目录本身要保持完整、结构稳定。在 IAR 的实际处理中..表示上一级目录.表示当前目录。比如工程文件在D:\work\project\app\app.ewp你想引用D:\work\project\drivers\inc那么相对路径就写成$PROJ_DIR$\..\drivers\inc。这个..就是在告诉 IAR从 app 目录退一级回到 project 目录再往下进入 drivers。1.3 IAR 内置路径变量全家桶除了$PROJ_DIR$IAR 还提供了几个常用的内置路径变量搞清楚它们各自的含义能帮你应对更多场景。变量名含义$PROJ_DIR$当前打开工程文件 .ewp 所在的目录最常用$FW_DIR$当前工作区文件 .eww 所在的目录多工程协同时会用到$TOOLKIT_DIR$IAR 安装目录偶尔配置第三方库时用$CONFIG_DIR$工程配置文件目录一般用得少我实际项目里用到最多的就是$PROJ_DIR$。如果你打开的是 .ewp 工程文件而不是 .eww 工作区那么$FW_DIR$和$PROJ_DIR$在很多情况下也指向同一层目录。但为了严谨建议你习惯以$PROJ_DIR$为准它是与单个工程绑定的语义最清晰。这些变量在 IAR 的帮助文档里也有官方说明打开 Help 后搜索 Path variables 就能看到。不过官方描述比较简略真正好用还得靠实操去悟。1.4 团队协作和版本管理场景下这是必须项如果你只是自己在本地写代码用绝对路径确实也能跑。但只要涉及多人协作、Git/SVN 版本管理或者要用脚本做自动化编译相对路径就是从“能用”到“好用”的分水岭。版本管理工具记录的是文件内容的变化.ewp文件里如果存的是绝对路径每个人拉下来都要手动改一遍自己本地的路径改了之后又会把这个改动提交上去引发一堆无意义的冲突。而用$PROJ_DIR$之后.ewp文件里的路径配置对所有人生效大家 clone 下来就能直接编译省掉的不只是时间还有无尽的挫败感。2. 动手配置Additional include directories 完整实操2.1 找到配置入口先明确一下界面位置。在 IAR for ARM 8.x 和 9.x 里路径是Project Options C/C Compiler Preprocessor右侧有一个 Additional include directories 输入框。在 IAR for STM8 或者 IAR for 8051 这类较老的版本里入口也很接近一般在Options C/C Compiler Preprocessor页面下。有些老版本里这个输入框的名字可能叫 Include paths功能和 Additional include directories 完全一致。如果你在界面上没看到就在 Preprocessor 页签里翻一翻它通常是一个多行文本输入框。这个输入框的填写规则很宽容支持多行也支持用英文分号;分隔多个路径。我习惯一行一个路径看着清晰排查问题时也方便。2.2 先画清楚工程目录树配置之前我强烈建议你先把自己工程的目录结构理一遍。不要“凭感觉”写路径否则很容易多一层、少一层。下面是一个典型的 STM32 FreeRTOS 工程目录树后面所有例子都基于这个结构。MyProject/ ├── project/ │ ├── app.ewp // IAR 工程文件 │ ├── app.eww // 工作区文件 │ └── main.c ├── drivers/ │ ├── inc/ │ │ └── gpio.h │ └── src/ │ └── gpio.c ├── middlewares/ │ └── FreeRTOS/ │ ├── include/ │ │ └── FreeRTOS.h │ └── src/ │ └── tasks.c └── cmsis/ └── core_cm3.h假设 app.ewp 在 MyProject/project 目录下而我们需要在 Additional include directories 里添加 drivers/inc、middlewares/FreeRTOS/include、cmsis 三个目录。2.3 从工程文件位置开始逐层数路径先看第一个目标MyProject/drivers/inc。app.ewp 在MyProject/project下所以要先从 project 退回 MyProject也就是一个..然后再进入 drivers/inc。写成 IAR 的路径就是$PROJ_DIR$\..\drivers\inc第二个目标MyProject/middlewares/FreeRTOS/include。同样需要先退回 MyProject再进入 middlewares。写成$PROJ_DIR$\..\middlewares\FreeRTOS\include第三个目标MyProject/cmsis。这次只需要退一级然后直接进 cmsis不需要再往下层$PROJ_DIR$\..\cmsis如果你愿意也可以在输入框里这样写满三行$PROJ_DIR$\..\drivers\inc $PROJ_DIR$\..\middlewares\FreeRTOS\include $PROJ_DIR$\..\cmsis保存配置后重新编译头文件报错就应该明显减少。这里有个判断技巧如果编译时还提示找不到某个头文件先确认这个头文件到底在哪一层目录再顺着$PROJ_DIR$往前推一般很快就能发现问题出在层级不对。2.4 路径写好后怎么看有没有生效配置写完了怎么确认 IAR 真的按我们设想的方式解析路径最简单的办法是随便找一个之前报错的头文件在源码里按住 Ctrl 点击它如果能正确跳到头文件内容说明 include 路径已经生效。如果还想更直接一点可以打开.ewp工程文件看看。.ewp本质是一个 XML 文件用记事本就能打开。搜索 Additional include directories 附近的name标签你会看到类似这样的内容name$PROJ_DIR$\..\drivers\inc/name这说明 IAR 已经把配置原样保存进了工程文件而不是展开成一个绝对路径。这一步验证很有价值它意味着换一台电脑、换一个目录后只要工程内部结构不变这个路径就依然有效。2.5 工程文件里的路径到底长什么样顺着上一节再展开聊一下。我见过不少人担心$PROJ_DIR$会不会只是 IAR 界面上的一个花架子其实不是。IAR 在编译时会把这个变量替换成当前 .ewp 文件实际所在的绝对路径然后传给编译器。你可以在.ewp文件里看到变量名本身但编译器收到的是一串完整路径。这种设计的聪明之处在于工程作为一个整体内部文件之间的相对关系是稳定的而每个人把它放在磁盘的哪个位置是可变的。用$PROJ_DIR$把“可变”的部分抽象掉只保留“稳定”的内部结构路径问题自然就消失了。3. 从踩坑到避坑我总结的排错清单3.1 常见报错与对策速查表写相对路径不是写完就万事大吉实际使用时总会遇到一些奇奇怪怪的问题。我把这几年遇到、帮别人排查过的典型问题整理成了下面这张表。报错或现象最常见原因处理办法Error[Pe169]: cannot open source file xxx.hinclude 路径没配置或$PROJ_DIR$后层级写错确认头文件实际目录重新数一遍层级换电脑后大量头文件报错.ewp里存了绝对路径扫描所有name标签统一替换成$PROJ_DIR$相对路径路径加了但编译仍然找不到输入框里用全角分号或多余空格分隔统一用英文分号;或换行分隔避免行尾多余空格头文件能编译过但编辑器跳转失效IAR 索引没刷新Project Clean再重新 Build 一次必要时删除 Debug 目录重建报fatal error[Lms001]: license check failed这是 License 问题不是路径问题先解决 IAR 授权再回来检查 include 路径中文或空格目录导致解析异常个别工具链对空格敏感工程路径和目录名统一使用英文字母这张表建议截图存一份。很多路径问题本质上都是同一类错误基准目录搞错了或者分隔符、层级数写错了。用表里的思路去排查通常十分钟内能定位。3.2 那么多人把 $PROJ_DIR$ 后面数错就差一级这类问题最常见的场景就是..的数量不对。我举一个真实的例子工程文件在D:\project\code\app\app.ewp头文件在D:\project\shared\include。有人会写成$PROJ_DIR$\..\..\shared\include也有人写成$PROJ_DIR$\..\shared\include你觉得哪个对答案是$PROJ_DIR$\..\..\shared\include。因为 app.ewp 在D:\project\code\app这个三层路径下要回到D:\project需要从 app 退一级到 code再退一级到 project然后才能进入 shared。这就是两个..的原因。判断层级最笨但最可靠的方法从 .ewp 所在目录开始用文件管理器一级一级往上退每退一层记一个..退到目标目录的兄弟目录后再顺着往下写子目录名。这个物理操作比在脑子里空想要准确得多。3.3 一个容易忽略的细节大小写、分隔符和尾随斜杠Windows 系统对路径大小写不敏感所以Drivers和drivers写哪个都能找到。但团队协作时我建议你统一用小写或者严格按实际目录名的大小写来写。原因有两个一是将来如果你在 Linux 服务器上做自动化编译大小写错误就会直接暴露二是保持规范可以避免代码审查时被人纠出低级问题。分隔符方面IAR 同时支持反斜杠\和正斜杠/。比如$PROJ_DIR$\..\drivers\inc写成$PROJ_DIR$/../drivers/inc也没问题。我个人的习惯是用反斜杠因为 IAR 是 Windows 工具原生风格就是反斜杠。关键是团队内部保持一致不要今天反斜杠、明天正斜杠混着写虽然能跑但阅读体验很差。还有一个常见小毛病路径最后多写一个\。比如$PROJ_DIR$\..\drivers\inc\多一个尾随斜杠偶尔会触发奇怪的问题尤其是某些老版本 IAR。我的建议是不要写尾随斜杠干净利落。3.4 多人协作下如何避免路径配置被改乱团队协作时Additional include directories这块属于“公共区域”改坏了影响所有人。我自己的做法是在项目文档里写清楚工程目录树并标注“所有 include 路径统一使用 $PROJ_DIR$ 相对路径禁用绝对路径”。遇到不熟悉这套规则的新同事先给他的工程做一次路径巡检把绝对路径全部清扫干净。另外使用 Git 的话提交之前可以顺手打开 diff 看一眼.ewp文件的改动。如果发现某人把$PROJ_DIR$改成了C:\Users\xxx让他改回来再提交。这种检查成本很低但能避免后续每个人都被同一个问题耽误。4. 不只是头文件相对路径还能用在哪些地方4.1 链接器和汇编器里的路径也要改成相对很多人的习惯是给 C/C 编译器配好 include 路径就完事忽略了链接器和汇编器也可能需要路径。比如你工程里用到第三方静态库driverlib.a需要在Options Linker Library的 Additional libraries 里指定库文件路径或者在 Library paths 里指定库搜索路径。这些位置的填写规则和 include 路径完全一样同样支持$PROJ_DIR$相对写法。示例$PROJ_DIR$\..\lib\driverlib.a汇编器同理在Options Assembler Preprocessor里也有 include 路径配置。一个完整的工程迁移这几个位置都要检查一遍别只盯着 C/C 编译器。4.2 编译输出目录和中间文件目录的路径还有一个容易被忽略的使用场景是编译输出目录。IAR 默认会在工程目录下生成 Debug 或 Release 文件夹里面堆满 .o 文件、.map 文件、.lst 文件。如果工程目录很深或者你希望把编译产物集中放到一个 build 目录里可以在Options C/C Compiler Output里把 Object file directory 改成类似这样的路径$PROJ_DIR$\..\build\obj这样编译产生的临时文件就不会和源码混在一起也方便在版本管理里直接忽略掉整个 build 目录。很多大型工程的目录洁癖就是这么养成的源码归源码产物归产物路径全部用相对路径锚定。4.3 链接脚本和启动文件的相对定位链接脚本 .icf 文件、启动汇编文件 .s 往往也是工程里容易被“路径化”的文件。在Options Linker Config里如果你勾选了 Override default然后手动指定stm32f103.icf的路径建议同样写成$PROJ_DIR$\..\config\stm32f103.icf这样整个工程压缩包发给别人时只要目录结构不散架链接脚本就一定能被找到。我见过有人把 .icf 路径写成绝对路径结果换电脑后链接阶段爆出一堆莫名其妙的地址错误排查到最后才发现是链接脚本根本没加载进来。4.4 在源代码的 #include 中使用相对路径最后说一点和 include 搜索顺序有关的细节。C 代码里写#include gpio.h和#include gpio.h是有区别的。用双引号时编译器会先从当前源文件所在目录找这个头文件找不到才去 Additional include directories 里配置的路径找。用尖括号时编译器不搜当前目录直接按配置的 include 路径去找。这意味着如果你在一个.c文件里 include 同目录下的头文件用双引号就够了不需要额外配置 include 路径。只有那些跨目录、被多个源文件共享的头文件才需要放到 Additional include directories 里。合理利用这个规则能让你工程里的 include 路径列表保持精简而不是把所有头文件目录一股脑全塞进去配置几百行。5. 再聊几点实际使用中的心得写到这里该把核心操作都讲完了最后分享两个我在实际项目里积累的小经验。第一凡是看到 Additional include directories 里第一行是C:\或D:\开头的路径我基本可以断定这个工程换环境必炸。规范的做法是所有共享头文件都放在工程目录内部然后用$PROJ_DIR$作为锚点去引用。这个习惯花的时间很少但省掉的是每次换机器、换同事、重新发布代码时一两个小时的头文件排错时间。第二如果团队里有人用正斜杠有人用反斜杠混着写也没关系IAR 两种都能解析。真正要紧的是记住.ewp文件作为路径的“原点”不能随便挪动挪动了就要连带检查所有$PROJ_DIR$后面的层级。第三当你接手一个老工程时不要急着编译代码先花五分钟看一遍 Options 里的路径配置。这种事前检查比事后报错再排查要高效得多。路径配置干净了编译基本能一次通过路径配置一团乱麻后面就是无穷无尽的 include 报错在等你。