AUTOSAR模式接口:Simulink声明组、切换事件与ARXML导出
简介:围绕MATLAB环境下AUTOSAR软件开发的模式接口样例模型展开,面向汽车电子软件工程师、嵌入式开发者及高校相关方向学习者,帮助梳理从模式定义、接口设计到RTE配置、模型集成、代码生成与测试验证的完整链路。资源以Simulink/Stateflow模型和AUTOSAR描述文件为核心,包含模式管理与接口示例,便于对照理解信号通信、组件接口配置、生成代码的组织方式,并借助仿真测试核对模式切换与组件间通信逻辑,对入门AUTOSAR建模和工程落地有较强参考价值。压缩包共78个文件,约1.04MB,主要由mat数据、h头文件、arxml描述文件、slx模型、c源码以及rsp、mk等工程配置和构建脚本组成,类型分布覆盖建模、代码生成与编译产物,目录组织贴近实际工程流程。已有493人学习,适合作为模式接口建模与自动代码生成的实践样例,也可用于课程演示和自学查漏。
1. 模式接口先立规矩:一份样例模型能省掉的三类联调返工
模式接收端口接上了、枚举也选好了,联调时还是会出现「模式已经切到 RUN,初始化逻辑没跑」的情况——问题往往不在算法,而在模式切换事件没有绑到 runnable 上。这份基于 MATLAB 的 AUTOSAR 模式接口样例模型,把模式声明组、模式收发端口、模式切换触发事件和 ARXML 导出串成一条可运行的链路。它适合刚接手组件建模、被模式时序绕晕的人,也适合想把「模式约定」从口头沟通改成可校验配置的人。模型规模不大,但边界条件埋得比较全,拿来当模板比自己从空模型摸一遍省事。
2. 拆样例:模式声明组、模式端口与模式切换事件在 Simulink 里的落点
拿到一个模式接口样例,最容易犯的错是先看端口、后看声明组。实际顺序应该反过来:声明组决定枚举长什么样,端口只是把这个枚举引出去,触发事件则决定谁会因为这个枚举变化而被调用。三者是引用关系,不是并列关系,改任何一处都会顺带改 ARXML 里的短名和引用路径。
2.1 模式声明组:initialMode 与 onTransitionValue 决定了什么
模式声明组(ModeDeclarationGroup)在 ARXML 里是一组模式声明加上一个初始模式引用,落到 Simulink 侧就是一个枚举类型加一个默认值。样例模型里通常放 4 个业务模式:启动、运行、休眠、关机,再加一个过渡值。过渡值不是业务模式,它的作用是告诉接收方「现在正在切,先别动」。
这个过渡值是整份样例里最容易被删掉的东西。删掉之后模型照样能生成代码,仿真也能跑通,但接收方在切换窗口内会读到一个尚未稳定的枚举,状态机就容易翻车。我一般会把过渡值显式声明成一个独立模式(比如 TRANSITION),而不是复用某个业务值,这样在生成的枚举里能一眼看出来。
| 字段 | 作用 | 填错的后果 |
|---|---|---|
| 模式声明列表 | 定义枚举成员名 | 改名等于改接口,基线 ARXML 大面积飘红 |
| initialMode | ECU 启动时接收方看到的默认值 | 填成运行态,上电瞬间逻辑提前动作 |
| onTransitionValue | 切换窗口内的哨兵值 | 不填则接收方读到中间态,状态机跑飞 |
| 声明组短名 | 被端口和事件引用 | 与集成侧命名不一致时导入报未定义 |
initialMode的选择有个朴素原则:选一个「什么都不做」的模式。启动态、空闲态都行,但不要选运行态。样例模型里初始模式给的是 STARTUP,接收方的第一句判断就是「不是 RUN 就输出保持」,这样上电到第一次模式切换之间不会产生任何副作用。
2.2 模式端口的方向:谁提供模式、谁请求模式
模式端口有两种语义,新手最容易混。一种是接收模式,组件是消费者,端口在组件上是 R-Port,接口类型是模式切换接口;另一种是请求模式,组件是发起方,端口在组件上是 P-Port,但对端是模式管理器。样例模型里两种都给了,一个接收端口加一个请求端口,正好覆盖「我跟着模式走」和「我想让模式变」两个场景。
请求端口不是「我说了算」。多个组件同时请求时,由模式管理器做仲裁,组件侧不应该在模型里写「谁先请求谁优先」这类逻辑。我见过有人在请求端口后面接一个优先级选择器,仿真时看着对,上车联调就完全对不上——仲裁权不在应用组件手里,这段逻辑写多少都是白写。
在 Simulink 里区分这两种端口,看的是端口种类和接口引用。接收端口引用模式声明组,请求端口引用同一个声明组但方向相反。样例模型把两者放在同一个组件里,导出 ARXML 后能在组件类型下看到两个方向相反的端口,对照着看一遍比读文档快。
2.3 模式切换事件:OnEntry、OnExit、OnTransition 的触发边界
模式切换事件是边沿语义,不是电平语义。进入 RUN 的那一拍触发一次,之后模式一直保持 RUN,事件不会再触发。这一点和周期采样完全不同,很多人把初始化逻辑挂在 OnEntry 上,然后发现「模式没变,初始化就只跑了一次」,其实是自己把边沿当成了电平。
三种事件的边界要分清。OnEntry 在进入目标模式时触发,适合做初始化、打开输出;OnExit 在离开源模式时触发,适合做清理、关断;OnTransition 针对一对具体的源模式和目标模式,适合处理「从运行切到休眠」这种特定迁移。样例模型里各挂了一个 runnable,方便对照生成的 RTE 调用顺序。
一个 runnable 只挂一个触发事件是最稳的做法。挂多个事件时,导出阶段经常报事件冲突,即便勉强生成,RTE 侧的调用点也会变得难以追踪。多个模式分支怎么办?我的习惯是拆成多个 runnable,或者在一个 runnable 内部用模式值做分支判断,前者更清晰,后者更省函数。样例模型两种写法都留了,可以按团队规范二选一。
提示:OnTransition 事件必须同时指定源模式和目标模式,只指定目标模式时,工具可能把它退化成 OnEntry 处理,导出后行为会变。
3. 从空模型到可导出 ARXML:模式接口的建模步骤与脚本
样例模型可以直接打开看,但真要落到自己的组件上,还是得走一遍「建模型、配属性、绑事件、导 ARXML」的流程。这一章按顺序给四步,每一步都有可执行的脚本,脚本里的属性名在不同 MATLAB 版本上可能略有差异,先用methods看一眼当前版本支持什么,比照着文档猜要快。
3.1 环境与模型骨架:先固定配置集再动端口
顺序很重要:先把配置集固定下来,再往里加端口和事件。反过来做的话,改配置集时求解器类型一变,已经配好的离散任务和事件时刻会被重算,之前调好的时序全部作废。这是我踩过的坑,返工一次大概半天。
% 1) 确认工具箱在位 v = ver; names = {v.Name}; hasAutosar = any(contains(names, 'AUTOSAR')); fprintf('AUTOSAR Blockset: %d\n', hasAutosar); % 2) 新建模型并固定配置集 mdl = 'autosar_mode_demo'; if bdIsLoaded(mdl), close_system(mdl, 0); end new_system(mdl); open_system(mdl); cs = getActiveConfigSet(mdl); set_param(cs, 'SolverType', 'Fixed-step'); set_param(cs, 'Solver', 'FixedStepDiscrete'); set_param(cs, 'FixedStep', '0.01'); set_param(cs, 'StartTime', '0'); set_param(cs, 'StopTime', '10'); set_param(mdl, 'SystemTargetFile', 'autosar.tlc'); % 3) 打开 AUTOSAR 属性(等价于界面上的配置) autosar.api.create(mdl, 'init');第一步只是探测,不做硬性断言,工具箱不在位时后面的命令会直接报错,比assert更好定位。第二步把求解器钉死在离散定步长,步长取 0.01 秒,是为了和常见的 RTE 任务周期对齐,仿真波形和上车后的调用节奏能对得上。第三步的autosar.api.create会给模型挂上 AUTOSAR 属性容器,后面所有add、set都往这个容器里写。
| 配置项 | 建议值 | 原因 |
|---|---|---|
| SystemTargetFile | autosar.tlc | 只有这个目标会同时产出代码和 ARXML 契约 |
| Solver | FixedStepDiscrete | 模式事件挂在离散任务上,变步长会让事件时刻不可复现 |
| FixedStep | 0.01 | 与 RTE 任务周期一致,便于逐拍对照 |
| StopTime | 10 | 至少覆盖一次完整的模式迁移链路 |
还有一项容易漏:模型的保存格式和编码。ARXML 是文本契约,模型里如果混进了非 ASCII 的注释,导出后可能出现转义差异,diff 时看着像改了一大片,实际只改了一行注释。样例模型的注释全部用英文短句,就是为这个。
3.2 用脚本补齐模式声明组与端口属性
界面点选和脚本配置是等价的,但脚本可复现、可进版本库,团队里两个人配出来的东西不会漂。下面这段把声明组、接收端口、请求端口一次配完。
arProps = autosar.api.getAUTOSARProperties(mdl); % 1) 模式声明组:4 个业务模式 + 1 个过渡值 add(arProps, '/', 'ModeDeclarationGroup', 'MDG_VehicleMode'); mdg = find(arProps, '/', 'ModeDeclarationGroup', 'MDG_VehicleMode'); add(arProps, mdg, 'ModeDeclaration', 'STARTUP'); add(arProps, mdg, 'ModeDeclaration', 'RUN'); add(arProps, mdg, 'ModeDeclaration', 'SLEEP'); add(arProps, mdg, 'ModeDeclaration', 'SHUTDOWN'); add(arProps, mdg, 'ModeDeclaration', 'TRANSITION'); set(arProps, mdg, 'InitialMode', 'STARTUP'); set(arProps, mdg, 'OnTransitionValue', 'TRANSITION'); % 2) 接收端口:组件消费模式 add(arProps, '/', 'ReceiverPort', 'RP_VehicleMode'); rp = find(arProps, '/', 'ReceiverPort', 'RP_VehicleMode'); set(arProps, rp, 'PortKind', 'Mode'); % 属性名随版本可能不同 set(arProps, rp, 'Interface', 'MDG_VehicleMode'); % 3) 请求端口:组件发起模式请求 add(arProps, '/', 'SenderPort', 'PP_RequestMode'); pp = find(arProps, '/', 'SenderPort', 'PP_RequestMode'); set(arProps, pp, 'PortKind', 'ModeRequest'); set(arProps, pp, 'Interface', 'MDG_VehicleMode');声明组的五个模式里,前四个是业务值,最后一个是过渡值,通过OnTransitionValue指过去。接收端口的PortKind设成 Mode,接口引用声明组;请求端口的PortKind设成 ModeRequest,接口还是同一个声明组,但语义完全不同。这两行如果写反,导出的 ARXML 会把方向搞错,集成侧导入时不会报错,只会在联调时表现为「模式请求发不出去」。
写完立刻回读一遍,确认属性真的落进去了,别等导出才发现某一行没生效。
% 回读校验:属性写没写进去,看一眼就知道 disp(get(arProps, rp, 'Interface')); disp(get(arProps, mdg, 'InitialMode'));3.3 绑定 runnable 与模式切换事件
runnable 和事件的绑定是这份样例的核心。做法是先建 runnable,再给它挂一个模式切换事件,事件的类别和模式值分开设。下面这段建了三个 runnable,分别对应进入运行、离开运行、从运行切到休眠。
% 进入 RUN add(arProps, '/', 'Runnable', 'RE_EnterRun'); re1 = find(arProps, '/', 'Runnable', 'RE_EnterRun'); add(arProps, re1, 'ModeSwitchEvent', 'MSE_EnterRun'); set(arProps, [re1 '/MSE_EnterRun'], 'Category', 'OnEntry'); set(arProps, [re1 '/MSE_EnterRun'], 'ModeDeclarationGroup', 'MDG_VehicleMode'); set(arProps, [re1 '/MSE_EnterRun'], 'ModeDeclaration', 'RUN'); % 离开 RUN add(arProps, '/', 'Runnable', 'RE_ExitRun'); re2 = find(arProps, '/', 'Runnable', 'RE_ExitRun'); add(arProps, re2, 'ModeSwitchEvent', 'MSE_ExitRun'); set(arProps, [re2 '/MSE_ExitRun'], 'Category', 'OnExit'); set(arProps, [re2 '/MSE_ExitRun'], 'ModeDeclarationGroup', 'MDG_VehicleMode'); set(arProps, [re2 '/MSE_ExitRun'], 'ModeDeclaration', 'RUN'); % 从 RUN 迁到 SLEEP add(arProps, '/', 'Runnable', 'RE_RunToSleep'); re3 = find(arProps, '/', 'Runnable', 'RE_RunToSleep'); add(arProps, re3, 'ModeSwitchEvent', 'MSE_RunToSleep'); set(arProps, [re3 '/MSE_RunToSleep'], 'Category', 'OnTransition'); set(arProps, [re3 '/MSE_RunToSleep'], 'ModeDeclarationGroup', 'MDG_VehicleMode'); set(arProps, [re3 '/MSE_RunToSleep'], 'SourceMode', 'RUN'); set(arProps, [re3 '/MSE_RunToSleep'], 'TargetMode', 'SLEEP');前三段结构一样,差别只在于 Category 和模式参数。OnEntry 和 OnExit 各填一个模式值就够了,OnTransition 必须把源和目标都填上,少填一个会被工具当成普通进入事件处理,导出后行为和预期不一致,而且不会报错,属于典型的静默失效。
runnable 建完之后,还要在模型里给它一个函数体,否则生成代码会出现空实现。样例用 MATLAB Function 块承接,函数体如下。
function [y, active] = modeStep(mode, u) %#codegen % mode 为模式声明组枚举;TRANSITION 期间保持上一拍输出 persistent yPrev if isempty(yPrev) yPrev = single(0); end if mode == MDG_VehicleMode.TRANSITION y = yPrev; % 过渡期不动作,避免半状态输出 elseif mode == MDG_VehicleMode.RUN y = u * 2; % 占位算法,按实际控制律替换 else y = single(0); end yPrev = y; active = (mode == MDG_VehicleMode.RUN);persistent在这里是必要的,过渡期要输出上一拍的值,没有历史值就只能清零,清零在控制律里等于一次冲击。函数开头的空判断是首次调用的保护,缺了它第一次进过渡期会输出未初始化值。active输出给外部做诊断,方便在仿真波形里看出模式判断有没有落在预期分支。
3.4 导出前的自检:validateModel 与 ARXML 差异比对
导出前跑一次模型校验,能挡掉大半低级问题。校验通过不等于逻辑对,但校验不通过一定有问题,顺序不要搞反。
% 1) 模型级校验 autosar.api.validateModel(mdl); % 2) 生成代码与 ARXML slbuild(mdl); % 3) 列出产物,确认 ARXML 真的生成了 files = dir(fullfile(pwd, '**', '*.arxml')); for k = 1:numel(files) fprintf('%s\t%d bytes\n', files(k).name, files(k).bytes); endvalidateModel会检查端口引用、事件绑定、命名冲突这几类问题,输出通常是告警列表,行号指向模型里的具体位置。slbuild走完整代码生成流程,耗时比仿真长,但只有它能产出 ARXML。第三步用递归dir找产物,比记固定目录名可靠,不同版本的输出目录命名并不一致。
拿到 ARXML 之后,做一次往返导入,确认文件是自洽的。
% 往返校验:能被重新导入,说明引用关系没断 importer = arxml.importer('MDG_VehicleMode.arxml');文件名按实际导出结果改。往返导入最大的价值不是「能打开」,而是能在导入报告里看到共享包被引用了几次、有没有生成重复定义。共享包重复是模式接口最常见的集成问题,早发现比在集成环境里查两天强。
4. 生成代码与 RTE 对接:模式切换在 C 代码里长什么样
模型跑通只是第一步,真正交付的是 C 代码和 ARXML。模式接口在代码里的体现有两处:一处是 runnable 的函数原型,另一处是 RTE 提供的模式读写接口。这两处的命名都跟着 ARXML 短名走,所以短名一旦定版就别动。
4.1 代码映射配置:函数原型、文件名与可重入性
代码映射决定 runnable 叫什么、放哪个文件、能不能重入。模式相关的 runnable 改动频率高,我一般让它单独成文件,diff 的时候只看到自己改的那几个函数,不会被无关代码淹没。
| 配置项 | 建议值 | 理由 |
|---|---|---|
| 函数名 | 与 runnable 短名一致,RE_ 前缀 | 集成侧按名字对表,改名等于改契约 |
| 文件打包 | 按 runnable 分文件 | 模式逻辑改动频繁,减少 diff 噪声 |
| 可重入 | 单实例组件不开 | 开了会增加 RTE 生成复杂度,收益为零 |
| 采样率 | 继承模式事件 | 模式触发 runnable 不要额外绑周期 rate |
采样率这一项要特别说。模式触发的 runnable 本身没有周期概念,如果在映射里又给它绑一个周期 rate,生成的代码会出现两套调用点,一套来自模式切换,一套来自周期任务,运行起来谁先谁后取决于任务调度,属于典型的玄学问题。样例模型里这一类 runnable 全部不绑周期。
4.2 从生成的 RTE 调用看模式事件的时序
生成的 RTE 头文件里,模式相关接口大致是读写两组。下面这段是结构示意,不是逐字生成结果,但命名规律和调用关系是一致的,拿自己工程的Rte_*.h对照着看能对上。
/* Rte_VehicleMode.h —— 结构示意 */ Std_ReturnType Rte_Mode_RP_VehicleMode_MDG_VehicleMode(VehicleMode_ModeType *mode); void Rte_Switch_PP_RequestMode_MDG_VehicleMode(VehicleMode_ModeType mode); /* 模式切换事件触发点,示意 */ void Rte_ModeSwitch_MDG_VehicleMode(VehicleMode_ModeType newMode) { /* 1. 源模式 OnExit */ RE_ExitRun(); /* 2. 更新模式值(过渡期内为 onTransitionValue) */ /* 3. 目标模式 OnEntry */ RE_EnterRun(); /* 4. 特定迁移 OnTransition */ RE_RunToSleep(); }常规顺序是先执行源模式的 OnExit,再更新模式值,最后执行目标模式的 OnEntry,OnTransition 夹在中间。过渡值在这个窗口内对接收方可见,所以接收方读模式的第一句必须判过渡值,不能直接拿去做switch。这段顺序在不同版本、不同配置下会有细微差别,以生成的 RTE 为准,不要背结论。
请求端口的调用是单向的,组件调Rte_Switch_*发出请求,能不能切成由模式管理器决定。所以请求之后不要立刻假设模式已经变了,正确的做法是等下一次模式接收端口的读值,或者用模式切换事件来驱动后续逻辑。样例模型里请求和接收是解耦的,中间没有任何假设。
4.3 一份可对照的检查清单
模型、ARXML、C 代码三处要能对上,对不上的时候按下面这张表逐项排查,比漫无目的地翻文件快。
| 检查项 | 模型里看什么 | 生成代码里看什么 |
|---|---|---|
| 模式枚举完整 | 声明组是否含过渡值 | 枚举定义是否有对应成员 |
| 端口方向 | 接收是 R-Port、请求是 P-Port | RTE 接口是读还是写 |
| 事件绑定 | runnable 是否只挂一个事件 | 函数是否只在一处被调用 |
| 初始模式 | initialMode 是否为静止态 | 初始化段是否读到该值 |
| 过渡处理 | 函数体首句是否判过渡值 | 过渡分支是否保留上一拍输出 |
这张表我自己每次交付前都会过一遍,五分钟能过完,能挡掉大部分集成期返工。尤其是第三行,一个 runnable 挂两个事件的情况,模型里不报错,代码里会出现两个调用点,最难查。
5. 避坑与排查:模式不切换、校验不过、集成对不上
模式接口的问题有个共同点:大多不报错,只表现为行为不对。所以排查思路不是看日志,而是按「模型配置 → ARXML 契约 → 生成代码」三层逐层对齐。下面五条是我这几年遇到频率最高的,按现象、原因、解决写。
5.1 建模阶段的三个高频坑
现象:模式切到 RUN,初始化 runnable 一次都没执行。原因通常是 runnable 还挂着周期采样事件,模式切换事件只是「加上了」,原来的没删。工具允许一个 runnable 存在多个触发引用,导出时不一定报错,但 RTE 只会挑其中一个生成调用点。解决办法是把周期事件删掉,只留模式切换事件,然后重新导出,在生成的 RTE 里确认函数只出现一次。
现象:接收方在切换瞬间读到非法枚举,状态机跳到默认分支。原因是声明组没配onTransitionValue,或者配了但接收方代码没判。前者在 ARXML 里表现为缺少过渡值引用,后者在代码里表现为switch没有 default 保护。两边都要补:声明组显式声明过渡值,接收方函数第一句先判过渡并保持输出。
现象:改了一个模式名,ARXML diff 出来几百行。模式名就是 ARXML 里的短名,改名等于改接口,所有引用它的端口、事件、RTE 接口都会跟着变。更麻烦的是集成侧如果还用旧名,导入时不会报错,只会生成两套并行定义。我的做法是名字定版后只增不改,需要新语义就加一个新声明,旧声明留着不动。
5.2 导出与集成阶段的两个坑
现象:ARXML 导入集成环境报「声明组未定义」或「重复定义」。前者多半是共享包没一起导,后者是复制模型时把接口一起复制了,出现两份同名不同 UUID 的定义。解决办法是把模式声明组放在共享包里,组件模型只做引用;复制模型时先确认接口是引用还是内联。往返导入一次能提前发现这类问题。
现象:ARXML 能打开,生成代码里却没有模式切换函数。三个可能:系统目标文件不是autosar.tlc、autosar.api.create没执行过、runnable 没做代码映射。按顺序查,先看配置集,再看属性容器在不在,最后看映射表。第三条最容易漏,模型里 runnable 建了,但没映射到模型里的函数,生成阶段直接跳过。
注意:不要手改导出的 ARXML 来「修」问题。手改的部分下次导出就没了,而且 UUID 会变,集成侧会认为是两个不同的接口。
6. 把模式接口纳入脚本化回归:一个能进 CI 的校验套路
模式接口最怕的不是一次配错,是「上次配对了,这次改别的东西把它带歪了」。所以我习惯把关键检查写成函数,改完模型跑一遍,几十秒出结果,比人工翻 ARXML 靠谱。下面这个函数做三件事:校验模型、导出 ARXML、和基线做文本比对。
function ok = checkModeInterface(mdl, baselineArxml) % 模式接口回归检查:模型校验 + 导出 + 与基线比对 ok = false; % 1) 模型级校验,有告警直接返回 try autosar.api.validateModel(mdl); catch ME fprintf('validate failed: %s\n', ME.message); return; end % 2) 重新生成,确保 ARXML 是最新的 slbuild(mdl); % 3) 与基线比对:只比文本差异行数,不做语义解析 files = dir(fullfile(pwd, '**', '*.arxml')); if isempty(files) fprintf('no arxml generated\n'); return; end newArxml = fullfile(files(1).folder, files(1).name); txt1 = fileread(baselineArxml); txt2 = fileread(newArxml); d = diffRows(txt1, txt2); fprintf('diff rows: %d\n', d); % 4) 差异行数超过阈值视为接口被改动,需要人工确认 ok = (d <= 20); end function n = diffRows(a, b) % 粗粒度行比对:够用即可,不引入额外依赖 la = strsplit(a, newline); lb = strsplit(b, newline); n = abs(numel(la) - numel(lb)); m = min(numel(la), numel(lb)); for k = 1:m if ~strcmp(la{k}, lb{k}) n = n + 1; end end end这个函数的价值不在算法多精,而在阈值可调。模式接口正常改动一般十几行,超过二十行基本就是动了短名或者端口方向,值得停下来看一眼。diffRows用的是最笨的逐行比较,没有引入文本比对工具箱的依赖,CI 机器上不用额外装东西。
再往前一步,可以把检查项做成表驱动,新增检查项只加一行,不用改函数结构。我现在的做法是维护一张小表:声明组是否含过渡值、接收端口是否引用正确接口、每个 runnable 是否只有一个事件、初始模式是否为静止态。四项都过,接口基本就是稳的。
| 检查项 | 判定方式 | 失败时的典型原因 |
|---|---|---|
| 声明组含过渡值 | 查 ARXML 里的过渡值引用 | 复制模型时漏配 |
| 端口接口引用 | 比对端口与声明组短名 | 改名后没同步 |
| runnable 单事件 | 统计事件引用数 | 加了新事件没删旧的 |
| 初始模式静止态 | 读 initialMode 字段 | 沿用别人的配置 |
血泪经验是:这套检查一定要在提交前跑,不要等集成环境报错再回头查。集成环境里模型、ARXML、RTE 三层都在,任何一层出问题都会表现成「模式不对」,定位成本是本地的好几倍。从那以后我每次动模式接口,哪怕只改一行注释,也会先把基线 ARXML 拉出来跑一遍这个脚本,确认差异行数在阈值内再提交。希望帮到你。
本文还有配套的精品资源,点击获取