C Function
Использование Cи-кода в моделях
Описание
Блок C Function позволяет в моделях Engee использовать код на языке Си и разделяемые библиотеки, имеющие Си API.
При подключении динамической библиотеки, использующей внутренние статические переменные, стоит убедиться в том, что она предоставляет API для инициализации/деинициализации этих переменных. Соответствующие функции необходимо вызывать во вкладках StartCode и TerminateCode редактора исходного кода. В противном случае, статические переменные будут сохранять свои последние значения между симуляциями, что может приводить к неправильным результатам при повторных запусках модели. |
Использование
Для интеграции исходного Cи кода в модель Engee необходимо:
-
Добавить в модель блок C Function из раздела Базовые/Пользовательские функции библиотеки блоков;
-
Нажать на кнопку Редактировать исходный код, после чего будет открыт редактор исходного кода.
Редактор исходного кода
Рабочая область редактора исходного кода состоит из четырех вкладок:
-
Output code — содержит код, выполняющийся на каждом шаге расчета модели;
-
Start code — содержит код, выполняющийся один раз при инициализации модели;
-
Terminate code — содержит код, выполняющийся один раз при остановке модели;
-
SharedCode — содержит код, позволяющий обмениваться глобальными (разделяемыми) переменными и функциями между экземплярами блока C Function.
В каждой из секций выполняется подсветка синтаксиса языка Cи.
В секции Output code расположен блок специальных комментариев, имеющих значение для симулятора и генератора кода.
Комментарии позволяют связать переменные в исходном коде с сигналами (входами и выходами) блока C Function, а также указать дополнительную информацию, необходимую для сборки кода и его использования в модели.
Особенности генерации Си кода из блока C Function
Блок C Function поддерживает генерацию Cи кода. При генерации кода блок C Function превращается в функции, оборачивающие содержимое всех четырех секций исходного кода.
В данный момент генератор Cи-кода не выдает дополнительную информацию для системы сборки, которая бы основывалась на директивах блока C Function. Директивы указываются в разделе Build options настроек блока:
Повторное использование блока
Вкладка SharedCode
исходного кода C Function позволяет задавать общий код, доступный для обмена данными и функциями между экземплярами блоков C Function с одинаковым Function name.
Для того чтобы использовать функциональность SharedCode
, нужно создать блок C Function, указать для него Function name, и затем сохранить этот блок в пользовательскую библиотеку . После этого его можно добавлять в модель в нескольких местах, где он будет сохранять доступ к общим данным и функциям. В результате при моделировании создается только одна общая разделяемая библиотека и один набор функций, который используется всеми экземплярами этого блока C Function.
При генерации кода для блоков с одинаковым Function name создаются только общие функции init
, step
и term
, а код из вкладки SharedCode
добавляется в проект один раз. Это упрощает управление кодом и обеспечивает доступ к общим данным во всех экземплярах блока C Function.
Рабочие переменные
Вкладка WorkVariables блока C Function позволяет задавать статические рабочие переменные, которые остаются специфичными для каждого экземпляра блока, даже если у нескольких блоков одинаковое значение Function name. Эти рабочие переменные, похожи на параметры, но отличаются возможностью изменять их значения из исходного кода блока во время выполнения. Такой подход позволяет хранить и изменять данные, которые используются только в пределах определенного экземпляра блока.
Для каждой рабочей переменной на вкладке WorkVariables указывается имя (Name) и размер в байтах (Size in bytes). Внутри исходного кода эти переменные доступны через указатель на структуру Work
, который присутствует в интерфейсах всех трех функций блока: step
, init
, и term
.
Пример:
Здесь в исходном коде используется выравнивание, где макрос is_aligned
проверяет корректность выравнивания переменных. Переменные work_variable1
и work_variable3
заранее определены на вкладке WorkVariables и доступны через структуру Work
.
Работа с массивами
Когда данные передаются из Julia в код на языке Cи, важно учитывать различия в индексации и структуре массивов. Это актуально для многомерных массивов, которые могут транспонироваться (изменять порядок индексов) при передаче. Ниже приведены основные моменты, которые необходимо учитывать при работе с массивами:
Подробнее о работе с массивами
-
Одномерные массивы — при передаче одномерного массива из Julia в Cи массив сохраняет свою структуру. Индексация в Cи начинается с
0
, поэтому при обращении к элементам массива нужно учитывать этот сдвиг. Например, если в параметре блока C-function одномерный массив записан как a =[1, 2, 3]
, то на языке Cи:#include <stdio.h> int main() { int a[3] = {1, 2, 3}; printf("%d", a[2]); // Выведет 3 return 0; }
В этом примере для получения значения элемента массива с индексом
2
используется обращениеa[2]
, что вернет значение3
. -
Многомерные массивы — многомерные массивы могут изменять порядок индексов при передаче из Julia в Cи. В Cи-коде массив будет транспонирован, что означает, что при доступе к элементам массива нужно учитывать изменение порядка индексов. Например, есть массив в Julia:
a = [1 2 3; 4 5 6] #Julia интерпретирует это как матрицу 2x3 2x3 Matrix{Int64} 1 2 3 4 5 6 #доступ к элементу массива a [1,2] 2
Тогда в Си:
#include <stdio.h> int main() { int a[3][2] = { {1, 4}, {2, 5}, {3, 6} }; printf("%d", a[1][0]); // Выведет 2 return 0; }
Массив
[1 2 3; 4 5 6]
в Julia становится массивомa[2][3]
в Cи, где первый индекс указывает на строку, а второй — на столбец. В данном примере на Cи для получения элемента со значением2
используется индексацияa[1][0]
. Следовательно, массив в Cи фактически хранит элементы транспонированного массива Julia, что необходимо учитывать при доступе к элементам.
При работе с трехмерными и более сложными массивами индексация также может меняться. Например, если массив в Julia имеет размерность [2, 3, 4]
, то в Cи он будет представлен как [4][3][2]
. Это необходимо учитывать при обращении к элементам массива в Cи-коде.
Пример трехмерного массива в Julia:
a = reshape(1:24, 2, 3, 4)
[:, :, 1] =
1 3 5
2 4 6
[:, :, 2] =
7 9 11
8 10 12
[:, :, 3] =
13 15 17
14 16 18
[:, :, 4] =
19 21 23
20 22 24
println(a[1, 2, 3]) # Выведет 13
В С:
#include <stdio.h>
int main() {
int a[4][3][2] = {
{
{1, 4},
{2, 5},
{3, 6}
},
{
{7, 10},
{8, 11},
{9, 12}
},
{
{13, 16},
{14, 17},
{15, 18}
},
{
{19, 22},
{20, 23},
{21, 24}
}
};
printf("%d", a[2][1][0]); // Выведет 13
return 0;
}
Общее правило преобразования индексов из Julia в Cи заключается в изменении порядка:
Это означает, что индексы в Cи записываются в обратном порядке. |
Main
Number of input ports — количество входных портов
1 (по умолчанию)
Определяет количество входных портов блока. Значение параметра Number of input ports будет соответствовать количеству входных портов.
Number of output ports — количество выходных портов
1 (по умолчанию)
Определяет количество выходных портов блока. Значение параметра Number of output ports будет соответствовать количеству выходных портов.
Function name — идентификатор общего кода для нескольких блоков
нет (по умолчанию)
Укажите строковое значение для объединения кода блоков C Function. Блоки с одинаковым Function name будут использовать общий код и обмениваться данными через вкладку исходного кода SharedCode блока C Function.
Если оставить поле пустым, то выбранный блок C Function останется автономным (по умолчанию).
Sample time (-1 for inherited) — интервал между шагами расчета
−1 (по умолчанию)
Укажите интервал между шагами расчета как неотрицательное число. Чтобы наследовать шаг расчета, установите для этого параметра значение −1
.
Ports
Вход
Label — имя (текстовая метка) входного порта
нет (по умолчанию)
Имя (текстовая метка) входного порта. По умолчанию ячейка имени не заполнена (имя не задано).
Variable name — имя переменной входного порта в исходном Си-коде
input1 (по умолчанию)
Имя переменной входного порта в коде:
+
/* Этот код вызывается на каждом шаге расчета модели */
output1 = input1 * param1;
где input1
— имя переменной входного порта; output1
— имя переменной выходного порта; param1
— имя переменной параметра.
Type — тип данных переменной входного порта в исходном Cи-коде
double (по умолчанию)
| float
| uint8_t
| int16_t
| uint16_t
| int32_t
| uint32_t
| int64_t
| uint64_t
Тип данных переменной входного порта в исходном Cи-коде. Тип данных переменной должен совпадать с типом данных сигнала.
Поддерживаются базовые типы данных языка Си.
Size — размерность входного сигнала
1 (по умолчанию)
Размерность входного сигнала.
В поле Size используется нотация Julia, где размер записывается в виде кортежа, например, (2, 3, 4) . Однако при работе с этим портом в Cи-коде необходимо учитывать изменение порядка индексов. Это значит, что если порт задан с размером (2, 3, 4) , то в Cи-коде переменная будет иметь размерность [4][3][2] . Это правило аналогично тому, как работают параметры: порядок индексов в Cи транспонируется по сравнению с Julia.
|
Выход
Label — имя (текстовая метка) выходного порта
нет (по умолчанию)
Имя (текстовая метка) выходного порта. По умолчанию ячейка имени не заполнена (имя не задано).
Variable name — имя переменной выходного порта в исходном С-коде
output1 (по умолчанию)
Имя переменной выходного порта в коде:
+
/* Этот код вызывается на каждом шаге расчета модели */
output1 = input1 * param1;
где input1
— имя переменной входного порта; output1
— имя переменной выходного порта; param1
— имя переменной параметра.
Type — тип данных выходной переменной в исходном C-коде
double (по умолчанию)
| float
| uint8_t
| int16_t
| uint16_t
| int32_t
| uint32_t
| int64_t
| uint64_t
Тип данных выходной переменной в исходном Cи-коде. Тип данных переменной должен совпадать с типом данных сигнала.
Поддерживаются базовые типы данных языка Си.
Size — размерность выходного сигнала
1 (по умолчанию)
Размерность выходного сигнала.
В поле Size используется нотация Julia, где размер записывается в виде кортежа, например, (2, 3, 4) . Однако при работе с этим портом в Cи-коде необходимо учитывать изменение порядка индексов. Это значит, что если порт задан с размером (2, 3, 4) , то в Cи-коде переменная будет иметь размерность [4][3][2] . Это правило аналогично тому, как работают параметры: порядок индексов в Cи транспонируется по сравнению с Julia.
|
Parameters
Number of parameters — укажите число параметров
1 (по умолчанию)
Число параметров, используемых в блоке.
Parameter — определяет параметр как переменную
1 (по умолчанию)
Определяет параметр как переменную для использования в исходном коде:
-
Name
— имя параметра, по умолчаниюparam1
. Может быть изменено. Имена новых Parameter называютсяparam2
и далее по возрастанию. -
Value
— значение параметра, по умолчанию1
. Может быть изменено. Значение новых Parameter равно нулю.
Для работы параметров необходимо зайти в редактор кода блока С Function (Редактировать исходный код) во вкладку OutputCode и внести имя нужного параметра. По умолчанию:
/* Этот код вызывается на каждом шаге расчета модели */
output1 = input1 * param1;
где input1
— переменная входного сигнала; output1
— переменная выходного сигнала.
WorkVariables
Number of work variables — число рабочих переменных
0 (по умолчанию)
Число рабочих переменных, используемых в блоке для хранения данных, специфичных для каждого экземпляра. Рабочие переменные доступны через структуру Work
и могут использоваться в функциях init
, step
и term
для записи и чтения данных.
Каждая переменная задается с указанием имени (Name) и размера (Size), что позволяет хранить данные различных типов. Если переменной присвоен больший размер, то ее можно использовать как массив.
WorkVariable — определяет рабочую переменную
1 (по умолчанию)
Определяет рабочую переменную для использования в исходном коде:
-
Name — имя параметра, по умолчанию
work_variable1
. Может быть изменено. Имена новых WorkVariable называютсяwork_variable2
и далее по возрастанию. -
Size in bytes — объем памяти, выделяемый для переменной, в байтах. Этот параметр позволяет указать размер переменной, будь то скаляр или массив.
Для работы переменных необходимо зайти в редактор кода блока С Function (Редактировать исходный код) во вкладку OutputCode и внести имя переменной.
Build options
Source files — подключение файлов исходного кода
нет (по умолчанию)
Используется для подключения дополнительных файлов исходного кода. Должен включать путь и имя файла вместе с расширением.
Например:
/user/project/src/example.c
Include directories — определение пути к каталогу заголовочных файлов
нет (по умолчанию)
Используется для определения пути к каталогу, содержащему заголовочные файлы.
Например:
/user/project/include
Library directories — определение пути к каталогу библиотек
нет (по умолчанию)
Используется для определения пути к каталогу, содержащему разделяемые библиотеки.
Например:
/user/project/third_party
Headers — подключение заголовочных файлов
stdint.h math.h (по умолчанию)
Используется для подключения заголовочных файлов. Должен включать имя заголовочного файла вместе с расширением.
Например:
example.h
Defines — определение дополнительных директив #define
нет (по умолчанию)
Используется для определения дополнительных директив #define.
Например:
defines LOWER=0 UPPER=300 STEP=20
Libraries — подключение библиотек
нет (по умолчанию)
Используется для подключения библиотек. Должен включать имя библиотеки вместе с расширением.
Например:
libexample.so
Фактический тип данных, как и поддержка возможных типов данных, зависит от пользовательского кода внутри блока. |