Программные интерфейсы джойстика и таймера (original) (raw)
Программные интерфейсы джойстика и таймера
Автор: Евгений Музыченко
Источник: Компьютер Пресс
Опубликовано: 28.07.2003
Исправлено: 10.12.2016
Версия текста: 1.0
Последние две подсистемы Windows MME, оставшиеся неописанными в рамках данного цикла - это интерфейсы с джойстиками (Joystick) и таймерами (Timer). Назначение первого понятно, а второй служит для управления мультимедийными виртуальными таймерами (multimedia timers).
Обе подсистемы введены в Windows 3.1, и впоследствии стали стандартными компонентами Win32.
Описание подсистем есть во встроенной системе помощи любой современной среды программирования, а также в MSDN SDK. Microsoft поддерживает в Internet справочную систему MSDN Online, где интерфейс джойстика описан в разделе http://msdn.microsoft.com/library/psdk/multimed/joy_42er.htm, а интерфейс таймера - в разделе http://msdn.microsoft.com/library/psdk/multimed/mmtime_4msz.htm.
Поскольку оба интерфейса предельно просты, я счел излишним снабжать статью демонстрационной программой, в которой не удалось бы показать возможности интерфейсов без дополнительной идеи и объемного кода, не имеющего прямого отношения к предмету.
Краткие сведения об аппаратном устройстве джойстиков
Традиционный джойстик IBM PC представляет собой простое аналоговое (analog, analogue) устройство, состоящее из двух переменных резисторов сопротивлением 100 кОм и двух кнопок с контактами на замыкание. Оси резисторов через систему ортогонального привода связаны с рукояткой джойстика таким образом, что горизонтальное перемещение рукоятки приводит к повороту оси резистора X, а вертикальное - резистора Y. То есть, перемещение рукоятки в двух измерениях раскладывается на два независимых перемещения в перпендикулярных направлениях - X и Y. Направления X и Y называются осями (axis) джойстика.
Кнопки джойстика нумеруются от 1 до максимума; кнопка 1 обычно размещается на конце рукоятки.
Адаптер джойстика раньше входил в состав большинства многофункциональных адаптеров ввода/вывода (Multi I/O, или мультикарт), а впоследствии стал обязательной частью типовой звуковой карты универсального назначения. Интерфейс также предельно прост: ток, протекающий через каждый из переменных резисторов, заряжает времязадающий конденсатор одновибратора, который срабатывает через интервал времени, пропорциональный сопротивлению резистора. Срабатывание одновибраторов отражается в соответствующих разрядах порта состояния джойстика. Другие разряды порта состояния сигнализируют о замыкании контактов кнопок.
Программа, работающая с джойстиком, или его драйвер в ОС, принудительно разряжает времязадающие конденсаторы, после чего дожидается срабатывания одновибраторов, в цикле опрашивая порт состояния. Количество проходов цикла до изменения разряда состояния линейно пропорционально сопротивлению резистора и, следовательно, положению манипулятора измеряемой оси.
Параметры времязадающих цепей интерфейса подобраны таким образом, чтобы время заряда конденсатора до порога срабатывания не превышало нескольких микросекунд (от десятков до сотен проходов цикла ожидания). Если одновибратор не успел сработать в течение нескольких тысяч проходов цикла - это означает, что джойстик не подключен вообще.
Вместо классической рукоятки джойстик может быть выполнен в виде руля и педали, двух педалей, единой площадки с возможностью наклона во всех направлениях, и других конструкций, в которых сохраняется основной принцип - наличие не более двух осей независимого перемещения.
Кроме традиционных аналоговых джойстиков, существуют цифровые (digital), что попросту означает наличие вместо переменных резисторов переключателей, срабатывающих при наклонах рукоятки. Такие джойстики сигнализируют только о наклоне рукоятки в одном из восьми направлений, но не о величине или скорости перемещения. По сути, такой "цифровой" джойстик содержит постоянные резисторы, подключаемые к интерфейсу при замыкании контактов переключателей.
Стандартный адаптер является двухканальным, то есть - допускает подключение двух независимых джойстиков, что часто используется играми с двумя игроками. Поскольку все органы управления отслеживаются независимо, возможно также объединение двух джойстиков в один с количеством осей и кнопок до четырех. Таким образом реализуются джойстики с осями R (обычно ось X второго канала) и Z (обычно ось Y второго канала).
Также существуют джойстики со специальными (proprietary) интерфейсами, выполненными в виде отдельных адаптеров. Такие интерфейсы позволяют подключать джойстики с большим количеством осей перемещения, кнопок, переключателей и других элементов управления.
Основные черты и понятия интерфейса джойстика
Типы джойстиков
В Win16 поддерживаются традиционные джойстики, имеющие до трех осей (X, Y, Z) и до четырех кнопок. Джойстики, имеющие более двух осей и/или двух кнопок, используют объединение каналов одного стандартного адаптера.
Win32 поддерживает различные типы джойстиков, имеющих до шести осей перемещения манипуляторов, и до 32 кнопок (переключателей), имеющих два состояния (нажато/отпущено, включено/выключено и т.п.).
Кроме традиционных осей X, Y и Z, подсистема поддерживает ось R (Rudder - руль), и две дополнительных оси U и V. Перемещения по каждой из осей отслеживаются независимо от других, и конструктивно джойстик может быть выполнен как с совмещенными, так и с раздельными манипуляторами.
Ряд джойстиков имеет манипулятор точки обзора POV (point of view), которым обычно переключаются виды в играх (вперед, назад, в стороны). Значения этого манипулятора представляются в градусах по часовой стрелке относительно направления вперед.
В традиционном джойстике значения координат по осям U и V вычисляются, как среднее арифметическое между двумя координатами одного канала адаптера. Координата U вычисляется по первому каналу, V - по второму. Значение POV вычисляется из координаты Z, если она есть, иначе - из координаты R. Другими словами - при нехватке физических осей значения координат будут взаимозависимыми, и приложения должны использовать только одну из зависимых координат.
На многих джойстиках значения осей не являются полностью независимыми - например, педали газа и тормоза могут быть заведены на одну ось так, что нажатие на газ приводит к возрастанию значения координаты, а нажатие на тормоз - к ее уменьшению.
Номера устройств
В отличие от прочих подсистем Windows MME, устройство джойстика не требует обязательного открывания перед сеансом работы. Вместо этого в каждой функции интерфейса указывается номер устройства в системе. Как и все в MME, устройства джойстиков нумеруются с нуля.
Такой способ обращения имеет некоторое неудобство, так как в течение сеанса работы возможна установка новых джойстиков либо удаление существующих, что приводит к изменению нумерации устройств. Поэтому ответственность за неизменность номеров возлагается на пользователя компьютера.
Логические значения координат
Количество проходов цикла ожидания до срабатывания одновибратора называется "сырой" (raw) позицией джойстика, и используется в основном внутри подсистемы. Для удобства работы драйвер джойстика приводит эти значения к нормированным, логическим (logical) величинам, представляемым числами в диапазоне 0..65535 (значения типа WORD). Если не определено иначе, программа получает по запросам значения именно логических координат, а "сырые" остаются только на уровне драйвера и служебных программ.
Калибровка
Для получения логических значений координат аналоговый джойстик после установки нуждается в калибровке. Калибровка заключается в установке манипуляторов в заданные положения, для которых драйвер по "сырым" координатам вычисляет поправочные коэффициенты для приведения их к логическим.
Калибровка (а также первоначальная установка джойстика) выполняется с помощью мастера (wizard), запускаемого из панели управления иконкой "Игровые контроллеры".
Частота опроса
Традиционный интерфейс джойстика не поддерживает генерации прерываний при изменении состояния манипуляторов, поэтому требуется постоянный опрос (polling) интерфейсного порта по прерываниям от системного таймера. В Windows эту задачу выполняет драйвер джойстика; приложению необходимо лишь задать требуемую частоту опроса (polling frequency). Увеличение частоты опроса приводит к повышению точности отслеживания, но одновременно - к увеличению доли накладных расходов системы, уменьшение частоты - к отставанию программных событий от реальных и снижению плавности перемещения.
Сама процедура опроса занимает сравнительно небольшое время - не более нескольких микросекунд. Накладные расходы вызываются в основном обработкой прерываний при повышенной частоте системного таймера.
Режим захвата
Информацию о состоянии каждого из джойстиков приложение может получать как путем регулярного опроса, так и посредством уведомляющих сообщений. Опрашивать джойстик можно в любой момент, но уведомляющие сообщения доступны только в режиме захвата (capture), который в некотором роде аналогичен открыванию устройства в остальных подсистемах MME. В режиме захвата Windows направляет приложению поток сообщений о состоянии джойстика.
Захватить устройство джойстика можно только один раз; последующие попытки захватить джойстик до его освобождения, даже от имени той же самой задачи, будут отвергаться с кодом ошибки JOYERR_NOCANDO.
Уведомление об изменении состояния
В режиме захвата Windows посылает заданному окну приложения сообщения, уведомляющие об изменении состояния джойстика - перемещении манипуляторов или нажатии/отпускании кнопок. Сообщения посылаются либо регулярно через заданные промежутки времени, либо нерегулярно - при каждом изменении состояния джойстика.
Дребезг
Простота используемого адаптером метода определения положения манипуляторов чревата значительными (порядка единиц процентов) случайными и систематическими ошибками, когда при неподвижном манипуляторе значение его координаты от раза к разу определяется различным. Это явление называется дребезгом (bouncing) и требует специальных мер подавления, например - установки порога чувствительности.
Явление дребезга присуще также контактам кнопок, которые в момент замыкания и размыкания могут вместо одного перепада давать серию коротких ложных перепадов. Однако действия, порождаемые кнопками, как правило, занимают достаточное время, чтобы к моменту очередного опроса состояния кнопки ее контакты успели успокоиться.
Порог чувствительности при уведомлении
По умолчанию, подсистема уведомляет приложение в режиме захвата при минимально воспринимаемом изменении положения манипуляторов. Если приложению требуется более грубое отслеживание состояния манипуляторов, можно установить порог (threshold) чувствительности, задаваемый в логических единицах перемещения (позиции). Установка порога также позволяет подавить "дребезг" значений координат, порожденный неточностью метода измерения сопротивления.
Основные черты и понятия интерфейса таймера
Разрешение
Разрешением (resolution) называется точность отсчета временных интервалов. Стандартный таймер Windows, управляемый функцией SetTimer, имеет достаточно низкое разрешение (порядка 50 мс в Windows 9x, порядка 10 мс - в Windows NT). Этого вполне хватает для отсчета интервалов при общении с пользователем, однако недостаточно для приложений реального времени. Мультимедийный таймер предоставляет возможность установки произвольного разрешения, вплоть до одной миллисекунды.
Фактически разрешение таймера означает период времени между аппаратными прерываниями от системного таймера. Если при стандартном разрешении они возникают сравнительно редко, несколько десятков раз в секунду, то при разрешении в 1 мс ядро Windows будет получать 1000 прерываний в секунду, что приводит к некоторому возрастанию накладных расходов.
Виды событий
Мультимедийный таймер поддерживает два вида событий: однократное (One shot) и периодическое (Periodic). Однократное событие возникает при истечении заданного временного интервала. Периодическое событие возникает каждый раз после истечения интервала, и фактически представляет собой однократное событие с "перезарядкой".
Уведомление о наступлении событий
Подсистема таймера поддерживает только один способ уведомления о наступлении таймерного события - вызов заданной программной функции. Для вызова функции подсистемой создается отдельная задача (thread) с приоритетом TIME_CRITICAL, из которой и вызывается заданная функция. Однажды созданная задача продолжает существовать до завершения приложения, обслуживая все последующие таймерные события.
Точность
Под точностью (accuracy) понимается способность таймерной подсистемы вызвать при уведомлении заданную программную функцию как можно ближе к моменту наступления события. Точность вызова увеличивется с повышением разрешения таймера, однако, поскольку вызов функции выполняется вспомогательной задачей, имеет место задержка на диспетчеризацию самой задачи. По умолчанию точность составляет примерно 7-10 мс, при разрешении 1 мс - 0-1 мс. При нехватке памяти, когда активизируется процесс подкачки (swapping) точность может резко падать до нескольких десятков миллисекунд.
Поскольку ни одна платформа Win32 не является системой реального времени, нет никаких гарантий точности работы таймера. Даже высший уровень приоритета вызывающей задачи не гарантирует от задержек на срочные системные операции, длительность которых в системах Windows никак не регламентируется.
Общая схема взаимодействия программы и подсистем
Работа с джойстиком обычно начинается с получения количества устройств функцией joyGetNumDevs, хотя некоторые программы всегда работают с первым устройством (нулевой номер). При помощи функции jouGetDevCaps можно получить характеристики выбранного джойстика, в частности - количество и конфигурацию его осей.
Если программа поддерживает собственные регулярные таймерные события - она может просто периодически опрашивать состояние джойстика функциями joyGetPos / joyGetPosEx. Первая пригодна лишь для работы с традиционным джойстиком, вторая - для джойстика любого типа.
При необходимости получать сообщения об изменении состояния джойстика необходимо захватить его функцией joySetCapture, указав ключ окна (window handle), которое будет получать уведомляющие сообщения. Попутно функцией joySetThreshold можно задать порог чувствительности, чтобы сообщения посылались в "прореженном" виде - не на каждую единицу перемещения, а на каждую вторую, третью и т.п. Освобождение джойстика от захвата выполняется функцией joyReleaseCapture.
Работа с таймером обычно начинается с обращения к функции timeGetDevCaps для получения параметров разрешения, чтобы согласовать требуемое программе разрешение с возможностями аппаратуры и платформы.
Блоки программы, требующие работы с повышенным разрешением таймера, обрамляются вызовами функций timeBeginPeriod и timeEndPeriod; такие блоки могут вкладываться друг в друга (подсистема поддерживает стек, в котором сохраняются параметры разрешения), однако каждый вызов timeBeginPeriod должен быть впоследствии закрыт соответствующим ему вызовом timeEndPeriod.
Для запроса таймерного события применяется функция timeSetEvent, которая "заводит" таймер на заданный временной интервал. В зависимости от параметров, результатом функции будет либо однократное событие, либо серия периодических событий, следующих друг за другом через заданный промежуток времени.
При необходимости досрочно отменить запрошенное событие используется функция timeKillEvent. При этом необходимо иметь в виду, что вызов функции уведомления происходит из независимой задачи, и уведомление может произойти в тот же момент, что и отмена события. Для исключения коллизий в таких случаях нужно использовать механизмы синхронизации процессов.
Независимо от таймерных событий, приложение может в любой момент запросить текущее системное время функциями timeGetSystemTime и timeGetTime. Частота изменения, или гранулярность, системного времени зависит от текущего разрешения таймера. Установленное разрешение таймера влияет также и на результат функции GetTickCount и ее производных.
При запросе таймерных событий с высокой (сотни раз в секунду) частотой имеет смысл принять меры к минимизации накладных расходов и увеличению точности отслеживания времени. Например - уменьшить до предела объем кода в функции уведомления, зафиксировать код функции и используемые им данные в памяти посредством функции VirtualLock, и т.п.
Средства разработки, включаемые файлы и библиотеки
Как и ранее, описывается программирование на языке C/C++ в среде Microsoft Visual C++.
Все необходимые константы, типы, структуры и прототипы функций подсистемы определяется в файле MMSYSTEM.H, который по умолчанию включается в компиляцию из общего файла WINDOWS.H. Дополнительные, редко используемые константы определены в файле MMREG.H.
Интерфейсные функции импортируются из библиотеки WINMM.LIB.
Структуры, используемые в интерфейсе
Структура JOYCAPS
Описывает свойства и характеристики устройства джойстика. Все поля заполняются только подсистемой и драйвером джойстика.
WORD wMid; WORD wPid; CHAR szPname [MAXPNAMELEN]; UINT wXmin; UINT wXmax; UINT wYmin; UINT wYmax; UINT wZmin; UINT wZmax; UINT wNumButtons; UINT wPeriodMin; UINT wPeriodMax; UINT wRmin; UINT wRmax; UINT wUmin; UINT wUmax; UINT wVmin; UINT wVmax; UINT wCaps; UINT wMaxAxes; UINT wNumAxes; UINT wMaxButtons; CHAR szRegKey [MAXPNAMELEN]; CHAR szOEMVxD [MAXOEMVXD];
- wMid, wPid - идентификаторы разработчика (Manufacturer) и самого драйвера (Product). Полный список известных на момент выпуска SDK идентификаторов определен в файле MMREG.H
- szPname - имя устройства в виде строки ASCIIZ.
- wXmin - минимальное значение координаты X.
- wXmax - максимальное значение координаты X.
- wYmin - минимальное значение координаты Y.
- wYmax - максимальное значение координаты Y.
- wZmin - минимальное значение координаты Z.
- wZmax - максимальное значение координаты Z.
- wNumButtons - количество кнопок.
- wPeriodMin - минимальный допустимый период опроса состояния в режиме захвата, в миллисекундах.
- wPeriodMax - максимальный допустимый период опроса состояния.
- wRmin - минимальное значение координаты R.
- wRmax - максимальное значение координаты R.
- wUmin - минимальное значение координаты U.
- wUmax - максимальное значение координаты U.
- wVmin - минимальное значение координаты V.
- wVmax - максимальное значение координаты V.
- wCaps - флаги характеристик джойстика. Имена констант флагов имеют префикс JOYCAPS_:
HASZ | Имеет ось Z. |
---|---|
HASR | Имеет ось R (руль). |
HASU | Имеет ось U. |
HASV | Имеет ось V. |
HASPOV | Имеет манипулятор POV. |
POV4DIR | Манипулятор POV поддерживает только дискретные направления (нейтраль, вперед, вправо, назад, влево). |
POVCTS | Манипулятор POV поддерживает плавное изменение направления. |
- wMaxAxes - максимальное количество осей перемещения.
- wNumAxes - количество используемых в данное время осей перемещения.
- wMaxButtons - максимальное количество поддерживаемых кнопок.
- szRegKey - название ключа реестра, представляющего джойстик.
- szOEMVxD - строка идентификации системного драйвера (VxD).
Структура JOYINFO
Описывает состояние традиционного джойстика (до трех осей и до четырех кнопок). Введена в Win16, может использоваться в Win32, если информации достаточно для описания состояния опрашиваемого джойстика. Заполняется подсистемой.
UINT wXpos; UINT wYpos; UINT wZpos; UINT wButtons;
- wXpos - позиция по оси X.
- wYpos - позиция по оси Y.
- wZpos - позиция по оси Z.
- wButtons - состояние кнопок, отображаемое битовыми флагами. Константы флагов имеют имена вида JOY_BUTTONn, где n - номер кнопки (1..4).
Структура JOYINFOEX
Описывает состояние любого поддерживаемого в Win32 джойстика (до 6 осей и до 32 кнопок). Заполняется подсистемой, кроме полей dwSize и dwFlags.
DWORD dwSize; DWORD dwFlags; DWORD dwXpos; DWORD dwYpos; DWORD dwZpos; DWORD dwRpos; DWORD dwUpos; DWORD dwVpos; DWORD dwButtons; DWORD dwButtonNumber; DWORD dwPOV; DWORD dwReserved1; DWORD dwReserved2;
- dwSize - размер структуры в байтах. Заполняется приложением, используется для определения версии интерфейса.
- dwFlags - флаги параметров запроса. Делятся на три группы: флаги запрашиваемых значений, флаги калибровки и вспомогательные флаги.
Флаги запрашиваемых значений (префикс JOY_RETURN в именах констант)
X | Запрашивается координата X в поле dwXpos. |
---|---|
Y | Запрашивается координата Y в поле dwYpos. |
Z | Запрашивается координата Z в поле dwZpos. |
R | Запрашивается координата R в поле dwRpos. |
U | Запрашивается координата U в поле dwUpos. |
V | Запрашивается координата V в поле dwVpos. |
POV | Запрашивается значение Point of View в поле dwPow (в дискретных единицах). |
POVCTS | Запрашивается значение Point of View в поле dwPow (в единицах по 0.1 градуса). |
ALL | Запрашиваются все возможные значения координат. |
CENTERED | За центральное положение манипулятора принимается среднее значение координат по каждой из осей. |
RAWDATA | Запрашиваются сырые (некалиброванные) значения координат. |
BUTTONS | Запрашивается состояние кнопок в поле dwButtons. |
Вспомогательные флаги (префикс JOY_):
USEDEADZONE | Создание вблизи центрального значения координат "мертвой зоны", внутри которой возвращается одно и то же (среднее) значение координаты. Для того, чтобы значение начало изменяться, необходим вывод манипулятора за пределы зоны. Может использоваться для подавления дребезга в области нейтрального положения. |
---|
Флаги калибровки (префикс JOY_CAL_):
READ3 | Запрос некалиброванных значений X, Y, Z. |
---|---|
READ4 | Запрос некалиброванных значений X, Y, Z, R. |
READ5 | Запрос некалиброванных значений X, Y, Z, R, U. |
READ6 | Запрос некалиброванных значений X, Y, Z, R, U, V. |
READXONLY | Запрос некалиброванного значения X. |
READYONLY | Запрос некалиброванного значения Y. |
READXYONLY | Запрос некалиброванных значений X, Y. |
READZONLY | Запрос некалиброванного значения Z. |
READRONLY | Запрос некалиброванного значения R. |
READUONLY | Запрос некалиброванного значения U. |
READVONLY | Запрос некалиброванного значения V. |
READALWAYS | Обращаться к порту состояния джойстика, даже если драйвер обнаружил, что джойстик не подключен. |
- dwXpos - значение координаты X.
- dwYpos - значение координаты Y.
- dwZpos - значение координаты Z.
- dwRpos - значение координаты R.
- dwUpos - значение координаты U.
- dwVpos - значение координаты V.
- dwButtons - битовая шкала состояния кнопок. Нажатые кнопки 1..32 отображаются установленными разрядами 0..31. Для удобства определены константы с именами JOY_BUTTONn, где n - номер кнопки (1..32).
- dwButtonNumber - номер последней нажатой кнопки.
- dwPOV - значение параметра POV в сотых долях градуса. Определены специальные константы вида JOY_POVxxx, где xxx - дискретное положение манипулятора (CENTERED (-1) - нейтраль, FORWARD (0) - вперед, RIGHT (9000) - вправо, BACKWARD (18000) - назад, LEFT (27000) - влево.
- dwReserved1, dwReserved2 - служебные поля.
Назначение флагов вида JOY_CAL_xxx из документации непонятно. Судя по тексту драйвера, эти флаги запрашивают отдельные некалиброванные значения, однако на практике наличие любого из этих флагов приводит к полному обнулению результирующей структуры.
Структура TIMECAPS
Описывает параметры мультимедийного таймера.
UINT wPeriodMin; UINT wPeriodMax;
- wPeriodMin - минимальный измеряемый период времени (максимальное поддерживаемое разрешение).
- wPeriodMax - максимальный измеряемый период времени (минимальное поддерживаемое разрешение).
Структура MMTIME
Описывает время в терминах различных мультимедийных потоков. Используется для получения текущего времени функцией timeGetSystemTime, заполняется подсистемой.
UINT wType; union { DWORD ms; DWORD sample; DWORD cb; DWORD ticks; struct { BYTE hour; BYTE min; BYTE sec; BYTE frame; BYTE fps; BYTE dummy; BYTE pad [2] } smpte; struct { DWORD songptrpos; } midi; } u;
- wType - формат времени, описываемого структурой. Константы формата имеют префикс TIME_:
BYTES | Количество байтов от начала мультимедийного потока |
---|---|
MIDI | Время в стандарте MIDI (Sond Position Pointer) |
MS | Время в миллисекундах |
SAMPLES | Количество звуковых блоков с начала звукового (Audio) потока |
SMPTE | Время в стандарте SMPTE (в кадрах) |
TICKS | Время в тиках от начала MIDI–потока |
- ms - счетчик миллисекунд, для случая TIME_MS.
- sample - счетчик звуковых блоков, для случая TIME_SAMPLES.
- cb - счетчик байтов, для случая TIME_BYTES.
- ticks - счетчик тиков, для случая TIME_TICKS.
- smpte - набор данных в формате SMPTE, для случая TIME_SMPTE.
- midi - данные в формате MIDI–времени.
- hour, min, sec - счетчик полных часов, минут и секунд.
- frame - счетчик кадров внутри последней секунды.
- fps - кадров в секунду (24, 25, 29, 30).
- dummy, pad - выравнивание на границу двойного слова.
- songptrpos - позиция указателя композиции в MIDI (Song Position Pointer).
Уведомления, передаваемые программе
Для сообщения программе об изменениях состояния джойстика его подсистема помещает сообщения в очередь заданного окна, это единственный возможный способ уведомления. Для получения уведомляющих сообщений устройство джойстика должно быть переведено в режим захвата.
Для сообщения об истечении заданного временного интервала подсистема таймера вызывает заданную программную функцию (callback), это также единственный способ уведомления от таймера.
Уведомление о состоянии джойстика
Для уведомления о состоянии джойстика используются сообщения, отражающие произошедшие изменения. Константы для кодов сообщений имеют префикс MM_JOY:
- nMOVE - изменение координат X/Y.
- nZMOVE - изменение координаты Z.
- nBUTTONDOWN - нажатие кнопки.
- nBUTTONUP - отпускание кнопки.
Вместо n в именах присутствует цифра 1 или 2, обозначающая номер джойстика. Во всех сообщениях параметр wParam содержит битовую шкалу нажатых кнопок. Параметр lParam представляет значения координат: в сообщениях ZMOVE - Z (младшее слово), в остальных сообщения - X (младшее слово) и Y (старшее слово). Для выделения координат из двойного слова удобно использовать макросы LOWORD и HIWORD.
Как видно, в подсистеме предусмотрены сообщения лишь для трехосевых джойстиков. Об изменении координат R, U, V и POV приложению никак не сообщается - для этого необходимо организовать периодический опрос собственными силами - например, по периодическому таймерному событию.
Уведомление о наступлении таймерного события
При наступлении таймерного события подсистема вызывает заданную программную функцию из специально созданной вспомогательной задачи (thread), имеющей уровень приоритета TIME_CRITICAL. Однажды созданная вспомогательная задача существует до завершения процесса; с ее помощью также выполняются все последующие отработки возникающих таймерных событий.
Набор интерфейсных функций подсистем
В отличие от ранее описанных подсистем MME, интерфейсы джойстика и таймера достаточно своеобразны и не допускают какой-либо систематизации набора функций. Поэтому все имена функций приводятся полностью, без сокращений.
Перечень интерфейсных функций
joyGetNumDevs | Запрос количества устройств джойстика |
---|---|
joyGetDevCaps | Запрос параметров и возможностей устройства джойстика |
joyGetPos | Запрос состояния традиционного джойстика |
joyGetPosEx | Запрос состояния любого джойстика |
joyGetThreshold | Запрос порога чувствительности |
joySetThreshold | Установка порога чувствительности |
joySetCapture | Захват джойстика |
joyReleaseCapture | Освобождение джойстика от захвата |
timeGetDevCaps | Запрос параметров таймера |
timeGetSystemTime | Запрос системного времени в виде структуры MMTIME |
timeGetTime | Запрос системного времени в миллисекундах |
timeBeginPeriod | Начало работы с заданным разрешением |
timeEndPeriod | Конец работы с заданным разрешением |
timeSetEvent | Запрос таймерного события |
timeKillEvent | Отмена таймерного события |
CallbackProc | Прототип функции уведомления таймера |
Значения, возвращаемые интерфейсными функциями
За редким исключением, все функции интерфейса возвращают результат типа MMRESULT, эквивалентный типу UINT. Значение MMSYSERR_NOERROR, равное нулю, означает успешное выполнение функции, любое другое значение указывает на ошибку. Константы для кодов ошибок имеют префиксы MMSYSERR_ (общая ошибка мультимедийной подсистемы), JOYERR_ (ошибка подсистемы джойстика) и TIMERR_ (ошибка подсистемы таймера):
MMSYSERR_BADDEVICEID | Недопустимый номер устройства |
---|---|
MMSYSERR_NOTENABLED | Драйвер не активизирован |
MMSYSERR_NODRIVER | Драйвер отсутствует |
MMSYSERR_NOMEM | Недостаточно памяти |
MMSYSERR_NOTSUPPORTED | Запрошенная функция не поддерживается |
MMSYSERR_INVALFLAG | Недопустимый флаг |
MMSYSERR_INVALPARAM | Недопустимый параметр |
MMSYSERR_ERROR | Неопределенная ошибка |
JOYERR_NOERROR | Успешное завершение запроса |
JOYERR_PARMS | Недопустимые параметры запроса |
JOYERR_NOCANDO | Невозможно выполнить запрос |
JOYERR_UNPLUGGED | Джойстик не подключен |
TIMERR_NOERROR | Успешное завершение запроса |
TIMERR_NOCANDO | Невозможно выполнить запрос |
TIMERR_STRUCT | Недопустимый размер структуры |
Описание интерфейсных функций джойстика
joyGetNumDevs - запрос количества устройств
UINT joyGetNumDevs (void);
Возвращает количество установленных в системе устройств джойстика.
joyGetDevCaps - запрос параметров и возможностей джойстика
MMRESULT joyGetDevCaps ( UINT DevNum, JOYCAPS *Caps, UINT CapsSize );
Служит для определения параметров и возможностей джойстика.
- DevNum - номер устройства, начиная с нуля.
- Caps - указатель структуры типа JOYCAPS.
- CapsSize - размер структуры в байтах.
При успешном завершении функция заполняет поля переданной указателем структуры параметрами устройства.
joyGetPos - запрос текущего состояния традиционного джойстика
MMRESULT joyGetPos ( UINT DevNum, JOYINFO *Info );
- DevNum - номер устройства, начиная с нуля.
- Info - указатель структуры состояния традиционного джойстика типа JOYINFO, которая будет заполнена текущими значениями позиций манипуляторов и состояний кнопок.
joyGetPosEx - запрос текущего состояния любого джойстика
MMRESULT joyGetPosEx ( UINT DevNum, JOYINFOEX *Info );
- DevNum - номер устройства, начиная с нуля.
- Info - указатель структуры состояния джойстика типа JOYINFOEX, которая будет заполнена текущими значениями позиций манипуляторов и состояний кнопок.
Перед обращением к функции необходимо заполнить поля dwSize и dwFlags передаваемой структуры. Первое отражает версию программного интерфейса, а второе - режимы выполнения запроса и набор возвращаемых подсистемой параметров джойстика.
joyGetThreshold - запрос порога чувствительности к перемещению
MMRESULT joyGetThreshold ( UINT DevNum, UINT *Threshold );
- DevNum - номер устройства, начиная с нуля.
- Threshold - указатель переменной типа UINT, в которую возвращается текущий порог чувствительности. Порог измеряется в логических элементарных единицах перемещения и означает максимальное перемещение по любой из координат, при котором еще не генерируются уведомляющие сообщения в режиме захвата. По умолчанию установлено нулевое значение - сообщения генерируются при перемещении на каждую элементарную единицу координаты.
joySetThreshold - установка порога чувствительности к перемещению
MMRESULT joySetThreshold ( UINT DevNum, UINT Threshold );
- DevNum - номер устройства, начиная с нуля.
- Threshold - значение порога чувствительности в элементарных логических единицах перемещения.
joySetCapture - захват джойстика
MMRESULT joySetCapture ( HWND Win, UINT DevNum, UINT Period, BOOL OnChange );
- Win - ключ окна (window handle), которое будет получать уведомляющие сообщения от подсистемы.
- DevNum - номер устройства, начиная с нуля.
- Period - период опроса состояния джойстика драйвером, в миллисекундах.
- OnChange - режим посылки уведомляющих сообщений. При значении TRUE сообщения посылаются только при изменении состояния джойстика; при значении FALSE - после каждого опроса драйвера, то есть - регулярно через интервал, определяемый параметром Period. В регулярном режиме после каждого опроса джойстика драйвером посылается пара сообщений - MOVE и ZMOVE.
joyReleaseCapture - освобождение джойстика
MMRESULT joyReleaseCapture ( UINT DevNum );
- DevNum - номер устройства, начиная с нуля.
Освобождает джойстик от ранее установленного режима захвата, выполненного функцией joySetCapture, прекращая посылку уведомляющих сообщений и делая джойстик доступным для нового захвата.
Описание интерфейсных функций таймера
timeGetDevCaps - запрос параметров и возможностей таймера
MMRESULT timeGetDevCaps ( TIMECAPS *Caps, UINT CapsSize );
Служит для определения параметров таймера - минимального и максимального поддерживаемого разрешения.
- Caps - указатель структуры типа TIMECAPS.
- CapsSize - размер структуры в байтах.
При успешном завершении функция заполняет поля переданной указателем структуры параметрами разрешения.
timeGetSystemTime - запрос системного времени в виде структуры
MMRESULT timeGetSystemTime ( MMTIME *Time, UINT StructSize );
- Time - указатель структуры типа MMTIME, в которую будет возвращено значение системного времени.
- StructSize - размер структуры в байтах.
При успешном завершении функция заносит в поле ms значение системного времени в миллисекундах с момента загрузки системы. Поле wType функцией не рассматривается, и после выполнения запроса всегда устанавливается в значение TIME_MS.
timeGetTime - запрос системного времени в миллисекундах
Возвращает значение системного времени в миллисекундах с момента загрузки системы.
timeBeginPeriod - начало работы с заданным разрешением
MMRESULT timeBeginPeriod ( UINT Period );
- Period - интервал между прерываниями аппаратного таймера, корректирующими счетчик системного времени. Значение должно лежать внутри диапазона допустимых разрешений таймера (структура TIMECAPS). Если оно выходит за пределы допустимого диапазона, используется ближайшее из предельных значений.
Функция переводит системный таймер Windows в режим с заданным разрешением, настраивая аппаратный таймер на частоту прерываний, соответствующую наивысшему из затребованных всеми приложениями разрешений. Если в системе есть приложения, установившие более высокое разрешение таймера - частота прерываний аппаратного таймера не изменяется.
Частота обновления системного времени, получаемого функциями timeGetSystemTime, timeGetTime, GetTickCount и им подобными, определяется частотой аппаратного системного таймера.
Каждый вызов timeBeginPeriod должен быть впоследствии "закрыт" соответствующим вызовом timeEndPeriod, обозначающим блока программы, работающего с заданным разрешением.
timeEndPeriod - конец работы с заданным разрешением
MMRESULT timeEndPeriod ( UINT Period );
- Period - длительность периода таймера, определяющее разрешение, с которым завершается работа. Значение должно совпадать с указанным в предшествующем вызове функции timeBeginPeriod.
Функция завершает работу с указанным разрешением таймера, восстанавливая предыдущее разрешение.
timeSetEvent - запрос таймерного события
UINT timeSetEvent ( UINT Delay, UINT Resolution, TIMECALLBACK *TimeProc, DWORD UserData, UINT EventType );
- Delay - интервал времени до наступления таймерного события в миллисекундах.
- Resolution - разрешение таймера при отсчете интервала, в миллисекундах. Нулевое значение требует максимально возможного разрешения. Этот параметр не увеличивает текущего разрешения системного таймера; если текущего разрешения недостаточно - необходимо использовать функцию timeBeginPeriod.
- TimeProc - указатель функции уведомления.
- UserData - произвольное 32-разрядное значение, передаваемое функции уведомления при вызове в качестве параметра.
- EventType - тип таймерного события: TIME_ONESHOT - однократное, TIME_PERIODIC - периодическое.
Функция "заряжает" виртуальный таймер на заданный интервал времени, возвращая идентификатор будущего таймерного события (виртуального таймера). В случае неудачи возвращается нулевое значение. Идентификатор события передается также функции уведомления в ее вызове при наступлении события.
Однократное событие возникает один раз после истечения указанного интервала, периодическое - через каждый промежуток времени заданной величины. Для досрочной отмены однократного события или для прекращения периодических событий необходимо использовать функцию timeKillEvent.
Одновременно приложение может запросить произвольное количество таймерных событий, каждое из которых наступает независимо от остальных.
timeKillEvent - отмена таймерного события
MMRESULT timeKillEvent ( UINT EventId );
- TimerId - идентификатор отменяемого таймерного события (виртуального таймера).
Функция отменяет таймерное событие, которое еще не произошло, а также отменяет регулярную генерацию периодического события.
Вход в функцию еще не гарантирует отмены события. Если событие наступает после входа в функцию, но до достижения точки блокировки переключения задач, вспомогательная задача может успеть получить управление и выполнить вызов функции уведомления.
При попытке отмены уже отработанного события возвращается ошибка MMSYSERR_INVALPARAM, больше никаких действий не выполняется. Однако, если с момента отработки события до вызова timeKillEvent выполняется функция timeSetEvent, данный идентификатор события может быть назначен повторно, вследствие чего может быть ошибочно отменен. Поэтому при работе с событиями необходимо уделять пристальное внимание корректному пути выполнения блоков программы, используя системные средства синхронизации процессов.
TimerProc - функция приложения, вызываемая при уведомлении
Эта функция определяется приложением, подсистема таймера вызывает ее при выполнении уведомлений, передавая в ее аргументах параметры события. Прототип функции имеет следующий вид (имя CallbackProc приведено для примера, реальное имя выбирается приложением):
void CALLBACK CallbackProc (
UINT EventId, UINT Msg, DWORD UserData, DWORD Param1, DWORD Param2 );
- EventId - идентификатор события.
- Msg - неиспользуемый параметр.
- UserData - 32-разрядное значение, указанное приложением при запросе события в функции timeSetEvent.
- Param1, Param2 - неиспользуемые параметры.
В Win16 функция может вызываться в контексте обработчика прерывания, поэтому безопасно может использовать лишь ограниченный набор функций Windows: EnterCriticalSection, LeaveCriticalSection, midiOutLongMsg, midiOutShortMsg, OutputDebugString, PostMessage, PostThreadMessage, SetEvent, timeGetSystemTime, timeGetTime, timeKillEvent, timeSetEvent. Обращение к другим системным функциям, как и к функциям подсистемы, может вызвать непредсказуемые последствия.
В Win32 вызов функции выполняется из отдельной задачи, поэтому никаких ограничений на выполняемые действия не накладывается. Однако нужно помнить, что вспомогательная задача имеет приоритет TIME_CRITICAL, и выполнение в ее контексте длительных операций может вызвать торможение остальных задач процесса и/или системы.
Вспомогательная задача создается один раз и существует до полного завершения процесса. Подсистема вызывает из этой задачи функции уведомления для всех событий, которые будут запрошены за время жизни процесса.
Параметры Msg, Param1 и Param2 сохранены только в целях унификации прототипа - все подсистемы MME для удобства используют для функций уведомления один и тот же прототип.
Недостатки подсистем джойстика и таймера
Так же, как и подсистемы Wave/MIDI, подсистемы джойстика и таймера в Windows 95/98 остались 16-разрядными, как и в Windows 3.x. Из-за этого каждое обращение к ним из Win32–приложения сопровождается двойной сменой режима исполнения (thunking), приводящее, к дополнительным накладным расходам. Тем не менее, использование мультимедийного таймера - единственный способ достичь более высокого временнОго разрешения по сравнению со стандартным.
В Windows NT/2000 все подсистемы сделаны изначально 32-разрядными, так что описанных проблем там не возникает.
Нетрадиционные применения интерфейса джойстика
Интерфейс джойстика может использоваться не только для подключения манипуляторов. Он может рассматриваться, как универсальный входной порт с четырьмя аналоговыми и четырьмя цифровыми входами.
Схемотехника аналоговых входов ориентирована на измерение сопротивлений 0..100 кОм, включенных между входом и питающим напряжением +5 В, путем измерения протекающего через вход тока. Следовательно, интерфейс может быть использован для измерения любых аналоговых величин, значения которых преобразованы в пропорциональные им токи соответствующих величин - освещенности, температуры, влажности и т.п. Нельзя только забывать о значительной погрешности метода, которая делает его непригодным для точных измерений.
Цифровые входы интерфейса могут подключаться к любым цепям, обеспечивающим замыкание входа на землю - герконам, реле, переключателям, транзисторам и выходам микросхем ТТЛ с открытым коллектором, электронным ключам и т.п. Например, при наличии источника бесперебойного питания (UPS) с простым интерфейсом, выдающим только сигнал перехода на батарейное питание, этот сигнал может быть принят через интерфейс джойстика специально написанной для этого программой, которая может инициировать нормальное завершение работы системы до того, как батарея UPS полностью разрядится и питание системы будет выключено аварийно.
---
Любой из материалов, опубликованных на этом сервере, не может быть воспроизведен в какой бы то ни было форме и какими бы то ни было средствами без письменного разрешения владельцев авторских прав.