Введение
Всем здравствуйте! Несложно придумать ситуацию, в которой автоматическая сборка схемы в Simulink сильно упростила бы жизнь. Допустим, вы проводите эксперименты с КИХ-фильтром и часто меняете его порядок. Схема при этом, конечно, изменяется, но при этом строится она всё время по одному и тому же алгоритму. Это прекрасно видно на этой картинке:
Можно заметить, что от порядка фильтра (N) меняется только количество блоков задержки и усиления — структура же всегда одинаковая. В таких случаях можно применить подход, который я опишу далее.
Функция инициализации
В Simulink есть специальный инструмент, который позволяет автоматически задать параметры схемы перед началом моделирования. Представляет он из себя код на языке Matlab (его мы будем называть функцией инициализации), в котором задаются требуемые характеристики модели. Чтобы начать писать эту функцию, нужно зайти в Modeling -> Model Settings -> Model Properties -> Callbacks -> InitFcn*. Также во вкладку Model Properties можно войти, кликнув правой кнопкой мыши по пустому месту в схеме. Ниже будут рассмотрены основные функции, которые нам потребуются.
Стоит отметить, что также функции инициализации можно использовать в маскированных подсистемах (раздел Code в редакторе маски). Если вы поняли о чём я — пользуйтесь, если нет — не берите в голову :)
Функция add_block
Добавить блок на схему можно с помощью функции add_block. Первым её параметром нужно передать путь к блоку, а вторым — путь к месту, куда этот блок нужно добавить. Допустим, я хочу добавить блок Constant на самый верхний уровень схемы. Сделать это можно с помощью следующего кода:
add_block('simulink/Sources/Constant', 'modelName/newBlockName')
Код сработает при условии, что файл модели называется "modelName". Путь к любому блоку можно узнать следующим образом: заходим в Simulation -> Library Browser, далее кликаем правой кнопкой мыши по интересующей нас библиотеке и нажимаем на Open ... library. В открывшемся окне ищем интересующий нас блок. Сверху будет путь, куда нужно будет лишь добавить название требуемого блока. Путь к блоку для примера использования функции add_block, который был приведён выше, можно отследить по следующему скриншоту:
Записываем то, что выделено красным, как путь, и добавляем название блока. Получили "simulink/Sources/Constant". Нужно отметить, что название библиотеки в Library Browser почти всегда не совпадает с названием, которое используется в пути. Допустим, Audio Toolbox называется audiolibv1.
Добавим в нашу схему подсистему (Subsystem). Чтобы блок появился в подсистеме, а не на самом верхнем уровне, нужно написать следующее:
add_block('simulink/Sources/Constant', 'modelName/Subsystem/newBlockName')
Думаю, что логика тут понятна. Далее для удобства будем считать, что блок у нас всё-таки располагается на самом верхнем уровне модели.
Добавить блок и сразу же задать его параметр можно следующим образом:
add_block('simulink/Sources/Constant', 'modelName/newBlockName', 'Value', '25')
Можно такими "парами" задать и сразу несколько параметров блока. Откуда брать названия этих параметров рассмотрим далее.
Параметры блоков, функции get_param и set_param
У каждого блока в Simulink есть параметры, которые обычно задаются вручную (например, параметр Constant value у блока Constant). Получить и задать их значения можно и программно, для этого используются функции get_param и set_param соответственно.
Приравняем к нулю параметр Value у ранее добавленного нами блока Constant:
set_param('modelName/newBlockName', 'Value', '0')
Теперь получим этот параметр и запишем в переменную v:
v = get_param('modelName/newBlockName', 'Value')
Важно заметить, что значение параметра, которое записывается в textbox, является строкой, даже если по логике это должно быть число. Так что в данном случае значение v будет равно '0', в не 0.
Чтобы понять, как называется нужный параметр, нужно обратиться к документации. Допустим, нас интересует всё тот же блок Constant и его поле Constant value . В разделе Parameters находим параметр Constant value и ищем пункт Programmatic Use — в нём и будет находится интересующая нас информация. На скриншоте ниже видно, что параметр этот называется 'Value', это название и использовалось в предыдущем примере кода.
Таким образом можно найти уникальные параметры блока, но есть и общие параметры — допустим, размеры и тому подобное. Для таких параметров была создана отдельная страничка в документации. Уникальные параметры тоже собраны в одном месте, но так их искать, как мне кажется, не очень удобно.
Функция add_line
Блоки необходимо соединять друг с другом, это тоже можно сделать программно — для этого существует функция add_line. Использовать её крайне просто. Допустим, в подсистеме Subsystem модели model, есть два блока. Первый называется Gain1, а второй — Scope1. Соединим их программно:
add_line('model/Subsystem', 'Gain1/1', 'Scope1/1')
Первый параметр указывает место в модели, где находятся наши блоки. Второй и третий параметры — порты, которые нужно соединить. Название порта формируется просто — сначала идёт название блока, потом '/n', где n — номер порта (обычно они нумеруются сверху вниз). Информация о портах, конечно, есть в документации (раздел Ports).
Функции delete_block и delete_line
Информации выше уже достаточно, чтобы автоматизировать построение ряда схем. Только вот если вы запустите модель два раза подряд, то на второй раз ничего не скомпилируется, потому что два блока с одним и тем же названием в одно место модели добавить нельзя, а вы именно это и попытаетесь сделать. Из-за этого перед каждым автоматическим построением схемы нужно удалять то, что уже есть. Для этого существуют функции delete_block и delete_line.
Удалить блок можно достаточно просто. Удалим блок с названием blockName, который находится на верхнем уровне модели с названием model:
delete_block('model/blockName')
Удалить линии, которые соединяют блоки, уже сложнее. Самый простой вариант — получить дескрипторы инцидентных линий для каждого блока (будем называть линию и блок инцидентными, если эта линия входит в какой-либо порт этого блока), и, используя их, всё удалить. Приступим к получению дескрипторов всех инцидентных линий для всё того же блока blockName:
lh = get_param('model/blockName', 'LineHandles')
Переменная lh является структурой, в которой нас интересуют поля Inport и Outport. Они являются векторами, в которых находятся дескрипторы линий входящих в блок и выходящих из блока соответственно. Если у порта нет инцидентных линий, то на месте искомого дескриптора будет -1. Теперь мы в состоянии удалить все инцидентные линии:
for i = 1 : length(lh.Inport)
try
delete_line(lh.Inport(i));
end
end
for i = 1 : length(lh.Outport)
try
delete_line(lh.Outport(i));
end
end
Конструкция try ... end использовалась для того, чтобы код корректно сработал в ситуации, когда у порта нет инцидентной линии, ведь выражение delete_line(-1) вызовет ошибку.
Почти всё! Осталось только узнать, как найти все блоки по указанному пути. Сделать это несложно — с помощью следующего кода мы получим вектор со всеми хэндлами блоков верхнего уровня модели с названием model:
blocks = Simulink.findBlocks('model')
Осталось только "пройтись" по полученному вектору blocks и удалить все инцидентные линии для каждого блока:
for i = 1 : length(blocks)
lh = get_param(blocks(i), 'LineHandles');
for j = 1 : length(lh.Inport)
try
delete_line(lh.Inport(j));
end
end
for j = 1 : length(lh.Outport)
try
delete_line(lh.Outport(j));
end
end
end
Ну а после этого можно удалить все блоки:
delete_block(blocks)
Вот и всё! Теперь ваших знаний достаточно, чтобы автоматически собирать большинство схем в Simulink!
Пример использования
Конечно, без простенького примера я вас не оставлю:
%Параметры схемы
bias = '0.5';
freq = '2*pi*10';
%Удаляем линии и блоки
blocks = Simulink.findBlocks('habr');
for i = 1 : length(blocks)
lh = get_param(blocks(i), 'LineHandles');
for j = 1 : length(lh.Inport)
try
delete_line(lh.Inport(j));
end
end
for j = 1 : length(lh.Outport)
try
delete_line(lh.Outport(j));
end
end
end
delete_block(blocks);
%Добавляем блоки
add_block('simulink/Sources/Sine Wave', 'habr/Sine');
add_block('simulink/Math Operations/Bias', 'habr/Bias');
add_block('simulink/Sinks/Scope', 'habr/Scope');
%Меняем параметры блоков
set_param('habr/Sine', 'position', [100 100 150 150]);
set_param('habr/Bias', 'position', [200 100 250 150]);
set_param('habr/Scope', 'position', [300 100 350 150]);
set_param('habr/Sine', 'Frequency', freq);
set_param('habr/Bias', 'Bias', bias);
%Соединяем блоки
add_line('habr', 'Sine/1', 'Bias/1');
add_line('habr', 'Bias/1', 'Scope/1');
Тут можно задавать значения переменных bias и freq, меняя тем самым величину постоянного смещения синусоиды и её частоту соответственно. Сама схема выглядит так:
Надеюсь, что статья вам помогла!