Аппаратное обеспечение IBM PC

         

Драйверы мыши в MS-DOS


Как это не странно, ни BIOS, ни MS-DOS версий вплоть до 4.01 не содержат программной поддержки мыши. Для того, чтобы задействовать это устройство, вам надо использовать драйвер мыши или специальную резидентную программу, выполняющую функцию драйвера мыши. Как правило, это программное обеспечение поставляется вместе с мышью.

Для подключения драйвера мыши файл CONFIG.SYS должен содержать строку следующего вида:

device=c:\mouse\mouse.sys

Если используется резидентная программа, она обычно вызывается в файле AUTOEXEC.BAT:

c:\mouse\mouse.com

Драйвер мыши выполняет следующие функции:

отслеживает перемещения курсора и нажатия на клавиши мыши;

рисует на экране курсор, повторяющий движения мыши в графическом или текстовом режимах;

предоставляет программам интерфейс для работы с мышью, основанный на вызове прерывания INT33h.



Инициализация мыши


На входе: AX = 0000h.

На выходе: AX = состояние мыши:

0000h - драйвер мыши или мышь не установлены; FFFFh - драйвер и мышь установлены;

BX = количество клавиш у мыши:

2 - две клавиши; 0 - больше или меньше, чем две; 3 - мышь системы Mouse Systems (имеет три клавиши).

Эта функция выполняет аппаратный сброс оборудования мыши и программную установку драйвера мыши в начальное состояние. С помощью функции 21h можно выполнить установку драйвера в исходное состояние, не выполняя аппаратного сброса мыши.

При установке в исходное состояние для программ, работающих в текстовом режиме, выполняются следующие действия:

курсор перемещается в центр экрана и гасится;



разрешается перемещение курсора по всей поверхности экрана, причем на экране отсутствуют зоны, в которых курсор является невидимым;

устанавливается режим отображения курсора - инвертирование атрибута символа, на который указывает курсор;

для изображения курсора выбирается нулевая страница видеопамяти;

разрешается эмуляция светового пера (хотя это вам едва ли понадобится);

устанавливается начальная скорость перемещения курсора.

Мы подготовили функцию для инициализации мыши из программы, составленной на языке Си:

/** *.Name ms_init *.Title Инициализация мыши * *.Descr Эта функция выполняет аппаратный сброс мыши, * устанавливает в начальные значения внутренние * переменные ее драйвера. Дополнительно определяется * количество клавиш мыши. * *.Proto int ms_init(int *nbottoms) * *.Params int *nbottoms - указатель на переменную * типа int, в которую будет записано количество * клавиш, имеющихся в мыши. * *.Return 0 - плата или драйвер не установлены; * -1 - плата установлена, инициализация * выполнена успешно; * * В переменную nbottoms записывается количество * клавиш мыши: * * 2 - две клавиши; * 0 - больше или меньше, чем две; * 3 - мышь системы Mouse Systems, три клавиши. * *.Sample ms_sampl1.c **/

#include <dos.h> #include <conio.h>

union REGS reg;

int ms_init(int *nbottoms) {

reg.x.ax = 0; int86(0x33,&reg,&reg);

*nbottoms = reg.x.bx; return reg.x.ax;

}



Как устроена мышь?


Мышь - это небольшая коробочка с двумя или тремя клавишами, которая соединяется с компьютером тонким кабелем:

Рис. 2. Внешний вид мыши

Сверху на корпусе расположены кнопки. Обычно их две или три. Назначение этих кнопок полностью определяется программным обеспечением. Снизу виден шарик. Он обычно покрыт резиной для лучшего сцепления с поверхностью стола.

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

Для того, чтобы "выбрать" какой-нибудь объект, обычно требуется указать на этот объект курсором и нажать на одну из кнопок мыши.

Если вы откроете корпус мыши, вы увидите простой механизм, состоящий из шарика, двух осей с резиновыми валиками, двух дисков с отверстиями и четырех фотодатчиков:

Рис. 3. Внутреннее устройство мыши

Когда вы перемещаете мышь по поверхности стола, вращение шарика передается через резиновые валики двум дискам с отверстиями. Около каждого диска расположены фотодатчики (по два на диск). Они фиксируют направление вращения и угол поворота дисков. Во время движения мыши фотодатчики вырабатывают импульсы, которые передаются в компьютер. Количество этих импульсов линейно зависит от величины перемещения мыши.

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

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

через последовательный порт (COM1, COM2);

через специальный адаптер, который вставляется в слот расширения материнской платы компьютера.


Выбирая мышь, используйте тот способ подключения, который вам более удобен. При этом не следует забывать о существовании компьютеров, не имеющих слотов расширения (обычно это компьютеры типа Lap-Top). Для таких компьютеров больше подойдет мышь, подключающаяся через последовательный порт.

Что касается программного интерфейса, то можно выделить два типа:

трехкнопочная мышь системы Mouse Systems

двухкнопочная мышь Microsoft

Некоторые мыши могут эмулировать оба типа. Эмулируемый тип зависит от состояния переключателя, находящегося на нижней крышке корпуса мыши или от того, была ли нажата клавиша мыши во время включения питания компьютера.

Мы рекомендуем вам приобрести двухкнопочную мышь фирмы Microsoft. Эта мышь удобна в работе, имеет переходник для подключения к последовательному порту и адаптер для подключения к слотам расширения. Кроме того, если вы используете мышь фирмы Microsoft, есть гарантия, что все фирменное программное обеспечение будет правильно работать с вашей мышью.


МЫШЬ


3.1.

3.2.

3.3.

Вместе с появлением персональных компьютеров возникло и получило огромную популярность графическое устройство ввода информации - мышь. В настоящее время практически каждый персональный компьютер оснащен этим устройством. Более того, многие программы специально ориентированы на использование мыши. Например, графический редактор Picture Maker из пакета Story Editor. В этом редакторе вы можете сделать почти всю работу, не прикасаясь к клавиатуре (за исключением операции набора текстовых строк).

Что это за устройство и почему оно используется так же часто, как и клавиатура персонального компьютера?



Определить чувствительность мыши


На входе: AX = 001Bh.

На выходе: BX = горизонтальная чувствительность в миках на точку (пиксель);

CX = вертикальная чувствительность в миках на точку (пиксель);

DX = значение порога удвоения, мики в секунду.

Функция позволяет определить текущие значения для чувствительности мыши и порога удвоения.



Определить номер видеостраницы


На входе: AX = 001Eh.

На выходе: BX = номер видеостраницы.

Функция возвращает номер видеостраницы, на которой в настоящее время отображается курсор мыши.



Определить положение курсора


На входе: AX = 0003h.

На выходе: BX = состояние клавиш мыши:

бит 0 = 1 - нажата левая клавиша; бит 1 = 1 - нажата правая клавиша; бит 2 = 1 - нажата средняя клавиша (для мыши системы Mouse Systems);

CX = координата X (по горизонтали); DX = координата Y (по вертикали).

Функция 03h возвращает текущие (на момент вызова функции) координаты курсора мыши и состояние клавиш.

Для графических режимов координаты располагаются в различных диапазонах, в зависимости от текущего видеорежима:

Размер экрана Номер режима Диапазон координат X Y 320x200 4,5 0..638 0..199 640x200 6 0..639 0..199 320x200 0Dh 0..638 0..199 640x200 0Eh 0..639 0..199 640x350 0Fh 0..639 0..349

Программы, работающие в текстовом режиме, должны разделить полученные координаты на 8 (как координату X, так и координату Y).

Приведем функцию, предназначенную для использования в программах, составленных на языке Си:

/** *.Name ms_query *.Title Определение текущих координат курсора * *.Descr Эта функция определяет текущие координаты * курсора мыши и состояние клавиш на * момент вызова. Определенное состояние * записывается в структуру MOUSE_STATE, * описанную в файле sysp.h: * * typedef struct _MOUSE_STATE_ { * unsigned bottoms; * unsigned x; * unsigned y; * } MOUSE_STATE; * * Адрес структуры передается функции в качестве * параметра. * *.Proto MOUSE_STATE *ms_query(MOUSE_STATE *state); * *.Params MOUSE_STATE *state - указатель на структуру, * описывающую состояние мыши. * *.Return Функция возвращает значение своего параметра. * *.Sample ms_samp1.c **/

#include <stdio.h> #include <dos.h> #include <conio.h> #include "sysp.h"

union REGS reg;

MOUSE_STATE *ms_query(MOUSE_STATE *state) {

reg.x.ax = 3; int86(0x33,&reg,&reg);

state->bottoms = reg.x.bx; state->x = reg.x.cx; state->y = reg.x.dx;

return(state); }

Приведем пример программы, которая запрашивает номер видеорежима, устанавливает его и динамически отображает координаты курсора и состояние клавиш мыши. После завершения работы программа восстанавливает первоначальный видеорежим:


#include <dos.h> #include <stdio.h> #include <conio.h> #include "sysp.h"

union REGS reg; void main() {

int botm, i; MOUSE_STATE state; unsigned old_videomode, new_videomode; char buf[20], *bufptr;

// Определяем текущий видеорежим

reg.x.ax = 0x0f00; int86(0x10, &reg, &reg); old_videomode = reg.h.al;

// Устанавливаем новый видеорежим:

// Устанавливаем максимально допустимую длину строки

buf[0] = 10; printf("\nВведите десятичный номер видеорежима: "); bufptr = cgets(buf);

// Преобразуем введенное число к формату int

new_videomode = atoi(bufptr);

reg.h.ah = 0; reg.h.al = new_videomode; int86(0x10, &reg, &reg);

// Инициализируем мышь, определяем количество клавиш

if(!ms_init(&botm)) { printf("\nМышь не установлена"); exit(-1); }

printf("\nУстановлена мышь: "); switch (botm) { case 2: printf("двухклавишная"); break; case 3: printf("трехклавишная, системы Mouse Systems"); break; case 0: default: printf("неизвестной системы"); break; } printf("\n\nСостояние мыши:\n\n");

// Включаем курсор

ms_on();

while(!kbhit()) { ms_query(&state); printf("%2d x:%5d y:%5d", state.bottoms, state.x, state.y); for(i=0;i<18;i++) printf("\b"); } getch();

ms_off();

reg.h.ah = 0; reg.h.al = old_videomode; int86(0x10, &reg, &reg);

}

Однако использование функции 03h - не самый лучший способ работы с мышью. Программа должна постоянно следить за координатами курсора или за состоянием клавиш. Это может привести к непроизводительным затратам процессорного времени на опрос состояния.

Немного позже мы рассмотрим другие способы определения состояния мыши.


Определить положение курсора при нажатии клавиши


На входе: AX = 0005h;

BX = клавиша, при нажатии которой запоминается состояние мыши: 0 - левая; 1 - правая; 2 - средняя.

На выходе: AX = состояние клавиш мыши:

бит 0 = 1 - нажата левая клавиша; бит 1 = 1 - нажата правая клавиша; бит 2 = 1 - нажата средняя клавиша (для мыши системы Mouse Systems);

BX = количество нажатий на заданную клавишу, обнуляется после вызова функции;

CX = координата X (по горизонтали);

DX = координата Y (по вертикали).

В отличие от функции 03h эта функция возвращает программе не текущее состояние мыши, а запомненное в момент последнего нажатия на клавишу, заранее определенную при вызове функции. Она также возвращает количество нажатий на заданную клавишу, которое вы можете использовать для обнаружения двойных нажатий.

Функция для определения состояния мыши при нажатии на заданную клавишу:

/** *.Name ms_querp *.Title Определение состояния мыши при нажатии на клавишу * *.Descr Эта функция определяет координаты курсора мыши * и состояние клавиш в момент нажатия на заранее * заданную клавишу. * Определенное состояние записывается в структуру * MOUSE_STATE, описанную в файле sysp.h: * * typedef struct _MOUSE_STATE_ { * unsigned bottoms; * unsigned x; * unsigned y; * } MOUSE_STATE; * * Адрес структуры передается функции в качестве * параметра. * *.Proto int ms_querp(MOUSE_STATE *state, int bottom); * *.Params MOUSE_STATE *state - указатель на структуру, * описывающую состояние мыши. * * int bottom - параметр определяет клавишу, * при нажатии на которую необходимо * запомнить состояние: * 0 - левая клавиша, * 1 - правая клавиша, * 2 - средняя клавиша. * *.Return Функция возвращает количество нажатий на * заданную клавишу с момента последнего * вызова этой функции. * *.Sample ms_samp3.c **/

#include <stdio.h> #include <dos.h> #include <conio.h> #include "sysp.h"

union REGS reg;

int ms_querp(MOUSE_STATE *state, int bottom) {

reg.x.ax = 5; reg.x.bx = bottom; int86(0x33,&reg,&reg);

state->bottoms = reg.x.ax; state->x = reg.x.cx; state->y = reg.x.dx;

return(reg.x.bx); }



Определить положение курсора при отпускании клавиши


На входе: AX = 0006h;

BX = клавиша, при отпускании которой запоминается состояние мыши: 0 - левая; 1 - правая; 2 - средняя.

На выходе: AX = состояние клавиш мыши:

бит 0 = 1 - нажата левая клавиша; бит 1 = 1 - нажата правая клавиша; бит 2 = 1 - нажата средняя клавиша (для мыши системы Mouse Systems);

BX = количество нажатий на заданную клавишу, обнуляется после вызова функции;

CX = координата X (по горизонтали); DX = координата Y (по вертикали).

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

Функция для определения состояния мыши при отпускании заданной клавиши:

/** *.Name ms_querr *.Title Определение состояния мыши при отпускании клавиши * *.Descr Эта функция определяет координаты курсора мыши * и состояние клавиш в момент отпускания заранее * заданной клавиши. * Определенное состояние записывается в структуру * MOUSE_STATE, описанную в файле sysp.h: * * typedef struct _MOUSE_STATE_ { * unsigned bottoms; * unsigned x; * unsigned y; * } MOUSE_STATE; * * Адрес структуры передается функции в качестве * параметра. * *.Proto int ms_querr(MOUSE_STATE *state, int bottom); * *.Params MOUSE_STATE *state - указатель на структуру, * описывающую состояние мыши. * * int bottom - параметр определяет клавишу, * при отпускании которой необходимо * запомнить состояние: * 0 - левая клавиша, * 1 - правая клавиша, * 2 - средняя клавиша. * *.Return Функция возвращает количество отпусканий * заданной клавиши с момента последнего * вызова этой функции. * *.Sample ms_samp3.c **/

#include <stdio.h> #include <dos.h> #include <conio.h> #include "sysp.h"

union REGS reg;

int ms_querr(MOUSE_STATE *state, int bottom) {

reg.x.ax = 6; reg.x.bx = bottom; int86(0x33,&reg,&reg);

state->bottoms = reg.x.ax; state->x = reg.x.cx; state->y = reg.x.dx;

return(reg.x.bx); }

Приведем пример программы, которая определяет и выводит на экран состояние мыши при нажатии и отпускании левой клавиши:


#include <dos.h> #include <stdio.h> #include <conio.h> #include "sysp.h"

union REGS reg; void main() {

int botm, i; MOUSE_STATE state; unsigned old_videomode, new_videomode; char buf[20], *bufptr;

// Определяем текущий видеорежим

reg.x.ax = 0x0f00; int86(0x10, &reg, &reg); old_videomode = reg.h.al;

// Устанавливаем новый видеорежим:

// Устанавливаем максимально допустимую длину строки

buf[0] = 10; printf("\nВведите десятичный номер видеорежима: "); bufptr = cgets(buf);

// Преобразуем введенное число к формату int

new_videomode = atoi(bufptr);

reg.h.ah = 0; reg.h.al = new_videomode; int86(0x10, &reg, &reg);

// Инициализируем мышь, определяем количество клавиш

if(!ms_init(&botm)) { printf("\nМышь не установлена"); exit(-1); }

printf("\nУстановлена мышь: "); switch (botm) { case 2: printf("двухклавишная"); break; case 3: printf("трехклавишная, системы Mouse Systems"); break; case 0: default: printf("неизвестной системы"); break; } printf("\n\nСостояние мыши:\n\n");

// Включаем курсор

ms_on();

while(!kbhit()) {

// Определяем состояние мыши при нажатии на левую клавишу

i = ms_querp(&state, 0);

// Если были нажатия на левую клавишу, выводим состояние мыши

if(i != 0) {

// Перед выводом на экран отключаем курсор, затем включаем его // снова.

ms_off(); printf("Нажатие: %2d x:%5d y:%5d\n", state.bottoms, state.x, state.y); ms_on();

}

// Выводим состояние при отпускании клавиши

i = ms_querr(&state, 0); if(i != 0) {

ms_off(); printf("Отпускание: %2d x:%5d y:%5d\n\n", state.bottoms, state.x, state.y); ms_on(); } } getch();

ms_off();

reg.h.ah = 0; reg.h.al = old_videomode; int86(0x10, &reg, &reg);

}


Определить порог удвоения скорости


На входе: AX = 0013h.

На выходе: DX = значение порога удвоения, мики в секунду.

Если вы перемещаете мышь со скоростью, превышающей порог удвоения, заданный функцией 13h, аппаратура мыши удваивает величину перемещения. Таким образом, используя медленное перемещение мыши, вы можете точно устанавливать курсор на требуемый элемент изображения. Если вам необходимо переместить курсор на значительное расстояние по экрану, вы можете увеличить скорость перемещения мыши.

При инициализации устанавливается значение порога, равное 64 микам в секунду (1/3 дюйма в секунду). Если вам надо установить это значение, вы можете при вызове функции 13h задать DX=0.



Определить размер буфера состояния драйвера


На входе: AX = 0015h.

На выходе: BX = размер буфера, требующийся для хранения состояния драйвера мыши.

Если вам требуется временно сохранить состояние драйвера мыши, а затем восстановить его, вы можете воспользоваться специально предназначенными для этого функциями 16h и 17h. Для этих функций требуется буфер, в котором будет храниться состояние драйвера. Размер буфера можно определить с помощью функции 15h.

Когда может потребоваться запоминание и восстановление состояния драйвера? Например, при использовании мыши резидентными (TSR) программами желательно сохранить состояние драйвера перед началом работы TSR-программы и восстановить его перед завершением работы TSR-программы.



Определить содержимое счетчиков перемещения


На входе: AX = 000Bh.

На выходе: CX = перемещение по горизонтали с момента последнего вызова функции;

DX = перемещение по вертикали с момента последнего вызова функции.

Функция позволяет определить относительное перемещение мыши с момента последнего вызова этой функции. Результат возвращается в указанных выше регистрах. Для измерения перемещения используется единица mickey - "мики". Один мик соответствует 0.005 дюйма (т.е. 1/200 дюйма).

Отрицательные значения перемещения означают, соответственно, движение влево и вверх, положительные - вправо и вниз.

Для преобразования миков в пиксели (точки экрана) можно использовать функцию 1Bh, которая будет описана немного позже.

Функция для определения относительного перемещения:

/** *.Name ms_querm *.Title Определение показания счетчика перемещений * *.Descr Эта функция определяет относительное перемещение * мыши с момента последнего вызова функции. * * Определенное состояние записывается в структуру * MOUSE_STATE, описанную в файле sysp.h: * * typedef struct _MOUSE_STATE_ { * unsigned bottoms; * unsigned x; * unsigned y; * } MOUSE_STATE; * * Адрес структуры передается функции в качестве * параметра. Перемещение определяется в миках * - 1/200 долях дюйма. * *.Proto MOUSE_STATE *ms_querm(MOUSE_STATE *state); * *.Params MOUSE_STATE *state - указатель на структуру, * описывающую состояние мыши. * *.Return Функция возвращает значение своего параметра. * *.Sample ms_samp7.c **/

#include <stdio.h> #include <dos.h> #include <conio.h> #include "sysp.h"

union REGS reg;

MOUSE_STATE *ms_querm(MOUSE_STATE *state) {

reg.x.ax = 0xB; int86(0x33,&reg,&reg);

state->bottoms = 0; state->x = reg.x.cx; state->y = reg.x.dx;

return(state); }

Для иллюстрации приведем программу, постоянно вызывающую эту функцию и отображающую на экране величину относительного перемещения мыши:

#include <dos.h> #include <stdio.h> #include <conio.h> #include "sysp.h"

union REGS reg; void main() {


int botm, i; MOUSE_STATE state; unsigned old_videomode, new_videomode; char buf[20], *bufptr;

// Определяем текущий видеорежим

reg.x.ax = 0x0f00; int86(0x10, &reg, &reg); old_videomode = reg.h.al;

// Устанавливаем новый видеорежим:

// Устанавливаем максимально допустимую длину строки

buf[0] = 10; printf("\nВведите десятичный номер видеорежима: "); bufptr = cgets(buf);

// Преобразуем введенное число к формату int

new_videomode = atoi(bufptr);

reg.h.ah = 0; reg.h.al = new_videomode; int86(0x10, &reg, &reg);

// Инициализируем мышь, определяем количество клавиш

if(!ms_init(&botm)) { printf("\nМышь не установлена"); exit(-1); } printf("\n\nСостояние мыши:\n\n");

// Включаем курсор

ms_on();

while(!kbhit()) { ms_querm(&state); printf("x:%5d y:%5d", state.x, state.y); for(i=0;i<15;i++) printf("\b"); } getch();

ms_off();

reg.h.ah = 0; reg.h.al = old_videomode; int86(0x10, &reg, &reg); }


Определить тип мыши


На входе: AX = 0024h.

На выходе: BH = верхний (major) номер версии драйвера;

BL = нижний (minor) номер версии драйвера;

CH = тип мыши:

1 - Bus Mouse; 2 - Serial Mouse; 3 - Inport Mouse; 4 - PS/2 Mouse; 5 - HP Mouse;

CL = номер используемого прерывания (IRQ): 0 - IBM PS/2; 2,3,4,5,7 - IBM PC.

Эта функция дает информацию о типе используемой мыши, версии драйвера мыши и об используемом номере прерывания.



Отключить драйвер мыши


На входе: AX = 001Fh.

На выходе: AX = результат выполнения:

001Fh - драйвер отключен; FFFFh - отключение невозможно;

ES:DX = вектор предыдущего драйвера мыши.

После вызова этой функции драйвер мыши полностью отключается. Вектор прерывания INT33h остается определенным, однако теперь выполняется только одна функция прерывания INT 33h - функция 21h (программный сброс мыши).

Функцию 1Fh удобно использовать для временной замены драйвера на собственную систему обслуживания мыши. Сначала вы отключаете драйвер функцией 1Fh, запоминая вектор предыдущего драйвера, возвращаемого в регистрах ES:DX. Затем устанавливаете собственную систему обслуживания мыши. Впоследствии вы восстанавливаете значение этого вектора.



Получить адрес альтернативного драйвера событий


На входе: AX = 0019h;

CX = маска событий, для которой требуется получить адрес драйвера (формат маски соответствует функции 18h).

На выходе: CX = маска событий или 0000h, если заданной маске не соответствует ни один установленный драйвер событий;

ES:DX = адрес драйвера событий, использующий заданную маску событий.

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



Прерывание для обслуживания мыши


Драйвер мыши, независимо от того, реализован он через устанавливаемый драйвер или резидентную программу, определяет обработчик прерывания INT 33h. Этот обработчик выполняет все операции, связанные с обслуживанием мыши:

сброс мыши и установка драйвера в исходное состояние;

включение/выключение курсора мыши;

установка курсора в определенное место экрана;

определение текущих координат курсора и текущего состояния клавиш;

определение координат курсора и состояния клавиш в момент нажатия на клавишу и в момент отпускания клавиши;

определение области на экране, в пределах которой может перемещаться курсор;

определение области на экране, в пределах которой курсор не будет виден;

определение формы графического и текстового курсоров;

определение величины перемещения мыши в сотых долях дюйма;

подключение к драйверу пользовательской процедуры, получающей управление при нажатии на заданную клавишу или при перемещении мыши;

запоминание и восстановление состояния драйвера;

управление эмуляцией светового пера;

управление скоростью движения курсора;

задание/определение используемой видеостраницы;

управление драйвером мыши.

Приведем подробное описание всех функций прерывния INT 33h, используемых при работе с мышью.

3.3.1.

3.3.2.

3.3.3.

3.3.4.

3.3.5.

3.3.6.

3.3.7.

3.3.8.

3.3.9.

3.3.10.

3.3.11.

3.3.12.

3.3.13.

3.3.14.

3.3.15.

3.3.16.

3.3.17.

3.3.18.

3.3.19.

3.3.20.

3.3.21.

3.3.22.

3.3.23.

3.3.24.

3.3.25.

3.3.26.

3.3.28.

3.3.29.

3.3.30.

3.3.31.

3.3.32.

3.3.33.

3.3.34.



Сбросить драйвер мыши


На входе: AX = 0021h.

На выходе: AX = результат: 0021h - драйвер сброшен успешно; FFFFh - невозможно сбросить драйвер (например, нет драйвера);

BX = количество клавиш на корпусе мыши.

Функция аналогична функции 00h, но она не выполняет аппаратного сброса оборудования мыши.



Сохранить состояние драйвера


На входе: AX = 0016h;

ES:DX = адрес буфера для записи состояния драйвера.

На выходе: Не используются.

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



Установить альтернативный драйвер событий


На входе: AX = 0018h;

CX = маска вызова: бит 0 - вызов при перемещении мыши; бит 1 - вызов при нажатии левой клавиши; бит 2 - вызов при отпускании левой клавиши; бит 3 - вызов при нажатии правой клавиши; бит 4 - вызов при отпускании правой клавиши; бит 5 - вызов при одновременном нажатии клавиши мыши и клавиши SHIFT на клавиатуре; бит 6 - вызов при одновременном нажатии клавиши мыши и клавиши CTRL на клавиатуре; бит 7 - вызов при одновременном нажатии клавиши мыши и клавиши ALT на клавиатуре; 7Fh - вызов при любом событии; 00h - отключение драйвера событий;

ES:DX = адрес (дальний) подключаемого драйвера событий.

На выходе: AX = результат установки: 0018h - драйвер успешно установлен; FFFFh - ошибка при установке драйвера.

По сравнению с функцией 0Ch эта функция обеспечивает дополнительные возможности:

проверка состояния клавиш SHIFT, CTRL, ALT во время нажатия на клавиши мыши.

возможность одновременной установки до трех драйверов событий, каждый из которых использует свою маску событий, задаваемых в регистре CX.

При попытке установить два драйвера с одной и той же маской событий функция возвращает в регистре AX код ошибки FFFFh. В этом случае вы можете использовать функцию 19h для получения адреса предыдущего установленного драйвера событий, отключить его и повторить попытку подключения своего драйвера.

Вы можете использовать функцию 18h для отключения драйвера событий, если укажете в регистрах ES:DX его адрес и зададите в регистре CX значение маски, равное 0.



Установить частоту прерываний для Inport Mouse


На входе: AX = 001Ch

BX = код скорости прерываний:

1 - нет прерываний; 2 - 30 прерываний в секунду; 4 - 50 прерываний в секунду; 8 - 100 прерываний в секунду; 16 - 200 прерываний в секунду.

На выходе: Не используются

Мышь периодически вырабатывает сигнал прерывания, по которому драйвер считывает текущее состояние мыши. С помощью функции 1Ch вы можете изменять частоту появления прерываний, но только для мыши системы Inport Mouse (Вы можете определить тип используемой мыши с помощью функции 24h).

Если используется большая частота прерываний, возрастает точность определения состояния мыши, но уменьшается общая производительность системы.



Установить чувствительность мыши


На входе: AX = 001Ah;

BX = горизонтальная чувствительность в миках на точку (пиксель);

CX = вертикальная чувствительность в миках на точку (пиксель);

DX = значение порога удвоения, мики в секунду.

На выходе: Не используются.

Эта функция является комбинацией функций 0Fh и 13h. Она позволяет одновременно устанавливать чувствительность мыши и порог удвоения скорости.



Установить драйвер событий


На входе: AX = 000Ch;

CX = маска вызова: бит 0 - вызов при перемещении мыши; бит 1 - вызов при нажатии левой клавиши; бит 2 - вызов при отпускании левой клавиши; бит 3 - вызов при нажатии правой клавиши; бит 4 - вызов при отпускании правой клавиши; бит 5 - вызов при нажатии средней клавиши; бит 6 - вызов при отпускании средней клавиши; 7Fh - вызов при любом событии; 00h - отключение драйвера событий;

ES:DX = адрес (дальний) подключаемого драйвера событий.

На выходе: Регистры не используются.

Функция позволяет программе создать свой собственный драйвер (обработчик) событий, связанных с перемещением мыши и нажатием/отпусканием клавиш мыши.

Адрес подготовленной программы-драйвера передается при вызове функции в регистровой паре ES:DX. Драйвер должен быть оформлен в виде дальней процедуры, завершающейся командой дальнего возврата RETF. Когда драйвер получает управление, в регистрах процессора содержатся следующие значения:

AX Маска вызова, такая же, как и при вызове функции0Ch.

BX Состояние клавиш мыши:

бит 0 - левая клавиша; бит 1 - правая клавиша; бит 2 - средняя клавиша.

CX Горизонтальная координата курсора мыши.

DX Вертикальная координата курсора мыши.

SI Относительное перемещение мыши по горизонтали в миках.

DI Относительное перемещение мыши по вертикали в миках.

DS Сегмент данных драйвера мыши.

Так как регистр DS при вызове драйвера событий содержит сегмент данных драйвера мыши, ваш драйвер событий должен позаботиться о правильной установке этого регистра. Ваш драйвер событий не обязан сохранять и восстанавливать содержимое регистра DS и других регистров процессора.

Отметим, что если вам необходимо отключить драйвер, выполните повторный вызов функции 0Ch, записав в регистр CX нулевое значение. Если ваша программа, устанавливающая собственный драйвер событий, завершает свою работу и передает управление MS-DOS, предварительно она обязательно должна отключить драйвер событий.

Приведем функцию, которую мы разработали для подключения драйвера событий:


/** *.Name ms_seth *.Title Установка драйвера событий * *. Descr Эта функция выполняет установку драйвера событий. * *.Proto void ms_seth(int mask, void (far *hand)()) * *.Params int mask - маска событий; * void (far *hand)() - адрес драйвера событий * *.Return Ничего * *.Sample ms_samp8.c **/

#include <dos.h> #include <conio.h>

union REGS reg; struct SREGS segregs;

void ms_seth(int mask, void (far *hand)()) {

reg.x.ax = 0x14; reg.x.cx = mask; reg.x.dx = FP_OFF(hand); segregs.es = FP_SEG(hand);

int86x(0x33,&reg,&reg,&segregs);

}

Составление программы драйвера событий имеет некоторые особенности. Драйвер событий вызывается не из программы пользователя, а из драйвера мыши. При этом сегментный регистр DS будет указывать на сегмент данных драйвера мыши, а не на сегмент данных вашей программы.

Мы подготовили образец драйвера событий, использовав язык ассемблера:

;** ;.Name ms_handl ;.Title Драйвер событий ; ;.Descr Драйвер событий вызывается драйвером мыши, ; когда происходит какое-нибудь событие из числа ; заданных при установке драйвера событий. ; Функция не должна вызываться из программы ; пользователя, ее вызывает только драйвер мыши. ; ;.Proto void far ms_handl(void); ; ;.Params Не используются ; ;.Return Ничего ; ;.Sample ms_samp8.c ;**

DOSSEG DGROUP GROUP _DATA

_DATA SEGMENT WORD PUBLIC 'DATA' _DATA ENDS

_TEXT SEGMENT WORD PUBLIC 'CODE' ASSUME cs:_TEXT, ds:DGROUP, ss:DGROUP

; Флаг вызова драйвера событий

extrn _ms_flag:word

; Внешние переменные для записи содержимого регистров

extrn _ms_bx:word extrn _ms_cx:word extrn _ms_dx:word extrn _ms_si:word extrn _ms_di:word extrn _ms_ds:word

public _ms_handl

_ms_handl proc far

mov _ms_ds, ds

; Так как на входе в драйвер событий регистр DS указывает на ; сегмент данных драйвера мыши, устанавливаем его на сегмент ; данных программы;

push ax mov ax, DGROUP mov ds, ax pop ax

mov _ms_bx, bx mov _ms_cx, cx mov _ms_dx, dx mov _ms_si, si mov _ms_di, di

; Устанавливаем флаг вызова драйвера в 1, сигнализируя ; программе о том, что произошло событие.



mov _ms_flag, 1

ret

_ms_handl endp

_TEXT ENDS

END

Этот драйвер при вызове устанавливает глобальную переменную _ms_flag в единицу, затем переписывает содержимое всех нужных регистров в соответствующие глобальные переменные. Программа, установив драйвер событий и сбросив флаг _ms_flag, может выполнять какие-либо действия (например, вывод на экран движущегося изображения), постоянно проверяя флаг _ms_flag. Как только произойдет какое-либо событие (нажатие или отпускание клавиши мыши, перемещение мыши) драйвер событий установит флаг в единицу. Программа при этом может узнать состояние мыши, прочитав содержимое глобальных переменных _ms_bx, _ms_dx, и т.д.

Этот простейший вариант использования драйвера событий иллюстрируется следующей программой:

#include <dos.h> #include <stdio.h> #include <conio.h> #include "sysp.h"

extern void far ms_handl(void);

// Флаг драйвера событий. При вызове драйвер событий // запишет в эту переменную значение 1.

unsigned ms_flag;

// Область для содержимого регистров на входе // в драйвер событий.

unsigned ms_bx; unsigned ms_cx; unsigned ms_dx; unsigned ms_si; unsigned ms_di; unsigned ms_ds;

int botm;

main () {

ms_flag=0; // Инициализируем мышь, определяем количество клавиш

if(!ms_init(&botm)) { printf("\nМышь не установлена"); exit(-1); }

// Подключаем драйвер событий, устанавливаем маску таким образом, // чтобы драйвер вызывался при нажатии на левую или правую // клавиши мыши.

ms_seth(2 | 8, ms_handl);

// Включаем курсор

ms_on();

// Ожидаем вызова драйвера событий.

for(;;) { if(ms_flag) { ms_off();

printf("\nСостояние регистров " "на входе драйвера:" "\nms_bx: %0X" "\nms_cx: %0X" "\nms_dx: %0X" "\nms_si: %0X" "\nms_di: %0X" "\nms_ds: %0X", ms_bx, ms_cx, ms_dx, ms_si, ms_di, ms_ds);

printf("\nНажмите любую клавишу..."); getch(); exit(0); } } }

Драйвер событий может также организовать очередь событий, записывая в эту очередь состояние мыши на момент появления события и время появления события.

Прикладная программа будет затем извлекать события из очереди и анализировать их.


Установить курсор


На входе: AX = 0004h;

CX = устанавливаемая координата X (по горизонтали);

DX = устанавливаемая координата Y (по вертикали).

На выходе: Регистры не используются.

Обычно курсор мыши устанавливает не программа, а оператор. Однако с помощью функции 04h программа тоже может установить курсор в заданную позицию. Для текстового режима устанавливаемые номера строки и столбца должны быть умножены на 8.

Если программа пытается установить курсор в область, где курсор невидим (эта область задается функцией 10h), то она сможет это сделать. Курсор при этом исчезнет с экрана, что не всегда желательно.

Если при помощи функций 07h или 08h область для перемещения курсора была ограничена, то при попытке установить курсор за границу этой области, он будет установлен в точку, которая находится внутри границы и находится на минимальном расстоянии от точки, заданной при вызове функции.

Функция для установки курсора:

/** *.Name ms_setcr *.Title Установка курсора в заданную точку * *.Descr Эта функция выполняет установку курсора мыши * в точку, заданную координатами X и Y. * *.Proto void ms_setcr(int x, int y) * *.Params int x - горизонтальная координата курсора; * int y - вертикальная координата курсора. * *.Return Ничего * *.Sample ms_samp2.c **/

#include <dos.h> #include <conio.h>

union REGS reg;

void ms_setcr(int x, int y) {

reg.x.ax = 4; reg.x.cx = x; reg.x.dx = y;

int86(0x33,&reg,&reg); }

Приведем пример простой программы, которая устанавливает курсор в левый верхний угол экрана:

#include <stdio.h> #include <conio.h> #include "sysp.h"

void main() {

int botm;

// Инициализируем мышь

if(!ms_init(&botm)) { printf("\nМышь не установлена"); exit(-1); }

// Включаем курсор и ожидаем нажатия на клавишу

printf("\n\nКурсор мыши включен, " "для выключения нажмите любую клавишу");

ms_on();

// Устанавливаем курсор в левый верхний угол экрана

ms_setcr(0,0);

getch();

// Выключаем курсор

ms_off(); }



Установить номер видеостраницы


На входе: AX = 001Dh;

BX = номер видеостраницы.

На выходе: Не используются.

Данная функция задает номер видеостраницы, на которой будет отображаться курсор мыши. По умолчанию для отображения используется страница 0. Подробнее о видеостраницах вы узнаете из следующего тома книги.



Установить область исключения для курсора


На входе: AX = 0010h;

CX, DX = координаты (X, Y) верхнего левого угла области исключения;

SI, DI = координаты (X, Y) нижнего правого угла области исключения.

На выходе: Регистры не используются.

Функция позволяет задать на экране прямоугольную область, в которой автоматически выключается изображение курсора мыши - область исключения. Эта область отменяется функциями 01h (включить курсор мыши) и 00h (инициализация).

Оператор может поместить курсор мыши в область исключения, при этом изображение курсора пропадет.

Основное назначение этой функции - предоставить программе возможность изменять содержимое области экрана не выключая изображение курсора. Недостаток функции - вы можете "потерять" курсор мыши, если он случайно окажется в области исключения.



Включить эмуляцию светового пера


На входе: AX = 000Dh.

На выходе: Регистры не используются.

Если ваша программа использует световое перо (например, она написана на языке Бейсик и вызывает функцию PEN), вы можете заменить световое перо на мышь. После включения режима эмуляции драйвер запоминает координаты курсора при нажатии на клавиши мыши. Эти координаты могут быть впоследствии считаны функцией PEN или функцией 04h прерывания INT 10h, предназначенной для работы со световым пером.



Включить курсор мыши


На входе: AX = 0001h.

На выходе: регистры не используются.

Для управления видимостью курсора драйвер мыши использует внутренний счетчик. Этот счетчик можно увеличивать, вызывая функцию 01h прерывания INT33h, или уменьшать при помощи функции 02h этого же прерывания.

После инициализации драйвера функцией 00h счетчик устанавливается равным -1. После первого вызова функции 01h счетчик становится равным 0. При этом курсор мыши становится видимым, его можно перемещать по экрану.

Если счетчик равен 0, то следующие вызовы функции 01h игнорируются драйвером. Для того, чтобы погасить курсор, используйте функцию 02h, которая при вызове уменьшает каждый раз содержимое счетчика на единицу.

Функция 01h сбрасывает область, в которой курсор не отображается (если такая область была ранее установлена функцией 10h).

Вызов функции из Си:

/** *.Name ms_on *.Title Включение курсора мыши * *.Descr Эта функция увеличивает на 1 индикатор * уровня видимости курсора. Если индикатор * равен нулю, курсор появляется на экране. * Значение индикатора не превышает * нуля даже при многократных вызовах этой функции. * *.Proto void ms_on(void) * *.Params Не используются * *.Return Ничего * *.Sample ms_sampl1.c **/

#include <dos.h> #include <conio.h>

union REGS reg;

void ms_on(void) {

reg.x.ax = 1; int86(0x33,&reg,&reg); }



Восстановить драйвер мыши


На входе: AX = 0020h.

На выходе: Не используются.

Функция восстанавливает связь между мышью и драйвером, отключенную вызовом функции 1Fh.



Восстановить состояние драйвера


На входе: AX = 0017h;

ES:DX = адрес буфера, содержащего состояние драйвера.

На выходе: Не используются.

Функция позволяет восстановить состояние драйвера из буфера, в который оно было записано при помощи функции 16h.



Выключить эмуляцию светового пера


На входе: AX = 000Eh.

На выходе: Регистры не используются.

Эта функция выключает режим эмуляции светового пера.



Выключить курсор мыши


На входе: AX = 0002h.

На выходе: регистры не используются.

Эта функция уменьшает на единицу счетчик видимости курсора. Если содержимое счетчика становится равным -1, изображение курсора пропадает с экрана.

Если ваша программа использует для вывода на экран метод прямой записи в экранную память, перед обновлением содержимого экрана необходимо погасить курсор, а после завершения обновления высветить его опять. Это связано с тем, что драйвер мыши "помнит" старое значение атрибута символа, на который указывал курсор до обновления содержимого видеопамяти. Вы изменили атрибут, записав новое значение непосредственно в экранную память. Теперь, если установить курсор мыши на другой символ, изображение старого символа будет испорчено - появится прямоугольник (как бы еще одно изображение курсора мыши).

Вызов функции:

/** *.Name ms_off *.Title Выключение курсора мыши * *.Descr Эта функция уменьшает на 1 индикатор уровня * видимости курсора. После вызова этой функции * курсор, если он был на экране, исчезает. * Многократные обращения будут последовательно * уменьшать индикатор и затем потребуют * многократных вызовов функции ms_on для * его включения. * *.Proto void ms_off(void) * *.Params Не используются * *.Return Ничего * *.Sample ms_sampl1.c **/

#include <dos.h> #include <conio.h>

union REGS reg;

void ms_off(void) {

reg.x.ax = 2; int86(0x33,&reg,&reg);

}

Приведем программу, демонстрирующую применение описанных выше функций. Программа инициализирует мышь, делает видимым курсор мыши и "прячет" курсор после нажатия на любую клавишу:

#include <stdio.h> #include <conio.h> #include "sysp.h"

void main() {

int botm;

// Инициализируем мышь, определяем количество клавиш

if(!ms_init(&botm)) { printf("\nМышь не установлена"); exit(-1); }

printf("\nУстановлена мышь: "); switch (botm) { case 2: printf("двухклавишная"); break; case 3: printf("трехклавишная, системы Mouse Systems"); break; case 0: default: printf("неизвестной системы"); break; }

// Включаем курсор и ожидаем нажатия на клавишу

printf("\n\nКурсор мыши включен, " "для выключения нажмите любую клавишу");

ms_on();

getch();

// Выключаем курсор

ms_off(); printf("\nКурсор выключен, " "для завершения нажмите любую клавишу");

getch();

}



Задать диапазон движения курсора по горизонтали


На входе: AX = 0007h;

CX = минимальная координата X (по горизонтали);

DX = максимальная координата X.

На выходе: Регистры не используются.

Данная функция позволяет ограничить диапазон перемещений курсора мыши по горизонтали.

Вызов функции:

/** *.Name ms_rangx *.Title Задание диапазона перемещения курсора по горизонтали * *.Descr Эта функция ограничивает область перемещения * курсора по горизонтали в пределах [xmin, xmax]. * *.Proto void ms_rangx(int xmin, int xmax) * *.Params int xmin - минимальная координата X курсора; * int xmax - максимальная координата X курсора. * *.Return Ничего * *.Sample ms_samp4.c **/

#include <dos.h> #include <conio.h>

union REGS reg;

void ms_rangx(int xmin, int xmax) {

reg.x.ax = 7; reg.x.cx = xmin; reg.x.dx = xmax;

int86(0x33,&reg,&reg); }



Задать диапазон движения курсора по вертикали


На входе: AX = 0008h;

CX = минимальная координата Y (по вертикали);

DX = максимальная координата Y.

На выходе: Регистры не используются.

Данная функция позволяет ограничить диапазон перемещений курсора мыши по вертикали.

Вызов функции:

/** *.Name ms_rangy *.Title Задание диапазона перемещения курсора по вертикали * *.Descr Эта функция ограничивает область перемещения * курсора по вертикали в пределах [ymin, ymax]. * *.Proto void ms_rangy(int ymin, int ymax) * *.Params int ymin - минимальная координата Y курсора; * int ymax - максимальная координата Y курсора. * *.Return Ничего * *.Sample ms_samp4.c **/

#include <dos.h> #include <conio.h>

union REGS reg;

void ms_rangy(int ymin, int ymax) {

reg.x.ax = 8; reg.x.cx = ymin; reg.x.dx = ymax;

int86(0x33,&reg,&reg);

}

Приведем текст программы, которая ограничивает диапазон перемещений курсора мыши по экрану:

#include <stdio.h> #include <conio.h> #include "sysp.h"

void main() {

int botm;

// Инициализируем мышь

if(!ms_init(&botm)) { printf("\nМышь не установлена"); exit(-1); }

// Включаем курсор и ожидаем нажатия на клавишу

printf("\n\nКурсор мыши включен, для выключения" " нажмите любую клавишу");

ms_on();

// Задаем границы, в которых должен перемещаться курсор

ms_rangx(20, 100); ms_rangy(50, 100);

getch();

// Выключаем курсор

ms_off(); }



Задать форму курсора в графическом режиме


На входе: AX = 0009h;

BX = номер позиции точки-указателя графического курсора (от -16 до 16);

CX = номер строки точки-указателя (от -16 до 16);

ES:DX = указатель на битовое изображение курсора.

На выходе: Регистры не используются.

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

Регистры ES:DX указывают на область длинной 64 байта. Эта область состоит из двух массивов длиной по 32 байта. Первый массив представляет из себя логическую маску размером 16х16 битов, которая накладывается на участок видеопамяти с использованием логической операции "И". Второй массив - тоже маска размером 16х16, но она накладывается с использованием логической операции "Исключающее ИЛИ", инвертируя отдельные точки изображения.

Номера позиции и строки точки-указателя, устанавливаемые по умолчанию, равны 0 (BX=CX=0). Это соответствует верхней левой точке в изображении курсора. Значения BX=CX=15 соответствуют нижней правой точке.

Приведем исходный текст функции для изменения формы курсора из программы, составленной на языке Си:

/** *.Name ms_gform *.Title Задание формы курсора в графическом режиме * *.Descr Эта функция определяет форму курсора мыши * для графического режима. Дополнительно можно * задать положение точки-указателя, которая * соответствует координатам курсора. * *.Proto void ms_gform(int xt, int yt, char _far *form) * *.Params int xt - позиция точки указателя; * int yt - номер строки точки-указателя; * char *form - указатель на массив, описывающий * курсор. * *.Return Ничего * *.Sample ms_samp5.c **/

#include <dos.h> #include <conio.h>

union REGS reg; struct SREGS segregs;

void ms_gform(int xt, int yt, char _far *form) {

reg.x.ax = 9; reg.x.bx = xt; reg.x.cx = yt; reg.x.dx = FP_OFF(form); segregs.es = FP_SEG(form);

int86x(0x33,&reg,&reg,&segregs); }


Мы подготовили пример программы, изменяющий форму курсора в графическом режиме:

#include <dos.h> #include <stdio.h> #include <conio.h> #include "sysp.h"

union REGS reg;

char form[64] = {

// Массив маски по "И"

255,255, 255,255, 255,255, 255,255, 255,255, 255,255, 255,255, 255,255, 255,255, 255,255, 255,255, 255,255, 255,255, 255,255, 255,255, 255,255,

// Массив маски по "Исключающее ИЛИ"

127,254, 127,254, 127,254, 127,254, 127,254, 127,254, 127,254, 0,0, 0,0, 127,254, 127,254, 127,254, 127,254, 127,254, 127,254, 127,254, };

void main() {

int botm, i; MOUSE_STATE state; unsigned old_videomode, new_videomode; char buf[20], *bufptr;

// Определяем текущий видеорежим

reg.x.ax = 0x0f00; int86(0x10, &reg, &reg); old_videomode = reg.h.al;

// Устанавливаем новый видеорежим:

// Устанавливаем максимально допустимую длину строки

buf[0] = 10; printf("\nВведите десятичный номер видеорежима: "); bufptr = cgets(buf);

// Преобразуем введенное число к формату int

new_videomode = atoi(bufptr);

reg.h.ah = 0; reg.h.al = new_videomode; int86(0x10, &reg, &reg);

// Инициализируем мышь

if(!ms_init(&botm)) { printf("\nМышь не установлена"); exit(-1); }

// Задаем новую форму для курсора мыши

ms_gform(0,0, &form[0]);

// Включаем курсор

ms_on();

getch();

ms_off();

reg.h.ah = 0; reg.h.al = old_videomode; int86(0x10, &reg, &reg); }


Задать форму курсора в текстовом режиме


На входе: AX = 000Ah;

BX = тип курсора: 0 - определяемый программно; 1 - определяемый аппаратно;

CX = маска экрана (для BX=0) или начальная строка курсора (для BX=1);

DX = маска курсора (для BX=0) или конечная строка курсора (для BX=1).

На выходе: Регистры не используются.

С помощью этой функции программа может изменять форму курсора мыши в текстовом режиме.

В зависимости от содержимого регистра BX драйвер мыши использует курсор, определяемый аппаратными средствами, либо курсор, определяемый программно. По умолчанию используется "программный курсор", который отображается в виде символа с инвертированным значением атрибута. Курсор, сформированный аппаратными средствами, выглядит аналогично обычному текстовому курсору, его форма - прямоугольник. Размер этого прямоугольника можно задавать при помощи регистров CX и DX.

Для курсора, определяемого программно, вначале выполняется операция логического "И" над содержимым видеопамяти в том месте, куда указывает курсор, и маской экрана. Затем выполняется операция "Исключающее ИЛИ" с маской курсора.

Младший байт масок соответствует ASCII-коду символа, старший - это байт атрибута символа.

Значения, используемые по умолчанию - BX=7700h, CX=FFFFh.

Если вам надо изменить цвет курсора, не меняя его форму, задайте CX=00FFh, BX=xx00h, где xx определяет цвет (смотри описание формата байта атрибутов в третьем томе книги).

Приведем функцию для изменения формы курсора в текстовом примере и программу, создающую курсор в виде вертикальной стрелки, направленной вверх на синем фоне:

/** *.Name ms_tform *.Title Задание формы курсора в текстовом режиме * *.Descr Эта функция определяет форму курсора мыши для * текстового режима. * *.Proto void ms_tform(int type, int mask1, int mask2) * *.Params int type - тип курсора: * 0 - программный, 1 - аппаратный; * int mask1 - AND-маска экрана * или первая строка аппаратного курсора * int mask2 - XOR-маска курсора * или последняя строка аппаратного курсора * *.Return Ничего * *.Sample ms_samp6.c **/

#include <dos.h> #include <conio.h>

union REGS reg;

void ms_tform(int type, int mask1, int mask2) {

reg.x.ax = 0xA; reg.x.bx = type; reg.x.cx = mask1; reg.x.dx = mask2;

int86(0x33,&reg,&reg);

} #include <dos.h> #include <stdio.h> #include <conio.h> #include "sysp.h"

union REGS reg;

void main() {

int botm, i; MOUSE_STATE state;

// Инициализируем мышь

if(!ms_init(&botm)) { printf("\nМышь не установлена"); exit(-1); }

// Задаем новую форму для курсора мыши

ms_tform(0, 0, 0x1418);

// Включаем курсор

ms_on();

getch();

ms_off(); }



Задать скорость перемещения курсора мыши


На входе: AX = 000Fh;

CX = количество миков на 8 точек по горизонтали;

DX = количество миков на 8 точек по вертикали.

На выходе: Регистры не используются.

Функция определяет "чувствительность" мыши к перемещению по поверхности стола, т.е. устанавливает соответствие между величиной перемещения мыши по столу и величиной перемещения курсора мыши по экрану.

При инициализации драйвера мыши используются следующие значения: CX=8, DX=16.



Задать увеличенный графический курсор (PC MOUSE)


На входе: AX = 0012h;

BH = ширина курсора в словах;

CH = количество строк в изображении курсора;

BL = номер позиции точки-указателя графического курсора (от -16 до 16);

CL = номер строки точки-указателя (от -16 до 16);

ES:DX = указатель на битовое изображение курсора.

На выходе: Регистры не используются.

Эта функция позволяет задать увеличенный по размеру курсор мыши, но она определена только для мыши системы PCMOUSE.



Заменить драйвер событий


На входе: AX = 0014h;

CX = маска вызова: бит 0 - вызов при перемещении мыши; бит 1 - вызов при нажатии левой клавиши; бит 2 - вызов при отпускании левой клавиши; бит 3 - вызов при нажатии правой клавиши; бит 4 - вызов при отпускании правой клавиши; бит 5 - вызов при нажатии средней клавиши; бит 6 - вызов при отпускании средней клавиши; 7Fh - вызов при любом событии; 00h - отключение драйвера событий;

ES:DX = адрес (дальний) подключаемого драйвера событий.

На выходе: CX = маска предыдущего драйвера событий;

ES:DX = адрес предыдущего драйвера событий (т.е. адрес заменяемого драйвера событий).

Функция аналогична функции 0Ch, однако ее основное назначение - временная замена драйвера событий. Например, подпрограмма в начале своей работы может установить свой драйвер событий, а перед завершением - активизировать драйвер, использовавшийся ранее.



ЧАСЫ РЕАЛЬНОГО ВРЕМЕНИ


4.1.

4.2.

4.3.

4.4.

4.5.

4.6.

4.7.

Компьютеры IBM AT и PS/2 оснащены часами реального времени. Эти часы питаются от аккумулятора, поэтому их показания не пропадают при выключении компьютера.

Доступ к часам реального времени возможен либо через ячейки КМОП-памяти, либо через специальные функции BIOS (что более предпочтительно с точки зрения независимости работы программы от особенностей аппаратуры).

Использование регистров КМОП-памяти часами реального времени приведено в таблице:

Регистр Назначение
0 счетчик секунд
1 регистр секунд будильника
2 счетчик минут
3 регистр минут будильника
4 счетчик часов
5 регистр часов будильника
6 счетчик дней недели (1 - воскресенье)
7 счетчик дней месяца
8 счетчик месяцев
9 счетчик лет (последние две цифры текущего года)

0aH регистр состояния A

7 6 5 4 3 2 1 0 T-T-T-T-T-T-T-¬ ¦ ¦ ¦ ¦ LT+T+-+T+T+-+-+T- ¦ L=T=- L=====¦= переключатель скорости (установлен в 0110) ¦ L=========== 22-разрядный делитель (установлен в 010) L=============== Флаг обновления, 0 означает готовность данных для чтения.

0bH регистр состояния B

7 6 5 4 3 2 1 0 T-T-T-T-T-T-T-¬ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ LT+T+T+T+T+T+T+T- ¦ ¦ ¦ ¦ ¦ ¦ ¦ L= 1 - использование летнего времени ¦ ¦ ¦ ¦ ¦ ¦ ¦ (daylight savings enable); ¦ ¦ ¦ ¦ ¦ ¦ ¦ 0 - стандартное время (установлен в 0) ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ L=== 12 или 24-часовой режим. 0 - 12-часовой ¦ ¦ ¦ ¦ ¦ ¦ режим (установлен в 1) ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ L===== режим данных BCD. 1 - двоичный, 0 - BCD. ¦ ¦ ¦ ¦ ¦ (установлен в 0) ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ L======= разрешение прямоугольной волны. ¦ ¦ ¦ ¦ 1 - включение прямоугольной волны. ¦ ¦ ¦ ¦ (установлен в 0) ¦ ¦ ¦ ¦ ¦ ¦ ¦ L========= разрешение прерывания по окончанию ¦ ¦ ¦ изменения данных (установлен в 0) ¦ ¦ ¦ ¦ ¦ L=========== разрешение прерывания будильника ¦ ¦ (установлен в 0) ¦ ¦ ¦ L============= разрешение периодических прерываний ¦ (установлен в 0) ¦ L=============== флаг обновления, 0 означает готовность данных для чтения КМОП-памяти.


0cH регистр состояния C. Биты состояния прерывания, их можно только читать.

0dH регистр состояния D. Если бит 7 равен 0, это означает, что разрядился аккумулятор, питающий КМОП-память.

Часы реального времени вырабатывают аппаратное прерывание IRQ8, которому соответствует прерывание с номером 70h. Это прерывание может вырабатываться по трем причинам:

Прерывание по окончанию изменения данных. Вырабатывается при установленном в 1 бите 4 регистра состояния B после каждого обновления регистров часов.

Прерывание будильника вырабатывается при совпадении регистров часов и регистров будильника и при установленном в 1 бите 5 регистра состояний B.

Периодическое прерывание вырабатывается с интервалом примерно 1 миллисекунда при установленном в 1 бите 6 регистра состояний B.

При срабатывании будильника BIOS вырабатывает прерывание INT 4Ah. Программа может подготовить собственный обработчик для этого прерывания.

Для работы с часами реального времени вы можете обращаться непосредственно к перечисленным выше ячейкам КМОП-памяти, используя порты 70h и 71h. Однако лучше всего воспользоваться функциями 2 - 7 прерывания 1Ah, описанными ниже.


Использование часов реального времени


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

Так как установленное время срабатывания будильника хранится в КМОП-памяти, питающейся от аккумулятора, будильник не будет сброшен при случайном выключении компьютера.

Для работы с часами реального времени мы подготовили следующую функцию:

/** *.Name timer *.Title Работа с часами реального времени * *.Descr Эта функция предназначена для обслуживания * системных часов реального времени через * прерывание INT 1Ah. * *.Proto int timer(char fn, SYSTIMER *tm) * *.Params char fn - выполняемая функция: * * RTC_GET_TIME - прочитать показания часов; * RTC_SET_TIME - установить часы; * RTC_GET_DATE - прочитать дату; * RTC_SET_DATE - установить дату; * RTC_SET_ALARM - установить будильник; * RTC_CLEAR_ALARM - сбросить будильник. * * Все эти константы описаны в файле sysp.h * * SYSTIMER tm - структура, содержащая данные * для установки часов или * показания часов: * * typedef struct _SYSTIMER_ { * * char hour; // часы * char min; // минуты * char sec; // секунды * unsigned year; // год * char month; // месяц * char day; // число * char daylight_savings; // флаг * // использование * // летнего времени * // (для включения режима * // должен быть равен 1) * * } SYSTIMER; * *.Return 0 - успешное выполнение функции; * -1 - часы реального времени отсутствуют * в компьютере; * *.Sample setalarm.c **/

#include <stdio.h> #include <dos.h> #include "sysp.h"

union REGS reg;

int timer(char fn, SYSTIMER *tm) {

reg.h.ah = fn;

switch (fn) {

case RTC_SET_TIME:

reg.h.ch = tm->hour; reg.h.cl = tm->min; reg.h.dh = tm->sec; reg.h.dl = tm->daylight_savings;

break;

case RTC_SET_DATE:

reg.x.cx = tm->year; reg.h.dh = tm->month; reg.h.dl = tm->day;

break;

case RTC_SET_ALARM:

reg.h.ch = tm->hour; reg.h.cl = tm->min; reg.h.dh = tm->sec;


break;

}

int86(0x1a,&reg,&reg);

if(reg.x.cflag == 1) return(-1);

switch (fn) {

case RTC_GET_TIME:

tm->hour = reg.h.ch; tm->min = reg.h.cl; tm->sec = reg.h.dh;

break;

case RTC_GET_DATE:

tm->year = reg.x.cx; tm->month = reg.h.dh; tm->day = reg.h.dl;

break; }

return(0); }

Эта функция выполняет все виды обслуживания часов реального времени, которые поддерживаются BIOS.

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

Перед установкой будильника программа подключает свой обработчик прерывания 4Ah. Это прерывание вызывается при срабатывании будильника. Перед завершением работы программа сбрасывает будильник и восстанавливает вектор прерывания 4Ah.

#include <stdio.h> #include <stdlib.h> #include <dos.h> #include "sysp.h"

// Выключаем проверку стека и указателей

#pragma check_stack( off ) #pragma check_pointer( off )

// Макро для выдачи звукового сигнала

#define BEEP() _asm { \ _asm mov bx,0 \ _asm mov ax, 0E07h \ _asm int 10h \ }

void main(void);

// Описание программы-обработчика прерывания // будильника

void _interrupt _far alarm(void);

// Переменная для хранения старого // вектора будильника

void (_interrupt _far *old_4a)(void);

void main(void) {

char *month_to_text[] = {

"январь", "февраль", "март", "апрель", "май", "июнь", "июль", "август", "сентябрь", "октябрь", "ноябрь", "декабрь"

};

SYSTIMER tmr;

// Определяем текущие дату и время

timer(RTC_GET_DATE, &tmr); timer(RTC_GET_TIME, &tmr);

// Выводим дату и время на экран

printf("\nСейчас %d год, %s, %d число." "\n", bcd2bin(&(tmr.year)), month_to_text[bcd1bin(&(tmr.month)) - 1], bcd1bin(&(tmr.day)));

printf("\nВремя - %02.2d:%02.2d:%02.2d" "\n", bcd1bin(&(tmr.hour)), bcd1bin(&(tmr.min)), bcd1bin(&(tmr.sec)));



// Для установки будильника увеличиваем // счетчик минут на единицу. Для упрощения // программы мы не проверяем счетчик на // переполнение, поэтому если текущее // значение счетчика минут равно 59, // будильник не сработает. Вы можете сами // немного усовершенствовать программу для // проверки переполнения.

bin1bcd(bcd1bin(&(tmr.min)) + 1, &(tmr.min));

// Выводим на экран время, когда сработает // будильник.

printf("\nВремя срабатывания будильника" "- %02.2d:%02.2d:%02.2d" "\n", bcd1bin(&(tmr.hour)), bcd1bin(&(tmr.min)), bcd1bin(&(tmr.sec)));

// Подключаем свой обработчик прерывания // будильника, старое значение вектора // 0x4a сохраняем

old_4a = _dos_getvect(0x4a);

_dos_setvect(0x4a, alarm);

// Устанавливаем будильник

timer(RTC_SET_ALARM, &tmr);

printf("\nБудильник установлен. Для отмены " "и завершения программы нажмите" "\nлюбую клавишу...");

getch();

// Сбрасываем будильник и восстанавливаем // вектор прерывания будильника

timer(RTC_CLEAR_ALARM, &tmr);

_dos_setvect(0x4a, old_4a);

exit(0); }

// ---------------------------------- // Преобразование однобайтового // числа из формата BCD в двоичный // формат. // ----------------------------------

int bcd1bin(char *bcd) {

return( ((*bcd) & 0x0f) + 10 * (((*bcd) & 0xf0) >> 4) );

}

// ---------------------------------- // Преобразование двухбайтового // числа из формата BCD в двоичный // формат. // ----------------------------------

int bcd2bin(char *bcd) {

return( bcd1bin(bcd) + 100 * bcd1bin(bcd + 1) );

}

// ---------------------------------- // Преобразование однобайтового // числа из двоичного формата // формат BCD. // ----------------------------------

int bin1bcd(int bin, char *bcd) {

int i;

i = bin / 10;

*bcd = (i << 4) + (bin - (i * 10));

}

// ---------------------------------- // Программа получает управление // при срабатывании будильника. // Ее назначение - выдать звуковой сигнал. // ----------------------------------

void _interrupt _far alarm(void) {

BEEP(); BEEP(); BEEP(); BEEP(); BEEP(); BEEP(); BEEP();

}


Прочитать дату из часов реального времени


На входе: AH = 04h.

На выходе: CH = столетие в BCD-формате ;

CL = год в BCD-формате (например, CX=1991h означает 1991 год);

DH = месяц в BCD-формате;

DL = число в BCD-формате;

CF = CY = 1, если часы реального времени не установлены.



Прочитать показания часов реального времени


На входе: AH = 02h.

На выходе: CH = часы в BCD-формате (например, 13h означает 13 часов);

CL = минуты в BCD-формате;

DH = секунды в BCD-формате;

CF = CY = 1, если часы реального времени не установлены.



Сброс будильника


На входе: AH = 07h.

На выходе: не используются.

Эта функция позволяет сбросить будильник, например, для того чтобы установить его заново на другое время.



Установить будильник


На входе: AH = 06h;

CH = часы в BCD-формате;

CL = минуты в BCD-формате;

DH = секунды в BCD-формате.

На выходе: CF = CY = 1, если часы реального времени не установлены.

Эта функция позволяет установить будильник на заданное время. Когда будильник "зазвенит", будет вызвано прерывание INT4Ah (это прерывание вызывают модули BIOS после прихода аппаратного прерывания от часов реального времени IRQ8, т.е. прерывания с номером 70h). Программа, использующая функцию будильника, должна подготовить обработчик прерывания INT 4Ah, завершающий свою работу выполнением команды IRET.

Программа может установить только один будильник.



Установить часы реального времени


На входе: AH = 03h;

CH = часы в BCD-формате (например, 13h означает 13 часов);

CL = минуты в BCD-формате;

DH = секунды в BCD-формате;

DL = 1, если необходимо использовать летнее время (daylight savings time option).

На выходе: не используются.



Установить дату в часах реального времени


На входе: AH = 05h;

CH = столетие в BCD-формате ;

CL = год в BCD-формате (например, CX=1991h означает 1991 год);

DH = месяц в BCD-формате;

DL = число в BCD-формате;

На выходе: не используются.



Генерация случайных чисел


Последнее, что мы сделаем с таймером - научимся получать от него случайные числа.

Для генерации случайных чисел лучше всего использовать канал 2 в режиме 3. В регистр счетчика канала мы занесем значение, равное диапазону нужных нам случайных чисел. Например, если мы запишем в регистр число 80 и запустим канал таймера, получаемые случайные числа будут лежать в диапазоне от 0 до 79.

Функция rnd_set() предназначена для начальной инициализации генератора случайных чисел:

/** *.Name rnd_set *.Title Инициализация генератора случайных чисел * *.Descr Эта функция инициализирует канал 2 таймера * для использования в качестве генератора * случайных чисел. * *.Proto void rnd_set(int bound) * *.Params int bound - верхняя граница для генерируемых * случайных чисел. * *.Return Ничего * *.Sample random.c **/

#include <stdio.h> #include <conio.h>

void rnd_set(int bound) {

// Устанавливаем режим 3 для второго канала таймера

outp(0x43, 0xb6);

// Загружаем регистр счетчика таймера - сначала // младший, затем старший байты

outp(0x42, bound & 0x00ff);

outp(0x42, (bound &0xff00) >> 8);

// Разрешаем работу канала

outp(0x61, inp(0x61) | 1); }

Через некоторое время после инициализации с помощью функции rnd_get() можно получить готовое случайное число:

/** *.Name rnd_get *.Title Получение от таймера случайного числа * *.Descr Эта функция получает случайное число от * таймера, который был предварительно * проинициализирован функцией rnd_set. * *.Proto int rnd_get(void) * *.Params Отсутствуют. * *.Return Случайное число в диапазоне от 0, до * уменьшенного на 1 значения, заданного в * качестве параметра функции rnd_set(). * *.Sample random.c **/

#include <stdio.h> #include <conio.h>

int rnd_get(void) {

int i;

// Выдаем команду CLC для фиксирования // текущего значения регистра канала 2 таймера

outp(0x43, 0x86);

// Вводим младший и старший байты счетчика

i = inp(0x42); i = (inp(0x42) << 8) + i;

return(i); }

Для иллюстрации использования этих функций мы подготовили следующую программу:


#include <stdio.h> #include "sysp.h"

void main(void); void main(void) {

int i, j;

printf("\nГенератор случайных чисел." "\nНажмите любую клавишу," "\nдля завершения работы нажмите CTRL-C." "\n");

for(;;) {

// Устанавливаем диапазон генерации случайных // чисел и инициализируем таймер

rnd_set(80);

// Ожидаем нажатия клавиши.

getch();

// После нажатия на клавишу получаем // случайное число

j = rnd_get();

// Выводим на экран строку символов "-", // длина которой равна полученному случайному // числу.

for(i=0; i < j; i++) putchar(219);

printf("\n"); }

}

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

Генератор случайных чисел. Нажмите любую клавишу, для завершения работы нажмите CTRL-C.

-------------------------- ------------------------------------------------ ------ -------------------------------------- ------------------ ------------------------------------------------------------------ ------------------------ ------------------------------------------------------ ------------------------------------------------------ -- ---------------------------------- -------------- -------------- ------------------------------------------------------------------------ ------------------------------ ---------------------------------------------------------- -------------------------------- ------------------------------------------------------------------ ---------------------------------------------------- ------------------------------------------ ---------------------------------------------------------- ---------------------------------------- ----------------------


и 8254 состоят из трех


Таймеры 8253 и 8254 состоят из трех независимых каналов, или счетчиков. Каждый канал содержит регистры:

состояния канала RS (8 разрядов);

управляющего слова RSW (8 разрядов);

буферный регистр OL (16 разрядов);

регистр счетчика CE (16 разрядов);

регистр констант пересчета CR (16 разрядов).

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

GATE - управляющий вход;

CLOCK - вход тактовой частоты;

OUT - выход таймера.

Регистр счетчика CE работает в режиме вычитания. Его содержимое уменьшается по заднему фронту сигнала CLOCK при условии, что на вход GATE установлен уровень логической 1.

В зависимости от режима работы таймера при достижении счетчиком CE нуля тем или иным образом изменяется выходной сигнал OUT.

Буферный регистр OL предназначен для запоминания текущего содержимого регистра счетчика CE без остановки процесса счета. После запоминания буферный регистр доступен программе для чтения.

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

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

Упрощенная схема взаимодействия регистров канала приведена на рисунке:

-----¬ -----¬ ----------------¬ ¦ CR +-->--+ CE +->-+ +-<--- GATE L----- L-T--- ¦ Управляющая ¦ ¦ ¦ логика канала +-<--- CLOCK ¦ ¦ ¦ --+--¬ ¦ +->--- OUT ¦ OL ¦ ¦ ¦ L----- L----------------

Возможны шесть режимов работы таймера. Они разделяются на три типа:

Режимы 0, 4 - однократное выполнение функций.

Режимы 1, 5 - работа с перезапуском.

Режимы 2, 3 - работа с автозагрузкой.

В режиме однократного выполнения функций перед началом счета содержимое регистра констант пересчета CR переписывается в регистр счетчика CE по сигналу CLOCK, если сигнал GATE установлен в 1. В дальнейшем содержимое регистра CE уменьшается по мере прихода импульсов CLOCK. Процесс счета можно приостановить, если подать на вход GATE уровень логического 0. Если затем на вход GATE подать 1, счет будет продолжен дальше. Для повторения выполнения функции необходима новая загрузка регистра CR, т.е. повторное программирование таймера.



При работе с перезапуском не требуется повторного программирования таймера для выполнения той же функции. По фронту сигнала GATE значение константы из регистра CR вновь переписывается в регистр CE, даже если текущая операция не была завершена.

В режиме автозагрузки регистр CR автоматически переписывается в регистр CE после завершения счета. Сигнал на выходе OUT появляется только при наличии на входе GATE уровня логической 1. Этот режим используется для создания программируемых импульсных генераторов и генераторов прямоугольных импульсов (меандра).

В компьютере IBMPC/XT/AT/PS2 задействованы все три канала таймера.

Канал 0 используется в системных часах времени суток (не следует путать с часами реального времени, реализованными на другой микросхеме). Этот канал работает в режиме 3 и используется как генератор импульсов с частотой примерно 18.2 Гц. Именно эти импульсы вызывают аппаратное прерывание INT 8h.

Канал 1 используется для регенерации содержимого динамической памяти компьютера. Выход канала OUT используется для запроса к каналу прямого доступа DMA, который и выполняет обновление содержимого памяти. Вам не следует перепрограммировать этот канал, так как это может привести к нарушениям в работе основной оперативной памяти компьютера.

Канал 2 подключен к громкоговорителю компьютера и может быть использован для генерации различных звуков или музыки, либо как генератор случайных чисел. Канал использует режим 3 таймера 8253/8254.


Программирование таймера на уровне портов


Таймеру соответствуют четыре порта ввода/вывода со следующими адресами:

40h - канал 0;

41h - канал 1;

42h - канал 2;

43h - управляющий регистр.

Приведем формат управляющего регистра:

7 6 5 4 3 2 1 0 T-T-T-T-T-T-T-¬ ¦ ¦ ¦ ¦ ¦ LT+T+T+T+T+-+T+T- LT- LT- L=T=- L= BCD: 0 - двоичный счет; ¦ ¦ ¦ 1 - двоично-десятичный счет. ¦ ¦ ¦ ¦ ¦ L===== M: 000 - режим 0; ¦ ¦ 001 - режим 1; ¦ ¦ X10 - режим 2; ¦ ¦ X11 - режим 3; ¦ ¦ 100 - режим 4; ¦ ¦ 101 - режим 5. ¦ ¦ ¦ L========== RW: 00 - код команды CLC (запомнить CE); ¦ 01 - чтение/запись старшего байта; ¦ 10 - чтение/запись младшего байта; ¦ 11 - чтение/запись младшего, затем ¦ старшего байта. ¦ L============== SC: 00 - канал 0; 01 - канал 1; 10 - канал 2; 11 - код команды RBC (чтение состояния канала).

Поле BCD определяет формат константы, использующейся для счета - двоичный или двоично-десятичный. В двоично-десятичном режиме константа задается в диапазоне 1-9999.

Поле M определяет режимы работы микросхемы 8254:

0 - прерывание от таймера;

1 - программируемый ждущий мультивибратор;

2 - программируемый генератор импульсов;

3 - генератор меандра;

4 - программно-запускаемый одновибратор;

5 - аппаратно-запускаемый одновибратор.

Мы будем рассматривать только режим 3, так как именно он используется в каналах 0 и 2.

Поле RW определяет способ загрузки констант через однобайтовый порт. Если в этом поле задано значение 00, это управляющее слово будет использоваться для фиксации текущего содержимого регистров счетчика CE в буферном регистре OL с целью чтения программой. Это код команды CLC - фиксация регистров. Код канала, для которого будет выполняться фиксация, должен быть указан в поле SC. Поля M и BCD при этом не используются.

Поле SC определяет номер канала, для которого предназначено управляющее слово. Если в этом поле задано значение 11, будет выполняться чтение состояния канала.

Приведем формат команды RBC чтения слова состояния канала:

7 6 5 4 3 2 1 0 T-T-T-T-T-T-T-¬ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ LT+T+T+T+T+T+T+T- LT- ¦ ¦ ¦ ¦ ¦ L= равно 0. ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ L=== 1 - выбор канала 0. ¦ ¦ ¦ ¦ L===== 1 - выбор канала 1. ¦ ¦ ¦ L======= 1 - выбор канала 2. ¦ ¦ ¦ ¦ ¦ L========= STAT: 0 - читать состояние каналов; ¦ ¦ 1 - не читать состояние каналов. ¦ ¦ ¦ L=========== CNT: 0 - запомнить текущее содержимое CE; ¦ 1 - не запоминать содержимое CE. ¦ L============== код команды RBC - 11.


С помощью этой команды вы можете выполнять операции чтения состояния каналов либо запоминание регистра счетчика CE каналов. Можно выполнять эти операции как для отдельных каналов, так и для всех каналов одновременно, если установить соответствующие биты (1, 2, 3) в 1.

Формат слова состояния канала напоминает формат регистра управляющего слова, за исключением двух старших разрядов 7 и 6:

7 6 5 4 3 2 1 0 T-T-T-T-T-T-T-¬ ¦ ¦ ¦ ¦ ¦ ¦ LT+T+T+T+T+-+T+T- ¦ ¦ LT- L=T=- L= BCD: 0 - двоичный счет; ¦ ¦ ¦ ¦ 1 - двоично-десятичный счет. ¦ ¦ ¦ ¦ ¦ ¦ ¦ L===== M: 000 - режим 0; ¦ ¦ ¦ 001 - режим 1; ¦ ¦ ¦ X10 - режим 2; ¦ ¦ ¦ X11 - режим 3; ¦ ¦ ¦ 100 - режим 4; ¦ ¦ ¦ 101 - режим 5. ¦ ¦ ¦ ¦ ¦ L========== RW: 00 - код команды CLC (запомнить CE); ¦ ¦ 01 - чтение/запись старшего байта; ¦ ¦ 10 - чтение/запись младшего байта; ¦ ¦ 11 - чтение/запись младшего, затем ¦ ¦ старшего байта. ¦ ¦ ¦ L============= FN: флаг перезагрузки констант; L=============== OUT: состояние выхода OUT.

Разряд FN используется, в основном, в режимах 1 и 5 для определения, произошла ли загрузка константы из регистра CR в регистр счетчика CE.

Разряд OUT позволяет определить состояние выходной линии канала OUT в момент выполнения команды RBC.

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

вывести в порт управляющего регистра с адресом 43h управляющее слово;

требуемое значение счетчика посылается в порт канала (адреса 40h...42h), причем вначале выводится младший, а затем старший байты значения счетчика.

Сразу после этого канал таймера начнет выполнять требуемую функцию.

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

вывести в порт управляющего регистра код команды CLC (команда запоминания содержимого регистра CE);

вывести в порт управляющего регистра код команды запроса на чтение/запись в регистры канала (поле RW должно содержать 11);

двумя последовательными командами ввода из порта нужного канала ввести младший и старший байты текущего сосотояния счетчика CE.



Для чего вам может понадобиться перепрограммирование каналов таймера?

Если вам надо повысить точность измерения времени, выполняемого с помощью канала 0 таймера, вы можете увеличить частоту генерируемых этим каналом импульсов (стандартно 18,2 Гц). По окончании измерений режим работы канала необходимо восстановить для правильного функционирования системы.

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

Приведем пример программы, отображающей слово состояния и содержимое счетчика для всех трех каналов таймера:

#include <stdio.h> #include <conio.h>

main() {

unsigned i;

printf("\n\nКанал 0" "\n-------" "\n");

// Читаем слово состояния канала, // команда 0xe2 = 11100010B

outp(0x43, 0xe2);

printf("\nСлово состояния канала: %02.2X", inp(0x40));

// Читаем текущее состояние регистра счетчика // канала. Для этого вначале выдаем команду CLC // для канала 0. Код этой команды - 0x00

outp(0x43, 0x00);

// Вводим младший и старший байты счетчика // и отображаем его.

i = inp(0x40); i = (inp(0x40) << 8) + i;

printf("\nРегистр счетчика: %04.4X",i);

// Повторяем те же действия для 1 и 2 каналов.

printf("\n\nКанал 1" "\n-------" "\n");

outp(0x43, 0xe4); printf("\nСлово состояния канала: %02.2X",inp(0x41));

outp(0x43, 0x40);

i = inp(0x41); i = (inp(0x41) << 8) + i;

printf("\nРегистр счетчика: %04.4X",i);

printf("\n\nКанал 2" "\n-------");

outp(0x43, 0xe8); printf("\nСлово состояния канала: %02.2X",inp(0x42));

outp(0x43, 0x80);

i = inp(0x42); i = (inp(0x42) << 8) + i;

printf("\nРегистр счетчика: %04.4X",i);

exit(0);

}


СИСТЕМНЫЙ ТАЙМЕР


5.1.

5.2.

5.3.

5.4.

5.5.

5.6.

Кроме часов реального времени, любой компьютер (даже простейший IBM PC) содержит устройство, называемое системным таймером. Это устройство подключено к линии запроса на прерывание IRQ0 и вырабатывает прерывание INT 8h приблизительно 18,2 раза в секунду (точное значение - 1193180/65536 раз в секунду).

При инициализации BIOS устанавливает свой обработчик для прерывания таймера. Этот обработчик каждый раз увеличивает на 1 текущее значение четырехбайтовой переменной, располагающейся в области данных BIOS по адресу 0000:046Ch - счетчик тиков таймера. Если этот счетчик переполняется (прошло более 24 часов с момента запуска таймера), в ячейку 0000:0470h заносится 1.

Другое действие, выполняемое стандартным обработчиком прерывания таймера - контроль за работой двигателей НГМД. Если после последнего обращения к НГМД прошло более 2 секунд, обработчик прерывания выключает двигатель. Ячейка с адресом 0000:0440h содержит время, оставшееся до выключения двигателя. Это время постоянно уменьшается обработчиком прерывания таймера. Когда оно становится равно 0, обработчик выключает двигатель НГМД.

Последнее действие, которое выполняет обработчик прерывания таймера - вызов прерывания INT 1Ch. После инициализации системы вектор INT 1Ch указывает на команду IRET, т.е. ничего не выполняется. Программа может установить собственный обработчик этого прерывания для того чтобы выполнять какие-либо периодические действия.

Необходимо отметить, что прерывание INT 1Ch вызывается обработчиком прерывания INT 8h до сброса контроллера прерывания, поэтому во время выполнения прерывания INT 1Ch все аппаратные прерывания запрещены. В частности, запрещены прерывания от клавиатуры.

Обработчик прерывания INT 1Ch должен заканчиваться командой IRET. Если же вы подготавливаете собственный обработчик для прерывания INT 8h, перед завершением его работы необходимо сбросить контроллер прерываний. Это можно сделать, например, так:

mov al, 20h out 20h, al


Приведенный ниже рисунок иллюстрирует механизм обработки прерывания таймера:

Обработчик INT 8h Обработчик INT 1Ch ----------------¬ ¦ ¦ ----------------------------¬ ¦ ------------+----------¬ ¦ Увеличение счетчика ¦ ¦ ¦ Выполнение действий, ¦ ¦ по адресу 0000:046Ch, ¦ ¦ ¦ определенных в ¦ ¦ проверка его на пере- ¦ ¦ ¦ программе ¦ ¦ полнение. ¦ ¦ L-----------T----------- L-------------T-------------- ¦ ¦ ¦ ¦ ¦ --------------+-------------¬ ¦ ------------+----------¬ ¦ Проверка времени послед- ¦ ¦ ¦ IRET ¦ ¦ него обращения к НГМД, ¦ ¦ L-----------T----------- ¦ если оно больше 2 секунд, ¦ ¦ ¦ ¦ выключение двигателя НГМД.¦ ¦ ¦ L-------------T-------------- ¦ ¦ ¦ ¦ ¦ --------------+-------------¬ ¦ ¦ ¦ Вызов прерывания +>- ¦ ¦ INT 1Ch. +<----------------- L-------------T-------------- ¦ --------------+-------------¬ ¦ Сброс контроллера ¦ ¦ прерываний. ¦ L-------------T-------------- ¦ --------------+-------------¬ ¦ IRET ¦ L----------------------------

Таймер обычно реализуется на микросхеме Intel 8253 (для компьютеров IBM PC и IBM XT) или 8254 (для компьютеров IBM AT и IBM PS/2). Следующий раздел книги посвящен описанию микросхемы 8254.

Мы не будем подробно рассказывать о всех возможностях этих микросхем, так как обычно используются только несколько режимов работы (а чаще всего один). Полное описание вы сможете найти в справочной литературе по микросхемам 8253/8254 , а также по их отечественным аналогам К1810ВИ53 и К1810ВИ54.


Средства BIOS для работы с таймером


Для работы с таймером (точнее говоря, для работы с каналом 0 таймера) BIOS содержит две функции прерывания INT1Ah. Они позволяют прочитать текущее содержимое счетчика и изменить его.

Функция 00h предназначена для чтения содержимого счетчика таймера:

На входе: AH = 00h.

На выходе: CX = старший байт счетчика;

DX = младший байт счетчика;

AL = 0, если с момента перезапуска таймера прошло более 24-х часов.

Изменить содержимое счетчика таймера можно с помощью следующей функции:

На входе: AH = 01h;

CX = старший байт счетчика;

DX = младший байт счетчика.

На выходе: не используются.

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

Однако следует учитывать, что точность формирования задержки определяется частотой обновления счетчика таймера (18.2 Гц), и может оказаться недостаточной для некоторых приложений.

Мы подготовили функцию для формирования задержек с помощью таймера:

/** *.Name tm_delay *.Title Формирование задержки по таймеру * *.Descr Эта функция формирует задержку, используя * системный таймер. * *.Proto void tm_delay(int ticks) * *.Params int ticks - величина задержки в тиках * таймера (за одну секунду таймер * тикает 18.2 раза). * *.Return Ничего * *.Sample tm_samp1.c **/

#include <dos.h> #include <conio.h>

void tm_delay(int ticks) {

_asm {

push si

mov si, ticks mov ah, 0 int 1ah

mov bx, dx add bx, si

delay_loop:

int 1ah cmp dx, bx jne delay_loop

pop si } }

Функция использует только одно слово регистра таймера, что позволяет формировать задержки длительностью до 65536 тиков таймера. Приведенная ниже программа демонстрирует использование функции для генерации примерно десятисекундной задержки :

#include <stdio.h> #include "sysp.h"

main() {

printf("\nДля выполнения программной задержки примерно" "\nна 10 секунд нажмите любую клавишу."); getch();


printf("\n\nВремя пошло...");

tm_delay(18 * 10);

printf("\nГотово!");

exit(0); }

BIOS компьютеров IBM  AT содержит еще две интересные функции для работы с таймером. Это функции 83h и 86h прерывания INT 15h.

Функция 83h позволяет запустить таймер на счет, указав адрес некоторого байта в оперативной памяти. Программа, запустившая таймер, сразу после запуска получает управление. По истечении времени, заданного при запуске таймера, функция устанавливает старший бит указанного байта в единицу, сигнализируя таким образом программе о завершении указанного временного интервала. Программа может также отменить работу таймера в этом режиме.

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

Приведем формат вызова функции 83h прерывания INT 15h:

На входе: AH = 83h;

AL = код подфункции:

0 - установить интервал, запустить таймер; 1 - отменить работу таймера;

CX = старший байт времени работы счетчика, задается в микросекундах;

DX = младший байт счетчика;

ES:BX = адрес байта, в котором по истечении интервала времени старший бит будет установлен в 1.

На выходе: не используются.

Функция 86h специально предназначена для формирования задержек. Она позволяет определять время задержки в микросекундах, что достаточно удобно для многих задач. Во время выполнения задержки разрешены прерывания. Формат вызова функции:

На входе: AH = 86h;

CX = старший байт времени задержки, задается в микросекундах;

DX = младший байт времени задержки.

На выходе: не используются.


Средства MS-DOS для работы с таймером


MS-DOS использует четыре функции прерывания INT 21h для работы с системным таймером. Эти функции позволяют узнать и установить текущие дату и время. MS-DOS версии 3.30 и более поздних версий при установке времени и даты изменяет также показания часов реального времени.

Для получения текущей даты используется функция 2Ah:

На входе: AH = 2Ah.

На выходе: DL = день (0...31);

DH = месяц (1...12);

CX = год (1980...2099);

AL = номер дня недели:

0 - воскресенье; 1 - понедельник; 2 - вторник; ......... 6 - суббота.

Обратите внимание на то, что функция возвращает вам номер дня недели, который она вычисляет на основе даты.

Для установки даты используйте функцию 2Bh:

На входе: AH = 2Bh;

DL = день (0...31);

DH = месяц (1...12);

CX = год (1980...2099).

На выходе: AL = 0, если установка выполнена правильно; AL = FFh, если при установке были заданы неправильные параметры.

Для того, чтобы определить текущее время, можно воспользоваться функцией 2Ch:

На входе: AH = 2Ch.

На выходе: CH = часы (0...24);

CL = минуты (0...59);

DH = секунды(0...59);

DL = сотые доли секунды (0...99).

Точность времени, полученного при помощи этой функции, определяется таймером (время обновляется 18.2 раза в секунду).

Для установки времени можно использовать функцию 2Dh:

На входе: AH = 2Dh;

CH = часы (0...24);

CL = минуты (0...59);

DH = секунды(0...59);

DL = сотые доли секунды (0...99).

На выходе: AL = 0, если установка выполнена правильно; AL = FFh, если при установке были заданы неправильные параметры.

Стандартные библиотеки трансляторов Microsoft QC 2.5 и C 6.0 содержат многочисленные функции для работы с датой и временем. Они основаны на описанных выше функциях MS-DOS и предоставляют широкие возможности для отображения даты и времени в различных форматах. Подробное описание этих функций и примеры их использования вы найдете в документации на библиотеки. К сожалению, в этих библиотеках нет функций для организации программных задержек.



Таймер и музыка


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

Как мы уже говорили, канал 2 микросхемы 8254 связан с громкоговорителем компьютера. Однако громкоговоритель не просто соединен с выходом OUT канала 2. Порт вывода 61h также используется для управления громкоговорителем. Младший бит порта 61h подключен ко входу GATE канала 2 таймера. Этот бит при установке в 1 разрешает работу канала, т.е. генерацию импульсов для громкоговорителя.

Дополнительно для управления громкоговорителем используется бит 1 порта 61h. Если этот бит установлен в 1, импульсы от канала 2 таймера смогут проходить на громкоговоритель.

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

запрограммировать канал 2 таймера на нужную частоту (т.е. загрузить регистр счетчика канала нужным значением);

для включения звука установить в 1 два младших бита порта 61h.

Так как остальные 6 битов порта 61h используются для других целей, установка младших битов должна выполняться таким образом, чтобы значения остальных битов не были изменены. Для этого вначале надо считать байт из порта 61h в рабочую ячейку памяти, установить там нужные биты, затем вывести новое значение байта в порт 61h.

Очевидно, что для выключения звука надо сбросить два младших бита порта 61h в 0 (при этом нельзя изменять значение остальных битов этого порта).

Мелодия (одноголосая), как известно, состоит из нот, разделенных или не разделенных паузами. При проигрывании мелодии необходимо для каждой ноты программировать соответствующим образом канал 2 таймера и включать громкоговоритель (с помощью порта 61h) на определенное время, равное длительности ноты. Затем программа должна выключить динамик и выдержать паузу перед проигрыванием следующей ноты, если такая пауза требуется.

Программа может генерировать звуки и другим способом, не используя таймер. Для этого нужно сбросить младший бит порта 61h и, управляя битом 1 этого порта, формировать импульсы для громкоговорителя. Т.е. программа должна устанавливать этот бит то в 0, то в 1 с некоторым периодом. Высота генерируемого звука будет соответствовать этому периоду.


Можно также комбинировать эти два способа, получая разнообразные звуковые эффекты.

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

Основная идея заключается в использовании прерывания 1Ch, которое вырабатывается таймером с частотой примерно 18,2 Гц. Ваш обработчик этого прерывания осуществляет контроль за выборкой нот из массива, содержащего мелодию, и программирование микросхемы 8254. Например, один раз в полсекунды обработчик проверяет, не пора ли прекратить звучание одной ноты и начать проигрывание следующей ноты. Если пора, он выключает громкоговоритель и перепрограммирует канал 8254 на новую частоту, соответствующую следующей ноте.

Основное преимущество использования таймера для проигрывания мелодии - независимость констант, используемых для программирования канала таймера от производительности системы. Ваша мелодия будет звучать одинаково и на медленной IBM XT и на Super-AT с процессором 80486, но при условии, что вы будете использовать таймер и для организации задержек при исполнении мелодии.

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

Мы подготовили функцию, предназначенную для генерации на громкоговорителе тона заданной частоты и длительности:

/** *.Name tm_sound *.Title Формирование тона заданной длительности * *.Descr Эта функция предназначена для генерации * на громкоговорителе тона заданной * длительности и частоты. * *.Proto void tm_sound(int freq, int time); * *.Params int freq - частота в герцах; * int time - длительность в тиках * таймера (за одну секунду таймер * тикает 18.2 раза). * *.Return Ничего * *.Sample play.c **/

#include <stdio.h> #include <conio.h> #include "sysp.h"

void tm_sound(int freq, int time) {

int cnt, i;

// Задаем режим канала 2 таймера

outp(0x43, 0xb6);

// Вычисляем задержку для загрузки в // регистр счетчика таймера



cnt = 1193180L / freq;

// Загружаем регистр счетчика таймера - сначала // младший, затем старший байты

outp(0x42, cnt & 0x00ff);

outp(0x42, (cnt &0xff00) >> 8);

// Включаем громкоговоритель. Сигнал от // канала 2 таймера теперь будет проходить // на вход громкоговорителя.

outp(0x61, inp(0x61) | 3);

// Выполняем задержку.

tm_delay(time);

// Выключаем громкоговоритель.

outp(0x61, inp(0x61) & 0xfc);

}

Если подготовить для этой функции таблицы частот и длительностей, то можно с ее помощью проигрывать простейшие мелодии. В следующем примере мы так и поступили:

#include <stdio.h> #include <conio.h> #include "sysp.h"

void main(void); void sound(int, int);

// Массив частот для мелодии

int mary[] = { 330, 294, 262, 294, 330, 330, 330, 294, 294, 294, 330, 392, 392, 330, 294, 262, 294, 330, 330, 330, 330, 294, 294, 330, 294, 262, 0 };

// Массив длительностей

int del[] = { 5, 5, 5, 5, 5, 5, 10, 5, 5, 10, 5, 5, 10, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 20 };

void main(void) {

int i;

for(i=0 ;mary[i] != 0 ;i++) tm_sound(mary[i], del[i]);

}

Запускайте эту программу и слушайте, как она работает!

Для подготовки таблиц частот по нотам вам помогут учебники по элементарной теории музыки и следующая таблица, в которой приведены частоты для второй октавы. Для других октав при понижении или повышении тона значения частот надо умножать (при повышении тона) или делить (при понижении тона) на 2.

Нота Частота, Гц

До 261,7 До-диез 277,2

Ре 293,7 Ре-диез 311,1

Ми 329,6

Фа 349,2 Фа-диез 370,0

Соль 392,0 Соль-диез 415,3

Ля 440,0 Ля-диез 466,2

Си 493,9

Приведем еще одну программу, генерирующую звук без использования таймера. Эта программа формирует импульсы при помощи манипуляций с разрядом 1 порта 61h:

#include <stdio.h> #include <conio.h>

#define FREQUENCY 200 #define CYCLES 10000

void main(void);

void main(void) {

int cnt;

// Во время генерации звука прерывания должны // быть запрещены.

_disable();

_asm {



// Загружаем количество циклов - периодов // генерируемых импульсов

mov dx, CYCLES

// Отключаем громкоговоритель от таймера

in al, 61h and al, 0feh

// Цикл формирования периода

sound_cycle:

// Формируем первый полупериод, подаем // на громкоговоритель уровень 1

or al, 2 out 61h, al

// Формируем задержку

mov cx, FREQUENCY

first: loop first

// Формируем второй полупериод, подаем // на громкоговоритель уровень 0

and al, 0fdh out 61h, al

// Формируем задержку

mov cx, FREQUENCY

second: loop second

// Если сформированы не все периоды, переходим // к формированию следующего периода.

dec dx jnz sound_cycle

}

// Разрешаем прерывания.

_enable();

// Выключаем громкоговоритель.

outp(0x61, inp(0x61) & 0xfc);

}

Так как в этой программе для формирования полупериодов используется задержка с помощью команды LOOP, высота генерируемого тона будет зависеть от производительности системы. Такой зависимости можно избежать, если перед началом работы измерять производительность и соответствующим образом корректировать константу, загружаемую в регистр CX перед вызовом команды LOOP.

Измерение производительности лучше всего выполнять с помощью таймера, определяя время выполнения команды LOOP.