可以使用 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 中的工作流。
指定源代码和依存关系
指定包含您的 C 函数的外部源代码文件。
- 从 Simulink 工具条中,打开配置参数。
- 在左窗格中,选择仿真目标。
- 选择包含头文件,并输入具有 #include 标记的头文件的名称。
- 选择源文件,并输入源文件的路径和名称。如果模型和源文件在不同目录中,请在文件名之前输入包含源文件的目录。
- 选择包含目录,并输入存储其他编译信息(如头文件)的文件夹。
要验证您的自定义代码可以成功解析和编译,请点击验证。
注意:
如果头文件声明了一个函数,但源文件未定义该函数,则默认情况下,该函数在 C Caller 模块对话框中不可见。您可以在配置参数中设置未定义函数的处理参数,以指定在这种情况下的其他行为,包括引发错误、生成桩函数或忽略该条件。
注意:
要使用 For Each 子系统中的或具有连续采样时间的 C Caller 模块,或要优化该模块在条件输入分支执行中的使用,该模块调用的自定义代码函数必须为确定性的,即始终对相同的输入产生相同的输出。通过使用仿真目标窗格中的确定性函数和按函数指定参数,确定哪些自定义代码函数是确定性的。
N 维数组处理
Simulink 可以将 N 维数组数据传递给 C Caller 模块中的自定义代码函数,并从这些模块中接收数据。当您这样做时,必须指定正确的数组布局才能获得预期的结果。
您可以指定矩阵数据在 C 函数中的处理顺序。如果需要,传递给 C 函数和从 C 函数传递的矩阵数据会转换为指定的数组布局。如果未指定数组布局,矩阵数据将按照与 Simulink 数据相同的顺序通过 C 函数,并且由于行-列优先混乱,可能会出现计算错误。确保对所有 Simulink 数据使用相同的默认函数数组布局。
- 列优先 - C 函数按列优先顺序处理输入数组数据。假设您有一个 3×3 矩阵。在 C 函数中,按以下顺序访问此矩阵:第一列、第二列和第三列。
- 行优先 - C 函数按行优先顺序处理输入数组数据。假设您有一个 3×3 矩阵。在 C 函数中,按以下顺序访问此矩阵:第一行、第二行和第三行。
- 任何 - C 函数与输入数组数据的布局无关。例如,如果函数只对数据执行按元素运算,就会出现这种情况。
- 未指定 - C 函数不假定输入数组数据的布局。与任何设置相比,您只能按列优先设置生成代码。尝试按行优先设置生成代码会产生错误。
- 在默认函数数组布局下选择一个数组布局选项。
- 如果您需要对代码中的某些函数应用特定的数组布局,请点击例外(按函数) 以选择这些函数。
- 点击应用以接受您的更改。
如果您的 C 函数只接受标量和/或向量输入,则默认函数数组布局设置不起作用。
调用 C Caller 模块并指定端口
您可以在 Simulink 画布中键入 C Caller,以开始在 Simulink 中集成您的自定义 C 代码。或者,将 User-Defined Functions 库中的一个 C Caller 模块拖到画布上。双击该模块打开模块参数对话框,查看您的函数名称和端口设定。
- 点击“刷新”按钮 导入源代码及其依存关系。
- 您的 C 函数显示在函数名称下。如果没有看到完整的函数列表,请点击 重新导入源代码。
- 要查看源文件中的函数定义,请点击 。所选函数的源代码显示在 MATLAB® 编辑器中。如果源代码不可用,将显示头文件中的函数声明。
- 要更改源文件及其依存关系,或定义并选择函数数组布局,请点击自定义代码设置按钮 打开模型配置参数中的仿真目标窗格。
将 C 函数参数映射到 Simulink 端口
您可以使用 C Caller 模块中的端口设定表,或通过命令行创建 FunctionPortSpecification 对象,将源代码中的 C 函数参数映射到 Simulink 端口。在源代码中,头文件包含要连接到 Simulink 端口的 C 函数参数。
端口设定表显示您的参数的详细内容,以及它们如何连接到您在 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 |
函数返回 | “输出” |
double u | “输入”、“参数”、“常量” |
double *u double u[] double u[][2] double u[2][3] | “InputOutput”(默认值)、“输出”、“输入”、“参数” |
const double *u const double u[] const double u[][2] const double u[2][3] | “输入”(默认值)、“参数” |
使用 “InputOutput” 作用域映射 C 函数中指针传递的输入。对于使用 “InputOutput” 作用域创建的端口,输入和输出端口的名称相同。“InputOutput” 作用域支持重用输入和输出端口的缓冲区。这可以根据信号大小和模块布局来优化内存的使用。
要将 C 函数参数映射到 “InputOutput” 作用域,请将该变量定义为函数中的指针。
然后,在端口设定表中将作用域设置为 “InputOutput”,并将结果函数输出赋给自定义函数中的输入变量。
您可以在自定义代码中使用全局变量,将它们映射到适当的 Simulink 作用域。要在模型中使用全局变量,请从模型设置 > 配置参数 > 仿真目标中选择 Enable global variables as function interfaces。您可以将全局变量映射到 C Caller 模块上的“输入”、“输出”、“InputOutput” 或“全局”作用域。这些作用域的可用性取决于自定义代码中全局变量的使用情况。
“全局”作用域使您能够在自定义代码和 C Caller 模块之间传输数据,并允许您在计算期间对模块使用全局变量。使用“全局”作用域传输的值在模块接口上不可见。下表显示示例代码段及其默认端口和可用端口。
示例代码 | Simulink Scope |
double data; void foo(void) { int temp = data; } | 全局变量数据只读取变量 data。可用作用域包括: “输入”(默认值) “全局” |
double data; void bar(void) { data = 0; } | 数据写入全局变量。可用作用域包括: “输出”(默认值) “全局” “InputOutput” |
double data; void foo2(void) { data = data + 1; } | 可对全局变量读写数据。可用作用域包括: “全局”(默认值) “InputOutput” “输出” |
标签
指示 Simulink 模块中对应参数的标签。默认情况下,参数标签与参数名称相同,除非您更改它。更改作用域以配置端口标签的选项。
作用域 | Simulink 端口标签 |
“输入”、“输出” | 端口名称 |
“InputOutput” | 输入和输出端口中的端口名称 |
“全局” | 端口名称和全局变量名称 |
“参数” | 参数名称 |
“常量” | 常量值的表达式。 使用输入参数名称的 size 表达式,例如 size(in1,1) |
类型
指定参数的数据类型。C 函数中的数据类型必须与 Simulink 中的等效数据类型匹配。下表显示您可以在 C Caller 模块中使用的受支持的 C 数据类型,以及等效的 Simulink 数据类型。
C 参数数据类型 | Simulink 数据类型 |
有符号字符/无符号字符 | int8/unit8 |
char | int8 或 uint8,具体取决于编译器 |
int/unsigned int* | int32/unit32 |
short/unsigned short* | int16/unit16 |
long/unsigned long* | int32/uint32 或 int64/unit64,取决于操作系统 |
long long/unsigned long long* | int64/uint64 |
float | 单精度 |
双精度 | 双精度 |
int8_t/uint8_t* | int8/uint8 |
int16_t/unit16_t* | int16/unit16 |
int32_t/unit32_t* | int32/uint32 |
int64_t/unit64_t* | int64/uint64 |
bool | 布尔 |
typedef struct {…} AStruct** | Bus: AStruct |
typedef enum {..} AnEnum** | Enum: AnEnum |
* 如果 C Caller 采用整数类型,例如 int16_t,您可以将其修改为具有匹配的基类型的定点类型,例如修改为 fixdt(1, 16, 3)。 ** C Caller 同步按钮提示您将 C 函数使用的结构体或枚举类型导入为 Simulink 总线和枚举类型。 |
大小
指定参数中的数据维度。
C 参数维度 | Simulink 端口维度 |
double u | 标量 (1) |
double u[] double u[][2] | 继承 -1(默认值) 如果该参量用于输出端口,则必须指定其大小,并且该大小无法继承,除非该参量映射到 “InputOutput” 作用域,或模型配置参数在单独进程中仿真自定义代码处于选中状态。 |
double *u | 继承 -1(默认值) 如果该参量用于输出端口,则必须指定其大小,并且该大小无法继承,除非该参量映射到 “InputOutput” 作用域,或模型配置参数在单独进程中仿真自定义代码处于选中状态。 对于全局变量,大小是标量 (1)。 |
double u[2][3] | 大小为 [2, 3]。 |
创建 FunctionPortSpecification 对象并编辑 C Caller 模块属性
要以编程方式更改端口设定表属性,您可以创建 FunctionPortSpecification 对象并修改其属性。要为模型中的所选 C Caller 模块创建 FunctionPortSpecification 对象,请在命令行中键入:
CPrototype 属性是只读属性,它显示 C 函数输入变量的声明。InputArgument 和 ReturnArgument 属性创建了 FunctionArgument 对象,您可以根据上面为端口设定表定义的规则进一步编辑其属性。
要修改 C Caller 模块中的全局参数,请使用 getGlobalArg 创建 GlobalArguments 对象的句柄并修改其属性。
创建自定义 C Caller 库
建议创建一个库模型来对 C Caller 模块进行分组并保持模型组织良好。还可以将数据字典链接到库以保留代码中定义的自定义类型。当您有多个模型或一个使用自定义 C 代码的模型引用层次结构时,使用库模型尤其有用。
- 打开一个新库模型。在仿真选项卡上,选择新建 > 库。
- 在建模选项卡上,在设计下,点击仿真自定义代码。
- 根据您的代码,在语言选项中选择 C 或 C++,并确保导入自定义代码框已选中。
- 按照指定源代码和依存关系中的说明添加源文件及其依存关系。
- 创建 C Caller 模块来调用 C 函数。
- 要将库模型中的模块插入 Simulink 模型,只需将该模块拖到模型中即可。
您也可以使用 Simulink 代码导入器从自定义代码创建 C Caller 模块库。
调试自定义代码
通过启动外部调试器并在自定义代码中设置断点,您可以从 Simulink 内部调试您的代码。
从模型生成代码
C Caller 支持代码生成。在从您的模型生成的代码中,C Caller 模块的每次执行对应于对与该模块相关联的外部 C 函数的一次调用。为了编译生成的代码,模型配置参数的代码生成 > 自定义代码窗格必须填充有关自定义代码的正确信息。
限制
- 初始化/终止自定义代码设置 - 如果您需要为自定义代码分配和取消分配内存,请在自定义代码设置的初始化函数和终止函数字段中插入 allocate 和 deallocate,或者使用 C Function 模块。
- 复数数据支持 - C Caller 模块不支持 Simulink 中的复数数据类型。
- 变量参数 - 不支持 C 语言中的变量参数,例如 int sprintf(char *str, const char *format, ...)。
- C++ 语法 - C Caller 模块不直接支持原生 C++ 语法。您需要编写 C 函数包装器来与 C++ 代码对接。