Текущий раздел:

Описания RDS

Руководство программиста

Глава 2. Создание моделей блоков

§2.9. Использование таймеров

§2.9.2. Циклический таймер

# Руководство программиста

## Глава 2. Создание моделей блоков

### §2.9. Использование таймеров

#### §2.9.2. Циклический таймер

Описывается циклический таймер, постоянно вызывающий модель блока через заданный интервал времени. Рассматривается пример блока, изображающего мигающую индикаторную лампочку.

Рассмотрим для примера блок, изображающий мигающую индикаторную лампочку на каком-либо пульте. Внешний вид этой лампочки может быть любым – мигание будет реализовано при помощи векторной картинки блока. Блок будет иметь логический вход «Flash», при подаче на который единицы изображение блока должно мигать с частотой 1 Гц, привлекая внимание оператора. Для реализации самого мигания будет использоваться внутренняя логическая переменная «State», значение которой будет инвертироваться по таймеру с задержкой в половину секунды, что и даст частоту в 1 Гц. В векторной картинке блока с этой переменной будет связан цвет изображающего индикатор прямоугольника (рис. 55). При нулевом значении переменной прямоугольник будет иметь цвет, заданные при его создании (в данном случае – белый), а при единичном – альтернативный (красный). Таким образом, меняя значение «State», можно выбирать один из двух цветов прямоугольника.

Рис. 55. Связь логической переменной с цветом в векторной картинке блока

Вместо прямоугольника можно было бы использовать круг или произвольный многоугольник, можно было бы вместо цвета фигуры связать с переменной «State» видимость какого-либо набора объектов (и сделать, например, мигающую надпись) и т.п. – в данном случае внешний вид блока ограничен только фантазией создателя векторной картинки, функция модели от этого не изменится.

Блок будет иметь следующую структуру переменных:

Смещение | Имя | Тип | Размер | Вход/выход | Пуск | Начальное значение
--- | --- | --- | --- | --- | --- | ---
0 | Start | Сигнал | 1 | Вход | ✓ | 1
1 | Ready | Сигнал | 1 | Выход |  | 0
2 | Flash | Логический | 1 | Вход | ✓ | 0
3 | State | Логический | 1 | Внутренняя |  | 0

Чтобы не тратить зря системные ресурсы, как обычно, включим для блока запуск только по сигналу, и у входа «Flash» установим флаг «пуск», чтобы модель запустилась при срабатывании связи, подключенной к этому входу.

Модель блока с обычными макроопределениями для переменных будет иметь следующий вид:

      //====== Модель простого мигающего блока ======
      // Функция модели блока
      extern "C" __declspec(dllexport) int RDSCALL SimpleFlash(
              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 State (*((char *)(pStart+2*RDS_VSZ_S+RDS_VSZ_L)))
        // Вспомогательная переменная – указатель на личную область
        // В личной области хранится только идентификатор таймера
        RDS_TIMERID *data=(RDS_TIMERID*)(BlockData->BlockData);

        switch(CallMode)
          { // Инициализация блока
            case RDS_BFM_INIT:
              // Отводим место для хранения идентификатора таймера
              BlockData->BlockData=data=new RDS_TIMERID;
              // Создаем таймер, интервал пока не устанавливаем
              *data=rdsSetBlockTimer(NULL,0,
                      RDS_TIMERM_LOOP|RDS_TIMERS_TIMER,FALSE);
              break;

            // Очистка данных блока
            case RDS_BFM_CLEANUP:
              // Уничтожаем таймер
              rdsDeleteBlockTimer(*data);
              // Освобождаем память, где он хранился
              delete data;
              break;

            // Проверка типа статических переменных
            case RDS_BFM_VARCHECK:
              if(strcmp((char*)ExtParam,"{SSLL}"))
                return RDS_BFR_BADVARSMSG;
              return RDS_BFR_DONE;

            // Такт расчета
            case RDS_BFM_MODEL:
              if(Flash) // Включаем мигание
                { // Запускаем таймер с интервалом 500 мс
                  rdsRestartBlockTimer(*data,500);
                  // Сразу "зажигаем" изображение блока
                  State=1;
                }
              else // Выключаем мигание
                { // Останавливаем таймер
                  rdsStopBlockTimer(*data);
                  // "Гасим" изображение блока
                  State=0;
                }
              break;

            // Срабатывание таймера
            case RDS_BFM_TIMER:
              // Инвертируем состояние
              State=!State;
              break;
          }
        return RDS_BFR_DONE;
      // Отмена макроопределений для переменных
      #undef State
      #undef Flash
      #undef Ready
      #undef Start
      #undef pStart
      }
      //=================================================

В этой модели в личной области данных блока хранится идентификатор таймера, с которым эта модель работает. Для удобства доступа к хранимому идентификатору в начале модели вводится вспомогательная переменная data, которой сразу присваивается указатель на личную область данных, приведенный к типу RDS_TIMERID*. При вызове в режиме RDS_BFM_INIT модель отводит память для хранения идентификатора таймера и, как обычно, запоминает указатель на нее в структуре данных блока. Затем функцией rdsSetBlockTimer создается сам таймер. Первый параметр функции – NULL, поскольку мы создаем новый таймер, а не изменяем параметры существующего. Интервал срабатывания таймера мы зададим позднее, при запуске таймера, поэтому в rdsSetBlockTimer он не задается – второй параметр функции равен нулю. Создаваемый таймер будет циклическим (константа RDS_TIMERM_LOOP), при его срабатывании модель будет вызываться в режиме RDS_BFM_TIMER (константа RDS_TIMERS_TIMER), таймер не запускается сразу после создания (последний параметр функции – FALSE).

При вызове модели в режиме RDS_BFM_CLEANUP (при удалении блока, закрытии RDS, отключении модели от блока и т.п.) созданный таймер удаляется функцией rdsDeleteBlockTimer. Технически, можно было бы этого не делать – при отключении модели от блока RDS автоматически удаляет все созданные блоком объекты, включая таймеры, однако, хороший стиль программирования обычно подразумевает явное удаление созданных объектов (если все время полагаться на автоматику, можно по привычке забыть уничтожить какой-либо объект, автоматическое удаление которого не предусмотрено).

В такте расчета (режим RDS_BFM_MODEL) модель анализирует значение логического входа Flash. Если оно равно единице («истина»), таймер запускается с интервалом 500 мс. Начиная с этого момента модель будет вызываться в режиме RDS_BFM_TIMER два раза в секунду по системным часам. Кроме того, переменной State при запуске таймера присваивается значение 1, чтобы изображение блока «зажглось» сразу, а не через 500 миллисекунд, когда сработает таймер. Если же значение Flash равно нулю («ложь»), таймер останавливается функцией rdsStopBlockTimer и его изображение «гасится» присваиванием нуля переменной State.

Наконец, при вызове модели в режиме RDS_BFM_TIMER, значение State инвертируется, что приведет к смене цвета изображения блока. Таким образом, цвет блока будет меняться два раза в секунду, что и создаст эффект мигания индикатора.

Рис. 56. Проверка работы
мигающего блока

Для проверки работы блока можно подключить к его входу «Flash» стандартную переключающуюся кнопку (рис. 56). В режиме расчета при нажатой кнопке изображение блока должно мигать.

Приведенная выше модель имеет один существенный недостаток. Представим себе, что ко входу «Flash» подключен выход какого-либо блока, работающего каждый такт. Выход этого блока будет передаваться на вход «Flash» в каждом такте расчета, что будет каждый раз приводить к запуску модели нашего мигающего блока. В реакции на такт расчета наш блок анализирует значение Flash и запускает или останавливает таймер. Если единичное значение будет передаваться на вход модели в каждом такте, значит, таймер также будет перезапускаться в каждом такте. Фактически, этот постоянный перезапуск не даст ему считать, модель не будет вызываться в режиме RDS_BFM_TIMER, переменная State не будет инвертироваться, и блок не будет мигать.

Чтобы устранить этот эффект, необходимо запускать таймер при единичном значении «Flash» только в том случае, если он еще не запущен. Таким образом повторные срабатывания модели не приведут к перезапуску таймера. При этом останавливать таймер нужно при совпадении двух условий: значение «Flash» равно нулю и таймер в данный момент работает.

Для реализации такого поведения модели можно добавить в блок новую логическую переменную-флаг, взводить ее при запуске таймера и сбрасывать при остановке, а в такте расчета сравнивать ее со значением Flash и управлять таймером, если они не совпадают. Мы сделаем по-другому – при вызове модели в такте расчета будем проверять, запущен ли таймер, при помощи сервисной функции чтения параметров таймера rdsGetBlockTimerDescr. Для этого нужно ввести в функцию модели дополнительную переменную – структуру, в которую будут записаны запрошенные параметры таймера (выделена цветом):

      //====== Модель простого мигающего блока ======
      // Функция модели блока
      extern "C" __declspec(dllexport) int RDSCALL SimpleFlash(
              int CallMode,
              RDS_PBLOCKDATA BlockData,
              LPVOID ExtParam)
      {
        RDS_TIMERDESCRIPTION descr;
      // Макроопределения для статических переменных
      #define pStart ((char *)(BlockData->VarTreeData))
      #define Start (*((char *)(pStart)))
      // …

В этой структуре нас будет интересовать единственное поле On, имеющее значение TRUE для работающего таймера и FALSE для остановленного. Реакцию модели на такт расчета перепишем следующим образом (изменения выделены цветом):

            case RDS_BFM_MODEL:
              // Запрашиваем параметры таймера
              descr.servSize=sizeof(descr);
              rdsGetBlockTimerDescr(*data,&descr);
              if(Flash) // Включаем мигание, если таймер остановлен
                { if(!descr.On) // Таймер остановлен
                    { rdsRestartBlockTimer(*data,500);
                      State=1;
                    }
                }
              else // Выключаем мигание, если таймер работает
                { if(descr.On) // Таймер работает
                    { rdsStopBlockTimer(*data);
                      State=0;
                    }
                }
              break;

Прежде всего мы присваиваем полю servSize структуры описания таймера descr размер этой структуры в байтах – sizeof(descr). Без этого функция чтения параметров таймера, как и многие другие сервисные функции RDS, откажется работать. Далее мы вызываем функцию rdsGetBlockTimerDescr, которая заполнит структуру descr описанием таймера, идентификатор которого находится в *data. Затем мы запускаем таймер в том случае, если Flash – истина, и таймер не работает в данный момент (descr.On имеет значение FALSE), и останавливаем его, если Flash – ложь, и таймер работает (descr.On имеет значение TRUE). Теперь многократные повторные передачи значения 1 на вход Flash никак не повлияют на мигание нашего блока.

Можно заметить, что блок будет мигать только в режиме расчета, т.к. таймеры с параметром RDS_TIMERS_TIMER останавливаются вместе с расчетом. Если требуется, чтобы при остановленном расчете блок продолжал мигать, следует заменить константу RDS_TIMERS_TIMER на RDS_TIMERS_SYSTIMER. В этом случае таймер продолжит считать и при остановке расчета, и изображение блока будет мигать и в режиме моделирования. В режиме редактирования изображение мигать не будет – хотя модель будет вызываться по таймеру и значение переменной State будет инвертироваться, в режиме редактирования векторная картинка блока не отражает состояния его переменных, то есть значение переменной State не будет влиять на внешний виде блока. Если бы вместо векторной картинки мы задали для блока программное рисование, добавив в его модель соответствующую реакцию, он мигал бы и в режиме редактирования.