Си функция
Использование Cи кода в моделях.
Тип: CFunction
Путь в библиотеке:
|
Описание
Блок Си функция позволяет в моделях Engee использовать код на языке Си и разделяемые библиотеки, имеющие Си API.
При подключении динамической библиотеки, использующей внутренние статические переменные, стоит убедиться в том, что она предоставляет API для инициализации/деинициализации этих переменных. Соответствующие функции необходимо вызывать во вкладках StartCode и TerminateCode редактора исходного кода. В противном случае статические переменные будут сохранять свои последние значения между симуляциями, что может приводить к неправильным результатам при повторных запусках модели.
|
Использование
Для интеграции исходного Cи кода в модель Engee необходимо:
-
Добавить в модель блок Си функция из раздела Базовые/Пользовательские функции библиотеки блоков;
-
Нажать на кнопку Редактировать исходный код, после чего будет открыт редактор исходного кода.
Редактор исходного кода
Рабочая область редактора исходного кода состоит из четырех вкладок:
-
OutputCode
— содержит код, выполняющийся на каждом шаге расчета модели; -
StartCode
— содержит код, выполняющийся один раз при инициализации модели; -
TerminateCode
— содержит код, выполняющийся один раз при остановке модели; -
SharedCode
— содержит код, позволяющий обмениваться глобальными (разделяемыми) переменными и функциями между экземплярами блока Си функция.
В каждой из секций выполняется подсветка синтаксиса языка Cи.
В секции OutputCode
расположен блок специальных комментариев, имеющих значение для симулятора и генератора кода.
Комментарии позволяют связать переменные в исходном коде с сигналами (входами и выходами) блока Си функция, а также указать дополнительную информацию, необходимую для сборки кода и его использования в модели.
Особенности генерации Си кода из блока Си функция
Блок Си функция поддерживает генерацию Cи кода. При генерации кода блок Си функция превращается в функции, оборачивающие содержимое всех четырех секций исходного кода.
В данный момент генератор Cи кода не выдает дополнительную информацию для системы сборки, которая бы основывалась на директивах блока Си функция. Директивы указываются в разделе Build options настроек блока:
Повторное использование блока
Вкладка SharedCode
исходного кода блока Си функция позволяет задавать общий код, доступный для обмена данными и функциями между экземплярами блоков Си функция с одинаковым именем в параметре Имя функции.
Для того чтобы использовать функциональность SharedCode
, нужно создать блок Си функция, указать для него имя в параметре Имя функции, и затем сохранить этот блок в пользовательскую библиотеку . После этого блок можно добавлять в модель в нескольких местах, где он будет сохранять доступ к общим данным и функциям. В результате при моделировании создается только одна общая разделяемая библиотека и один набор функций, который используется всеми экземплярами блока Си функция.
При генерации кода для блоков с одинаковым именем в параметре Имя функции создаются только общие функции init
, step
и term
, а код из вкладки SharedCode
добавляется в проект один раз. Это упрощает управление кодом и обеспечивает доступ к общим данным во всех экземплярах блока Си функция.
Рабочие переменные
Вкладка Рабочие параметры блока Си функция позволяет задавать статические рабочие переменные, которые остаются специфичными для каждого экземпляра блока, даже если у нескольких блоков одинаковое значение параметра Имя функции. Эти рабочие переменные, похожи на параметры, но отличаются возможностью изменять их значения из исходного кода блока во время выполнения. Такой подход позволяет хранить и изменять данные, которые используются только в пределах определенного экземпляра блока.
Для каждой рабочей переменной на вкладке Рабочие параметры указывается Имя и Размер в байтах. Внутри исходного кода эти переменные доступны через указатель на структуру Work
, который присутствует в интерфейсах всех трех функций блока: step
, init
, и term
. Пример:
Здесь в исходном коде используется выравнивание, где макрос is_aligned
проверяет корректность выравнивания переменных. Переменные work_variable1
и work_variable3
заранее определены на вкладке Рабочие параметры и доступны через структуру Work
.
Работа с массивами
Когда данные передаются из Julia в код на языке Cи, то важно учитывать различия в индексации и структуре массивов. Это актуально для многомерных массивов, которые могут транспонироваться (изменять порядок индексов) при передаче. Ниже приведены основные моменты, которые необходимо учитывать при работе с массивами:
Подробнее о работе с массивами
-
Одномерные массивы — при передаче одномерного массива из Julia в Cи массив сохраняет свою структуру. Индексация в Cи начинается с
0
, поэтому при обращении к элементам массива нужно учитывать этот сдвиг. Например, если в параметре блока Си функция одномерный массив записан как 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и записываются в обратном порядке. |
Работа с шинами
В блоке Си функция поддерживается работа с шинами как на входных/выходных портах, так и в параметрах блока. Ниже описаны особенности настройки и использования шин:
Шины на портах
Чтобы задать шину на входном или выходном порте:
-
В параметре Type соответствующего порта (вкладка Ports в настройках блока) выберите тип
BusSignal
. -
После этого параметр Input bus type (или Output bus type) автоматически примет форму:
BusSignal{(), Tuple(), ()}
Здесь следует указать:
-
Тип шины явно, например
BusSignal{(:s1, :s2), Tuple(Float64, Int54), ), (}
.:s1
,:s2
— имена сигналов в шине;Tuple(Float64, Int64)
— их типы;), (
— их размерности; -
Или имя переменной, содержащей описание типа шины, определенной в рабочем пространстве Engee (в окне переменных) или в Си коде.
-
Все типы шин должны быть заданы явно — это особенность блока Си функция. Имена шин не должны содержать пробелов, так как они используются в Си коде, где пробелы в именах переменных недопустимы. |
Шины в параметрах
Если шина используется как параметр:
-
В поле Value нужно задать шину в виде именованного кортежа, например:
(s1 = 4.4, s2 = (b1 = 5, b))
-
Если параметр назван, например,
p
, то в Cи коде (исходный код блока Си функция) обращаться к элементам шины можно как к структурам:output1 = input1 * (p.s1 + p.s2.b1);
Основные
Количество входных портов —
количество входных портов
1 (по умолчанию)
Details
Определяет количество входных портов блока. Значение параметра Количество входных портов будет соответствовать количеству входных портов.
Количество выходных портов —
количество выходных портов
1 (по умолчанию)
Details
Определяет количество выходных портов блока. Значение параметра Количество выходных портов будет соответствовать количеству выходных портов.
Имя функции —
идентификатор общего кода для нескольких блоков
нет (по умолчанию)
Details
Укажите строковое значение для объединения кода блоков Си функция. Блоки с одинаковым именем в параметре Имя функции будут использовать общий код и обмениваться данными через вкладку исходного кода SharedCode
блока Си функция.
Если оставить поле пустым, то выбранный блок Си функция останется автономным (по умолчанию).
В параметре Имя функции можно указывать как абсолютный путь (начинается с Также допустимо указание только имени файла: в этом случае Engee сначала ищет файл в текущей директории, а затем — в путях поиска, описанных в Редактор пути Engee. |
Шаг расчёта —
интервал между шагами расчета
−1 (по умолчанию)
Details
Укажите интервал между шагами расчета как неотрицательное число. Чтобы наследовать шаг расчета, установите для этого параметра значение −1
.
Ports
Вход
Input port 1 —
входной порт №1
скаляр
| вектор
| матрица
Details
Для работы входного порта заполните/настройте следующие поля:
-
Label — имя (текстовая метка) входного порта. По умолчанию ячейка имени не заполнена (имя не задано).
-
Variable name — имя переменной входного порта в исходном Си-коде. По умолчанию
input1
./* Этот код вызывается на каждом шаге расчета модели */ output1 = input1 * param1;
где
input1
— имя переменной входного порта;output1
— имя переменной выходного порта;param1
— имя переменной параметра. -
Type — тип данных переменной входного порта в исходном Cи коде. Тип данных переменной должен совпадать с типом данных сигнала. Поддерживаются базовые типы данных языка Си:
double (по умолчанию)
|float
|int8_t
|uint8_t
|int16_t
|uint16_t
|int32_t
|uint32_t
|int64_t
uint64_t
|int128_t
|uint128_t
|bool
|BusSignal
-
Size — размерность входного сигнала
1 (по умолчанию)
.В поле Size используется нотация Julia, где размер записывается в виде кортежа, например, (2, 3, 4)
. Однако при работе с этим портом в Cи коде необходимо учитывать изменение порядка индексов. Это значит, что если порт задан с размером(2, 3, 4)
, то в Cи коде переменная будет иметь размерность[4][3][2]
. Это правило аналогично тому, как работают параметры: порядок индексов в Cи транспонируется по сравнению с Julia.
Выход
Output port 1 —
выходной порт №1
скаляр
| вектор
| матрица
Details
Для работы выходного порта заполните/настройте следующие поля:
-
Label — имя (текстовая метка) выходного порта. По умолчанию ячейка имени не заполнена (имя не задано).
-
Variable name — имя переменной выходного порта в исходном Си-коде. По умолчанию
output1
./* Этот код вызывается на каждом шаге расчета модели */ output1 = input1 * param1;
где
output1
— имя переменной выходного порта;input1
— имя переменной входного порта;param1
— имя переменной параметра. -
Type — тип данных переменной выходного порта в исходном Cи коде. Тип данных переменной должен совпадать с типом данных сигнала. Поддерживаются базовые типы данных языка Си:
double (по умолчанию)
|float
|int8_t
|uint8_t
|int16_t
|uint16_t
|int32_t
|uint32_t
|int64_t
uint64_t
|int128_t
|uint128_t
|bool
|BusSignal
-
Size — размерность выходного сигнала
1 (по умолчанию)
.В поле Size используется нотация Julia, где размер записывается в виде кортежа, например, (2, 3, 4)
. Однако при работе с этим портом в Cи коде необходимо учитывать изменение порядка индексов. Это значит, что если порт задан с размером(2, 3, 4)
, то в Cи коде переменная будет иметь размерность[4][3][2]
. Это правило аналогично тому, как работают параметры: порядок индексов в Cи транспонируется по сравнению с Julia.
Параметры
Количество параметров —
укажите число параметров
1 (по умолчанию)
Details
Число параметров, используемых в блоке.
Параметр 1 —
определяет параметр как переменную
1 (по умолчанию)
Details
Определяет параметр как переменную для использования в исходном коде:
-
Имя — имя параметра, по умолчанию
param1
. Может быть изменено. Имена новых параметров называютсяparam2
и далее по возрастанию. -
Величина — значение параметра, по умолчанию
0
. Может быть изменено. Значение новых параметров также по умолчанию равно нулю.
Для работы параметров необходимо зайти в редактор кода блока Си функция (Редактировать исходный код) во вкладку OutputCode
и внести имя нужного параметра. По умолчанию:
/* Этот код вызывается на каждом шаге расчета модели */
output1 = input1 * param1;
где input1
— переменная входного сигнала; output1
— переменная выходного сигнала.
Рабочие параметры
Количество рабочих переменных —
число рабочих переменных
0 (по умолчанию)
Details
Число рабочих переменных, используемых в блоке для хранения данных, специфичных для каждого экземпляра. Рабочие переменные доступны через структуру Work
и могут использоваться в функциях init
, step
и term
для записи и чтения данных.
Каждая переменная задается с указанием параметров Имя и Размер в байтах, что позволяет хранить данные различных типов. Если переменной присвоен больший размер, то ее можно использовать как массив.
Рабочий параметр 1 —
определяет рабочую переменную
1 (по умолчанию)
Details
Определяет рабочую переменную для использования в исходном коде:
-
Имя — имя параметра, по умолчанию
work_variable1
. Может быть изменено. Имена новых рабочих переменных называютсяwork_variable2
и далее по возрастанию. -
Размер в байтах — объем памяти, выделяемый для переменной, в байтах. Этот параметр позволяет указать размер переменной, будь то скаляр или массив.
Для работы переменных необходимо зайти в редактор кода блока Си функция (Редактировать исходный код) во вкладку OutputCode
и внести имя переменной.
Build options
Исходные файлы —
подключение файлов исходного кода
нет (по умолчанию)
Details
Используется для подключения дополнительных файлов исходного кода. Должен включать путь и имя файла вместе с расширением. Например:
/user/project/src/example.c
Включать каталоги — определение пути к каталогу заголовочных файлов
нет (по умолчанию)
Details
Используется для определения пути к каталогу, содержащему заголовочные файлы. Например:
/user/project/include
Библиотечные каталоги —
определение пути к каталогу библиотек
нет (по умолчанию)
Details
Используется для определения пути к каталогу, содержащему разделяемые библиотеки. Например:
/user/project/third_party
Заголовочных файлы —
подключение заголовочных файлов
stdint.h math.h (по умолчанию)
Details
Используется для подключения заголовочных файлов. Должен включать имя заголовочного файла вместе с расширением. Например:
example.h
Определение дополнительных директив —
определение дополнительных директив #define
нет (по умолчанию)
Details
Используется для определения дополнительных директив #define. Например:
defines LOWER=0 UPPER=300 STEP=20
Библиотеки —
подключение библиотек
нет (по умолчанию)
Details
Используется для подключения библиотек. Должен включать имя библиотеки вместе с расширением. Например:
libexample.so
Фактический тип данных, как и поддержка возможных типов данных, зависит от пользовательского кода внутри блока. |