拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

IAR报错排查实战:从错误码分诊到链接、授权与RTOS移植

IAR报错排查实战:从错误码分诊到链接、授权与RTOS移植

1. IAR报错处理的第一原则:别从最后一条错误开始读

刚上手 IAR Embedded Workbench 的人,十有八九会犯同一个毛病:编译失败,Build 窗口一拉到底,盯着最底下那条红色的 Error 开始搜。搜半天没结果,最后发现在输出窗口往上翻二十行,真正的根因早就写在那里了。我自己也这么干过整整一年,直到有一次被一个Error[Li005]折腾了一下午,才发现罪魁祸首是三条警告之前的一个头文件路径写错了。

IAR 的编译流程大致是:预处理器展开、编译器逐个翻译单元编译、汇编器处理.s文件、链接器把所有目标文件和库拼在一起。每一个阶段都有自己的错误编号前缀,这个前缀就是最便宜的诊断线索。Pe开头的多半是编译器(Parser/Compiler)抛的,比如Error[Pe020]、Fatal Error[Pe1696];Li是 Linker,典型的是Error[Li005];Lp是 Linker 的段放置(Placement),Error[Lp011]最常见;Lc跟链接器配置文件.icf的语法有关;LMS是 License Management System,也就是授权系统;Be通常是构建工具链本身或者命令行调用出的问题。搞清这一层映射,等于先把报错范围砍掉一大半。

真正麻烦的是"报错位置"和"根因位置"不一致。举个例子,某个.c文件里写了一行#include "app_config.h",但这个头文件被别人删了,编译器会先报一条Fatal Error[Pe1696]: cannot open source file,然后紧接着对这个文件里所有用到app_config.h中宏的地方报一串identifier is undefined。后面那几十条错误全是噪声,修好第一条就全消失了。所以我的习惯是:Build 窗口从上往下看,找到第一个"Fatal"级别的错误,先只解决它,其他一律当噪声忽略。IAR 遇到致命错误会跳过当前文件继续编下一个,所以后面的错误既可能是连锁反应,也可能是完全独立的第二个问题,得先排除掉连锁反应才看得清。

下面这张表是我自己在项目里沉淀下来的快速分诊表,平时对着它扫一遍,基本能定位到该去哪个配置页面翻东西:

报错前缀/关键词所属阶段首要排查位置典型成因
LMS001 / license授权IAR License Manager授权未激活、主机标识变化、版本与授权不匹配
Pe1696 / cannot open source file预处理Options → C/C++ Compiler → Preprocessor头文件路径缺失、$PROJ_DIR$用法错误
Pe020 / identifier is undefined编译头文件与宏定义头文件没包含、条件编译宏未开、拼写错误
Li005 / no definition链接工程 Group 与库配置源文件未加入工程、库未链接、函数名不匹配
Lp011 / section placement failed链接.icf文件与器件型号选错芯片、RAM/ROM 区间写错、大数组占用 RAM
Lc / config error链接配置.icf文件语法.icf表达式写错、region 定义冲突
The generation feature is not of version工程打开.ewp版本号工程由更高版本 IDE 创建
连接/下载失败调试Debugger → Setup → Driver仿真器驱动选错、SWD 引脚被复用、Flash loader 不匹配

再补一句关于环境的小经验:同一个 IAR 安装目录下可以并存多个产品线,比如 EWARM(ARM 内核)、EW8051(8051 内核,CC2530 属于这一类)、EWSTM8、EWAVR,它们各自有独立的可执行文件、独立的插件目录,甚至独立的授权。很多人第一次拿到 CC2530 的例程打不开,以为软件坏了,其实是装了 EWARM 却拿它去开 8051 工程,这个后面单独说。

2. 授权与许可类报错:Fatal Error[LMS001]的完整处理链路

Fatal Error[LMS001]: License check failed. Use the IAR License Manager to resolve the problem.这条报错在搜索引擎里被问得最多,因为它出现得毫无征兆——昨天还好好的,今天打开就报,甚至刚装完第一次编译就报。它跟代码一点关系都没有,纯粹是 IAR 在启动构建之前做的一次授权校验没通过。

授权校验这件事,IAR 做得比较严格。它在安装时会生成一个绑定当前机器的标识(通常跟网卡物理地址、主板信息、磁盘卷序列号这类硬件指纹相关),然后用这个标识去激活一份授权。校验的内容包括:授权文件是否存在且未过期、授权类型是否覆盖当前使用的产品线、绑定的硬件标识是否和当前机器一致、授权的节点数是否已被占满。任何一项不满足,都会在编译开始前直接拦截,连一个.c文件都不会编。

处理这条报错,正规路径只有一条:打开 IAR License Manager,看清楚当前状态,再按状态决定下一步动作。我习惯按这样的顺序走:

  1. 启动 IAR License Manager(在开始菜单里能搜到,或者从 IAR 的 Help 菜单进入)。
  2. 查看列表里有没有已激活的授权,以及它对应的产品线和到期时间。如果列表是空的,说明激活信息丢了,需要重新走一次激活流程。
  3. 如果有授权但状态异常,先检查系统时间。授权校验对系统时间很敏感,如果系统时间被改到了授权生效日之前,或者干脆错到了几年后,会直接判定授权无效。这个坑我踩过一次,主板电池没电导致时间回到出厂值,折腾了半天才发现。
  4. 如果提示绑定的标识不匹配,说明硬件指纹变了。换主板、换网卡、把系统盘插到另一台机器,都会触发这种情况。解决办法是走一次重新激活,把授权重新绑到当前机器上。
  5. 如果提示节点数已满,多半是之前的机器没有正确释放授权。这种情况需要联系授权管理员,在管理端释放掉旧节点。

要特别说一句:网上流传着大量所谓"密钥生成器""注册补丁""一键激活工具",我的建议是碰都别碰。第一,这类东西在合规层面站不住脚,商业项目里用会带来实打实的法律风险;第二,它们往往通过替换 IAR 安装目录下的授权相关文件来起作用,版本一升级就失效,还会把原本正常的安装搞得乱七八糟,最后连正版授权都激活不上去,只能整个卸载重装。我在上一家公司见过同事这么干,结果重装加清理注册表花了小半天。

还有一种容易被误判成授权问题的情况:报错发生在链接阶段,提示找不到某个库或者某个运行时组件。这本质上不是 LMS 报错,而是授权类型不覆盖对应的功能模块(比如某些高级优化选项、某些特定内核的支持包),报错文本里会提到具体的 feature 名称。这种时候要看清楚报错里的功能名,再去核对授权覆盖范围,而不是盲目重装。

顺便说一下多版本共存的处理。很多人机器上同时装着 EWARM 的 8.x 和 9.x,或者同时装着 EWARM 和 EW8051。每个大版本各自管理自己的授权,License Manager 里能看到多条记录。我的建议是:不要为了省事把老版本的授权文件拷到新版本的目录下,格式和校验规则可能不兼容,轻则报错重则让新版本也起不来。要升级就老老实实走一次激活流程。

3. 编译期报错:头文件路径、宏定义与"未定义标识符"三件套

Pe系列错误是日常遇到最多的一类,其中Fatal Error[Pe1696]: cannot open source file "xxx.h"又是最高频的。表面上看这是"文件找不到",实际上有五种截然不同的成因,需要分别处理。

第一种,路径压根没加。IAR 的头文件搜索路径在Project → Options → C/C++ Compiler → Preprocessor这一页的Additional include directories里配置。注意这里填的每一条路径,基准目录是工程文件(.ewp)所在目录,而不是.c文件所在目录。这个规则非常关键,很多人习惯性地写相对路径..\inc,结果发现只有放在工程同级的文件能找到,下层目录里的全丢。正确写法是用内置变量,比如$PROJ_DIR$\..\..\middlewares\inc,这样无论工程文件被挪到哪里,路径都不会断。

第二种,大小写和分隔符问题。Windows 文件系统不区分大小写,所以#include "GPIO.h"能找到gpio.h,一切正常。但如果你在 Linux 构建机或者区分大小写的文件系统上编译同一份代码,立刻报文件不存在。同理,反斜杠\在字符串里有转义含义,#include "hardware\led.h"在某些预处理实现下会被解释成带转义字符的路径。统一用正斜杠\改成/是最省心的做法,IAR 在 Windows 上同样接受正斜杠。

第三种,路径里有空格或中文。IAR 对包含空格和中文的路径兼容性时好时坏,尤其是当工程放在桌面或者"我的文档"这类带中文的默认目录下时。我见过一个案例,工程路径里有个中文目录名,单个头文件包含没问题,但一旦用了批处理脚本调用iarbuild就疯狂报错。所以我的习惯是:任何嵌入式工程都放在纯英文、无空格的短路径下,比如D:\work\stm32_f103\proj,这个习惯能省掉大量玄学问题。

第四种,文件真的不存在。比如从别人那里拷来的工程只带了源码没带Inc目录,或者从 Git 上拉下来忘了初始化子模块,中间件目录是空的。这种时候报错路径会明显指向一个中间件名字,一眼能看出来。

第五种,宏开关把整个头文件的内容"吃"掉了。这种情况比较隐蔽:文件明明存在,也不报cannot open,但所有接口都报未定义。原因是头文件的实体被#ifdef包了起来,而对应的宏没定义。典型的像 HAL 库的stm32f1xx_hal_conf.h,里面有几十个#define HAL_XXX_MODULE_ENABLED,注释掉哪一个模块,哪个模块的接口就全部消失。

Error[Pe020]: identifier "xxx" is undefined也是同理。看到这条错误,我的排查顺序是:

  1. 报错的标识符是不是标准类型?uint8_t、int32_t这类需要#include <stdint.h>;bool、true、false需要#include <stdbool.h>;size_t需要#include <stddef.h>。裸工程不包含任何头文件时,这些全都不认识。
  2. 是不是自定义类型?结构体、枚举、typedef都在头文件里,忘了包含就是一堆 undefined。
  3. 头文件包含了但被条件编译屏蔽了,检查该头文件顶部的宏保护。
  4. 是不是在.c文件里定义了函数却没在头文件里声明,然后在别的文件里调用?C 语言默认不允许隐式声明(C99 之后严格禁止),这时报的也是 undefined。
  5. 拼写错误。别笑,SysTick_Handler和SysTickHandler差一个下划线,编译器不会给你任何提示,只报找不到。

关于警告,我想多说一句。IAR 的警告分级可以通过Project → Options → C/C++ Compiler → Diagnostics调整,很多人嫌烦直接全关。我的做法恰恰相反:把警告开得比较全,然后把"较真的警告"逐条处理。原因是嵌入式代码里的警告往往指向真实缺陷,比如有符号无符号比较、隐式类型截断、未使用的变量、switch 缺 default 分支。这些问题在 PC 上顶多算出错,在 MCU 上可能直接导致数据错乱或者栈溢出。我养成的习惯是每次接入一份新代码,先把警告清零,再开始做功能,这样后面出问题时心理负担小很多。

4. 链接期报错:Li005、Lp011 与 .icf 链接文件的段放置

编译全部通过不代表能出固件。链接阶段是 IAR 报错里最考验理解深度的一块,也是很多人第一次接触.icf文件的地方。

Error[Li005]: no definition for "函数名" [referenced from xxx.o]的含义是:某个目标文件引用了这个符号,但链接器在所有输入里找不到它的定义。常见成因有四个。一是源文件没加进工程,比如FreeRTOS的tasks.c、queue.c忘了拖进 Group,编译阶段不会报错(因为没有文件引用它们的内部实现),到链接才炸。二是库文件没链接,比如用了sinf()却忘记在Linker → Library里勾选数学库,或者用的是精简版库不含浮点函数。三是条件编译导致的实现缺失,比如某个函数体被#if (configUSE_TRACE_FACILITY == 1)包着,而配置里是 0。四是 C 和 C++ 混编时忘了extern "C",导致符号名被 C++ 的 name mangling 破坏,链接器找不到对应名字。

Error[Lp011]: section placement failed是另一类高频错误,报错文本会带上一个"总大小"和一个"可用区间",比如提示有若干字节无处安放。这条错误的本质是:链接器手里的.icf文件定义了各个段可以放在哪些内存区间,而某个段的实际占用量超过了区间容量。排查方向有这么几个:

  • 器件型号选错。IAR 的Project → Options → General Options → Target里选的芯片决定了预定义的内存大小。选成 RAM 更小的型号,或者选成了 Flash 只有 64K 的版本而不是 128K 的版本,链接一定会失败。这个错误很容易被忽略,因为报错信息只谈容量不谈型号。
  • 常量数组没加 const。一个几百字节的查表数组,如果声明成uint8_t table[] = {...}而不加const,编译器会把它放到.data段,也就是既占 Flash 又占 RAM。加个const它就只待在 Flash 里了,通过__flash或者直接 const 修饰即可。这是真实项目中省 RAM 最立竿见影的一招。
  • 栈和堆开得太大。.icf里通常有__ICFEDIT_size_heap__和__ICFEDIT_size_stack__两个符号,直接决定堆栈预留空间。默认值有时是 8K 或者更大,小容量芯片上直接吃掉一半 RAM。
  • .icf里的区间写错了。手工改过链接文件的话,region 的起止地址、block的 size 表达式都可能是罪魁祸首。Lc开头的错误一般就是.icf的语法问题。

现在说回一个具体的例子,这个语法在很多代码库里能见到:

uint8_t ucheap[4096] __section(".heap") = {0};

这行的意图是把一个 4K 的数组强制放进名为.heap的段。__section(...)是 IAR 提供的段放置修饰符,旧版本里还有@ "section_name"的写法。问题在于,.heap这个名字在 IAR 的默认链接配置里是被 C 库运行时已经占用的段——它对应__ICFEDIT_size_heap__定义的那块区域。你手动往里塞一个 4K 数组,等于声称这块内存既归库管又归你管,链接器要么报 placement 冲突,要么在运行时出现堆区和你的数组重叠,表现就是"malloc 之后某个全局数组莫名被改写"这种极其难查的故障。

正确的做法是自己定义一个专属段,然后让.icf明确分配它的位置。.icf里加:

define symbol __MY_HEAP_start__ = 0x20004000; define symbol __MY_HEAP_end__ = 0x20004FFF; define region MY_HEAP_region = mem:[from __MY_HEAP_start__ to __MY_HEAP_end__]; place in MY_HEAP_region { section MY_HEAP };

然后 C 文件里这么写:

#pragma section = "MY_HEAP" uint8_t ucheap[4096] @ "MY_HEAP" = {0};

这里要注意两个细节。第一,#pragma section后面的名字和@后面的名字必须完全一致,大小写敏感。第二,.icf里section MY_HEAP的名字也要一致,且不带点号。如果你的工程里在别处已经有同名段,链接器会合并它们,所以命名最好带项目前缀避免撞车。

排查链接问题时我还依赖一个工具:Project → Options → Linker → List里可以生成.map文件。把这个文件打开,能看到每个段被放在了哪个地址、占了多少字节、还剩多少。Lp011 报错时,我第一件事就是翻.map的最后几行,那里通常会列出一个"总需求"和"可用空间"的对比,一眼就能看出是哪个段撑爆了。

5. 器件支持与插件类报错:CC2530、STM8、GD Addon 的坑

这一类报错不来自代码,来自"工具链配置和芯片对不对得上"。

先讲一个高频疑问:打开别人的工程,弹出一句 "The generation feature is not of version 18"。这句话的含义是,工程文件(.ewp/.eww)里记录的格式版本号高于当前 IDE 支持的版本,说白了就是"这个工程是用比我新的 IAR 建的,我打不开"。IAR 的工程文件是 XML,你可以用文本编辑器打开.ewp,在<state>节点下能看到类似<state>$version$</state>或者具体的版本号字段。反过来的情况也存在:用新版本打开老工程,通常能正常打开,只是首次保存后会提示工程格式升级。

这句话的处理办法没什么技巧,就三条路:升级到创建该工程的 IAR 版本或更高;让对方用低版本 IDE 重新导出一份工程;或者手工把.ewp里的版本号字段改成当前版本支持的数值——第三条我试过,简单工程能蒙混过关,但只要工程里用了高版本才有的配置项(比如新的优化选项、新的段放置语法),打开后照样会在编译时报一堆莫名其妙的错,所以不推荐作为常规手段。

再说产品线混用。IAR 的产品线是按内核划分的,EWARM 管 ARM Cortex-M/A/R,EW8051 管 8051 内核,EWSTM8 管 STM8,EWAVR 管 AVR,EWRL78 管瑞萨。它们共用一部分安装框架,但编译器、汇编器、链接器、调试驱动全是独立的。CC2530 是 8051 内核,必须用 EW8051;STM8 必须用 EWSTM8;STM32 用 EWARM。你装了 EWARM,双击 CC2530 的.eww,系统可能用 EWARM 去尝试打开,然后报器件找不到或者工程格式不识别。正确做法是右键选择用 EW8051 打开,或者干脆从 EW8051 的启动界面里选File → Open → Workspace。

然后是插件目录。IAR 安装根目录下有一个common\bin\Plugins之类的路径,里面放着供 IDE 调用的各种扩展,比如调试探针的接口组件、器件厂商提供的支持文件。这个目录平时不需要动,但如果杀毒软件误删了里面的文件,会表现为"某个仿真器突然找不到了"或者"某项功能报初始化失败"。遇到这类报错,先检查这个目录的完整性,必要时覆盖安装修复。

GD Addon 是兆易创新提供的器件支持包,解决的是 IAR 原生器件库里没有 GD32 系列的问题。安装它要注意几点:从官方渠道下载对应 IAR 版本的安装包,安装程序需要指定 IAR 的安装根目录,安装完成后必须重启 IAR 才能在Options → General Options → Target → Device的下拉列表里看到 GD32 的型号。我见过有人装完没重启就去找器件,然后以为安装失败,来回装了三遍。另一个常见问题是版本不匹配:Addon 的版本号和 IAR 大版本之间是有对应关系的,装错了会出现"器件列表里有 GD32,但一编译就报找不到启动文件或者找不到链接脚本",因为 Addon 里自带的.icf和.s文件没有被正确注册。

顺带提一个操作系统移植相关的坑。很多人从 ST 的官方例程或者 GitHub 上拷来一份 RT-Thread、FreeRTOS 工程,用 IAR 打开后发现启动文件报一堆语法错误,形如bad instruction、unexpected token。原因很简单:那份启动文件是给 GCC 写的,用的是 GNU 汇编语法,而 IAR 用的是自己的汇编器,语法完全不同(IAR 用PUBLIC、SECTION、THUMB、REQUIRE8这类伪指令,GCC 用.global、.section、.syntax unified之类)。这种情况下不要试图改那份文件,正确做法是从 IAR 的安装目录或者 CubeMX 里找 IAR 版本的启动文件替换掉,省时得多。

6. 移植 FreeRTOS 与 RT-Thread 时的报错集中区

实时操作系统移植是 IAR 报错的另一个高发场景,因为涉及到汇编、链接脚本、中断向量三者的配合。

第一类:中断向量重复定义。FreeRTOS 的port.c里通常定义了几个处理函数,比如vPortSVCHandler、xPortPendSVHandler、xPortSysTickHandler。同时,芯片的启动文件里默认已经定义了SVC_Handler、PendSV_Handler、SysTick_Handler这几个弱符号。如果不在FreeRTOSConfig.h里做映射,链接器会看到两套实现,报重复定义;更隐蔽的情况是链接器挑了一个而你想用的是另一个,编译链接都过了,但一调度就进 HardFault。标准做法是在FreeRTOSConfig.h里加上这几行:

#define vPortSVCHandler SVC_Handler #define xPortPendSVHandler PendSV_Handler #define xPortSysTickHandler SysTick_Handler

这样port.c里定义的名字就会被宏替换成启动文件里的名字,正好覆盖掉弱定义。

第二类:堆空间告急。FreeRTOS 自带heap_1.c到heap_5.c五个内存管理实现,它们自己维护一块静态数组作为堆。这块数组的大小由configTOTAL_HEAP_SIZE控制,默认值往往偏大,比如 17K。在 STM32F103C8T6 这种只有 20K RAM 的芯片上,17K 的堆加上栈、加上 BSS 段,Lp011 报错几乎是必然的。这时候要么把configTOTAL_HEAP_SIZE砍到 4K 到 6K,要么改用heap_4.c并配合更精细的内存规划。注意,这里的"堆"跟 IAR 的.icf里那个__ICFEDIT_size_heap__是两回事,如果你同时用 C 库的malloc和 FreeRTOS 的pvPortMalloc,那就是两块独立内存,非常容易算错总量。

第三类:__section相关的自定义段。上一节讲的__section(".heap")那种写法,在 RTOS 移植代码里偶尔能见到,多半是为了把某个内存池放到指定位置。处理思路和前面一样:定义自己的段名,在.icf里明确放置,不要跟系统保留段重名。

第四类:编译选项不匹配。移植 RT-Thread 时经常遇到Error[Pe147]之类的错误,指向内联汇编或者特定语法。这是因为 RT-Thread 的port.c里大量使用了行内汇编和编译器特定的扩展语法,不同编译器的写法不一样。RT-Thread 的代码库通常分libcpu/arm/cortex-m3/iar/和gcc/两个目录,必须选 iar 目录下的那份。选错了就是一堆语法错误。

第五类:宏配置冲突。FreeRTOSConfig.h和rtconfig.h里的宏如果和工程里其他地方定义的宏撞车,会报重复定义警告,某些情况下升级为错误。典型的是USE_HAL_DRIVER、STM32F103xB这类芯片相关的宏,不同来源的头文件可能会重复定义,用#undef处理掉或者统一到一处定义就行。

我在移植 RTOS 时有个固定的验证流程,分享给经常踩这块的同行:第一步先只让内核跑起来,任务里什么都不做,只有一个空循环加一个延时,确认调度器能切换;第二步加串口输出和printf重定向,确认中断能正常响应;第三步加外设驱动。分三步走的好处是,每一步只引入一个新的变量,出问题时排查范围极小。很多人一上来就把所有驱动都塞进去,结果在 HardFault 里挣扎一整天。

7. 调试下载阶段:仿真器找不到、Flash loader 报错的处理

代码编过了、链接过了、固件生成了,点下载却报错,这一段的报错文本跟前面完全不同,属于硬件连接和调试配置的范畴。

最常见的一条是"找不到调试探针"或者"无法连接到目标"。排查顺序我是这样走的。先看Project → Options → Debugger → Setup里的Driver选对没有——机器上插着 ST-Link,驱动却选成 J-Link,那当然找不到。改完之后,Download页签里的Flash loader也要跟着对应,因为不同探针支持的目标器件列表不一样。再往下看接口类型,SWD 还是 JTAG,引脚映射对不对,速度是不是设得太高(长杜邦线加高速率,通信会不稳定,降到 1MHz 试试)。最后看目标板供电,很多初学者忘了给板子上电,或者用探针供电但电流不够,一连接就掉。

还有一类很容易让人怀疑硬件的现象:第一次能下载,之后就连不上了。这通常是因为程序里把 SWD 用的引脚复用成了普通 GPIO。STM32 上 PA13/PA14 默认是 SWDIO/SWCLK,如果程序初始化时把它们配置成普通输出,下载完复位运行后,调试接口就被程序抢走了。解决办法是在下载配置里勾选"连接时复位"或者"在复位后暂停",实际 IAR 里对应Debugger → Reset里的选项,选择在复位状态下建立连接。另一个办法是手工把板子置于复位状态(按住复位键),点击下载,在连接建立的一瞬间松开。这招虽然土,但屡试不爽。

Flash 相关的报错还有几种:提示芯片被读保护,需要先全片擦除;提示 Flash loader 加载失败,一般是Flash loader路径下的.flash文件损坏或者跟器件不匹配,重新选一次器件就好;提示校验失败,可能是写进去的数据和读出来的不一致,这时候要怀疑时钟配置是否正确、Flash 等待周期是否设置合理。

调试器还有一个跟工程配置强相关的坑:优化等级太高导致单步调试时断点乱跳、变量看不见。IAR 默认在 Debug 配置下是低优化,但有些人为了对比性能会把 Release 配置拿去调试。高优化下变量可能被完全优化掉或者合并,static变量的值可能永远显示为上一次刷新。这时候如果看到一个变量在单步过程中数值诡异变化,别急着怀疑代码逻辑,先确认当前用的是哪个 configuration,以及优化等级是不是High (Balanced)或更高。

8. 几个我长期坚持的习惯,能省下大量排查时间

报错处理这件事,本质上是一个信息检索和假设验证的过程。工具再好,也比不上几条刻进肌肉记忆的习惯。

第一条,新环境先跑最小可运行工程。拿到一块新板子,或者在新电脑上装完 IAR,我做的第一件事永远是新建一个空工程,配好器件型号,写一个空的main加一个死循环,编译、下载、点灯。这一步不涉及任何中间件和第三方代码,纯粹验证工具链、授权、探针、Flash 算法这条链路是通的。链路通了之后,后面报的每一个错都可以归因到代码本身,不用再怀疑环境。这一步看起来浪费时间,实际上是我用过的最高效的"环境校准"手段。

第二条,把 Build 日志存成文件。IAR 的 Build 窗口可以右键保存输出,我习惯每次遇到难缠的报错就把日志存下来,文件名带上日期和一句简述,比如20240312_lp011_ram_overflow.log。存多了之后会发现,同一类问题在不同的项目里反复出现,直接翻旧日志比重新搜一遍快得多。命令行构建的情况下更简单:

iarbuild.exe app.ewp -build Debug -log warnings > build_debug.log 2>&1

把日志丢进全文搜索,或者用脚本统计各类错误码出现的频次,能快速判断这次是"老问题复发"还是"新问题"。

第三条,路径全部用$PROJ_DIR$打头。这个习惯解决的是工程共享问题。用相对路径和绝对路径混着写,换台机器、换个盘符就全军覆没。统一用$PROJ_DIR$,工程放在哪里都能编。

第四条,把每次报错的成因和解决方式记进一个 Markdown 文件。我的这个文件现在有三十多条,从授权绑定到.icf段冲突,从启动文件语法到 SWD 引脚复用。写的时候多花两分钟,下次遇到同类问题能省半小时。这个文件比任何论坛帖子都管用,因为它记录的恰好是我自己踩过的坑。

第五条,遇到玄学报错时,先做一次完整重建。IAR 的增量编译偶尔会因为依赖关系判断失误,出现"改了头文件但没重新编译相关源文件"的情况,表现就是报错指向的代码明明已经改好了。Project → Rebuild All一遍,如果问题消失,那就是增量编译的依赖缓存问题;如果问题依然存在,才值得深入分析。这个动作只需要几秒钟,却能排除掉相当一部分假故障。

最后再补一个容易被忽略的细节:IAR 的工程配置里有个Project → Options → Build Actions,可以配置编译前和编译后执行的命令。如果某个工程被前任维护者加了奇怪的预处理脚本,脚本执行失败也会以报错形式出现在 Build 窗口里,而且报错文本看起来跟编译毫无关系,比如"找不到某个批处理文件"。接手别人工程时,顺手看一眼这个页面,能挡掉一些莫名其妙的错误。

返回列表