Описание пользователя
Глава 3. Использование стандартных модулей автокомпиляции
§3.6. Принципы создания автокомпилируемых моделей блоков
§3.6.2. Работа со статическими переменными блока
Описывается использование в моделях статических переменных блока, которые могут быть его входами и выходами. Отдельно рассматриваются разные типы переменных и их возможности.
§3.6.2.1. Модели с простыми статическими переменными
Описывается использование в моделях статических переменных простых типов: целых, вещественных и логических. Это наиболее часто используемые типы входов и выходов блока. Также рассматриваются особенности использования объектов, создаваемых для работы с переменными, и создание моделей, срабатывающих не в каждом такте расчета, а только при изменении входов блока.
Вернемся к рассмотренной в §3.2 простейшей модели: сумматору, выдающему на вещественный выход «y» сумму вещественных входов «x1» и «x2». Эта модель вполне работоспособна, но у нее есть пара недостатков.
Во-первых, сумматор с такой моделью будет работать только в том случае, если в параметрах блока будет установлен флаг запуска каждый такт. По умолчанию этот флаг устанавливается для каждого вновь созданного блока, поэтому в §3.2 он не упоминается. Такой способ запуска приводит к тому, что функция модели будет принудительно выполняться в каждом такте, вычисляя значение выхода, даже если значения входов не изменились. В данном случае модель очень простая, и ее лишние запуски не приведут к существенному замедлению работы схемы, однако, для более сложных моделей замедление может стать заметным. В RDS считается хорошим тоном писать модели блоков, выходы которых зависят только от входов, так, чтобы они запускались только при изменении этих входов.
Во-вторых, описанная модель не рассчитана на то, что на один из ее входов может вместо вещественного числа поступить специальная константа (в математической библиотеке языка C она называется HUGE_VAL), используемая в качестве признака математической ошибки. Такую константу выдают на выход стандартные блоки RDS, попытавшиеся выполнить недопустимую математическую операцию – например, деление на ноль. Как правило, при обнаружении на любом своем входе этой константы, блок, не выполняя вычислений, выдает ее же на свой выход, сообщая тем самым всем соединенным с ним блокам о произошедшей ошибке. По желанию разработчика, можно предусмотреть и другие возможные реакции. На самом деле, такая простая модель, несмотря на отсутствие в ней каких-либо проверок входов, все равно выдаст на выход константу HUGE_VAL при поступлении ее на один из входов: так устроена операция сложения. Кроме того, включенный по умолчанию флажок «» в параметрах модели автоматически заменит результат невыполнимой математической операции на эту константу. Однако, более сложным моделям необходимо опознавать ошибочные значения входов и предпринимать по этому поводу какие-либо действия, поэтому, для примера, мы добавим в нашу модель такие проверки.
Исправим созданную ранее модель сумматора. Сначала разберемся с запуском каждый такт – это не требует внесения изменений в текст введенного фрагмента программы модели, достаточно изменить некоторые параметры блока и его переменных. Загрузим схему с блоком, созданным в §3.2 (если она не сохранена, нужно просто проделать заново все описанные там шаги), и откроем окно редактора модели двойным щелчком на этом блоке. На левой панели редактора выберем вкладку «» (см. рис. 324, если левая панель отсутствует, следует включить ее пунктом меню «») и нажмем кнопку «» под списком статических переменных (см. рис. 325). Откроется окно редактирования статических переменных блока, в котором нужно внести изменения, изображенные на рис. 366.
Рис. 366. Изменения в статических переменных простого сумматора
Прежде всего, необходимо включить флажки в колонке «» напротив входов блока «x1» и «x2». Это приведет к тому, что при срабатывании любой связи, подключенной к этим входам, сигналу запуска блока, то есть его первой переменной (в данном блоке она называется «Start» – это ее обычное имя, даваемое ей при создании блока), будет автоматически присвоено значение 1, а это, в свою очередь, вызовет запуск модели блока в следующем такте расчета. Кроме того, переменной «Start» нужно дать единичное значение по умолчанию, чтобы при самом первом запуске расчета модель сложила начальные значения своих входов и выдала сумму на выход. Если этого не сделать, модель запустится только после первого срабатывания связи, а начальные значения входов не будут обработаны. Может показаться, что достаточно просто дать выходу значение по умолчанию, равное сумме значений входов, однако, это не так. Перед первым запуском расчета производится начальная передача данных по связям, о которой модели блоков не информируются: если, например, к входам блока будут подключены поля ввода, на момент самого первого запуска расчета (или запуска после сброса) значения входов «x1» и «x2» будут не нулевыми, а равными значениям этих полей ввода, причем сигнал «Start» при этой начальной передаче данных автоматически взведен не будет. По этой причине в моделях, полагающихся на автоматический запуск при срабатывании входных связей, сигналу запуска следует присваивать единицу либо в качестве значения по умолчанию, как в данном случае, либо принудительно в реакции на событие запуска расчета RDS_BFM_STARTCALC.
Внеся изменения в статические переменные блока, следует закрыть окно редактирования переменных кнопкой «».
Теперь наша модель способна запускаться при срабатывании входных связей, но в параметрах блока, к которому она подключена, пока еще установлен ее запуск каждый такт. Необходимо перевести этот блок в режим запуска по сигналу, то есть запуска только при ненулевом значении переменной «». Поскольку этот блок – единственный, можно просто открыть окно его параметров и переключить флажок на вкладке «» (рис. 367). Если бы таких блоков было несколько, пришлось бы открывать окно параметров для каждого из них, поэтому переключим режим запуска модели блока при помощи функции групповой установки, которая обработает все блоки с нашей моделью во всех подсистемах. Для этого следует открыть окно групповой установки, выбрав в окне редактора модели пункт меню «» или нажать соответствующую ему кнопку. В окне следует включить флажок «» и подчиненный ему «» (рис. 368), после чего закрыть окно кнопкой «».
Рис. 367. Установка запуска по сигналу в окне параметров блока
Рис. 368. Установка запуска по сигналу в окне групповой установки
Теперь модель блока будет запускаться один раз при первом запуске расчета, и далее каждый раз при срабатывании одной из входных связей. С точки зрения пользователя поведение сумматора не изменится: он складывает свои входы, как и раньше (простая схема для тестирования сумматора изображена на рис. 328), но теперь его работа меньше нагружает систему.
Добавим в нашу модель проверку значений входов на константу, сигнализирующую о математической ошибке. Как уже объяснялось выше, для такой простой модели, да еще и с включенным перехватом ошибок математики, в этой проверке нет необходимости. Здесь эта проверка приводится только в качестве примера. Она будет полезна, а, в некоторых случаях, и обязательна, в более сложных блоках. Например, если бы модель выполняла не сложение, а деление, и перехват ошибок математики был бы выключен, подача на оба входа константы ошибки привело бы к исключению в модели (впрочем, как и деление на ноль, на которое тоже пришлось бы делать проверку).
В автоматически компилируемых моделях блоков в качестве значения, указывающего на математическую ошибку, лучше всего брать не константу HUGE_VAL, а содержимое глобальной переменной rdsbcppHugeDouble. В этой переменной хранится константа HUGE_VAL, полученная из RDS при помощи сервисной функции rdsGetHugeDouble. Вызов этой функции добавляется в программу модели без участия пользователя. Использование этой переменной вместо константы гарантирует, что во всех моделях блоков, независимо от того, какими компиляторами с какими версиями библиотек они созданы, в качестве ошибки математической операции используется одно и то же число. Следует учитывать, что эта переменная имеет тип double, и, поэтому, она пригодна только для работы с переменными блока того же типа. Для типа float в RDS нет специально выделенного значения ошибки, поэтому при создании автокомпилируемых моделей следует, по возможности, использовать именно тип double.
Поскольку занесение значения в глобальную переменную rdsbcppHugeDouble выполняется без нашего участия, все, что нам нужно сделать – это добавить в реакцию на такт расчета нашей модели (вкладка «» в редакторе) оператор if (цветом выделен добавленный в модель текст):
if(x1==rdsbcppHugeDouble || x2==rdsbcppHugeDouble)
y=rdsbcppHugeDouble;
else
y=x1+x2;
Теперь, если хотя бы один из входов блока будет равен значению rdsbcppHugeDouble, выходу будет присвоено это же значение, и вычисления выполнены не будут. При желании, при ошибке на входе можно взводить какой-либо дополнительный выход ошибки, или выдавать на выход нулевое значение – все зависит от того, каких целей добивается создатель модели.
В программе модели можно использовать не только простые математические операции, но и любые функции языка C – например, тригонометрические. Рассмотрим еще одну модель, которая будет вычислять синус или косинус входа блока. В этом блоке тоже будут только простые статические переменные: вещественный вход «x», вещественный выход «y» и целый вход «func», на который будет подаваться ноль, если необходимо вычислить синус, и единица, если косинус. Сразу заложим в этот блок запуск при срабатывании входных связей и введем в него проверку на поступление на вход значения-индикатора ошибки.
Создадим новый пустой блок, переключим его в режим работы по сигналу и создадим для него новую модель, как это было описано в §3.2. Для этого необходимо:
- нажать на свободном месте окна подсистемы правую кнопку мыши;
- выбрать в открывшемся контекстном меню пункт «» (см. рис. 317 а);
- нажать на созданном блоке (он будет выглядеть как белый квадрат с черной рамкой) правую кнопку мыши;
- выбрать в открывшемся контекстном меню пункт «» (см. рис. 317 б);
- на вкладке «» открывшегося окна включить флажок «» на панели «» (см. рис. 367);
- на вкладке «» установить флажок «», выбрать в выпадающем списке подключенный модуль автокомпиляции и нажать кнопку «» (см. рис. 318);
- в появившемся окне «» выбрать флажок «» (см. рис. 319) и закрыть окно кнопкой «» (если в используемой версии RDS нет шаблонов моделей, этот шаг будет пропущен и сразу откроется диалог сохранения);
- в диалоге сохранения модели ввести имя нового файла, в который будет записана создаваемая модель (см. рис. 321), и нажать кнопку «»;
- закрыть окно параметров блока кнопкой «».
После выполнения перечисленных выше действий откроется пустое окно редактора модели, в котором нужно создать структуру статических переменных блока и ввести реакцию на выполнение такта расчета. Для ввода статических переменных необходимо на вкладке «» левой панели окна редактора нажать кнопку «» (см. рис. 325) и заполнить таблицу переменных в окне следующим образом:
| Имя | Тип | Вход/выход | Пуск | Начальное значение |
|---|---|---|---|---|
| Start | Сигнал | Вход | ✓ | 1 |
| Ready | Сигнал | Выход | 0 | |
| x | double | Вход | ✓ | 0 |
| func | int | Вход | ✓ | 0 |
| y | double | Выход | 0 |
Теперь на вкладке «» в правой части окна редактора (см. рис. 324) нужно ввести следующий фрагмент программы:
if(x==rdsbcppHugeDouble)
{ y=rdsbcppHugeDouble;
return;
}
switch(func)
{ case 0: // Синус
y=sin(x);
break;
case 1: // Косинус
y=cos(x);
break;
default: // Ошибка
y=rdsbcppHugeDouble;
}
Этот фрагмент будет выполняться в каждом такте расчета, если в этом такте блок получит сигнал запуска, то есть, если значение сигнала готовности (переменной «Start») нашего блока не будет равно нулю. Мы дали этой переменной начальное значение, равное единице, поэтому наша модель обязательно выполнится при первом запуске расчета. Кроме того, мы поставили флажки в колонке «» напротив входов блока «x» и «func», поэтому при срабатывании связей, соединенных с этими входами, сигнал готовности автоматически взведется, и модель тоже выполнится. Рассмотрим введенный фрагмент программы подробно.
Самый первый оператор в этом фрагменте – сравнение значения входа блока x с глобальной переменной rdsbcppHugeDouble, в которой хранится значение-индикатор ошибки вычисления (HUGE_VAL). Если на входе нашего блока окажется это значение, оно же будет присвоено выходу y, и модель немедленно завершится оператором return. Таким образом, при получении признака ошибки наш блок просто передает его на выход, не выполняя никаких вычислений. Значение входа func мы с признаком ошибки не сравниваем – это целый вход, а для целых чисел в RDS не предусмотрено каких-либо индикаторов ошибок вычисления.
Далее выполняется оператор switch, в котором, в зависимости от значения входа func, вычисляется выход блока. Если значение func – 0 или 1, выходу y присваивается синус или косинус входа соответственно. При любом другом значении func выходу присваивается значение-индикатор ошибки rdsbcppHugeDouble.
Теперь можно скомпилировать модель (пункт меню редактора «» или кнопка с желтой шестеренкой) и проверить ее работу. Для тестирования модели можно собрать схему, изображенную на рис. 369 или подобную ей. На рисунке вход блока «x» соединен с выходом текущего времени «Time» стандартного блока-планировщика. Следует иметь в виду, что «Time» – скрытый выход, и он не будет отображаться отдельным пунктом в меню подключения связи (см. §2.7.1), для его выбора придется использовать пункт «» в этом меню.
Рис. 369. Тестирование модели вычисления синуса и косинуса
Запустив расчет, можно будет наблюдать на графике синусоиду или косинусоиду, в зависимости от значения, поданного на «func». Если вход «func» не будет равен нулю или единице, график рисоваться не будет, а в заголовке его вертикальной оси в скобках вместо текущего значения будет отображаться вопросительный знак – так в RDS выводится значение-индикатор ошибки.
В обоих приведенных выше примерах мы использовали переменные блока в программе так, как будто это переменные типов int и double, а не объекты специальных классов, за которыми скрыты настоящие значения (см. §3.6.1). Об этом можно не задумываться до тех пор, пока где-нибудь в программе не потребуется указатель на переменную блока. В этом случае необходимо принимать специальные меры, что мы сейчас проиллюстрируем на примере.
Создадим модель блока, разбивающего поступившее на его вход «x» вещественное число на целую и дробную части и выдающего целую часть на выход «integer», а дробную – на выход «fraction» (оба выхода сделаем вещественными). Блок будет иметь следующую структуру переменных:
| Имя | Тип | Вход/выход | Пуск | Начальное значение |
|---|---|---|---|---|
| Start | Сигнал | Вход | ✓ | 1 |
| Ready | Сигнал | Выход | 0 | |
| x | double | Вход | ✓ | 0 |
| integer | double | Выход | 0 | |
| fraction | double | Выход | 0 |
Создадим новый блок с автокомпилируемой моделью, зададим для него запуск по сигналу и введем в модель приведенную выше структуру статических переменных. Для разбиения вещественного числа на целую и дробную части логично использовать функцию modf из стандартной математической библиотеки. В «math.h» она описана следующим образом:
double modf(double x, double *ipart);
Здесь x – разбиваемое число, ipart – указатель на переменную, в которую функция запишет целую часть числа, а дробную часть функция вернет по значению. Поскольку «x», «integer» и «fraction» – статические переменные блока, имеющие тип «double», возникает желание ввести на вкладку «» редактора следующий текст:
fraction=modf(x,&integer);
Однако, если скомпилировать такую модель, компилятор выдаст сообщение об ошибке примерно следующего содержания: «сannot convert 'rdsbcstDouble*' to 'double*' in function rdsbcppBlockClass::rdsbcppModel» («невозможно преобразовать тип rdsbcstDouble* в тип double* в функции-члене rdsbcppModel класса rdsbcppBlockClass»). Действительно, несмотря на то, что в блоке переменная с именем «integer» имеет тип «double», объект integer в классе блока, с которым мы работаем в программе, имеет тип rdsbcstDouble (результат работы макроса, создающего этот класс, приведен в §3.6.1). Таким образом, выражение «&integer», которое мы подставили во второй параметр функции modf, имеет тип «указатель на rdsbcstDouble», а не «указатель на double», и компилятор не знает, что с ним делать.
Из этой ситуации есть два выхода. Во-первых, можно использовать вспомогательную переменную для вызова modf, а затем присвоить ее значение объекту integer:
double i_aux; fraction=modf(x,&i_aux); integer=i_aux;
Здесь вводится локальная переменная i_aux, указатель на которую передается в modf. Это «настоящая» переменная типа double, поэтому никаких ошибок не возникнет. После вызова modf в i_aux будет находиться целая часть числа, и ее можно присвоить integer (операторы присваивания для класса rdsbcstDouble переопределены, никакие указатели здесь не используются, поэтому ошибок тоже не возникнет).
Во-вторых, и этот вариант будет гораздо короче, можно воспользоваться функцией-членом GetPtr, которая есть в любом классе, создаваемом модулем автокомпиляции для простых статических переменных: эта функция возвращает указатель на скрытую в объекте класса переменную блока. Для переменных блока типа «double», обслуживаемых классом rdsbcstDouble, эта функция будет иметь тип «double*», то есть «указатель на double», что нам и требуется. Модель при этом будет выглядеть так:
fraction=modf(x,integer.GetPtr());
Вторым параметром в modf здесь передается указатель на вещественную переменную, скрытую в объекте integer, поэтому целая часть числа сразу будет записана в нужный выход блока.
На всякий случай, можно добавить в модель проверку входа на значение-индикатор ошибки. Если не добавлять эту проверку, функция modf запишет HUGE_VAL в переменную integer, а переменная fraction получит значение 0. Если такое поведение блока нам подходит, можно оставить в модели только вызов функции modf, как указано выше. Если же нужно сделать так, чтобы при поступлении значения ошибки на вход оба выхода получали это же значение, модель будет выглядеть так:
if(x==rdsbcppHugeDouble)
{ fraction=integer=rdsbcppHugeDouble;
return;
}
fraction=modf(x,integer.GetPtr());
Для тестирования созданной модели можно собрать схему, изображенную на рис. 370. Запустив расчет и вводя разные числа в поле ввода слева от блока, можно наблюдать на индикаторах справа целую и дробную части введенного числа.
Рис. 370. Тестирование модели разбиения числа на целую и дробную части