Bootstrap

使用C Caller 模块集成C代码到Simulink模型

可以使用 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 函数的外部源代码文件。

  1. 从 Simulink 工具条中,打开配置参数
  2. 在左窗格中,选择仿真目标
  3. 选择包含头文件,并输入具有 #include 标记的头文件的名称。

  1. 选择源文件,并输入源文件的路径和名称。如果模型和源文件在不同目录中,请在文件名之前输入包含源文件的目录。

  1. 选择包含目录,并输入存储其他编译信息(如头文件)的文件夹。

要验证您的自定义代码可以成功解析和编译,请点击验证

注意:

如果头文件声明了一个函数,但源文件未定义该函数,则默认情况下,该函数在 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 函数不假定输入数组数据的布局。与任何设置相比,您只能按列优先设置生成代码。尝试按行优先设置生成代码会产生错误。
  1. 默认函数数组布局下选择一个数组布局选项。
  2. 如果您需要对代码中的某些函数应用特定的数组布局,请点击例外(按函数) 以选择这些函数。
  3. 点击应用以接受您的更改。

如果您的 C 函数只接受标量和/或向量输入,则默认函数数组布局设置不起作用。

调用 C Caller 模块并指定端口

您可以在 Simulink 画布中键入 C Caller,以开始在 Simulink 中集成您的自定义 C 代码。或者,将 User-Defined Functions 库中的一个 C Caller 模块拖到画布上。双击该模块打开模块参数对话框,查看您的函数名称和端口设定。

  1. 点击“刷新”按钮  导入源代码及其依存关系。
  2. 您的 C 函数显示在函数名称下。如果没有看到完整的函数列表,请点击  重新导入源代码。
  3. 要查看源文件中的函数定义,请点击 。所选函数的源代码显示在 MATLAB® 编辑器中。如果源代码不可用,将显示头文件中的函数声明。
  4. 要更改源文件及其依存关系,或定义并选择函数数组布局,请点击自定义代码设置按钮  打开模型配置参数中的仿真目标窗格。

将 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 代码的模型引用层次结构时,使用库模型尤其有用。

  1. 打开一个新库模型。在仿真选项卡上,选择新建 > 
  2. 建模选项卡上,在设计下,点击仿真自定义代码
  3. 根据您的代码,在语言选项中选择 C 或 C++,并确保导入自定义代码框已选中。
  4. 按照指定源代码和依存关系中的说明添加源文件及其依存关系。
  5. 创建 C Caller 模块来调用 C 函数。
  6. 要将库模型中的模块插入 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++ 代码对接。

;