前言

这里用的编译开发环境(IDE)是IAR Embedded Workbench ,专门用于嵌入式系统的软件开发。到达这一步之前,所有的MCAL层、BSW层以及应用层全部搭建完毕,生成代码。


一、研发思路

在开始正式操作之前,一个很好的软件开发思路在这边记录一下:我们要学会去分析示例工程,去进行一个对照,这边的话,我手边是有一个已经搭建好的一个IAR编译工程,并且进行烧录确定能正常运行,这就作为我的示例工程,来做一个对照(实际上这种示例工程芯片厂商或者工具链厂商一般都是能找到资源的)。
那么我们要养成会分析示例工程的好习惯,比如我们以下面这个示例工程为例:
示例工程
从这个示例工程可以看出:
1.要包含的文件有Appl、BSW、Device、MCAL以及Output(这是编译过程中IAR自动生成的,搭建工程的时候可以忽略)
2.Appl、BSW以及MCAL都是和我们AUTOSAR架构相对应的部分,但是这里有一个Device部分不知道是什么,可以打开Device看看里面是啥:
在这里插入图片描述
这是一个典型的 NXP S32K14x 系列 MCU 的启动文件和系统初始化文件组织结构。 ,所以可以明确的知道这些文件来源于 NXP 提供的 SIP 包(Software Integration Package)或更广义的官方 SDK/设备支持包。 ,和我们自己的配置无关,你需要寻找对应芯片的这些启动资源。

二、操作步骤

1.新建+添加目录

第一步要学会新建,首先,你可以选择IAR的安装路径打开UI界面去操作,你也可以通过一个已经有的工程,你先在左上角找到File然后找到New Workspace,先建立一个工作区域:
在这里插入图片描述
然后,继续在左上角找到Project去新建一个New Project:
在这里插入图片描述

然后选择一个工程树模板(建议是Empty):
在这里插入图片描述
然后,选择一个文件保存路径,即新建完成。

接下来,第二步,要完成新建第一层目录 (注意我的表达,只是第一层目录)的工作,选中你要在哪个目录下面新建目录,选中这个上层的东西,然后右键找到Add然后到Add Group:
在这里插入图片描述
这边只给出一个示例,新建完成的样子,请模仿示例工程:

这边没有Output目录,大家发现没有,其实,Output是编译的时候自动生成的,没有是正常的。
好的,刚才我也强调了,这边只是第一层目录,后面还有第二层目录,那么第一层目录其实就是根据Autosar架构分出来的三层(也正好对应我们的分别在EB、Configurator以及Developer里面的工作),再加上芯片包里搭配的启动文件放置在Device目录下,那么第二层目录就要细分了(主要是根据本身工具生成的文件的目录结构来的)。第二层目录如下:
在这里插入图片描述

2.包含文件

2.1 Appl层

接下来我们需要一层一层的分析每一个目录下的文件应该包含什么,Davinci Configurator工具生成的Appl文件首先是第一层目录Appl下的GenData目录包含的内容:
在这里插入图片描述
首先,会发现在GenData这个目录下,还有一个目录是src,src是C语言中的标准组织形式中的source源代码文件的简写,里面的文件都是以_Cfg.c结尾的配置文件,而且你会发现这些文件的名字其实和我们在EB进行MCAL操作的名字是一一对应的,接下来我们来解决这些疑问:
1._Cfg.c结尾 :那是因为这是来源于Davinci的Configurator工具生成的文件。
2.MCAL操作的名字是一一对应的 :我们在EB进行完操作后生成arxml文件,然后导入Configurator进行后续操作,然后在Configurator里面进行导出的,这就是进行配置后的MCAL代码(动态代码)。
在这里插入图片描述

3.那这里放了MCAL的代码,那个MCAL目录下放的什么: 来源于芯片包的静态代码

除了src内部包含文件,可以看到在src外部,GenData内部也有一些.c文件,其实这一部分就对应的是在Davinci Configurator里面的除了和MCAL层相关的配置之外的配置,例如OS、EcuM以及RTE等:
在这里插入图片描述

这边就说完了Appl下的GenData目录下的内容,接下来可以看到Appl下的Source文件(直接从Configurator导出的Source文件复制过来即可)里面有什么文件:
在这里插入图片描述

这边很明显是分成三类,一类是我们在Davinci Deveolper里面的配置的Ct_LEDCtrl应用层组件 (即自己写的 SWC 代码)
在这里插入图片描述
第二类是_Callout_Stubs.c后缀的文件。对于这个后缀的理解,可以参考Gemini给出的解释,大致理解就是标准的流程需要调用的工具,这些工具就是这些文件:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
这里面只是定义函数,但并没有写函数的执行代码! 那为什么呢?:
事实:你的 BSW(基础软件)代码里已经写死了 Call EcuM_CheckWakeup(); 这样的指令。
后果:如果你没有这个函数(哪怕是空的),编译器(准确说是链接器 Linker)会直接报错:Undefined Reference(未定义引用)。
结局:你连 .elf 文件都生成不出来,更别说烧录了。
因为C语言代码是从上而下执行,在执行过程中,Autosar架构定制了一套标准流程,有一些函数必须要跑,如果你不需要这个函数发挥作用那么你留空就行,但是不能不写,不然代码无法往下执行!

作用:Stub 的存在,首先是为了骗过编译器,让你能把工程构建出来。
第三类是OsAppTask.c文件,这个可以理解为是 函数调度表
第四类是CddSbc.c文件,这个是复杂驱动的代码,你可以理解为这和SWC代码有相似的地方都是自己编写去使用的。

除了以上的Appl下的子目录,Appl下还有个单独的BrsAsrMain.c文件,这是什么作用:
BrsAsrMain.c 是 Vector 提供的一个标准模版文件(来源于Vector的MICROSAR包(MICROSAR 就是 Vector 卖给你的那一堆 BSW 源码和配套的生成工具。)),全称是 Basic Runtime System for AUTOSAR Main。它的作用可以用一句话概括:它是从芯片上电(汇编启动)到操作系统(OS)接管之间的“交接棒”。

注意:我们在添加文件的时候除了.c文件,.s以及.asm文件也得添加,原因是汇编和编译:
C 编译器:读取 main.c -> 翻译成汇编 -> 汇编器 -> 生成 main.o (机器码)。
汇编器:直接读取 startup.s -> 生成 startup.o (机器码)。
链接器 (Linker):把 main.o 和 startup.o 像拼积木一样拼起来,生成最终的 .elf 或 .hex 文件。

那么这个时候会问了:没有.h文件,光有.c文件有什么用?
在这里插入图片描述

2.2 BSW层

这里的BSW层的代码是Vector工具链厂商规定的流程性文件,和我们自己的配置无关,需要在Vector提供的包里面去复制过来,注意:Os层有.asm文件。
这里除了你配置的BSW层需要的静态代码之外,还有个VstdLib目录,是为了防止不同编译器(IAR, GCC, GreenHills)对标准 C 库(如 memcpy, memset)的实现不一致导致问题,Vector 自己实现了一套标准库,包含:vstdlib.c: 内存拷贝、字符串比较等基础算法的实现(为了一个统一的编译)
在这里插入图片描述

2.3 Device层

Device层都是包含一些芯片的启动代码基本都带有startup的字样,那么这个Device的文件来源于哪里呢?NXP的S32DS (S32 Design Studio)的配置生成(这里没有下载S32DS (S32 Design Studio),所以直接使用示例工程现成的)。
在 NXP 的原生开发流程(使用 S32DS)中,这些文件是这样诞生的:
1.新建工程:你在 S32DS 里点击 “New Project”,选择 S32K148 芯片。
2.自动拷贝:S32DS 会自动从它的 SDK 安装目录中,把这几个标准的启动文件拷贝到你的工程目录里。
3.配置工具:S32DS 内置了一个叫 Processor Expert (旧版) 或 Config Tools (新版) 的图形化工具。当你配置“我要用 S32K148”时,它会生成对应的 linker 文件(.icf 或 .ld)和 system 初始化代码。在这里插入图片描述

2.4 MCAL层

刚才也说过:MCAL目录下放的:** 来源于芯片包的静态代码
所以这边直接找操作流程,简单来说找到静态代码的路径:C:\NXP\AUTOSAR\S32K14X_MCAL4_2_RTM_HF3_1_0_1\eclipse\plugins ,然后找到你在EB里面配置的模块,选择对应的静态代码即可。
在这里插入图片描述
配置出来的是这样的:

3.编译预处理

其实逻辑就是把所有与之前添加的.c相关的.h文件所在路径全部添加上去。

3.1 操作

先学会在哪进行预处理(对编译器和汇编器都要进行预处理):
在这里插入图片描述
在这里插入图片描述
这边开始配置路径之后要使用相对路径,原因很简单:若使用绝对路径的话,文件发给别人,别人要自己手动一根根改成自己的路径,$PROJ_DIR$ 是一个变量,它会自动变成“当前工程文件 (.ewp) 所在的目录
一件非常重要的事情:如果你的.c文件所在目录不在当前工程目录,也就是填写的.h路径和.c文件不在同一个目录就会报错:
在这里插入图片描述

在这里插入图片描述
首先这里的BSW的.h文件,按之前配置的加上,同时有一个通用的_Common也得加上,还有对MCAL的操作的.h文件(和Configurator的那个Generate代码界面对应)。

还有个 宏定义
在这里插入图片描述
在这里插入图片描述

我不理解的点是为什么预编译为什么会填写两个宏定义,首先头文件里面包含了很多宏定义,为什么就写这两个在这里?
这两是 全局宏定义:
USE_STDPERIPH_DRIVER:决定“用哪套工具”
背景:STM32 的官方固件库(标准库)设计得很灵活。为了兼容不同的开发习惯,库文件的代码里写了大量的 #ifdef USE_STDPERIPH_DRIVER … #endif。
作用:如果你不在编译器选项里定义它,库文件里那些真正干活的功能代码就会被 #ifdef 挡在外面,编译器根本看不见,最后链接时就会报错说“找不到函数定义”。
为什么不写在头文件里? 虽然也可以写在 stm32f10x_conf.h 里,但写在编译器选项里更“霸道”且直观。这是一种强制手段,确保不管哪个文件,只要用了库,就一定是开启了标准库模式的。
STM32F10X_HD:决定“芯片的体格”
背景:STM32F10x 系列家族非常庞大,有“小容量”、“中容量”、“大容量”、“互联型”等。不同容量的芯片,其内部 Flash 大小、RAM 大小、甚至启动地址是不一样的。
作用:这个宏主要被 startup_stm32f10x_xx.s(启动文件)和 system_stm32f10x.c(系统初始化文件)使用。
如果你选了 _HD(大容量),启动文件就会分配适合大容量芯片的堆栈大小。
如果你选了 _MD(中容量),配置就会变小。
为什么必须写在这里? 因为启动文件通常是汇编写的,它最早运行,而且往往不包含复杂的 C 语言头文件逻辑。通过编译器选项直接传入这个宏,能确保从程序的第一行代码开始,就知道自己运行在什么规格的芯片上。

选设备

在这里插入图片描述

链接器

在这里插入图片描述
在这里插入图片描述

调试器
在这里插入图片描述

在这里插入图片描述

3.2 路径列表分析与修正建议

以下是针对各类路径的详细分析与注意事项:

A. 基础软件层 (BSW) - Vector MICROSAR

这些路径指向标准 BSW 模块(如 Can, Com, Os 等)。

  • 路径示例
    $PROJ_DIR$\BSW\_Common
    $PROJ_DIR$\BSW\BswM
    $PROJ_DIR$\BSW\Can
    ... (中间省略) ...
    $PROJ_DIR$\BSW\WdgM
    
  • 分析:Vector 的 BSW 模块头文件通常直接放在模块根目录下(例如 BSW\Can\Can.h),或者放在 BSW\Can\include 下。
  • 潜在风险请检查您的 BSW 文件夹结构。如果您编译时报错 file not found,请确认这些模块下是否有一个 include 子文件夹。如果有,您需要把路径改为 $PROJ_DIR$\BSW\Can\include。不过大多数 Vector SIP 包直接指到模块根目录也是可以的(因为头文件就在根目录)。
    在这里插入图片描述
    首先这里的BSW的.h文件,按之前配置的加上,同时有一个通用的_Common也得加上,还有对MCAL的操作的.h文件(和Configurator的那个Generate代码界面对应)。
B. 微控制器抽象层 (MCAL) - NXP 原厂驱动

这些路径指向硬件驱动(如 Adc, Mcu, Port 等)。

  • 路径示例
    $PROJ_DIR$\MCAL\Adc_TS_T40D2M10I1R0\include
    $PROJ_DIR$\MCAL\Base_TS_T40D2M10I1R0\include
    ... (中间省略) ...
    $PROJ_DIR$\MCAL\Wdg_TS_T40D2M10I1R0\include
    
  • 分析:注意这里您已经加上了 \include 后缀。这是非常正确的!NXP 的 MCAL 驱动通常把 .h 文件严格放在 include 子文件夹里,把 .c 放在 src 里。这些路径不要改动。
  • 修正建议:您的原始列表最后一行 $PROJ_DIR$\MCAL\Dio_TS_T40D2M10I1R0\include 与中间的一行重复了。虽然没坏处,但建议删除重复项以保持整洁。
    在这里插入图片描述
    这里注意有一个通用的叫做Base别忘记加上。
C. 应用与配置层 (Appl & GenData)

这些是生成的配置代码和您的应用代码。

  • 路径示例
    $PROJ_DIR$\Appl\GenData            <-- 存放生成的 _Cfg.h 和 _PBcfg.h
    $PROJ_DIR$\Appl\GenData\include    <-- 存放和MCAL驱动有关的配置文件的.h,和src里面的.c对应
    $PROJ_DIR$\Appl\GenData\Components <-- 存放生成的组件头文件(RTE和Memmap(内存映射))
    $PROJ_DIR$\Appl\Include            <-- 存放全局基础环境与静态集成文件
    
  • 分析:这部分至关重要。如果缺少 GenData,所有的模块配置(如 Dio_Cfg.h)都无法被找到,编译将直接失败。
  • \Appl\GenData\include <-- 存放和MCAL驱动有关的配置文件的.h,和src里面的.c对应:
    Rte_<SWC名>.h: 这是 RTE (Runtime Environment) 为每个软件组件生成的专属头文件。
    它定义了该组件能调用的 API(例如 Rte_Read_…, Rte_Write_…)。
    它定义了该组件内部 Runnable 的函数原型(例如 Ct_LEDCtrl_MainFunction)。
    _MemMap.h: 这是内存映射文件。AUTOSAR 要求每个组件的代码段、变量段都要用 MemMap 包裹,以便链接文件(Linker Script)将它们放到正确的 RAM/Flash 地址。
D. 启动与辅助代码 (Device & Shared)
  • 路径示例
    $PROJ_DIR$\Appl\shared\zBrs_...    <-- Vector BRS (启动辅助) 的相关头文件
    $PROJ_DIR$\Device                  <-- 芯片寄存器定义 (S32K148.h)
    
  • 分析Device 目录通常包含 NXP SDK 提供的寄存器定义头文件(如 S32K148.h),必须包含,否则 startup 代码或 Mcu 模块会报错。

4.编译错误处理

这一部分记录我自己碰到的一些报错以及我的解决方法,如果能帮到有同样错误的,那很开心!

4.1 路径问题

如果你的.c文件所在目录不在当前工程目录,也就是 填写的.h路径和.c文件不在同一个目录(即使是相对路径也只是在对应文件目录下搜索) 就会报错:
在这里插入图片描述
这个很简单,只需要重新把对应报错的路径换成.c和.h一样路径下就行。

4.2 文件内容问题

报错信息:Error[Li005]: no definition for "Dio_writeChannel" [referenced from C:\AUTOSAR\IAR2\Debug\Obj\Ct_LEDCtrl.o]
可以分析出来报错的点是没找到Dio_writeChannel函数的定义(直接找AI问也行),那么,我们再去看这个报错的文件是\Ct_LEDCtrl.o文件,也就是二进制文件,那么我们要去找对应的.c文件——Vscode使用Ctrl+Shift+F,搜索Dio_writeChannel即可找到Ct_LeDCtrl.c文件。
在这里插入图片描述
那么怎么修改呢,其实你可以直接问AI,我这边是问的Gemini 3 pro:
在这里插入图片描述

原来是要大写W,那简单!

报错信息:Error[Li005]: no definition for "Os_Task_Default_int_Task" [referenced from C:\AUTOSAR\IAR2\Debug\Obj\Os_Hal_Context_Lcfg.o]
这边还是一样的思路在Vscode里找文件,但是这边不是函数写错了,你会发现没有这函数,其实我们最小工程没有用到,但是我们配置了,按照Cfg文件的标准流程,我们需要去自己写一个.c函数文件,即使是空的也行,只要能往下走(Gemini牛逼):

/* 必须包含 Os.h,因为它定义了 TASK 这个宏 */
#include "Os.h"

/* * 这里的名字 "Default_int_Task" 必须和你报错信息里的名字一模一样!
 * 报错说:no definition for "Os_Task_Default_int_Task"
 * TASK宏会自动给名字加上 "Os_Task_" 前缀,所以这里括号里只写 Default_int_Task
 */
TASK(Default_int_Task)
{
    /* 这里是任务的开始 */
    
    /* * 将来你的业务逻辑(比如点灯代码)就写在这里 
     * Rte_Call_...();
     */

    /* 对于 Basic Task,必须调用 TerminateTask 结束,否则系统会跑飞 */
    TerminateTask();
}

如此一来,错误全部改完:
在这里插入图片描述

5.工程烧录问题

5.1 运行结果与代码逻辑不匹配

我在烧录的时候发现,我明明写的是灯闪烁的逻辑,但是灯却常亮,询问Gemini(真的牛),我才知道我的BSW状态管理只有一个上电和下电,没有一个运行,所有一直处于休眠状态:
在这里插入图片描述
这个时候你就需要给Run加port去Connect:
在这里插入图片描述
右键后出现Connect和Connect to new port,Connect是在Developer中新建过Port去点,但是我们这边的没有在Developer上新建,所以我们用Connect to new port,让系统自动帮我们建一个。
在这里插入图片描述
以上是选择对接的SWC模块,以下是Port的一些配置:
在这里插入图片描述
接着要去Developer里面进行操作,先保存Configurator然后再用Developer打开:
在这里插入图片描述
然后在Developer里面找到对应的Port:
在这里插入图片描述
现在就是要用接口去发送一个Run的请求,可以看到这个Port的Direction是Sender,现在分析逻辑对于这个Port来说的话,它只需要发送给一个run就行,只需要运行一次,发我们对接的那个组件Runnable(类似于信使),可以是int,因为int是初始化,只运行一次。

在这里插入图片描述
操作完之后,你就可以生成代码,然后生成完之后,你需要在应用层代码哪里自己撰写一下逻辑,只有你知道这个组件初始化时应该请求运行还是释放运行:

原因 说明
DaVinci 只生成框架 DaVinci 生成的是 RTE 接口和函数骨架,业务逻辑需要开发者填写
行为是应用特定的 只有你知道这个组件初始化时应该请求运行还是释放运行
配置 vs 实现分离 DaVinci 根据 ARXML 配置生成接口声明,但实现是手写代码
灵活性需求 有些组件初始化时可能需要条件判断(如根据诊断状态决定)

在这里插入图片描述
这个截图还有点问题,没有传参,代码正确如下:

BswM_ESH_RunRequest RunMode = REQUESTED;
(void)Rte_Write_CtLed_Request_ESH_RunRequest_0_requestedMode(RunMode);

接下来去烧录就没问题了!


总结

这就是利用IAR进行编译的全部内容,希望能帮助到大家!!

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐