Руководство программиста
Глава 2. Создание моделей блоков
§2.9. Использование таймеров
§2.9.4. Несколько таймеров в одной модели
Рассматриваются особенности работы модели блока с несколькими таймерами одновременно. Приводится пример блока, изображение которого вращается (для чего используется первый таймер) и мигает (по сигналу от второго таймера).
В примерах в §2.9.2 и §2.9.3 модель блока создавала единственный таймер, поэтому в реакции на его срабатывание не проверялось, какой именно таймер сработал. Если блок работает с несколькими таймерами одновременно, такая проверка необходима.
Создадим новый блок-индикатор, изображение которого может не только мигать, изменяя свой цвет, при подаче единицы на вход «Flash», но и вращаться вокруг своей оси при подаче единицы на вход «Rotate». Мигание будет реализовано как и в предыдущих моделях, а для вращения будет использована новая вещественная переменная «Angle», которая будет связана с углом поворота одного из векторных элементов картинки. Каждый раз при срабатывании таймера к этой переменной будет прибавляться небольшая постоянная величина, что приведет к вращению изображения с постоянной скоростью.
Чтобы изображение вращалось плавно, переменную «Angle» нужно изменять достаточно часто – например, десять раз в секунду (чаще менять ее не имеет смысла, максимальная частота обновления окна подсистемы в RDS равна 10 Гц). Мигать изображение должно значительно реже, например, один раз в две секунды. Таким образом, нам понадобятся два циклических таймера – один с интервалом в 100 миллисекунд (для увеличения «Angle»), другой – в одну секунду (для инвертирования «State»). В принципе, такой блок может обойтись и одним таймером в 100 мс, если считать число его срабатываний, и инвертировать переменную «State» после каждого десятого. Однако, модель в этом случае получается более сложной, поэтому проще всего создать два таймера и работать с ними независимо.
Для работы блоку нужны будут следующие переменные:
| Смещение | Имя | Тип | Размер | Вход/выход | Пуск | Начальное значение |
|---|---|---|---|---|---|---|
| 0 | Start | Сигнал | 1 | Вход | ✓ | 1 |
| 1 | Ready | Сигнал | 1 | Выход | 0 | |
| 2 | Flash | Логический | 1 | Вход | ✓ | 0 |
| 3 | Rotate | Логический | 1 | Вход | ✓ | 0 |
| 4 | State | Логический | 1 | Внутренняя | 0 | |
| 5 | Angle | double | 8 | Внутренняя | 0 |
Рассмотрим функцию модели блока:
// Мигающий и вращающийся блок // Структура личной области данных struct TRotateFlashData { RDS_TIMERID FlashTimer; // Таймер мигания RDS_TIMERID RotTimer; // Таймер вращения }; extern "C" __declspec(dllexport) int RDSCALL RotateFlash( int CallMode, RDS_PBLOCKDATA BlockData, LPVOID ExtParam) { // Макроопределения для статических переменных #define pStart ((char *)(BlockData->VarTreeData)) #define Start (*((char *)(pStart))) #define Ready (*((char *)(pStart+RDS_VSZ_S))) #define Flash (*((char *)(pStart+2*RDS_VSZ_S))) #define Rotate (*((char *)(pStart+2*RDS_VSZ_S+RDS_VSZ_L))) #define State (*((char *)(pStart+2*RDS_VSZ_S+2*RDS_VSZ_L))) #define Angle (*((double *)(pStart+2*RDS_VSZ_S+3*RDS_VSZ_L))) // Вспомогательная переменная – указатель на личную область TRotateFlashData *data=(TRotateFlashData*)(BlockData->BlockData); // Структура для получения параметров таймера RDS_TIMERDESCRIPTION descr; switch(CallMode) { // Инициализация блока case RDS_BFM_INIT: // Отведение памяти под личную область BlockData->BlockData=data=new TRotateFlashData; // Создание двух одинаковых таймеров data->FlashTimer=rdsSetBlockTimer(NULL,0, RDS_TIMERM_LOOP|RDS_TIMERS_TIMER,FALSE); data->RotTimer=rdsSetBlockTimer(NULL,0, RDS_TIMERM_LOOP|RDS_TIMERS_TIMER,FALSE); break; // Очистка данных блока case RDS_BFM_CLEANUP: // Уничтожение таймеров rdsDeleteBlockTimer(data->FlashTimer); rdsDeleteBlockTimer(data->RotTimer); // Освобождение памяти, занятой под личную область delete data; break; // Проверка типа статических переменных case RDS_BFM_VARCHECK: if(strcmp((char*)ExtParam,"{SSLLLD}")) return RDS_BFR_BADVARSMSG; return RDS_BFR_DONE; // Такт расчета case RDS_BFM_MODEL: // Подготовка структуры descr к чтению данных таймера descr.servSize=sizeof(descr); // Управление миганием rdsGetBlockTimerDescr(data->FlashTimer,&descr); if(Flash) // Включить мигание { if(!descr.On) // Таймер остановлен { // Запускаем таймер и "зажигаем" индикатор rdsRestartBlockTimer(data->FlashTimer,1000); State=1; } } else // Выключить мигание { if(descr.On) // Таймер работает { // Останавливаем и "гасим" индикатор rdsStopBlockTimer(data->FlashTimer); State=0; } } // Управление вращением rdsGetBlockTimerDescr(data->RotTimer,&descr); if(Rotate) // Включить врашение { if(!descr.On) // Таймер остановлен – запускаем rdsRestartBlockTimer(data->RotTimer,100); } else // Выключить вращение { if(descr.On) // Таймер работает – останавливаем rdsStopBlockTimer(data->RotTimer); } break; // Срабатывание таймера case RDS_BFM_TIMER: // Сравниваем с имеющимися таймерами if(ExtParam==((LPVOID)data->FlashTimer)) // Это – таймер мигания State=!State; else // Если не таймер мигания, значит, вращения Angle=fmod(Angle+0.4,2*M_PI); break; } return RDS_BFR_DONE; // Отмена макроопределений для переменных #undef Angle #undef State #undef Rotate #undef Flash #undef Ready #undef Start #undef pStart } //=================================================
Поскольку модель теперь использует два таймера, в личной области данных блока необходимо предусмотреть хранение идентификаторов обоих. Для этого перед функцией модели описывается структура TRotateFlashData с двумя полями: FlashTimer для идентификатора таймера, который будет управлять миганием изображения блока, и RotTimer для идентификатора таймера, управляющего вращением. При инициализации блока (режим RDS_BFM_INIT) модель отводит память под эту структуру и создает два одинаковых циклических таймера, не задавая пока им интервал срабатывания. При очистке данных блока (режим RDS_BFM_CLEANUP) оба таймера уничтожаются и занятая память освобождается. В этом отношении данная модель отличается от пары предыдущих только наличием двух таймеров вместо одного.
Реакция на такт расчета этой модели также похожа на дважды повторенную реакцию предыдущих. Сначала выполняются действия по запуску или остановке таймера мигания FlashTimer. В поле servSize структуры описания таймера descr записывается размер этой структуры, после чего вызывается функция rdsGetBlockTimerDescr. Если вход Flash получил значение 1 и таймер FlashTimer в данный момент не работает, этот таймер запускается с частотой 1 Гц (задержка в одну секунду), и переменной State присваивается единица, чтобы индикатор немедленно зажегся. Если значение Flash – нулевое, и таймер работает, модель останавливает его и присваивает переменой State ноль, гася индикатор.
Затем производится запуск или остановка вращения изображения блока в зависимости от значения переменной Rotate. Снова вызывается функция rdsGetBlockTimerDescr, но теперь уже для таймера RotTimer (в поле servSize структуры descr не записывается размер этой структуры, т.к. он там сохранился с предыдущего вызова). Если значение Rotate равно 1, и таймер не работает, он запускается с интервалом 100 мс. Если же значение Rotate – нулевое, и таймер работает, он останавливается.
Реакция на срабатывание таймера этой модели (режим RDS_BFM_TIMER) уже сильно отличается от предыдущих примеров. В модели теперь два таймера, и для них должны выполняться разные действия. Чтобы модель могла опознать таймер, срабатывание которого запустило ее в режиме RDS_BFM_TIMER, RDS передает функции модели в параметре ExtParam идентификатор этого таймера, приведенный к типу LPVOID. Этот идентификатор можно сравнить с идентификаторами таймеров, хранящихся в личной области данных блока, и определить таким образом, какой из них сработал.
Может возникнуть вопрос: почему приведение идентификатора таймера RDS_TIMERID к типу LPVOID (то есть void*, произвольному указателю) допустимо? Дело в том, что идентификатор таймера в RDS на самом деле представляет собой указатель на некую внутреннюю структуру, в которой хранятся параметры этого таймера. По совпадению идентификатора с указателем на одну из структур RDS и опознает таймер. Описание типа RDS_TIMERID в файле «RdsDef.h» выглядит следующим образом:
typedef LPVOID RDS_TIMERID;
Таким образом, идентификаторы таймеров можно свободно приводить к типу LPVOID.
При вызове нашей модели в режиме RDS_BFM_TIMER переданный в ExtParam указатель сравнивается с приведенным к типу LPVOID идентификатором таймера мигания data->FlashTimer. Если они совпали, значит, сработал таймер мигания, и нужно инвертировать переменную State. Если они не совпали, значит, сработавший таймер – таймер вращения, поскольку других таймеров в этой модели нет. В этом случае к переменной Angle добавляется константа 0.4 и при помощи функции взятия остатка от деления fmod из получившегося результата выбрасывается целое число полных оборотов (2π), чтобы избежать бесконечного роста значения Angle по мере вращения блока. Значение 0.4 радиана (23°) выбрано опытным путем – добавляя это значение к углу поворота изображения каждые 100 мс, мы будем наблюдать вращение со скоростью примерно 2 градуса в секунду, что выглядит достаточно убедительно. При желании, константу можно изменить, увеличив или уменьшив скорость вращения.
Рис. 57. Проверка работы
блока с двумя таймерами
Для проверки работы блока к его входам «Flash» и «Rotate» следует присоединить переключающиеся кнопки (рис. 57). В зависимости от нажатых кнопок, блок должен вращаться, мигать, или делать и то, и другое. Разумеется, он будет делать это только в режиме расчета, поскольку для обоих таймеров задана константа RDS_TIMERS_TIMER. Чтобы заставить его вращаться и мигать еще и в режиме моделирования, следует заменить эту константу на RDS_TIMERS_SYSTIMER.