Main Content

生成要导出到外部代码库的组件源代码

如果您有 Embedded Coder® 软件,您可以从建模组件中生成函数源代码,以便在外部代码库中使用。生成的代码不包括相关调度代码(例如,单步函数)。Simulink® 环境之外的控制逻辑会调用生成的函数代码。

建模选项

您可以为下列建模组件生成要导出的函数代码:

  • 导出函数模型(包含函数模块的模型,这些函数模块只包含函数调用子系统、函数调用模型模块或其他导出函数模型,如导出函数模型概述中所述)

  • 导出函数子系统(包含函数调用子系统的虚拟子系统)

要导出代码生成器为这些建模组件生成的代码,建模组件必须满足特定的要求 (Embedded Coder)

要求

  • 模型求解器必须为定步长离散求解器。

  • 对于会触发函数调用子系统的每个根级 Inport 模块,必须将其配置为输出一个函数调用触发器。这些 Inport 模块不能连接到 Asynchronous Task Specification 模块。

  • 模型或子系统,在根级必须仅包含以下模块:

    • 函数调用模块(如根级的 Function-Call Subsystem、Simulink Function、S-Functions 和 Function-Call Model 模块,如果求解器模型配置参数任务和采样时间选项 > 周期性采样时间约束设置为确保采样时间独立

    • Inport 和 Outport 模块(端口)

    • Constant 模块(包括解析为常量的模块,如 Add)

    • 采样时间为 Inf 的模块

    • Merge 和 Data Store Memory 模块

    • 虚拟连接模块(例如,Function-Call Split、Mux、Demux、Bus Creator、Bus Selector、Signal Specification 以及包含这些模块的虚拟子系统)

    • 信号查看器模块,如 Scope 模块(仅导出函数子系统)

  • 当模型或子系统的顶层出现常量模块时,您必须将模型或所在模型的配置参数优化 > 默认参数行为设置为内联

  • 模型或子系统中的模块必须支持代码生成。

  • 使用绝对时间或已用时间的模块必须位于周期函数调用子系统内,且在对应的函数调用根级 Inport 模块上指定了离散采样时间。请参阅导出使用绝对时间或执行时长的函数

  • 对于导出的系统,跨越其边界的数据信号不能为虚拟总线,也无法实现为 Goto-From 连接。跨导出边界的数据信号必须为标量、多路或非虚拟总线。

此外,对于导出函数模型,您不能为包含多个导出函数模型实例的基于速率的模型生成代码。例如,对于在仿真期间用于调度可重用的导出函数模型的测试框架模型,您不能为其生成代码。

对于导出函数子系统,还适用以下附加要求:

  • 跨导出函数子系统边界的触发信号必须为标量。不作为触发信号的输入输出数据信号不必是标量。

  • 当常量信号驱动导出函数子系统的输出端口时,该信号必须指定存储类。

导出使用绝对时间或执行时长的函数

当建模组件具有使用绝对时间或执行时长的模块时,如果要为该建模组件导出函数代码,这些模块必须位于函数调用子系统内,该子系统需要满足以下条件:

  • 配置为周期性执行

  • 其根级 Inport 模块配置为使用离散采样时间

要将函数调用子系统配置为周期性执行,请执行以下操作:

  1. 在函数调用子系统中,右键点击 Trigger 模块,并从上下文菜单中选择模块参数

  2. 将参数采样时间类型设置为周期

  3. 采样时间设置为函数调用发起方中指定的相同粒度(直接指定或通过继承)。

  4. 点击确定应用

有关详细信息,请参阅Timer Representation and Computation

导出函数子系统的限制

  • 子系统模块参数不控制包含生成代码的文件的名称。文件名以导出的子系统的名称开头。

  • 子系统模块参数不控制生成代码中顶层函数的名称。每个函数名称会反映触发该函数的信号的名称,如果信号未命名,则反映作为信号源的模块的名称。

  • 仅当 C++ 类代码接口打包的函数设定设置为默认单步方法时,才能导出配置了 C++ 类代码接口打包的函数调用系统。请参阅Interactively Configure C++ Interface (Embedded Coder)。导出的函数与单线程执行兼容。为了避免共享信号的潜在数据竞争情况,请从同一个执行线程调用类的所有成员。

  • 仅当其函数调用发起方在加速模式下为非内联时,代码生成器才会在加速模式下支持 SIL 或 PIL 模块。Stateflow® 图就是一个非内联的发起方。

  • 2 级 S-Function 发起方模块,如 Stateflow 图或内置 Function-Call Generator 模块,必须驱动 SIL 模块。

  • 您可以导出异步(采样时间)函数调用系统,但软件不支持异步系统的 SIL 或 PIL 模块。

  • 导出函数子系统不再使用 TLC 函数 LibIsFirstInit

工作流

要为导出的函数生成代码,请依次执行下表中列出的任务。

任务操作更多信息
1检查您对外部代码特性和集成要求的评估。

Choose an External Code Integration Workflow

2验证您正在导出的模型或子系统是否满足函数导出要求。

要求

3通过修改模型或子系统来满足数据接口要求。

Exchange Data Between External C/C++ Code and Simulink Model or Generated Code

4如有必要,配置函数原型。

Configure Entry-Point Function Interfaces for Simulink Function and Function Caller Blocks;对于基于速率的定步长模型,请参阅为模型入口函数配置生成的 C 函数接口Interactively Configure C++ Interface (Embedded Coder)

5如有必要,更新模型,将外部的应用程序特定代码放入生成的系统函数中。

Place External C/C++ Code in Generated Code

6通过创建和使用测试框架模型,验证函数在仿真期间的行为和性能是否符合预期。测试框架模型在仿真期间调度函数的执行。

配置模型、生成代码和仿真;如果您有 Simulink Test™ 软件,请参阅Test Authoring (Simulink Test)

7为代码生成配置模型或子系统。

Generate Code That Matches Appearance of External Code模型配置集自定义

8生成代码和代码生成报告。

代码生成

9检查生成的代码接口和静态代码度量。

Analyze Generated Data Code Interface ReportStatic Code Metrics (Embedded Coder)

10编译包含导出的函数代码的可执行程序。

在 Simulink 环境中编译集成的代码

11验证可执行程序的行为和性能是否符合预期。

验证和测试

选择集成方法

有多种方法可用于生成导出到外部开发环境的函数代码。下表比较了各种方法。您可选择最符合您的集成要求的方法。有关如何创建导出函数模型的详细信息,请参阅导出函数模型概述。有关为函数调用子系统生成代码的详细信息,请参阅生成要导出到外部代码库的组件源代码 (Embedded Coder)

条件或要求使用更多信息
  • 建模元素和生成代码之间的可追溯性

  • 本地输入(Inport 模块)和输出(Outport 模块)

函数调用子系统
  • 对生成的函数原型的控制

  • 形式输入参量(Argument Inport 模块)和输出参量(Argument Outport 模块)

  • 本地输入(Inport 模块)和输出(Outport 模块)

Simulink Function 模块
代码响应初始化事件Initialize Function 模块
代码响应重置事件Reset Function 模块
代码包括的入口函数超出代码生成器默认生成的入口函数(model_initializemodel_stepmodel_terminateS-FunctionS-Function 和代码生成
单模型执行框架要用作测试框架并导出为模型部分生成的代码导出函数子系统

生成导出函数模型的 C 函数代码

此示例说明如何为模型中的各个函数调用子系统生成函数代码,而不生成调度代码。

要生成要导出的函数代码,请执行下列操作:

  1. 创建一个包含要导出的函数的模型。

  2. 配置代码接口。

  3. 创建一个测试框架模型,用于在仿真期间调度函数的执行。

  4. 使用该测试框架模型对包含这些函数的模型进行仿真。

  5. 为包含这些函数的模型生成代码。

创建包含要导出的函数的模型

具有要导出的函数的模型必须满足模型根级别的架构约束。在根级别,有效模块为:

  • Inport

  • Outport

  • 函数调用子系统

  • Simulink Function

  • Goto

  • From

  • Merge

对于此示例,代码生成器为 Initialize Function、Terminate Function 和 Function-Call Subsystem 模块生成函数代码。Initialize Function 模块在发生模型初始化事件时执行,Terminate Function 模块在发生终止事件时执行。对于 Function-call Subsystem 模块,您需要将模块输入端口连接到用于断言函数调用信号的根 Inport 模块。子系统会根据接收到的函数调用信号而执行。

对于导出函数,模型 ComponentDeploymentFcn 包含两个函数调用子系统(IntegratorAccumulator)。函数调用子系统 Integrator 表示一个非周期性积分器函数。该函数在每次执行时对状态变量 x 的值应用一种前向积分方法和增益值 1.25。函数调用子系统 Accumulator 表示一个周期函数,该函数将状态变量 x 的值递增 1 并应用可调增益值 k。该函数每秒执行一次。

open_system('ComponentDeploymentFcn')

配置模型代码接口

模型 ComponentDeploymentFcn 配置为使用在 ComponentDeploymentCoderDictionary.sldd 中定义的服务代码接口。代码生成器使用链接的字典来建立默认代码映射,您可以使用代码映射编辑器或代码映射编程接口来查看和更改这些映射。

对于默认接口符合要求的元素,您不需要进行更改。对于此示例,为了满足代码接口要求,进行了以下调整:

  • 函数选项卡上,非周期性积分器和周期性累加器函数的默认函数自定义模板生成函数名称 CD_AperiodicCD_Periodic。为了符合这些函数的目标平台要求,代码映射通过指定函数名称 CD_integratorCD_accumulator 来覆盖默认函数名称。

  • 备用计时器服务接口与积分器函数相关联。代码生成器字典定义两个计时器服务接口:默认接口和备用接口 get_CD_tick,它符合目标平台计时器服务对函数名称的要求。要查看计时器服务设置,请在函数选项卡上,选择 Exported Function:Aperiodic 对应的行,然后点击铅笔图标。在出现的对话框中,可以看到计时器服务属性设置为 get_CD_tick。您也可以在属性检查器中设置该属性。

  • 参数选项卡上,为 Gain 模块增益参数 k 指定了代码标识符。要查看标识符设置,请在模型参数下,选择该参数对应的行,然后点击铅笔图标。在出现的对话框中,查看为标识符属性指定的值 k。标识符通知代码生成器如何在生成的代码中表示变量。您也可以在属性检查器中设置该属性。

  • 信号/状态选项卡上,为与 Discrete-Time Integrator 和 Unit Delay 模块相关联的状态指定了代码标识符。要查看标识符设置(dtidelay),请选择感兴趣的状态对应的行,然后点击铅笔图标。在出现的对话框中,查看为标识符属性指定的值。这些标识符通知代码生成器如何在生成的代码中表示对应的变量。您也可以在属性检查器中设置该属性。

要查看代码映射,请打开 Embedded Coder。然后,选择代码接口 > 组件接口。代码映射编辑器出现在 App 显示画面的底部。

在代码映射编辑器中,您可以更改发送方、接收方、数据传输和计时器服务的接口。要更改发送方、接收方和数据传输服务,请点击对应的选项卡,并在服务列中选择替代服务。要更改计时器服务,请点击函数选项卡,选择积分器函数对应的行,点击铅笔图标,并在出现的对话框中选择替代接口。默认接口使用执行期外数据通信方法。替代接口使用执行期间和直接访问数据通信。请考虑更改接口以查看生成的代码中的差异。如果您选择更改接口,则对于沿信号的数据路径调用的每个服务,请设置接口,使它们应用相同的数据通信方法。

有关服务接口的模型代码映射的详细信息,请参阅 C 服务接口 (Embedded Coder)Code Mappings Editor – C (Embedded Coder)

创建用于仿真的测试框架模型

导出函数时,生成的代码不包含调度器。创建一个测试框架模型来处理仿真期间的调度。不要使用该测试框架模型来生成您部署的代码。

模型 ComponentDeploymentFcnHarness 是一个测试框架。此模型向此示例中的其他模型提供函数调用信号以调度模型内容,包括模型初始化和终止事件。

open_system('ComponentDeploymentFcnHarness')

对于此模型,在 Embedded Coder 中,输出设置为 Simulation Only。此设置允许您在测试框架模型和用于生成代码的模型之间切换焦点,而无需打开单独的 Simulink 编辑器窗口。

对测试框架模型进行仿真

通过对测试框架模型进行仿真,验证包含要导出的函数的模型是否按预期执行。例如,对 ComponentDeploymentFcnHarness 进行仿真。

sim('ComponentDeploymentFcnHarness')

生成函数代码

Embedded Coder 中,通过双击 ComponentDeploymentFcn 将焦点切换到组件模型。然后,为要导出的函数生成代码。

slbuild('ComponentDeploymentFcn')
### Starting build procedure for: ComponentDeploymentFcn
### Successful completion of code generation for: ComponentDeploymentFcn

Build Summary

Top model targets built:

Model                   Action           Rebuild Reason                                    
===========================================================================================
ComponentDeploymentFcn  Code generated.  Code generation information file does not exist.  

1 of 1 models built (0 models already up to date)
Build duration: 0h 0m 12.713s

查看生成的代码

查看生成的代码。代码生成器在当前工作文件夹中创建文件夹 ComponentDeploymentFcn_ert_rtw,并将源代码文件放入该文件夹。生成的代码位于两个主要文件中:头文件 ComponentDeploymentFcn.h 和源代码文件 ComponentDeploymentFcn.cComponentDeploymentFcn.c 调用初始化函数、导出的函数 IntegratorAccumulator 以及终止函数。通过包含头文件,调用方可以访问模型数据和入口函数。

代码生成器还生成 rtwtypes.h 和服务头文件,默认情况下,代码生成器将该文件命名为 services.h。头文件 rtwtypes.h 定义生成的代码所需的数据类型、结构体和宏。头文件 services.h 是位于子文件夹 services 中的接口头文件。

要检查生成的代码,请使用生成的代码接口报告,或在 Embedded Coder 中使用代码视图。该报告有助于验证生成的接口代码符合目标平台要求。有关详细信息,请参阅Analyze Generated Service Code Interface Report (Embedded Coder)

编写接口代码

打开并查看代码接口报告。要编写用于您的执行框架的接口代码,请使用该报告中的信息。

  1. 通过添加指令 #include ComponentDeploymentFcn.h#include rtwtypes.h#include services.h 来包含生成的头文件。

  2. 将输入数据写入模型的 Inport 模块的生成代码。

  3. 调用生成的入口函数。

  4. 从模型 Outport 模块的生成代码中读取数据。

入口函数:

  • 初始化入口函数 void CD_initialize(void)。在启动时,调用一次此函数。该函数通过调用接口函数 get_CD_initialize_InBus_NVM,使用目标环境接收方服务从非易失性内存中读取数据。

  • 终止入口函数 void CD_terminate(void)。关闭时,调用一次此函数。该函数通过调用接口函数 getref_CD_terminate_OutBus_NVM 使用目标环境发送方服务将数据写入非易失性内存。

  • 导出的周期函数 void CD_accumulator(void)。根据需要调用此函数。该函数通过调用接口函数 getref_CD_accumulator_OutBus_y 使用目标环境发送方服务来写入函数输出。

  • 导出的非周期函数 void CD_integrator(void)。根据需要调用此函数。该函数通过调用接口函数 get_CD_integrator_inBus_u 使用目标环境接收方服务将输入读入该函数中。CD_integrator 函数还通过调用接口函数 get_tick_outside_CD_integrator 使用目标环境计时器服务获取目标环境函数时钟计时单元。

有关详细信息

为导出函数模型生成 C++ 函数和类代码

此示例说明如何为包含函数调用子系统的导出函数模型生成函数代码。代码生成器生成不包括调度代码的函数和类代码。

要生成要导出的函数代码,请执行下列操作:

  1. 创建一个包含要导出的函数的模型。

  2. 创建一个测试框架模型,用于在仿真期间调度函数的执行。

  3. 使用该测试框架模型对包含这些函数的模型进行仿真。

  4. 为包含这些函数的模型生成代码。

创建包含要导出的函数和 C++ 类接口的模型

包含带 C++ 模型类接口的导出函数的模型必须满足模型根级别的架构约束。对于 C++ 类的生成,在根级别有效的模块为:

  • Inport

  • Outport

  • 函数调用子系统

  • Goto

  • From

  • Merge

注意:若要导出的 Function-Call Subsystem 采用 C++ 类接口,则不支持 Simulink Function 模块。

代码生成器为 Function-Call Subsystem 模块生成函数代码。对于 Function-call Subsystem 模块,您需要将模块输入端口连接到用于断言函数调用信号的根 Inport 模块。子系统会根据接收到的函数调用信号而执行。

模型 CppClassFunctions 包含用于导出函数的函数调用子系统 f1f2f3

open_system('CppClassFunctions')

创建用于仿真的测试框架模型

导出函数时,生成的代码不包含调度器。创建一个测试框架模型来处理仿真期间的调度。不要使用该测试框架模型来生成您部署的代码。

模型 CppClassFunctionsHarness 是一个测试框架。该模型向此示例中的其他模型提供函数调用信号以调度模型内容。

open_system('CppClassFunctionsHarness')

对测试框架模型进行仿真

通过对测试框架模型进行仿真,验证包含要导出的函数的模型是否按预期执行。例如,对 CppClassFunctionsHarness 进行仿真。

sim('CppClassFunctionsHarness')

生成函数代码和报告

为要导出的函数生成代码和代码生成报告。例如,为 CppClassFunctions 生成代码。

slbuild('CppClassFunctions')
### Starting build procedure for: CppClassFunctions
### Successful completion of build procedure for: CppClassFunctions

Build Summary

Top model targets built:

Model              Action                        Rebuild Reason                                    
===================================================================================================
CppClassFunctions  Code generated and compiled.  Code generation information file does not exist.  

1 of 1 models built (0 models already up to date)
Build duration: 0h 0m 12.781s

查看生成的代码

从代码生成报告中,查看生成的代码。

  • ert_main.cpp 是模型的示例主程序(执行框架)。此代码显示如何调用导出的函数。该代码还显示如何初始化和执行生成的代码。

  • CppClassFunctions.cpp 调用初始化函数(包括 Initialize Function)和针对模型子系统组件 f1f2f3 导出的函数。

  • CppClassFunctions.h 声明模型数据结构体和一个对接导出的入口函数和数据结构体的公共接口。

  • rtwtypes.h 定义生成的代码所需的数据类型、结构体和宏。

编写接口代码

打开并查看代码接口报告。要编写用于您的执行框架的接口代码,请使用该报告中的信息。

  1. 通过添加指令 #include CppClassFunctions.h#include rtwtypes.h 来包含生成的头文件。

  2. 将输入数据写入模型的 Inport 模块的生成代码。

  3. 调用生成的入口函数。

  4. 从模型 Outport 模块的生成代码中读取数据。

输入端口:

  • rtU.U1 的类型为 real_T,维度为 1

  • rtU.U2 的类型为 real_T,维度为 1

  • rtU.U3 的类型为 real_T,维度为 1

入口函数:

  • 初始化入口函数 void initialize(void)。在启动时,调用一次此函数。

  • 导出的函数 void t_1tic_A(void)。根据需要调用此函数。

  • 导出的函数 void t_1tic_B(void)。根据需要调用此函数。

  • 导出的函数 void t_1tic_C(void)。根据需要调用此函数。

输出端口:

  • rtY.TicToc1 的类型为 int8_T,维度为 [2]

  • rtY.TicToc2 的类型为 int8_T,维度为 [2]

  • rtY.TicToc10 的类型为 int8_T,维度为 1

关闭示例模型

bdclose('CppClassFunctionsHarness')
bdclose('CppClassFunctions')

有关详细信息

为导出函数子系统生成代码

要为导出函数子系统生成代码,请执行以下操作:

  1. 验证您正在为其生成代码的子系统是否满足导出要求 (Embedded Coder)

  2. 在“配置参数”对话框中,执行以下操作:

    1. 将参数系统目标文件设置为基于 ERT 的系统目标文件,如 ert.tlc

    2. 如果您需要包含生成代码的 SIL 模块以用于验证目的,请将模型配置参数创建模块设置为 SIL

    3. 点击确定应用

  3. 右键点击子系统模块,并从上下文菜单中选择 C/C++ 代码 > 导出函数

    该操作创建并编译一个新模型 subsystem.slx,其中包含原始子系统的内容,并创建包含 Model 模块的 ScratchModel。此模块引用新创建的 subsystem.slx 模型。

    代码生成器生成代码并将其放在工作文件夹中。

    如果在步骤 2b 中将创建模块设置为 SIL,Simulink 将打开一个新窗口,其中包含表示生成代码的 S-Function 模块。此模块与原始子系统具有相同的大小、形状和连接线。

代码生成和模块创建现已完成。您可以像对待生成的 ERT 代码和 S-Function 模块一样,测试和使用这些代码和模块(可选)。有关可选的工作流任务的信息,请参阅指定自定义初始化函数名称指定自定义描述

指定自定义初始化函数名称

您可以为导出函数的初始化函数指定自定义名称,以作为 slbuild 命令的参量。该命令为以下形式:

blockHandle = slbuild('subsystem', 'Mode', 'ExportFunctionCalls',..
             'ExportFunctionInitializeFunctionName', 'fcnname')

fcnname 指定函数名称。例如,如果您指定名称 'myinitfcn',编译过程将生成类似以下内容的代码:

/* Model initialize function */
void myinitfcn(void){
...
}

指定自定义描述

您可以使用 Inport 模块的“模块属性”对话框输入导出函数的自定义说明。

  1. 对要导出代码的子系统,右键点击驱动其控制端口的 Inport 模块。

  2. 选择属性

  3. 常规选项卡的描述字段中,输入您的描述性文本。

在函数导出过程中,您输入的文本将输出到 Inport 模块的生成代码的头部中。例如,如果您打开一个模型并在端口 f1 的“模块属性”对话框中输入描述,代码生成器将生成类似以下内容的代码:

/*
 * Block description for RootInportFunctionCallGenerator generated from '<Root>/f1':
 *
 *  My custom description
*/
void t_1tic_A(void)
{
...
}

优化为导出函数子系统生成的代码

要优化为导出函数子系统生成的代码,请为每个跨子系统边界的输入信号和输出信号指定单独的存储类。

对于要导出的每个函数调用子系统,请执行以下操作:

  1. 右键点击子系统。

  2. 从上下文菜单中,选择模块参数(子系统)

  3. 选择代码生成选项卡。

  4. 函数打包设置为自动

  5. 点击确定应用

相关主题