使用 C Caller 模块集成 C 代码
您可以使用 C Caller 模块将新的或现有的 C 代码集成到 Simulink® 中。要在 Simulink 模型中创建自定义模块,C Caller 模块允许您调用在外部源代码和库中指定的外部 C 函数。C Caller 模块具有以下优势:
简单 C 函数的自动集成,包括命名空间下定义的函数
与 Simulink Coverage™、Simulink Test™ 和 Simulink Design Verifier™ 的集成
与 Simulink Coder™ 的集成
C Caller 模块和 C Function 模块允许您将 C 算法引入 Simulink。要对动态系统进行建模,请改用 S-Function Builder。接下来的步骤说明使用 C Caller 模块将 C 代码集成到 Simulink 中的工作流。
注意
C99 是 C 语言的标准版本,支持将自定义 C 代码集成到 Simulink 中。
指定源代码和依存关系
指定包含您的 C 函数的外部源代码文件。
从 Simulink 工具条中,打开配置参数。
在左窗格中,选择仿真目标。
选择包含头文件,并输入具有
#include
标记的头文件的名称。目录和文件路径可以是模型目录或当前工作目录的绝对和相对文件路径。请参阅Specify Relative Paths to Custom Code (Stateflow)。
提示
在下一步骤中输入源文件的信息后,您可以点击基于源文件自动填充,以使用源文件中包含的信息自动填充头文件名。
选择源文件,并输入源文件的路径和名称。如果模型和源文件在不同目录中,请在文件名之前输入包含源文件的目录。
选择包含目录,并输入存储其他编译信息(如头文件)的文件夹。
要验证您的自定义代码可以成功解析和编译,请点击验证。
注意
如果头文件声明了一个函数,但源文件未定义该函数,则默认情况下,该函数在 C Caller 模块对话框中不可见。您可以在配置参数中设置未定义函数的处理参数,以指定在这种情况下的其他行为,包括引发错误、生成桩函数或忽略该条件。
注意
要使用 For Each 子系统中的或具有连续采样时间的 C Caller 模块,或要优化该模块在条件输入分支执行中的使用,该模块调用的自定义代码函数必须为确定性的,即始终对相同的输入产生相同的输出。通过使用仿真目标窗格中的确定性函数和按函数指定参数,确定哪些自定义代码函数是确定性的。有关条件输入分支执行的示例,请参阅Use C Caller Block with Conditional Execution。
N 维数组处理
Simulink 可以将 N 维数组数据传递给 C Caller 模块中的自定义代码函数,并从这些模块中接收数据。当您这样做时,必须指定正确的数组布局才能获得预期的结果。请参阅默认函数数组布局和例外(按函数)。有关将数组数据用于 C Caller 模块的示例,请参阅Use Custom Image Filter Algorithms as Reusable Blocks in Simulink和Call Legacy Lookup Table Function Using C Caller Block。
您可以指定矩阵数据在 C 函数中的处理顺序。如果需要,传递给 C 函数和从 C 函数传递的矩阵数据会转换为指定的数组布局。如果未指定数组布局,矩阵数据将按照与 Simulink 数据相同的顺序通过 C 函数,并且由于行-列优先混乱,可能会出现计算错误。确保对所有 Simulink 数据使用相同的默认函数数组布局。
列优先 - C 函数按列优先顺序处理输入数组数据。假设您有一个 3×3 矩阵。在 C 函数中,按以下顺序访问此矩阵:第一列、第二列和第三列。
行优先 - C 函数按行优先顺序处理输入数组数据。假设您有一个 3×3 矩阵。在 C 函数中,按以下顺序访问此矩阵:第一行、第二行和第三行。
任何 - C 函数与输入数组数据的布局无关。例如,如果函数只对数据执行按元素运算,就会出现这种情况。
未指定 - C 函数不假定输入数组数据的布局。与任何设置相比,您只能按列优先设置生成代码。尝试按行优先设置生成代码会产生错误。请参阅数组布局 (Simulink Coder)。仅当需要与旧模型兼容时,才选择此选项。
要了解有关 Simulink 中行优先和列优先数组布局的更多信息,请参阅Default function array layout。
在默认函数数组布局下选择一个数组布局选项。
如果您需要对代码中的某些函数应用特定的数组布局,请点击例外(按函数) 以选择这些函数。
点击应用以接受您的更改。
如果您的 C 函数只接受标量和/或向量输入,则默认函数数组布局设置不起作用。
调用 C Caller 模块并指定端口
您可以在 Simulink 画布中键入 C Caller
,以开始在 Simulink 中集成您的自定义 C 代码。或者,将 User-Defined Functions 库中的一个 C Caller 模块拖到画布上。双击该模块打开模块参数对话框,查看您的函数名称和端口设定。
点击“刷新”按钮 导入源代码及其依存关系。
您的 C 函数显示在函数名称下。如果没有看到完整的函数列表,请点击 重新导入源代码。
要查看源文件中的函数定义,请点击 。所选函数的源代码显示在 MATLAB® 编辑器中。如果源代码不可用,将显示头文件中的函数声明。
要更改源文件及其依存关系,或定义并选择函数数组布局,请点击自定义代码设置按钮 打开模型配置参数中的仿真目标窗格。
将 C 函数参量映射到 Simulink 端口
您可以使用 C Caller 模块中的端口设定表,或通过命令行创建 FunctionPortSpecification
对象,将源代码中的 C 函数参量映射到 Simulink 端口。在源代码中,头文件包含要连接到 Simulink 端口的 C 函数参量。
extern void mean_filter(const unsigned char* src, unsigned char* dst, unsigned int width, unsigned int height, unsigned int filterSize);
端口设定表显示您的参量的详细内容,以及它们如何连接到您在 Simulink 中的 C Caller 模块。
该表包含以下各列:
名称
指定输入和输出参量的名称。名称是源代码中的 C 函数中定义的函数参量或参数名称。此列仅供参考。
作用域
指定 C 函数参量如何映射到 Simulink 作用域。您的参量具有根据函数定义确定的默认作用域,并且您可以根据源代码中的函数定义来更改作用域。
Simulink Scope | 作用域到模块的映射 |
---|---|
输入 | 模块输入端口 |
输出 | 模块输出端口 |
InputOutput | 模块输入和输出端口 |
全局 | 不适用 |
参数 | 模块可调参数 |
常量 | 常量值 |
对于指针传递的参量,当该参量具有常量限定符定义(如 const double *u
)时,该参量必须为 input 或 parameter 类型。如果没有常量限定符,则该参量默认为 InputOutput
,您可以将其更改为 Input
、Output
或 Parameter
作用域。在使用 Input
或 Parameter
作用域的情况下,请确保 C 函数不会修改指针指向的内存。如果参量是 Output
作用域,则在该函数的每次调用中,该指针指向的每个元素都应该重新分配。
C 参量 | Simulink Scope |
---|---|
函数返回 | 输出 |
| 输入、参数、常量 |
| InputOutput(默认值)、输出、输入、参数 |
| 输入(默认值)、参数 |
使用 InputOutput 作用域映射 C 函数中指针传递的输入。对于使用 InputOutput 作用域创建的端口,输入和输出端口的名称相同。InputOutput 作用域支持重用输入和输出端口的缓冲区。这可以根据信号大小和模块布局来优化内存的使用。
要将 C 函数参量映射到 InputOutput 作用域,请将该变量定义为函数中的指针。
extern void mean_filter(unsigned char* src, unsigned int width, unsigned int height, unsigned int filterSize);
然后,在端口设定表中将作用域设置为 InputOutput,并将结果函数输出赋给自定义函数中的输入变量。
您可以在自定义代码中使用全局变量,将它们映射到适当的 Simulink 作用域。要在模型中使用全局变量,请从模型设置 > 配置参数 > 仿真目标中选择 Automatically infer global variables as function interfaces。您可以将全局变量映射到 C Caller 模块上的输入、输出、InputOutput 或全局作用域。这些作用域的可用性取决于自定义代码中全局变量的使用情况。
全局作用域使您能够在自定义代码和 C Caller 模块之间传输数据,并允许您在计算期间对模块使用全局变量。使用全局作用域传输的值在模块接口上不可见。下表显示示例代码片段及其默认端口和可用端口。
示例代码 | Simulink Scope |
---|---|
double data; void foo(void) { int temp = data; } | 全局变量数据只读取变量 输入(默认值) 全局 |
double data; void bar(void) { data = 0; } | 数据写入全局变量。可用作用域包括: 输出(默认值) 全局 InputOutput |
double data; void foo2(void) { data = data + 1; } | 可对全局变量读写数据。可用作用域包括: 全局(默认值) InputOutput 输出 |
标签
指示 Simulink 模块中对应参量的标签。默认情况下,参量标签与参量名称相同,除非您更改了它。更改作用域以配置端口标签的选项。
作用域 | Simulink 端口标签 |
---|---|
输入、输出 | 端口名称 |
InputOutput | 输入和输出端口中的端口名称 |
全局 | 端口名称和全局变量名称 |
参数 | 参数名称 |
常量 | 常量值的表达式。 使用输入参量名称的 size 表达式,例如 |
类型
指定参量的数据类型。C 函数中的数据类型必须与 Simulink 中的等效数据类型匹配。下表显示您可以在 C Caller 模块中使用的受支持的 C 数据类型,以及等效的 Simulink 数据类型。
C 参量数据类型 | Simulink 数据类型 |
---|---|
signed char /unsigned char | int8 /uint8 |
char | int8 或 uint8 ,具体取决于编译器 |
int /unsigned int * | int32 /uint32 |
short /unsigned short * | int16 /uint16 |
long /unsigned long * | int32 /uint32 或 int64 /uint64 ,取决于操作系统 |
long long /unsigned long long * | int64 /uint64 |
float | single |
double | double |
int8_t /uint8_t * | int8 /uint8 |
int16_t /uint16_t * | int16 /uint16 |
int32_t /uint32_t * | int32 /uint32 |
int64_t /uint64_t * | int64 /uint64 |
bool | boolean |
typedef struct {…} AStruct ** | Bus: AStruct |
typedef enum {..} AnEnum ** | Enum: AnEnum |
* 如果 C Caller 采用整数类型,例如 ** C Caller 同步按钮提示您将 C 函数使用的结构体或枚举类型导入为 Simulink 总线和枚举类型。 |
大小
指定参量中的数据维度。
C 参量维度 | Simulink 端口维度 |
---|---|
| 标量 ( |
| 继承 如果该参量用于输出端口,则必须指定其大小,并且该大小无法继承,除非该参量映射到 InputOutput 作用域,或模型配置参数在单独进程中仿真自定义代码处于选中状态。 |
| 继承 如果该参量用于输出端口,则必须指定其大小,并且该大小无法继承,除非该参量映射到 InputOutput 作用域,或模型配置参数在单独进程中仿真自定义代码处于选中状态。 对于全局变量,大小是标量 ( |
| 大小为 |
注意
当使用指针类型作为输出端口时,您必须在 C 函数中写入基础缓冲区的每个元素。例如,如果使用指向一个 5×6 矩阵的指针作为输出,则必须写入到所有 30 个元素。否则,您可能会在数组中看到意外的值。
创建 FunctionPortSpecification
对象并编辑 C Caller 模块属性
要以编程方式更改端口设定表属性,您可以创建 FunctionPortSpecification
对象并修改其属性。要为模型中的所选 C Caller 模块创建 FunctionPortSpecification
对象,请在命令行中键入:
myCCallerConfigObj = get_param(gcb, 'FunctionPortSpecification')
myCCallerConfigObj = FunctionPortSpecification with properties: CPrototype: 'real_T add(real_T u1, real_T u2);' InputArguments: [1×2 Simulink.CustomCode.FunctionArgument] ReturnArgument: [1×1 Simulink.CustomCode.FunctionArgument] GlobalArguments: [1×0 Simulink.CustomCode.FunctionArgument]
CPrototype
属性是只读属性,它显示 C 函数输入变量的声明。InputArgument
和 ReturnArgument
属性创建了 FunctionArgument
对象,您可以根据上面为端口设定表定义的规则进一步编辑其属性。请参阅 FunctionPortSpecification
了解详细信息。要修改 C Caller 模块中的全局参量,请使用 getGlobalArg
创建 GlobalArguments
对象的句柄并修改其属性。
创建自定义 C Caller 库
建议创建一个库模型来对 C Caller 模块进行分组并保持模型组织良好。还可以将数据字典链接到库以保留代码中定义的自定义类型。当您有多个模型或一个使用自定义 C 代码的模型引用层次结构时,使用库模型尤其有用。
打开一个新库模型。在仿真选项卡上,选择新建 > 库。
在建模选项卡上,在设计下,点击仿真自定义代码。
根据您的代码,在语言选项中选择
C
或C++
,并确保导入自定义代码框已选中。按照指定源代码和依存关系中的说明添加源文件及其依存关系。
创建 C Caller 模块来调用 C 函数。
要将库模型中的模块插入 Simulink 模型,只需将该模块拖到模型中即可。
您也可以使用 Simulink 代码导入器从自定义代码创建 C Caller 模块库。请参阅基于 C/C++ 代码创建模块库。
调试自定义代码
通过启动外部调试器并在自定义代码中设置断点,您可以从 Simulink 内部调试您的代码。有关详细信息,请参阅Debug Custom C/C++ Code。
从模型生成代码
C Caller 支持代码生成。在从您的模型生成的代码中,C Caller 模块的每次执行对应于对与该模块相关联的外部 C 函数的一次调用。为了编译生成的代码,模型配置参数的代码生成 > 自定义代码窗格必须填充有关自定义代码的正确信息。请参阅模型配置参数:代码生成自定义代码 (Simulink Coder)。
限制
初始化/终止自定义代码设置 - 如果您需要为自定义代码分配和释放内存,请在自定义代码设置的初始化函数和终止函数字段中插入 allocate 和 deallocate,或者使用 C Function 模块。
复数数据支持 - C Caller 模块不支持 Simulink 中的复数数据类型。
变量参量 - 不支持 C 语言中的变量参量,例如
int sprintf(char *str, const char *format, ...)
。C++ 语法 - C Caller 模块不直接支持原生 C++ 语法。您需要编写 C 函数包装器来与 C++ 代码对接。
要测试包含 C Caller 模块的模型,请参阅测试集成的 C 代码 (Simulink Test)。
注意
如果模型具有自定义代码,则在更新或运行模型后,slprj
文件夹可能会因加载的自定义代码仿真可执行文件而锁定。当文件夹被锁定时,您无法删除它。要卸载可执行文件并解锁 slprj
文件夹,请使用 clear mex
命令。请参阅clear
。